Skip to content

DCM early access command output improvements#3066

Open
sfc-gh-jsommerfeld wants to merge 37 commits into
mainfrom
dcm-early-access-improvements
Open

DCM early access command output improvements#3066
sfc-gh-jsommerfeld wants to merge 37 commits into
mainfrom
dcm-early-access-improvements

Conversation

@sfc-gh-jsommerfeld

@sfc-gh-jsommerfeld sfc-gh-jsommerfeld commented May 29, 2026

Copy link
Copy Markdown

Pre-review checklist

  • I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
  • I've added or updated unit tests to verify correctness of my new code.
  • I've added or updated integration tests to verify correctness of my new code.
  • Manually tested on macOS
  • Manually tested on Windows
  • I've confirmed my changes are up-to-date with the target branch.
  • I've described my changes in RELEASE-NOTES.md (see when and how).
  • I've updated documentation if behavior changed.

Changes description

  • snow dcm plan
    • Shows a compressed file upload summary (file counter per path) with progress bar
    • Shows more granular changes for ALTER operations in the output
  • snow dcm deploy
    • Shows a compressed file upload summary (file counter per path) with progress bar
    • Shows a progress bar for the execution of the PLAN and DEPLOY phase
    • Shows more granular changes for ALTER operations in the output
  • snow dcm analyze
    • new DCM command to run a static analysis of all DCM definitions
    • returns any errors or warnings found - grouped per file and entity
    • meant for quickly checking iterative definition changes and catching errors before committing.
    • Validates syntax and dependencies. It runs faster than PLAN, but does not catch all possible errors. Always run PLAN to preview changes before deploying.
    • Shows a compressed file upload summary (file counter per path) with progress bar
  • snow dcm refresh
    • updated output formatting
  • snow dcm test
    • updated output formatting

sfc-gh-jsommerfeld and others added 28 commits May 27, 2026 07:57
Poll SYSTEM$GET_DCM_PROJECT_PROGRESS during async deploy and show a
phase checklist with per-phase progress for PLAN and DEPLOY.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add UPLOAD as the first checklist phase with folder summaries, per-phase
durations and colors, and a file-count progress bar without extra I/O.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use project scope for stage message, show upload summaries above the
UPLOAD phase, fix plan completion styling, and align Upload N file(s) lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
Simulate RENDER/COMPILE/PLAN phases for plan without progress API, simplify
upload summary lines, and remove extra indentation from progress output.

Co-authored-by: Cursor <cursoragent@cursor.com>
Surface the nested changes for ALTER entries as indented sub-lines (kind
plus item desc) so users can see what is actually changing instead of
just "ALTER ROLE …". Handles every observed payload shape: dict and
string item_id, set/unset attribute changes, modified entries that carry
their own nested changes, and collection wrappers. CREATE/DROP stay
terse because their changes are noisy attribute dumps. Each sub-line is
colored semantically (added/set green, removed/unset red,
modified/renamed yellow).

Co-authored-by: Cursor <cursoragent@cursor.com>
Render the UPLOAD checklist row first, then indent the stage-creation
message and folder counters beneath it (two-space indent, dim style),
so the live progress UI reads top-down as a single phase block instead
of a preamble followed by the phase header.

Co-authored-by: Cursor <cursoragent@cursor.com>
Gated on ENABLE_DCM_EARLY_ACCESS, the new `snow dcm analyze` command runs
the same EXECUTE DCM PROJECT ... ANALYZE query as `raw-analyze` but prints
a styled report instead of raw JSON: errors in red, issues in yellow, each
with their error code, source line/column, and (when applicable) the
fully-qualified definition identifier and domain.

A plan-style summary line is always rendered at the end. If nothing was
reported, a green `Analysis completed successfully.` is shown; otherwise
both counts are printed with the error count in red and the issue count
in yellow. The command exits with status 1 only when errors are present;
warning-only runs exit 0.

