Skip to content

Commit 87ac2ef

Browse files
authored
fix: colour plan migrations and scope output and stop progress lines merging (#837)
* fix: colour plan migrations and scope output and stop progress lines merging Coordinate every transient spinner or status line with persistent terminal writes through one shared coordinator, so connection, lifecycle, and diagnostic lines clear and redraw the live line instead of landing on it. Style plan migration and scope text with the shared colour roles, make scope rows compact by default with a --verbose flag for full details, and fix singular nouns in scope and janitor summaries. JSON output is unchanged. * fix: avoid audit progress deadlock and honour --no-color in scope output Share the transient line lock with the native progress projector so a reporter holding it can claim a resource terminal while another worker projects events. Pass the global --no-color flag through to scope text rendering, and describe the hollow marker as not used by this resource. * fix: keep concurrent sqb test progress rows intact in a terminal Render one live row for every running test and print each group header with its completed result under the shared coordinator lock, so concurrent starts no longer overwrite the spinner or leave orphaned rows. Update plan colour and janitor plural expectations to the new output.
1 parent e1160ce commit 87ac2ef

71 files changed

Lines changed: 2253 additions & 493 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/sqlbuild/.agents/skills/sqlbuild/references/docs/cli/scope.md‎

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ Online: https://sqlbuild.com/docs/cli/scope/
2626

2727
The command is read-only and offline. It does not connect to a warehouse, require warehouse credentials, or inspect relations. It reads the compiler-owned declaration-scope index and reuses a deterministic cache when the relevant source and configuration fingerprint has not changed. Building a cold cache uses the normal compiler expansion path, which loads the configured adapter and may execute authored Python macros; a warm cache hit reconstructs scope facts without importing macro modules.
2828

29-
Text output is deterministic, bounded, and requires no interactive pager.
29+
Text output is deterministic, bounded, and requires no interactive pager. In a color terminal it
30+
uses the same color conventions as other commands; redirected output and `NO_COLOR` produce plain
31+
text.
3032

3133
## Usage
3234

@@ -90,6 +92,7 @@ These identities are for inspection. SQL authored inside the owning model contin
9092
| `--page-size N` | Return at most `N` declarations per paged section (default: `100`). |
9193
| `--after CURSOR` | Continue after a declaration identity returned as the previous section's next cursor. |
9294
| `--paths MODE` | Render paths as `relative`, `compact`, or `none`. |
95+
| `--verbose`, `-v` | Show each declaration's scope details and column, and keep empty optional sections. |
9396
| `--json` | Write the canonical versioned JSON report instead of the text tree. |
9497

9598
Filters combine in a fixed order: definition path, kind, glob, then usage. `--dependency-depth`
@@ -107,11 +110,17 @@ The default text report keeps distinct facts in distinct sections:
107110
- **Scope chain** starts with the resource's owner folder, then shows parent folders and the project
108111
declaration roles. Its compact labels identify the access rule checked at each path.
109112

110-
Ordinary text rows show each declaration's qualified identity, definition location, visibility,
111-
reason for appearing, declaration-role root, and safe type or signature metadata where useful.
112-
`--explain` additionally shows its owning path, narrowest required placement, current consumers,
113-
dependencies, and the consumers affected by a placement mismatch. JSON includes the complete
114-
structured declaration report.
113+
By default, each text row shows a usage marker, the declaration's qualified identity, and its
114+
`file:line` definition. A one-line legend explains the markers. Empty optional sections
115+
(**Relationship grants**, **Nearby unavailable**, **Private retained**, **Relationship retained**,
116+
and **Diagnostics**) are omitted; the core sections are always shown, even when empty.
117+
118+
`--verbose` restores the full rows: kind, visibility, reason for appearing, declaration-role root,
119+
safe type or signature metadata where useful, and the definition column. It also shows empty
120+
optional sections. `--explain` always shows the explained declaration in full, plus its owning
121+
path, narrowest required placement, current consumers, dependencies, and the consumers affected by
122+
a placement mismatch. JSON includes the complete structured declaration report regardless of
123+
`--verbose`.
115124

116125
### See the whole scope at once
117126

@@ -160,28 +169,30 @@ Scope
160169
Path: models/commerce/orders.sql
161170

162171
Used (1)
163-
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:5:1
172+
└─ ● macro:formatted_order_total models/commerce/macros/orders.py:5
164173

165174
Scope chain
166175
├─ exact-owner-private models/commerce (1)
167176
├─ descendant-public models (0)
168177
└─ project global (2)
169178

170179
Available (1 of 3, 2 collapsed)
171-
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:5:1
180+
└─ ● macro:formatted_order_total models/commerce/macros/orders.py:5
172181
… 2 globals collapsed; run sqb scope model:orders --globals all
173182

174-
Relationship grants (0 of 0)
175-
(none)
183+
● used by this resource ○ not used by this resource --verbose for scope details
176184

177-
Nearby unavailable (0 of 0)
178-
(none)
179-
180-
Diagnostics (0)
181-
(none)
182185
Completeness: complete
183186
```
184187

188+
With `--verbose`, the same `Used` section shows why each declaration is visible:
189+
190+
```console
191+
$ sqb scope model:orders --verbose
192+
Used (1)
193+
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:5:1
194+
```
195+
185196
This answers several questions without opening declaration files: what the model actually uses,
186197
where that declaration came from, which owner and parent folders SQLBuild checks, how many
187198
project-wide declarations are available, and whether the result is complete. The scope-chain count
@@ -202,9 +213,9 @@ Scope
202213
Path: models/commerce/orders.sql
203214

204215
Used (3)
205-
├─ ● macro:add_tax [macro; project; dependency; params 1; role macros] macros/currency.py:1:1
206-
├─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:5:1
207-
└─ ● macro:round_money [macro; project; dependency; params 1; role macros] macros/currency.py:5:1
216+
├─ ● macro:add_tax macros/currency.py:1
217+
├─ ● macro:formatted_order_total models/commerce/macros/orders.py:5
218+
└─ ● macro:round_money macros/currency.py:5
208219
```
209220

210221
The remaining report sections follow the `Used` section as usual. The dependency graph comes from

‎src/sqlbuild/.agents/skills/sqlbuild/references/docs/concepts/declaration-scopes/explorer.md‎

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -75,30 +75,32 @@ Scope
7575
Path: models/commerce/orders.sql
7676

7777
Used (2)
78-
├─ ● constant:minimum_order_value [constant; descendant-public; inherited_ancestor; type integer; role models/commerce/constants] models/commerce/constants/limits.sql:1:1
79-
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:4:1
78+
├─ ● constant:minimum_order_value models/commerce/constants/limits.sql:1
79+
└─ ● macro:formatted_order_total models/commerce/macros/orders.py:4
8080

8181
Scope chain
8282
├─ exact-owner-private models/commerce (3)
8383
├─ descendant-public models (0)
8484
└─ project global (2)
8585

8686
Available (3 of 5, 2 collapsed)
87-
├─ ● constant:minimum_order_value [constant; descendant-public; inherited_ancestor; type integer; role models/commerce/constants] models/commerce/constants/limits.sql:1:1
88-
├─ ○ enum:order_status [enum; descendant-public; inherited_ancestor; members 4; type VARCHAR; role models/commerce/enums] models/commerce/enums/status.sql:1:1
89-
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:4:1
87+
├─ ● constant:minimum_order_value models/commerce/constants/limits.sql:1
88+
├─ ○ enum:order_status models/commerce/enums/status.sql:1
89+
└─ ● macro:formatted_order_total models/commerce/macros/orders.py:4
9090
… 2 globals collapsed; run sqb scope model:orders --globals all
9191

92-
Diagnostics (0)
93-
(none)
92+
● used by this resource ○ not used by this resource --verbose for scope details
93+
9494
Completeness: complete
9595
```
9696

9797
`●` marks a declaration included in `Used`: a direct usage by default, or a followed declaration
9898
dependency when `--dependency-depth` is nonzero. `○` marks a declaration present in the section but
9999
not in `Used`.
100100

101-
The compact labels translate to ordinary folder rules:
101+
Add `--verbose` to see each declaration's access details, for example
102+
`[enum; descendant-public; inherited_ancestor; members 4; type VARCHAR; role models/commerce/enums]`.
103+
Those labels translate to ordinary folder rules:
102104

103105
| Output label | Meaning in this report |
104106
|--------------|------------------------|
@@ -122,10 +124,10 @@ declarations. The expanded `Used` section is:
122124
```console
123125
$ sqb scope model:orders --used-only --dependency-depth 1
124126
Used (4)
125-
├─ ● constant:minimum_order_value [constant; descendant-public; inherited_ancestor; type integer; role models/commerce/constants] models/commerce/constants/limits.sql:1:1
126-
├─ ● macro:add_tax [macro; project; dependency; params 1; role macros] macros/currency.py:1:1
127-
├─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:4:1
128-
└─ ● macro:round_money [macro; project; dependency; params 1; role macros] macros/currency.py:5:1
127+
├─ ● constant:minimum_order_value models/commerce/constants/limits.sql:1
128+
├─ ● macro:add_tax macros/currency.py:1
129+
├─ ● macro:formatted_order_total models/commerce/macros/orders.py:4
130+
└─ ● macro:round_money macros/currency.py:5
129131
```
130132

131133
SQLBuild derives these edges from actual Python calls, including nested calls and calls reached
@@ -166,6 +168,8 @@ Explanation
166168
Required path: models/commerce
167169
Promotion impact: model:orders
168170

171+
● used by this resource ○ not used by this resource --verbose for scope details
172+
169173
Diagnostics (1)
170174
ERROR S008 models/commerce/macros/orders.py: Declaration 'macro:formatted_order_total' is currently descendant-public at 'models/commerce' (models/commerce/macros/orders.py); required exact-owner-private at 'models/commerce'. Consumers: model:orders. Move it to 'models/commerce/_sqlbuild/_macros/'
171175
Completeness: complete
@@ -181,8 +185,8 @@ cannot use it. The relevant section is:
181185
```console
182186
$ sqb scope model:orders --include-nearby
183187
Nearby unavailable (2 of 2)
184-
├─ ○ enum:finance_status [enum; descendant-public; sibling_scope; members 3; type VARCHAR; role models/finance/enums] models/finance/enums/status.sql:1:1
185-
└─ ○ macro:calculate_margin [macro; descendant-public; sibling_scope; params 2; role models/finance/macros] models/finance/macros/margin.py:4:1
188+
├─ ○ enum:finance_status models/finance/enums/status.sql:1
189+
└─ ○ macro:calculate_margin models/finance/macros/margin.py:4
186190
```
187191

188192
The declarations are known, but they belong to a different folder branch. Here, the inspected
@@ -218,13 +222,13 @@ report sections are:
218222
```console
219223
$ sqb scope test:orders__completed_only --used-only
220224
Used (1)
221-
└─ ● enum:order_status [enum; descendant-public; expected_model through model:orders; members 4; type VARCHAR; role models/commerce/enums] models/commerce/enums/status.sql:1:1
225+
└─ ● enum:order_status models/commerce/enums/status.sql:1
222226

223227
Relationship grants (1 of 1)
224-
└─ ● enum:order_status [enum; descendant-public; expected_model through model:orders; members 4; type VARCHAR; role models/commerce/enums] models/commerce/enums/status.sql:1:1
228+
└─ ● enum:order_status models/commerce/enums/status.sql:1
225229
```
226230

227-
The compact reason `expected_model through model:orders` means **available through expected output
231+
With `--verbose`, these rows show the reason `expected_model through model:orders`, which means **available through expected output
228232
for model `orders`**. With multiple expected models, SQLBuild combines their eligible file-based
229233
declarations into one deterministic set. Tests include macros in that union; scenarios include only
230234
enums and constants. Neither receives declarations defined inside a model's `MODEL()` header.
@@ -244,19 +248,15 @@ Move preview
244248
Destination: models/finance/orders.sql
245249
Ownership root: models
246250
Retained (2)
247-
├─ ○ macro:add_tax [macro; project; global; params 1; role macros] macros/currency.py:1:1
248-
└─ ○ macro:round_money [macro; project; global; params 1; role macros] macros/currency.py:5:1
251+
├─ ○ macro:add_tax macros/currency.py:1
252+
└─ ○ macro:round_money macros/currency.py:5
249253
Gained (2)
250-
├─ ○ enum:finance_status [enum; descendant-public; inherited_ancestor; members 3; type VARCHAR; role models/finance/enums] models/finance/enums/status.sql:1:1
251-
└─ ○ macro:calculate_margin [macro; descendant-public; inherited_ancestor; params 2; role models/finance/macros] models/finance/macros/margin.py:4:1
254+
├─ ○ enum:finance_status models/finance/enums/status.sql:1
255+
└─ ○ macro:calculate_margin models/finance/macros/margin.py:4
252256
Lost (3)
253-
├─ ● constant:minimum_order_value [constant; descendant-public; inherited_ancestor; type integer; role models/commerce/constants] models/commerce/constants/limits.sql:1:1
254-
├─ ○ enum:order_status [enum; descendant-public; inherited_ancestor; members 4; type VARCHAR; role models/commerce/enums] models/commerce/enums/status.sql:1:1
255-
└─ ● macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:4:1
256-
Private retained (0)
257-
(none)
258-
Relationship retained (0)
259-
(none)
257+
├─ ● constant:minimum_order_value models/commerce/constants/limits.sql:1
258+
├─ ○ enum:order_status models/commerce/enums/status.sql:1
259+
└─ ● macro:formatted_order_total models/commerce/macros/orders.py:4
260260
Invalidated usages (2)
261261
- constant:minimum_order_value
262262
- macro:formatted_order_total
@@ -287,16 +287,12 @@ Scope chain
287287
└─ project global (2)
288288

289289
Available (3 of 5, 2 collapsed)
290-
├─ ○ constant:minimum_order_value [constant; descendant-public; inherited_ancestor; type integer; role models/commerce/constants] models/commerce/constants/limits.sql:1:1
291-
├─ ○ enum:order_status [enum; descendant-public; inherited_ancestor; members 4; type VARCHAR; role models/commerce/enums] models/commerce/enums/status.sql:1:1
292-
└─ ○ macro:formatted_order_total [macro; descendant-public; inherited_ancestor; params 1; role models/commerce/macros] models/commerce/macros/orders.py:4:1
290+
├─ ○ constant:minimum_order_value models/commerce/constants/limits.sql:1
291+
├─ ○ enum:order_status models/commerce/enums/status.sql:1
292+
└─ ○ macro:formatted_order_total models/commerce/macros/orders.py:4
293293
… 2 globals collapsed; run sqb scope --at models/commerce/returns/new_return.sql --globals all
294294

295-
Relationship grants (0 of 0)
296-
(none)
297-
298-
Nearby unavailable (0 of 0)
299-
(none)
295+
● used by this resource ○ not used by this resource --verbose for scope details
300296

301297
Diagnostics (1)
302298
ERROR S013 models/commerce/returns/new_return.sql: Runtime usage and relationship facts are unavailable for a prospective path
@@ -327,8 +323,6 @@ Scope folders
327323
sqb scope model:orders --browse global/macros
328324
sqb scope model:orders --list global/macros
329325

330-
Diagnostics (0)
331-
(none)
332326
Completeness: complete
333327
```
334328

‎src/sqlbuild/.agents/skills/sqlbuild/references/exploring-a-project.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ Common questions:
4444

4545
Report sections: **Used**, **Scope chain** (owner folder, parents, project globals), **Available**
4646
(globals collapsed; `--globals all` expands them), **Relationship grants** (tests and scenarios),
47-
**Nearby unavailable**, **Diagnostics**. Use `--json` for machine-readable output.
47+
**Nearby unavailable**, **Diagnostics**. Rows show each declaration's identity and `file:line`, and
48+
empty optional sections are hidden; add `--verbose` for each declaration's access details and every
49+
section. Use `--json` for machine-readable output.
4850

4951
Placement rules the scope report reflects:
5052

‎src/sqlbuild/cli/commands/_helpers/entry/dispatch.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ def dispatch_cli_command(*, args: CliNamespace, handlers: CliEntrypointHandlers)
8989
paths=args.scope_paths,
9090
json_output=args.json,
9191
no_cache=args.no_cache,
92+
verbose=args.verbose,
93+
no_color=args.no_color,
9294
)
9395
)
9496

