Repository navigation
Expand file tree
/
Copy path.code-moniker.toml
More file actions
638 lines (588 loc) · 40.7 KB
/
Copy path.code-moniker.toml
File metadata and controls
638 lines (588 loc) · 40.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
default_rules = true
[rules.taxonomy]
patterns = [
"composition-root",
"dependency",
"encapsulation",
"hygiene",
"lifecycle",
"ownership",
"single-responsibility",
]
components = [
"catalog",
"code",
"connection",
"coverage",
"data-view",
"daemon",
"debugger",
"ddl-sync",
"editor",
"extension-host",
"language-server",
"query-engine",
"presentation",
"result",
"scratchpad",
"sql-analysis",
"syntax",
"webview",
"workbench",
]
# Test code is excluded by what it is, not only by how it is named: a Playwright page object and a
# VS Code runner config are scaffolding that no rule about the product has anything to say about.
# `e2e/` and `packages/shell/` stay in: both are named owners in the boundary aliases below, and
# a rule cannot allow a directory it no longer reads.
[exclude]
uris = [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.node-test.mjs",
"**/vscode-extension/tests/**",
]
[aliases]
typescript = "uri ~ 'lang:ts/**'"
syntax_factory = "kind = 'imports_symbol' AND target.segment('module') = 'codeMonikerSyntax' AND target.segment('path') = 'createCodeMonikerSyntaxParser'"
syntax_workbench_debugger_owner = "source.segment('module') = 'localCodeMonikerSyntax' OR source.segment('module') = 'indexController' OR source ~ '**/dir:packages/dir:shell/**' OR source ~ '**/dir:e2e/**' OR source.segment('module') = 'codeMonikerSyntax.test'"
workbench_daemon_lifecycle = "kind = 'imports_symbol' AND target.segment('module') = 'localCodeMoniker' AND (target.segment('path') = 'connectLocalCodeMoniker' OR target.segment('path') = 'ensureLocalCodeMonikerWorkspace')"
workbench_daemon_owner = "source.segment('module') = 'indexController' OR source ~ '**/dir:packages/dir:shell/**' OR source ~ '**/dir:e2e/**' OR source.segment('module') = 'localCodeMoniker.test'"
structural_edge = "kind = 'imports_symbol' OR kind = 'imports_module' OR kind = 'uses_type' OR kind = 'calls' OR kind = 'method_call' OR kind = 'instantiates' OR kind = 'extends' OR kind = 'implements'"
engine_package_target = "target ~ '**/dir:packages/**'"
host_local_target = "target ~ '**/dir:vscode-extension/dir:src/**'"
webview_symbol = "moniker ~ '**/dir:packages/dir:views/**'"
[profiles.smells]
enable = ["\\.code-single-responsibility-"]
[profiles.architecture]
enable = [
"syntax-workbench-debugger-ownership-stays-at-runtime-boundaries",
"workbench-daemon-lifecycle-stays-at-runtime-boundaries",
"extension-host-composition-root-is-not-a-service-locator",
"extension-host-workbench-encapsulation-does-not-re-export-engine",
"connection-service-ownership-stays-with-connection-manager",
"workbench-ownership-keeps-tree-drag-destination-neutral",
# declared by the package fragments
"ddl-sync-single-responsibility-excludes-runtime-orchestration",
"scratchpad-single-responsibility-keeps-files-passive",
"language-server-encapsulation-keeps-features-inside-server",
"query-engine-dependency-does-not-point-to-language-server",
"sql-analysis-dependency-stays-inside-sql-analysis",
"catalog-dependency-is-limited-to-sql-analysis",
"coverage-dependency-is-limited-to-sql-analysis",
"debugger-dependency-is-limited-to-sql-analysis",
"result-dependency-is-limited-to-sql-analysis-and-debugger",
"presentation-dependency-is-leaf",
"editor-dependency-is-limited-to-sql-analysis-and-presentation",
"webview-dependency-is-limited-to-catalog-debugger-result-presentation-and-editor",
"workbench-composition-root-stays-independent-of-extension-host",
"workbench-composition-root-keeps-standalone-runtime-independent",
"workbench-dependency-keeps-mcp-behind-runtime",
"result-row-update-builder-encapsulation-stays-in-result-engine",
"data-view-result-protocol-ownership-stays-in-result-engine",
"result-dependency-does-not-point-to-data-view",
"data-view-hidden-column-ownership-stays-in-hidden-columns",
"data-view-query-info-ownership-stays-in-state-contract",
"data-view-edit-policy-ownership-stays-with-the-engine-and-the-grid",
"syntax-grammar-vocabulary-encapsulation-stays-in-postgres-grammar",
"scratchpad-depends-on-engine-only",
"coverage-encapsulation-consumers-enter-through-the-index",
]
[[views]]
id = "postgres-workbench-boundaries"
title = "PostgreSQL Workbench boundaries"
scope = "."
intent = "Review ownership and dependency direction before changing a package boundary, the extension's activation, PostgreSQL Connections, the Workbench Index, the Cockpit, Scratchpads, or DDL synchronization."
summary = """
The shared engine supports editor surfaces and autonomous agents. packages/sql is the syntax leaf — it reads SQL and
PL/pgSQL and answers editor questions through its language server. packages/catalog says what a
database contains and keeps the Workbench Index of it. packages/dap debugs PL/pgSQL. packages/rows
reads and edits result rows. packages/coverage maps executed lines back to source.
packages/presentation owns the host-neutral visual vocabulary and default theme. packages/editor
owns the host-neutral Monaco surface and connects it to the SQL authoring server through the
official Monaco language-client stack. packages/views
renders what the others produce — React views that reach whatever hosts them only through the typed
protocol each view declares, because the same views must run under three shells: a VS Code webview,
a plain web page served by packages/shell, and the Electron application that shell grows into.
A shell composes the engine and adapts it to one runtime; it adds no judgement of its own. The
VS Code extension is one shell — activation, commands, the open Connections, the long-lived
controllers. packages/shell is the second: it runs the same engine, the same language server as a
child process, and the same views in a browser, which is why it is the seed of the Electron
application rather than a test harness. No package depends on any shell.
packages/runtime owns independent PostgreSQL sessions, Scratchpads and immutable captured observations,
composing catalog, debugging and coverage capabilities without an editor. packages/mcp is the stdio
adapter over that runtime. It does not attach to extension-owned sessions or depend on VS Code.
Every language capability — grammar vocabulary, lexical tokens, semantic names, completion — flows
through the SQL authoring server and its one client. A view that reaches for a host API, or a shell
that answers a language question itself, breaks the target on two of the three shells at once.
Each shell reaches the server through a KNOWN LSP client, never a homemade one: the extension
through VS Code's own LanguageClient, and the web and Electron shells through Monaco — Monaco is
the editor of the shells that are not VS Code, driven by monaco-languageclient over a transport the
shell provides. What this project writes itself is host plumbing only: starting the server,
carrying the transport, answering the host-bound requests. The same rule holds for the virtual
sources: their scheme, addressing and content are the engine's (catalog), each shell serves them
into its own document system, and their language features come from the server like every other
document's.
"""
[[views.boundaries]]
id = "sql-analysis"
owns = [
"the Code Moniker syntax boundary for SQL and PL/pgSQL",
"the grammar's vocabulary: generated from the grammars Code Moniker runs, read through one module",
"SQL as text: identifiers, lexing, formatting, document language",
"the query engine: analysis, relations, composition, join planning",
"the SQL authoring language server, its protocol, its one token legend, and the one client every surface asks it through",
"what a PL/pgSQL routine body's own names are: its variables, its parameters, its declared types",
]
forbids = [
"depending on any other package, or on the Extension Host",
"importing what the language server answers with, from outside the server",
"the query engine depending on the language server",
"secondary SQL or PL/pgSQL syntax providers",
"asking a host's own provider registry for what the server answers",
"a question the server asks its host that only one host can answer",
]
forbid_rules = [
"sql-analysis-dependency-stays-inside-sql-analysis",
"language-server-encapsulation-keeps-features-inside-server",
"query-engine-dependency-does-not-point-to-language-server",
"syntax-workbench-debugger-ownership-stays-at-runtime-boundaries",
"syntax-grammar-vocabulary-encapsulation-stays-in-postgres-grammar",
"scratchpad-depends-on-engine-only",
"coverage-encapsulation-consumers-enter-through-the-index",
]
rationale = """
The language server is the front door for editor features; a consumer asks it rather than importing
completion or semantic tokens. Anything needed outside that protocol is a worker called directly:
`query` to rewrite SQL, `text` to read it as text. The package is a leaf because the standalone DAP
executable ships it without VS Code.
There is one client, built on the protocol library the server itself is built on, and every surface
asks through it. A host supplies the two things that genuinely differ — how its documents reach the
server, and what carries the request — and VS Code's own language client satisfies that without
adaptation, which is the point: VS Code is one host of this server, not the condition for having
one. The composition shell runs the server as a child process over stdio, which is the shape a
standalone application takes.
Measured against that, August 2026. The Data View asked VS Code what to propose and how to colour.
VS Code answers with every provider registered for the language, so a second SQL extension installed
beside this one landed its proposals in a Workbench view wearing this one's vocabulary — and outside
VS Code there is no aggregator to ask at all. Colouring a PL/pgSQL body was worse: it existed only
inside a VS Code provider, and the server asked the Extension Host for it and relayed the answer, so
a body opened anywhere else was coloured by nobody and nothing said so.
"""
symbols = [
"ts:packages/sql/src/localCodeMonikerSyntax.class:StatelessCodeMonikerSyntaxRuntime",
"ts:packages/sql/src/analysis/codeMonikerSyntax.function:createCodeMonikerSyntaxParser",
"ts:packages/sql/src/languageServer/client.function:createSqlAuthoringClient",
"ts:packages/sql/src/routines/semanticTokens.function:plpgsqlSemanticTokens",
]
rules = [
"sql-analysis-dependency-stays-inside-sql-analysis",
"language-server-encapsulation-keeps-features-inside-server",
"query-engine-dependency-does-not-point-to-language-server",
]
[[views.boundaries]]
id = "catalog-and-index"
owns = [
"reading the PostgreSQL catalog and projecting it into virtual SQL sources",
"the Code Moniker session and the per-Connection Workbench Index",
"DDL support-schema provisioning SQL and notification reduction",
"the DDL listener lifecycle and the incremental refresh it triggers",
]
forbids = [
"depending on any package other than sql",
"runtime orchestration inside the pure DDL core",
"presentation or result-session state",
]
forbid_rules = [
"ts.refs.catalog.catalog-dependency-is-limited-to-sql-analysis",
"workbench-daemon-lifecycle-stays-at-runtime-boundaries",
]
rationale = "The catalog answers what a database contains; rows, coverage, debugging and rendering consume that answer. postgresDdlSync builds SQL and coalesces notifications deterministically, while ddlSync owns LISTEN state and refresh coordination. The virtual sources are the engine's: the code+moniker:// scheme, the resource index behind it and the canonical symbol addressing all live here, so a shell serves those documents into its own document system — VS Code through a content provider, Electron through its own — without owning what they say. VS Code is one integration modality; it governs nothing, and a virtual source only it can open is a regression against every other shell."
symbols = [
"ts:packages/catalog/src/indexController.class:WorkbenchIndexController",
"ts:packages/catalog/src/localCodeMoniker.class:LocalCodeMonikerSession",
"ts:packages/catalog/src/ddlSync.class:WorkbenchDdlSyncController",
"ts:packages/catalog/src/postgresCatalog.function:readPostgresCatalog",
"ts:packages/catalog/src/postgresDdlSync.function:buildWorkbenchDdlProvisioningSql",
]
rules = ["ts.refs.catalog.catalog-dependency-is-limited-to-sql-analysis", "ddl-sync-single-responsibility-excludes-runtime-orchestration", "workbench-daemon-lifecycle-stays-at-runtime-boundaries"]
[[views.boundaries]]
id = "debugger"
owns = [
"the DAP session and the pldbgapi backend",
"two-pass variable resolution",
"exact preservation of client-owned source URIs, with a sourceReference fallback",
]
forbids = [
"depending on any package other than sql",
"workspace daemons, indexed sources, catalog projection, or DDL synchronization",
]
forbid_rules = ["ts.refs.dap.debugger-dependency-is-limited-to-sql-analysis"]
rationale = "The VS Code adapter and the published standalone executable are two hosts over one library. Neither lets the debugger read or index the user's workspace; a Workbench host may supply canonical source URIs, which the DAP preserves exactly."
symbols = [
"ts:packages/dap/src/debugger/session/PlpgsqlDebugSession.class:PlpgsqlDebugSession",
"ts:packages/dap/src/debugger/session/sourceRegistry.function:clientSourceUris",
]
rules = ["ts.refs.dap.debugger-dependency-is-limited-to-sql-analysis"]
[[views.boundaries]]
id = "result-rows"
owns = [
"the LIMIT/OFFSET paging contract every result surface reads rows through",
"what a result is: its payload, its navigation, and writing it out",
"the Data View, in src/dataView: its protocol, its query, its editability, its pending edits, and which of its columns are hidden",
]
forbids = [
"depending on any package other than sql and dap",
"VS Code, a webview, or the catalog",
"reading, above the Data View, anything the Data View owns",
]
forbid_rules = [
"ts.refs.rows.result-dependency-is-limited-to-sql-analysis-and-debugger",
"result-dependency-does-not-point-to-data-view",
]
rationale = "A result comes from a query the SQL layer understands or a debugged call the DAP layer captured. The Scratchpad output and the Data View read rows through the same LIMIT/OFFSET contract; depending on the catalog or the views would close a cycle around the thing every result view renders. Two thirds of the package is the Data View, which is why it sits in a directory of its own: it reaches up for paging, the payload, navigation and the export writer, and nothing above it reaches down. What a host may not do is answer for the Data View itself — both of them held their own hidden columns, and both wrote out the ones the reader had hidden."
symbols = [
"ts:packages/rows/src/offsetQuery.class:OffsetResultSession",
"ts:packages/rows/src/dataView/hiddenColumns.class:HiddenColumns",
]
rules = [
"ts.refs.rows.result-dependency-is-limited-to-sql-analysis-and-debugger",
"result-dependency-does-not-point-to-data-view",
"data-view-result-protocol-ownership-stays-in-result-engine",
"data-view-hidden-column-ownership-stays-in-hidden-columns",
]
[[views.boundaries]]
id = "data-view-editing"
owns = [
"what a change is: a cell edit, a row taken away, a row added — and the row identity all three are held against",
"what may be written and why not: the column policy, the one table whole rows go to, what a deletion drags along",
"what is waiting: the three lists, the one door every move goes through, and the way back to what was held before it",
"how a change is written: the guarded UPDATE, the DELETE by identity, the INSERT of the columns filled in",
"the single transaction the three are written in, and the ROLLBACK that leaves the database as it was",
]
forbids = [
"a surface deciding whether a cell may be written, or wording the refusal itself",
"a surface assembling a statement that writes a row",
"a surface performing a move on the held changes other than by handing it to PendingEdits.move",
"taking a change while the transaction that would carry it is in flight",
]
forbid_rules = [
"data-view-edit-policy-ownership-stays-with-the-engine-and-the-grid",
"result-row-update-builder-encapsulation-stays-in-result-engine",
]
rationale = """
The hot path. A reader changes a cell, takes rows away, adds one and fills it in, reads back the
list of what is waiting, and then writes all of it at once or throws all of it away. Nothing here
touches PostgreSQL until they say so, and when they do it is one transaction: every statement or
none, guarded against the row having moved under them since it was read.
The journey is four questions asked in one place each. `editability` says what may be written and
why not, and both the grid and the held changes ask it, so a reader who is refused is refused in
the same words wherever they meet it. `dataView` says what a change is and what each one will do,
in the reader's language, before they commit to it. `pendingEdits` holds the three lists and is the
one door every move goes through. `updates` turns them into SQL, and nothing else assembles a
statement that writes a row.
A surface brings what it holds and no judgement: what may be written, which columns it is showing,
a connection to write through, a way to say what happened, a way to tell whatever shows the changes,
and — where it has one — a way to remember the move so its own undo stack keeps step. A surface
speaks the same way whether a change is being held or written, so it says that once. Everything
else is decided before it is handed anything.
Measured against that, August 2026. Both surfaces answered all six moves themselves, and what was
true of one move was true of the others only by coincidence. It had already stopped being true: a
write in flight refused the cell edits, because that was the only move that asked. A row added
meanwhile was held, and then dropped without a word when the transaction that never carried it
cleared what was waiting — the reader was told their changes had been applied, and the row they had
just added was gone. One door, one guard, one refusal, one undo entry, and the six moves named
together as `DataViewMove`: a seventh is added here, and both surfaces route it untouched.
"""
symbols = [
"ts:packages/rows/src/dataView/pendingEdits.class:PendingEdits",
"ts:packages/rows/src/dataView/pendingEdits.type:DataViewMove",
"ts:packages/rows/src/dataView/editability.function:reasonAgainstWriting",
"ts:packages/rows/src/dataView/updates.function:buildRowUpdates",
]
rules = [
"data-view-edit-policy-ownership-stays-with-the-engine-and-the-grid",
"result-row-update-builder-encapsulation-stays-in-result-engine",
"data-view-hidden-column-ownership-stays-in-hidden-columns",
]
[[views.boundaries]]
id = "presentation"
owns = [
"the typed visual roles shared by every rendered surface",
"the complete default Workbench theme",
"PostgreSQL visual identities independent of any host theme API",
]
forbids = [
"depending on another product package or a host",
"naming VS Code, Monaco, Electron, CSS framework, or transport concepts",
"consumer-specific theme projections",
]
forbid_rules = ["ts.refs.presentation.presentation-dependency-is-leaf"]
rationale = "A view names the role a visual value plays and a host may override that role. The shared vocabulary and its working defaults therefore precede every renderer; VS Code token names belong to the extension adapter, while future Monaco and Electron adapters consume the same roles without changing them."
symbols = [
"ts:packages/presentation/src/roles.const:WORKBENCH_THEME_ROLES",
"ts:packages/presentation/src/presentation.function:postgresVisual",
]
rules = ["ts.refs.presentation.presentation-dependency-is-leaf"]
[[views.boundaries]]
id = "editor"
owns = [
"the host-neutral Monaco editor surface consumed by every non-VS-Code renderer",
"the official Monaco language-client integration with the SQL authoring server",
"Monaco workers, editor lifecycle, models, and theme projection from presentation roles",
]
forbids = [
"depending on views, shell, Electron, or the VS Code Extension Host",
"implementing SQL or PL/pgSQL parsing, colouring, completion, or LSP transport semantics locally",
"depending on any product package other than sql and presentation",
]
forbid_rules = ["ts.refs.editor.editor-dependency-is-limited-to-sql-analysis-and-presentation"]
rationale = "Monaco is reusable infrastructure, not a view and not a shell. It consumes the SQL authoring and presentation contracts, while views embed it and shells provide transport. Keeping that direction explicit prevents an editor convenience from pulling host state or feature policy back into the engine."
symbols = ["ts:packages/editor/src/MonacoSqlEditor.function:MonacoSqlEditor"]
rules = ["ts.refs.editor.editor-dependency-is-limited-to-sql-analysis-and-presentation"]
[[views.boundaries]]
id = "webview-rendering"
owns = [
"the React result grid, its navigation, and the three views that nest it",
"the Cockpit graph and its interaction state",
"generic navigation structures supplied with routes by their host",
]
forbids = [
"depending on the Extension Host",
"depending on any package other than catalog, dap, rows, presentation and editor",
"PostgreSQL transport, Code Moniker sessions, or VS Code state",
"declaring the protocol it speaks: the Data View's belongs to the engine",
]
forbid_rules = ["ts.refs.views.webview-dependency-is-limited-to-catalog-debugger-result-presentation-and-editor"]
rationale = "A webview is a separate runtime, and for these views it is three runtimes: a VS Code webview, the composition shell's browser page, and the Electron renderer to come. The views render values and post messages; connections, sessions and command execution stay in whichever host is present and cross the boundary through the protocol each view declares. Measured against that, August 2026: the second grammar the views ran — Shiki over a TextMate file read from the extension's directory — is gone, and every colour on every source surface, the Cockpit preview included, is a kind of the language server's one legend, named by the view and valued by the host. SQL and PL/pgSQL editor features now cross the same host-neutral LSP boundary; browser packages may not register parallel Monaco providers."
symbols = [
"ts:packages/views/src/cockpit/graph/store.interface:CockpitStore",
"ts:packages/views/src/navigation/WorkbenchNavigation.function:WorkbenchNavigation",
]
rules = ["ts.refs.views.webview-dependency-is-limited-to-catalog-debugger-result-presentation-and-editor"]
[[views.boundaries]]
id = "activation"
owns = [
"extension activation and disposal",
"VS Code command registration",
"construction and wiring of the long-lived controllers",
"the public extension API",
]
forbids = [
"being imported as a service locator by a product module",
"owning feature persistence or semantic caches",
"holding logic that does not adapt anything to VS Code",
"registering a VS Code command anywhere but a module's registerCommands",
]
forbid_rules = ["extension-host-composition-root-is-not-a-service-locator"]
rationale = """
Activation chooses implementations and binds commands; feature state stays in the controller that
defines its lifecycle. The extension adapts the engine to VS Code and holds nothing else, because
the same engine is meant to run under a host that is not VS Code at all.
Measured against that, September 2026. The pure modules moved to the packages their own imports
named — the notebook file format seeded packages/scratchpad, debuggability joined the dap launch
contract, associations and the connection registry joined catalog, the coverage report shapes
joined coverage, graph navigation joined the views — and the package rules now keep each of them
there. The re-exports that made engine code look like extension code are gone, and a rule holds
that one.
What is left is named and guarded rather than enforced by a rule, because neither fact lives in
the reference graph. scripts/extension/check-extension-adapts.mjs lists each remaining file with
its reason: the adapters that are legitimately VS Code's — the DAP entry, the shared error line,
the theme projection, the webview transport and its API shim, the notebook renderer entry. The
last two debts dissolved in September 2026: coverage/client was a duplicate of the
ConnectionManager's own client plumbing and became ConnectionManager.openClient, and
debug/launchConfiguration joined the debugger's launch contract once its Connection needs were
declared as ports the host satisfies. The guard fails on a new entry, and on an entry that has
started adapting something.
A command is registered in a module's registerCommands. No reference rule can hold that: `vscode`
is an ambient module, `registerCommand` has no indexed symbol, and a rule written against it reports
zero violations while twenty-five sit in front of it. The guard is
scripts/extension/check-command-registrars.mjs instead, in the same `npm run check` the commit hook
runs — it names the three files excused from the convention and the reason each is, and it fails
both on a registration anywhere else and on an excuse that has stopped registering anything.
"""
symbols = ["ts:vscode-extension/src/extension.interface:PlpgsqlExtensionApi"]
rules = ["extension-host-composition-root-is-not-a-service-locator"]
[[views.boundaries]]
id = "connections"
owns = [
"saved Connection identities and their secrets",
"the open Connections and the events that announce them",
"dedicated PostgreSQL client construction",
]
forbids = [
"a feature building its own ConnectionService",
"owning the semantic index or a result session",
]
forbid_rules = ["connection-service-ownership-stays-with-connection-manager"]
rationale = "ConnectionManager is the single live facade over PostgreSQL transport. A feature building its own ConnectionService would create a second lifecycle nothing reconnects or disposes."
symbols = [
"ts:vscode-extension/src/connection/openConnections.class:ConnectionManager",
"ts:vscode-extension/src/connection/connectPostgres.class:ConnectionService",
"ts:vscode-extension/src/connection/savedConnections.class:ConnectionStore",
"ts:packages/catalog/src/savedConnection.interface:ConnectionConfig",
]
rules = ["connection-service-ownership-stays-with-connection-manager"]
[[views.boundaries]]
id = "scratchpads"
owns = [
"the Scratchpad file format and its Association value objects",
"workspace persistence of Scratchpads",
"dedicated execution connections and Transaction mode",
"hosting the LIMIT/OFFSET result state a notebook cell renders",
]
forbids = [
"connection or execution lifecycle inside the serialized file",
"reusing the shared interactive client for notebook execution",
]
forbid_rules = ["scratchpad-single-responsibility-keeps-files-passive"]
rationale = "A Scratchpad opens before anything connects, so reading and writing one must not need a live Connection. Execution, explicit transactions and LIMIT/OFFSET paging belong to the registerCommands host module, resultHost and the shared row engine."
symbols = [
"ts:packages/scratchpad/src/notebookFile.interface:SqlNotebookFile",
"ts:vscode-extension/src/scratchpad/workspace.class:SqlNotebookWorkspace",
"ts:vscode-extension/src/scratchpad/resultHost.class:SqlNotebookResultHost",
]
rules = ["scratchpad-single-responsibility-keeps-files-passive"]
[[views.boundaries]]
id = "tree-and-cockpit"
owns = [
"the Workbench tree projection of index snapshots",
"destination-neutral drag data interpreted by the receiving surface",
"the Cockpit host: navigation, snapshot validation, and the webview panel",
"selection synchronization between the tree and the graph",
]
forbids = [
"PostgreSQL transport construction",
"owning the Code Moniker session",
"the tree drag choosing the Cockpit or any other destination by default",
]
forbid_rules = ["workbench-ownership-keeps-tree-drag-destination-neutral"]
rationale = "Tree and Cockpit project what the index holds. The tree publishes object intent without choosing an outcome: SQL editors and Scratchpads compose, Data Views extend their query, and the Cockpit focuses the graph. Opening the graph is an explicit tree action. The host validates context and performs actions; the webview renders and manipulates protocol values."
symbols = [
"ts:vscode-extension/src/workbench/tree.class:WorkbenchTreeProvider",
"ts:vscode-extension/src/cockpit/view.class:WorkbenchGraphView",
"ts:vscode-extension/src/cockpit/treeSync.class:WorkbenchGraphTreeSync",
]
rules = ["workbench-ownership-keeps-tree-drag-destination-neutral"]
[[views.gotchas]]
id = "the-language-server-is-also-a-worker"
rationale = "The SQL language server is the front door for consumers, and at the same time a client of the Extension Host: it cannot parse. Code Moniker lives in the host, so the server sends `postgresql-workbench/syntax` back for every completion, every semantic token and every composition. Nothing in the directory layout shows this inversion; the protocol file does."
symbols = ["ts:packages/sql/src/languageServer/protocol.const:SQL_AUTHORING_SYNTAX_REQUEST"]
check = "code_moniker_rules uri=\"workspace\" action=\"run\" profile=\"architecture\" report=true limit=200"
[[views.gotchas]]
id = "a-rule-that-guards-nothing"
rationale = "Every boundary rule lives in the fragment of the package it constrains, and the workspace file keeps only the vocabulary that is general. A rule whose subject is renamed keeps passing while checking nothing, so `check --report` names any rule whose antecedent never matched, and each rule here was proved by planting a forbidden edge and watching it fire."
check = "code_moniker_rules uri=\"workspace\" action=\"run\" profile=\"architecture\" report=true limit=200"
[[views.gotchas]]
id = "a-move-is-not-an-indexed-edge"
rationale = "The door that every move on a Data View's held changes goes through is held by TypeScript and by a test, not by a rule — and that is not a choice. A call to `edits.move(...)` is a method call on a field, and this index links no reference from either surface to a method of `PendingEdits`: a rule written against one reports zero violations while both hosts sit in front of it. So the six mutators are private, which makes the door the only way in and a second one a compile error rather than a review comment; the moves are named in a record over their own union, so a seventh cannot be added without being named, labelled and performed; and pendingEdits.test proves the guard by holding a transaction open and attempting all six moves against it. What the graph can see is guarded by rules instead: the policy that refuses a write, and the three builders that assemble one."
symbols = ["ts:packages/rows/src/dataView/pendingEdits.class:PendingEdits"]
check = "npx vitest run packages/rows/src/dataView/pendingEdits.test.ts"
[[views.gotchas]]
id = "syntax-tokens-have-one-chain-of-ownership"
rationale = "Everything a colouring surface is told descends one chain, and each link has exactly one author. PostgreSQL authors the grammar: gram.y and kwlist.h generate tree-sitter-postgres, whose node-types.json is the complete list of what a parse can say. scripts/sql/generate-grammar-kinds.mjs reads that list from the two grammars Code Moniker actually runs — the locked SQL crate, and the vendored PL/pgSQL copy Code Moniker modifies for quoted block labels — and writes grammarKinds.ts with its provenance; the e2e grammar test parses with the real parser and refuses any kind the committed copy does not declare, both proved by planting. postgresGrammar.ts is the one reading of that list — the few real decisions over it — and a rule keeps every other layer behind it: debugger stepping, coverage labels, and the lexical layer all ask it. The server's legend is then COMPOSED, never re-spelled: it opens with the PL/pgSQL routine legend, whose tokens arrive already numbered and are pushed into the stream unchanged — correct only because that legend is this one's opening segment, which is now true by construction — and it closes with the lexical kinds, appended so an older client's numbering still holds. The views hold no list at all: they receive kinds by name, resolved against the legend the initialize handshake declared."
symbols = [
"ts:packages/sql/src/analysis/postgresGrammar.function:plpgsqlStep(procedureStatement:SyntaxNode)",
"ts:packages/sql/src/languageServer/features/semanticTokens.const:SQL_SEMANTIC_TOKEN_TYPES",
]
check = "npx vitest run e2e/postgres-grammar.test.ts e2e/sql-lexical-tokens.test.ts e2e/semantic-tokens.test.ts"
[[views.gotchas]]
id = "one-language-authority-is-guarded-by-a-script"
rationale = "The application is meant to run under a host that is not VS Code, and two ways of losing that are invisible to every other check here. A surface can ask VS Code's provider registry instead of this server — which answers with everyone's providers, and answers nothing at all outside VS Code. And a question the server sends back to its host can end up with only one host able to answer it, at which point the capability is VS Code's rather than the application's; the server catches such a failure, so the feature simply stops without a word. check-extension-adapts asks whether a file adapts anything to VS Code and is blind to both — the files at fault imported `vscode` and passed. scripts/test/check-language-authority.mjs asks the other question: it reads the requests the server actually sends, requires every host to answer each of them, and refuses any aggregator call in the extension. It fails on a host that stops answering, on a surface that goes back to asking VS Code, and on losing sight of the server itself; all three were proved by planting them."
symbols = ["ts:packages/sql/src/languageServer/client.function:createSqlAuthoringClient"]
check = "node scripts/test/check-language-authority.mjs"
[[views.gotchas]]
id = "lifecycle-concentration"
rationale = "All smell rules are blocking errors. WorkbenchIndexController and WorkbenchGraphView carry explicit technical debt: their snapshot-bound capability surfaces are not split yet."
symbols = [
"ts:packages/catalog/src/indexController.class:WorkbenchIndexController",
"ts:vscode-extension/src/cockpit/view.class:WorkbenchGraphView",
]
check = "code_moniker_rules uri=\"workspace\" action=\"run\" profile=\"smells\" report=true limit=200"
[[views.gotchas]]
id = "test-coverage-is-layered"
rationale = "Unit tests sit beside the code they cover and run under vitest. The e2e suites need a real PostgreSQL or a real Code Moniker parser; the VS Code suites need a real VS Code; the Playwright lanes drive it. A module with no unit callers is a layering fact, not evidence of no test."
check = "npm test && npm run test:e2e:run"
[[views.gotchas]]
id = "a-command-title-is-read-twice"
rationale = "The palette prints `category: title`; every menu prints `title` alone. A prefix written into the title therefore appears in full on a right-click, and a command with no category reads like one of VS Code's own. None of this is indexed — `package.json` holds no symbol, and the on-demand parser takes no JSON — so it is guarded by scripts/extension/check-manifest-text.mjs, which also refuses a second category root, and by check-command-registrars.mjs, which refuses a declared command nothing registers: VS Code accepts that declaration in silence and throws only when a reader runs it."
check = "npm run check"
[[ts.function.where]]
id = "code-webview-hygiene-enforces-function-casing"
severity = "error"
expr = "$camel OR $ts_callback OR ($webview_symbol AND name =~ ^[A-Z][A-Za-z0-9]*$)"
message = "Functions use camelCase; React components in the Workbench webview use PascalCase. Rename `{name}`."
rationale = "PascalCase is part of the React component contract and lets the hooks linter recognise stateful components. packages/views holds every React view, so the exemption stops exactly at that package."
[[ts.shape.annotation.where]]
id = "code-hygiene-keeps-comments-outside-callables"
severity = "error"
expr = "parent.shape != 'callable'"
message = "Move this explanation into naming or extracted code, outside the function."
rationale = "Comments inside functions hide executable intent and can mask gaps in line coverage. Functions should remain understandable through their names, structure, and tests."
[[ts.function.where]]
id = "code-single-responsibility-flags-long-callables"
severity = "warn"
expr = "lines <= 300"
message = "This callable is unusually long; verify that it still represents one coherent responsibility."
rationale = "Callables above this emergency bound deserve review for a concrete ownership or control-flow seam. The generous threshold avoids encouraging artificial indirection; this remains a review signal, not an architectural boundary."
[[ts.method.where]]
id = "code-single-responsibility-flags-long-callables"
severity = "warn"
expr = "lines <= 300"
message = "This callable is unusually long; verify that it still represents one coherent responsibility."
rationale = "Callables above this emergency bound deserve review for a concrete ownership or control-flow seam. The generous threshold avoids encouraging artificial indirection; this remains a review signal, not an architectural boundary."
[[ts.constructor.where]]
id = "code-single-responsibility-flags-long-callables"
severity = "warn"
expr = "lines <= 300"
message = "This callable is unusually long; verify that it still represents one coherent responsibility."
rationale = "Callables above this emergency bound deserve review for a concrete ownership or control-flow seam. The generous threshold avoids encouraging artificial indirection; this remains a review signal, not an architectural boundary."
[[ts.function.where]]
id = "code-single-responsibility-flags-long-parameter-lists"
severity = "warn"
expr = "count(param) <= 6"
message = "This callable has a long parameter list; check whether the parameters form a stable concept."
rationale = "Long parameter lists can reveal missing abstractions or excessive responsibility, but adapters and protocol handlers may legitimately need them."
[[ts.method.where]]
id = "code-single-responsibility-flags-long-parameter-lists"
severity = "warn"
expr = "count(param) <= 6"
message = "This callable has a long parameter list; check whether the parameters form a stable concept."
rationale = "Long parameter lists can reveal missing abstractions or excessive responsibility, but adapters and protocol handlers may legitimately need them."
[[ts.constructor.where]]
id = "code-single-responsibility-flags-long-parameter-lists"
severity = "warn"
expr = "count(param) <= 6"
message = "This callable has a long parameter list; check whether the parameters form a stable concept."
rationale = "Long parameter lists can reveal missing abstractions or excessive responsibility, but adapters and protocol handlers may legitimately need them."
[[ts.class.where]]
id = "code-single-responsibility-flags-large-classes"
severity = "warn"
expr = "count(method) <= 20 AND count(field) <= 30"
message = "This class has a large surface; verify that its responsibilities remain cohesive."
rationale = "Large classes often accumulate unrelated responsibilities. The TypeScript class domain and its native method and field collections deliberately exclude interfaces and type declarations from this implementation-oriented signal."
[[ts.class.where]]
id = "code-single-responsibility-flags-method-size-disharmony"
severity = "warn"
expr = "lines <= 300 OR count(method) < 6 OR cv(method, lines) <= 1.0"
message = "Method sizes vary strongly inside this class; inspect whether one method carries disproportionate responsibility."
rationale = "A strong size imbalance can expose orchestration or domain logic concentrated in one method. Classes of at most 300 lines are excluded because their distributions are too small to make this signal useful."
[[ts.class.where]]
id = "code-single-responsibility-flags-concentrated-method-fanout"
severity = "warn"
expr = "count(method) < 6 OR gini(method, fan_out(each)) <= 0.7"
message = "Outgoing dependencies are concentrated in a small part of this class."
rationale = "Concentrated fan-out can identify methods that coordinate too many collaborators. The TypeScript class domain deliberately excludes interfaces because signatures have no executable coordination to review; this remains only a navigation signal."
[[ts.refs.where]]
id = "syntax-workbench-debugger-ownership-stays-at-runtime-boundaries"
severity = "error"
expr = "$syntax_factory => $syntax_workbench_debugger_owner"
message = "Create the Code Moniker syntax provider only in the Workbench or DAP runtime boundary."
rationale = "Feature visitors receive SyntaxParser or narrower business ports; they never create clients, daemons, or parallel AST providers."
[[ts.refs.where]]
id = "workbench-daemon-lifecycle-stays-at-runtime-boundaries"
severity = "error"
expr = "$workbench_daemon_lifecycle => $workbench_daemon_owner"
message = "Connect to or ensure the Code Moniker workspace only at a Workbench runtime boundary."
rationale = "Feature modules receive indexed capabilities from their host; the Workbench index owner, composition shell, and end-to-end harnesses are the only production or proof boundaries that own a daemon-backed workspace lifecycle."