Supports `--save-output` to persist the raw JSON response under
`out/analyze.json` and download server-side artifacts to `out/analyze/`,
mirroring `snow dcm plan`.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wire `snow dcm analyze` into `DeployProgressTracker` with a new
`operation="analyze"` mode that exposes only the client-side UPLOAD
phase (no server-side RENDER/COMPILE/PLAN/DEPLOY rows). The UPLOAD line,
progress bar, checkmark, timestamp, duration, and dim per-folder upload
summaries now match `snow dcm deploy` byte-for-byte. After upload
completes the existing spinner runs the synchronous ANALYZE query, then
the formatted findings and summary line are rendered as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
The styled analyze report now prints just the actual diagnostic message
for each error / issue (still colored red / yellow respectively), grouped
under the source file and definition identifier. The previous per-finding
header line ("error [001597] line 10:0") is dropped, and the server-side
"DCM project ANALYZE error: " prefix is stripped from the message text.
The full raw payload (codes, source positions, types) is still preserved
in the JSON response and in `--save-output` artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
All DCM commands that go through a Reporter (plan, deploy, purge,
analyze, raw-analyze, refresh, test) now print an 80-character `=`
divider as the last visible line of their TABLE-format output. This
gives a clean visual break between the command's results and the next
shell prompt or the error box rendered for a failing run. The divider
is auto-suppressed in --format json / --format csv runs because
cli_console is silenced for machine-readable outputs.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the generic "Analysis ..." summary line emitted by both
AnalyzeReporter and AnalyzeErrorsReporter (success and failure branches)
with the more specific "Static Analysis of DCM Project files ..."
phrasing, and update unit, integration, and release-notes references
accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adjust the DCM analyze summary to "Static analysis of DCM Project files
found no errors." on success (lowercase "analysis") and apply the same
casing to the failure variant. Render source-file headers in the analyze
output in bold blue (new FILE_PATH_STYLE) so file names stand out from
the inline finding messages.

Co-authored-by: Cursor <cursoragent@cursor.com>
A PLAN or DEPLOY failure bubbles up through DeployProgressTracker.session()'s
exception handler, which called fail_upload() unconditionally and overwrote
an already-completed UPLOAD as failed. Make fail_upload() a no-op once the
UPLOAD phase has terminated (done or failed) so a downstream error stops at
its own phase row without retroactively marking the successful upload as
failed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the terse `N refreshed, M up-to-date.` summary with a two-line
styled summary printed above the `===` divider, mirroring the pattern
used by `snow dcm plan` / `snow dcm analyze`:

* Always: `N Dynamic Tables refreshed successfully.` (green) — counts
  every table that completed without error (REFRESHED + UP-TO-DATE).
* When applicable: `M Refreshes failed.` (red) — counts tables the
  server returned without statistics (previously the `unknown` bucket).

The command now exits with status 1 only when at least one refresh
failed; the styled summary still renders above the divider in that case
(via the same `_is_success` + post-hoc `typer.Exit` trick used by
`AnalyzeErrorsReporter`), instead of being swallowed into an "Error" box.

Snapshots, the mixed-success test, and the qa-only integration assertions
are updated to match the new wording.

Co-authored-by: Cursor <cursoragent@cursor.com>
The server now returns a single `issues[]` array per level (top-level,
file, per-definition) where each entry carries a `severity` field
(`ERROR`, `WARN`/`WARNING`, or `INFO`). Drop the legacy `errors[]`
parsing entirely and drive the reporter off severity:

* `ERROR` — red, triggers exit code 1 (unchanged).
* `WARN`/`WARNING` — yellow, informational.
* `INFO` — plain blue (new `INFO_STYLE` to distinguish from bold-blue
  source file headers), informational.

Unknown or missing severity values are coerced to `ERROR` so a buggy
server payload never silently downgrades a real problem.

The summary now breaks down by severity, omitting zero buckets and using
proper singular/plural plus an Oxford comma when three buckets are
present, e.g.:

* `Static analysis of DCM Project files found no errors.` (all clean)
* `Static analysis of DCM Project files found 1 error.`
* `Static analysis of DCM Project files found 1 error and 2 warnings.`
* `Static analysis of DCM Project files found 2 errors, 1 warning, and 3 infos.`

Each numeric segment is colored to match its severity. `raw-analyze`
keeps its simpler error-only summary and counts ERROR-severity issues
across all three levels of the response.

Tests are updated to use the new `issues[]`+`severity` shape; added
parametrized coverage that each severity uses the matching style
(`FAIL_STYLE` / `WARNING_STYLE` / `INFO_STYLE`) and that unknown
severity values still fail the run.

Co-authored-by: Cursor <cursoragent@cursor.com>
Previously the semantic style (added=green, removed=red, modified=yellow,
set=green, unset=red) was applied to BOTH the kind keyword and the
description text on every indented sub-line under an ALTER row. With
verbose ALTERs (e.g. role grants, data metric functions with nested
expectations) that meant a wall of red/green/yellow that drowned out
the actual entity names.