‎src/sqlbuild/cli/commands/_helpers/entry/parser.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ def _add_inspection_parsers(
528528
)
529529
scope_parser.add_argument("--json", action="store_true", default=False)
530530
scope_parser.add_argument("--no-cache", action="store_true", default=False)
531+
scope_parser.add_argument("--verbose", "-v", action="store_true", default=False)
531532

532533
debug_parser: argparse.ArgumentParser = subparsers.add_parser(CliCommand.DEBUG)
533534
debug_parser.add_argument("--json", action="store_true", default=False)

‎src/sqlbuild/cli/commands/_helpers/janitor_output/outputs.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from sqlbuild.executor.janitor.models import JanitorExecutionResult, JanitorPlan
1919
from sqlbuild.presentation.classes.cli_style import CliStyle
20+
from sqlbuild.presentation.main.count_noun import format_count_noun
2021

2122

2223
def write_janitor_disabled(*, invocation: JanitorInvocation) -> None:
@@ -121,7 +122,11 @@ def write_janitor_completion(
121122

122123

123124
def _deleted_message(*, result: JanitorExecutionResult) -> str:
124-
archived_prefix: str = f"Archived {len(result.archived)} relations. " if result.archived else ""
125+
archived_prefix: str = (
126+
f"Archived {format_count_noun(count=len(result.archived), singular='relation')}. "
127+
if result.archived
128+
else ""
129+
)
125130
return archived_prefix + _deletion_summary(result=result)
126131

127132

@@ -138,19 +143,21 @@ def _deletion_summary(*, result: JanitorExecutionResult) -> str:
138143
+ len(result.deleted_archives)
139144
+ len(result.deleted_query_diff_artifacts)
140145
)
146+
objects: str = format_count_noun(count=deleted_object_count, singular="object")
141147
pruned_state_count: int = len(result.pruned_direct_state) + len(result.pruned_virtual_state)
142148
non_checkpoint_state_count: int = deleted_state_count - len(result.deleted_checkpoints)
143149
if non_checkpoint_state_count or pruned_state_count:
144150
pruned_state_label: str = (
145-
"state tables" if result.pruned_virtual_state else "direct state tables"
151+
"state table" if result.pruned_virtual_state else "direct state table"
146152
)
147153
return (
148-
f"Deleted {deleted_object_count} objects, deleted {deleted_state_count} "
149-
f"state items, and pruned {pruned_state_count} {pruned_state_label}."
154+
f"Deleted {objects}, deleted "
155+
f"{format_count_noun(count=deleted_state_count, singular='state item')}, and pruned "
156+
f"{format_count_noun(count=pruned_state_count, singular=pruned_state_label)}."
150157
)
151158
if result.deleted_checkpoints:
152-
return (
153-
f"Deleted {deleted_object_count} objects and "
154-
f"{len(result.deleted_checkpoints)} checkpoints."
159+
checkpoints: str = format_count_noun(
160+
count=len(result.deleted_checkpoints), singular="checkpoint"
155161
)
156-
return f"Deleted {deleted_object_count} objects."
162+
return f"Deleted {objects} and {checkpoints}."
163+
return f"Deleted {objects}."

‎src/sqlbuild/cli/commands/_helpers/scope/command.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
ScopeReportFilters,
2929
)
3030
from sqlbuild.compiler.scopes.types import DeclarationKind, DiagnosticSeverity
31+
from sqlbuild.presentation.main.supports_color import supports_color
3132

3233

3334
def run_scope_command(
@@ -107,7 +108,11 @@ def run_scope_command(
107108
output: str = (
108109
serialize_scope_report(report=result)
109110
if request.json_output
110-
else render_scope_result(result=result, request=request)
111+
else render_scope_result(
112+
result=result,
113+
request=request,
114+
use_color=output_stream is None and not request.no_color and supports_color(),
115+
)
111116
)
112117
stream.write(output)
113118
return 1 if _failed(result=result) else 0

0 commit comments

Comments
 (0)