Restrict the color to the kind keyword only; the description (entity
identifier, attribute = value, etc.) now renders with the terminal
default so the eye lands on the verb and the names stay readable. The
parent ALTER row's operation keyword and its FQN coloring are unchanged.

Updated `test_detail_kind_keyword_is_colored_desc_is_default` to assert
the new contract: kind segment carries the semantic style, desc segment
has style "" (default).

Co-authored-by: Cursor <cursoragent@cursor.com>
Commit a82b15d (simplify upload copy) dropped the trailing-space pad on
the singular form, so adjacent rows in the upload details block no
longer lined up: "Upload 1 file from ..." vs "Upload 12 files from ...".

Restore the trailing space on the singular so the column after the
word stays aligned, e.g.:

  Upload 1 file  from sources/macros
  Upload 12 files from sources/definitions

Test asserts the literal two-space gap; a comment notes that the
double-space is intentional so future formatters / linters don't
"correct" it away.

Co-authored-by: Cursor <cursoragent@cursor.com>
Indented sub-lines previously used a flat two-space indent per depth,
which made it hard to tell which leaves belonged to which "modified"
parent in a busy ALTER (think ROLE grants or a DMF with nested
expectations).

Switch to a tree-style prefix using Unicode box-drawing characters in
narrow 3-column cells:

  ALTER    ROLE                 DCM_DEVELOPER
  ├─ removed  DATABASE_ROLE DCM_DEMO_1_DEV2.ADMIN_DEV2
  ├─ added    DATABASE_ROLE DCM_DEMO_1_DEV3.ADMIN_DEV3
  ├─ modified ON SCHEMA DCM_DEMO_1_DEV2.TEST_TEAM
  │  └─ added    OWNERSHIP
  ├─ removed  ROLE TEST_TEAM_OWNER_DEV2
  └─ added    ROLE TEST_TEAM_OWNER_DEV3

  ALTER    TABLE                ...ENRICHED_ORDER_DETAILS
  └─ added    SNOWFLAKE.CORE.NULL_COUNT$V1(CUSTOMER_CITY)
     └─ added    NO_MISSING_CITIES
        └─ set      expression = value = 0

Implementation:

* Replace ``PlanDetail.depth: int`` with ``is_last_chain: Tuple[bool, ...]``
  (one bool per ancestor level, ending with the node itself). Keep
  ``depth`` as a derived ``@property`` for callers that still want the
  count.
* Pre-expand ``collection`` wrappers in ``_flatten_changes`` /
  ``_flatten_changes_from_raw`` so the ``is_last`` computation reflects
  the *displayed* sibling list (collections produce no line of their
  own; their children take their place as siblings of their grandparent).
* New ``_render_tree_prefix`` turns the chain into ``├─ ``, ``└─ ``,
  ``│  ``, or ``   `` cells. The prefix renders with the default style
  so the colored verb (added/removed/modified/set/…) still catches the
  eye, matching the previous "only the keyword is colored" contract.
* Tests updated: dataclass equality assertions now pass
  ``is_last_chain=(...)``; the nested-grants indent check compares the
  column where each kind keyword starts (survives the tree prefix).
* Snapshots regenerated.

Co-authored-by: Cursor <cursoragent@cursor.com>
Group sub-changes under an ALTER row by category at every depth so users
scan creations first, then modifications, then deletions, matching the
top-level CREATE → ALTER → DROP ordering. Stable sort preserves
server-supplied order within each bucket.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the static yellow "…" placeholder with a braille spinner that
animates inside the live phase checklist, so the user sees activity on
the running phase itself instead of having a separate spinner appear
below the tracker.

* Adds an ANALYZE phase to the analyze tracker mode so the spinner has
  a place to render; the analyze command now runs through the same Live
  session as deploy/plan instead of opening a second cli_console spinner.
* Generalizes ``run_plan_execute`` into ``run_loader_phase(phase_name=,
  simulated_phases=)`` and routes both plan and analyze through it.
* Passes ``self`` to ``rich.live.Live`` and implements ``__rich__`` so
  Rich's refresh loop re-renders the tracker every ~100 ms; the spinner
  glyph is derived from ``time.monotonic()`` for a smooth cycle.
* Collapses three near-identical poll loops in ``run_deploy_poll`` into
  a single body behind an ``_ensure_live`` context manager.

Bundled cleanups while touching the file:

* Replaces the stringly-typed phase status with a ``PhaseStatus`` enum.
* Replaces the if/elif phase-list chain with a ``_PHASES_BY_OPERATION``
  dict so adding a new mode is one line.
* Hoists the three lazy ``get_cli_context`` imports to module level.
* Removes the unused ``DeployProgressTracker.run`` alias.

The ``mock_deploy_tracker`` fixture now stubs ``run_loader_phase`` to
invoke its closure so analyze command tests continue to exercise the
real ``manager.raw_analyze`` mock.

Co-authored-by: Cursor <cursoragent@cursor.com>
Three internal cleanups that follow up on the live-progress / analyze /
refresh work without changing observable output (apart from the test
summary, see below).

* Plan reporter: collapse _KIND_SORT_ORDER + _style_for_change_kind into
  a single _KIND_INFO[kind] = (sort_key, style) table. A new sub-change
  kind now lives in one place and can't drift between coloring and sort
  order.
* Plan reporter: merge _flatten_changes / _flatten_changes_from_raw and
  _expand_collections / _expand_collections_raw behind a small
  _ChangeReader adapter, eliminating ~80 lines of duplicated traversal
  code while keeping the strict (PlanChange) and fallback (raw dict)
  paths separated.
* Analyze reporter: move the top-level _top_level_issues tally from
  print_renderables into extract_data, mirroring the per-file and
  per-definition tally path so the summary counts stay correct even if
  the printing side ever short-circuits.
* Test reporter: align with the always-True _is_success + override
  process_payload(...) pattern adopted by AnalyzeErrorsReporter and
  RefreshReporter. Failures now render the styled "N passed, M failed"
  summary line above the === divider (instead of stuffing it into a
  CliError box) and exit 1 via typer.Exit, so the three reporters that
  can fail behave identically.

Snapshots regenerated; test_process_raises_cli_error_on_failures renamed
to test_process_exits_nonzero_on_failures to match the new contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
…LYZE phase

Backfills RELEASE-NOTES.md with the user-facing dcm changes that landed
earlier on this branch but weren't yet noted: the live phase checklist
(now with an inline animated spinner) for deploy/plan/analyze, the
tree-style sub-change rendering under ALTER rows (sorted added →
modified → removed), and the new "test" styled summary aligned with
analyze / refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the block-character bar (``[██████░░░░] 50%``) with a
pip/Rich-style minimal bar using heavy-horizontal cells (``━``) and a
``╺`` leading-edge transition cell at the boundary. The filled portion
is blue, the empty portion dim, and the percentage follows in blue —
no surrounding brackets.

Widens the bar from 20 to 30 cells so the leading-edge transition has
room to read as a smooth progress indicator. The line still comfortably
fits in an 80-column terminal alongside the phase name, timestamp, and
duration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Now that the parenthesised elapsed duration is shown for every running
and completed phase, the leading "HH:MM:SS" start timestamp is
redundant — it doubled the timing column without adding new
information. Drops it from the rendered phase line so each row reads as
``<name> <indicator>  (X.Ys)``.

Bumps the progress bar from 30 to 40 cells now that there's room to
spare on the line; the leading edge ``╺`` has more space to read as a
smooth indicator and the bar matches the pip-style aesthetic.

Renames the internal _Phase.hide_timestamp field to .hide_timing since
it now only suppresses duration display (used by ``_simulate_instant_phase``
for fast-forwarded RENDER/COMPILE rows in plan mode).

Co-authored-by: Cursor <cursoragent@cursor.com>
Top-level CREATE/ALTER/DROP rows still pad their operation keyword to a
fixed column for alignment between entities. Child rows under an ALTER
no longer pad — the tree prefix already provides visual indentation, so
a single space between keyword and description keeps the indented lines
compact:

    ALTER    ROLE                 DCM_DEVELOPER
    ├─ added DATABASE_ROLE DCM_DEMO_1_DEV3.ADMIN_DEV3
    ├─ added ROLE TEST_TEAM_OWNER_DEV3
    ├─ modified ON SCHEMA DCM_DEMO_1_DEV2.TEST_TEAM
    │  └─ added OWNERSHIP
    ├─ removed DATABASE_ROLE DCM_DEMO_1_DEV2.ADMIN_DEV2
    └─ removed ROLE TEST_TEAM_OWNER_DEV2

Snapshot tests and substring assertions updated; the now-unused
``_KIND_WIDTH`` constant is removed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the laconic ``N passed, M failed out of T total.`` line with
the more conversational ``Ran T data quality tests: N passed, M failed
expectation(s).``

The trailing noun (``expectation`` / ``expectations``) pluralizes with
the *last* visible bucket — ``unknown`` when present, otherwise
``failed`` — so the line reads naturally for every combination:

* "Ran 6 data quality tests: 5 passed, 1 failed expectation."
* "Ran 6 data quality tests: 4 passed, 2 failed expectations."
* "Ran 6 data quality tests: 6 passed, 0 failed expectations."
* "Ran 6 data quality tests: 4 passed, 1 failed, 1 unknown expectation."

Styling is preserved (total bolded, ``passed`` green, ``failed`` /
``unknown`` red); the empty-project line is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
The braille spinner glyph for a running ANALYZE / PLAN / DEPLOY phase
was yellow; every other "this is in flight" element on the row —
filled bar cells, leading-edge ``╺``, the percentage text, the bold
phase name itself — was blue. The mixed palette read as two different
signals instead of one. Switching the spinner to ``blue`` unifies the
entire running row under the active-indicator color.

Co-authored-by: Cursor <cursoragent@cursor.com>
H1 — _update_from_poll: ignore unknown / empty / non-string phase
  Earlier, a poll payload whose ``phase`` field was missing, empty,
  non-string, or not in the operation's phase list flipped every
  server-side phase to ``done`` on the very first poll while the
  query was still running. The tracker now refuses to mutate phase
  state from such payloads and logs at debug. Five regression tests
  added in ``TestUpdateFromPoll``.

M1 — _poll_progress: close the cursor
  Each poll opened a fresh cursor (1 Hz then 0.1 Hz) and never closed
  it. Wrapped in ``with self._conn.cursor() as cursor:`` so resources
  are released every tick.

M2 — _update_from_poll: use PhaseStatus enum members
  Replaced the ``("done", "failed")`` string-literal check with the
  enum members; the file is now internally consistent and won't
  silently break if ``PhaseStatus`` ever stops inheriting from str.

M3 — _poll_progress: validate sfqid before interpolating into SQL
  Defense-in-depth: ``sfqid`` is server-generated, but we never want
  an unvalidated string embedded into a SQL literal. A new
  ``_SFQID_RE`` UUID regex gates the interpolation; non-matching ids
  disable polling for the run.

M4 — run_loader_phase: leave phase state intact in silent mode
  Silent mode (JSON/CSV output, ``--silent``) has no UI to drive, so
  ``run_loader_phase`` now returns ``execute_fn()`` immediately
  without touching ``complete_upload`` / ``observe_running`` etc.
  This avoids leaking stale RUNNING entries that could mislead later
  introspection. ``TestRunLoaderPhaseSilent`` covers both the happy
  path and the exception path.

M5 — analyze: share a single error-counting iterator
  ``AnalyzeReporter`` and ``AnalyzeErrorsReporter`` used to count
  severities along divergent paths (raw-payload walk vs typed
  ``_AnalyzeFinding`` walk). Introduced ``_iter_findings`` + a
  ``_tally_by_severity`` Counter helper so both reporters tally off
  the same source — the totals can no longer disagree.

M6 — plan: render tree prefix dim
  The ``├─ │  └─`` glyphs in front of ALTER child rows now render
  with ``style="dim"`` so they recede visually; the colored
  ``added`` / ``removed`` / ``modified`` keyword that follows is
  what catches the eye.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add `snow dcm dependencies`, which runs the same analysis as
`analyze-errors` but renders the project's object dependencies as a
Mermaid flowchart written to a Markdown file. The CLI prints a link to
the generated file so it can be opened in an IDE Markdown preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-jsommerfeld sfc-gh-jsommerfeld requested a review from a team as a code owner June 4, 2026 16:15
Remove the blank line between the ANALYZE phase line and the summary,
indent all three summary lines by two spaces, and render them in dim
grey so the path and hint text feel secondary to the phase result.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sfc-gh-jsommerfeld sfc-gh-jsommerfeld requested a review from a team as a code owner June 6, 2026 02:52
sfc-gh-jsommerfeld and others added 7 commits June 5, 2026 19:57
Switch Mermaid curve from monotoneX to step so edges exit horizontally
from the right port of each source node and enter horizontally into the
left port of the target, with a single clean vertical bend in between.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Rename `snow dcm analyze` → `snow dcm compile` on the early-access
branch. Updates the command function, artifact names, reporter
command_name, debug-data mapping, and all test invocations.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant