@monosmith/plugin-sdk 0.5.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monosmith/plugin-sdk",
3
- "version": "0.5.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "The TypeScript SDK for writing smith plugins: the protocol's types, the serve loop, a test host, the conformance checks, templating and key-order-keeping JSON editing. No runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -207,6 +207,39 @@ export const protocolSchema = {
207
207
  },
208
208
  "capability": "latest",
209
209
  "notification": false
210
+ },
211
+ "sbom/components": {
212
+ "description": "Asks which components a project ships (its locked dependencies, read from a lockfile where the ecosystem has one and from the manifest otherwise), as package URLs, for the SBOM a release attaches.",
213
+ "params": {
214
+ "$ref": "#/$defs/ComponentsParams"
215
+ },
216
+ "result": {
217
+ "$ref": "#/$defs/ComponentsResult"
218
+ },
219
+ "capability": "components",
220
+ "notification": false
221
+ },
222
+ "derive": {
223
+ "description": "Asks which files a plugin derives from facts and the project graph (a tsconfig's references, a test config's display name). The plugin writes nothing: it answers with the content, and the host rewrites a file only when its ownership record allows.",
224
+ "params": {
225
+ "$ref": "#/$defs/DeriveParams"
226
+ },
227
+ "result": {
228
+ "$ref": "#/$defs/DeriveResult"
229
+ },
230
+ "capability": "derive",
231
+ "notification": false
232
+ },
233
+ "api/surface": {
234
+ "description": "Asks for the public API names of a project, at the working tree or at a git revision (its last release tag), so a release can compare them and say what bump the change needs.",
235
+ "params": {
236
+ "$ref": "#/$defs/APISurfaceParams"
237
+ },
238
+ "result": {
239
+ "$ref": "#/$defs/APISurfaceResult"
240
+ },
241
+ "capability": "apiSurface",
242
+ "notification": false
210
243
  }
211
244
  },
212
245
  "x-errorCodes": [
@@ -287,6 +320,42 @@ export const protocolSchema = {
287
320
  }
288
321
  ],
289
322
  "$defs": {
323
+ "APISurfaceParams": {
324
+ "description": "APISurfaceParams asks a plugin for the public API names of one project.",
325
+ "type": "object",
326
+ "properties": {
327
+ "project": {
328
+ "$ref": "#/$defs/Project"
329
+ },
330
+ "ref": {
331
+ "type": "string",
332
+ "description": "Ref is the git revision to read the project at (the tag of its last release). Empty means the working tree."
333
+ }
334
+ },
335
+ "required": [
336
+ "project"
337
+ ]
338
+ },
339
+ "APISurfaceResult": {
340
+ "description": "APISurfaceResult is the public API of a project: the names its entry points export. A host compares the names of two revisions; it never reads their meaning, so a plugin chooses its own spelling and keeps it stable.",
341
+ "type": "object",
342
+ "properties": {
343
+ "names": {
344
+ "type": "array",
345
+ "items": {
346
+ "type": "string"
347
+ },
348
+ "description": "Names are the exported names, sorted. A name an entry point other than the main one exports is prefixed with that entry point and a colon (\"./cli:run\"). An empty list is an answer: the project exports nothing."
349
+ },
350
+ "unreadable": {
351
+ "type": "string",
352
+ "description": "Unreadable says why the API could not be listed (no entry point, no source at the revision). When set, Names is empty and the host skips the project without a warning."
353
+ }
354
+ },
355
+ "required": [
356
+ "names"
357
+ ]
358
+ },
290
359
  "ActionStep": {
291
360
  "description": "ActionStep is a provider-native step: a GitHub action or an Azure task, with its inputs in the order they are written.",
292
361
  "type": "object",
@@ -531,6 +600,22 @@ export const protocolSchema = {
531
600
  "cancel": {
532
601
  "type": "boolean",
533
602
  "description": "Cancel says the plugin stops the work of a request when the host sends a cancel notification with its id, then answers that request with its result (if the work was already done) or the request-cancelled error. A host never sends the notification to a plugin without it."
603
+ },
604
+ "components": {
605
+ "type": "boolean",
606
+ "description": "Components lists a project's dependencies for its SBOM."
607
+ },
608
+ "derive": {
609
+ "type": "boolean",
610
+ "description": "Derive says which files the plugin derives from facts and the project graph (smith derive)."
611
+ },
612
+ "apiSurface": {
613
+ "type": "boolean",
614
+ "description": "APISurface lists the names a project exports, at a git revision, so a release can warn when the commits ask for a smaller bump than the API change needs."
615
+ },
616
+ "channels": {
617
+ "type": "boolean",
618
+ "description": "Channels says a prerelease version the plugin publishes never takes the place of the stable one: the plugin publishes under PublishParams.Tag, a release channel's name, without moving the registry's stable pointer (npm's latest), or its registry orders a prerelease below the release (PyPI, NuGet) and needs no tag. A host does not publish a package of a release channel with a plugin that lacks it."
534
619
  }
535
620
  }
536
621
  },
@@ -588,6 +673,124 @@ export const protocolSchema = {
588
673
  }
589
674
  }
590
675
  },
676
+ "Choice": {
677
+ "description": "Choice is one option on an axis of a project's make-up: a framework, a test runner, a style library. A plugin publishes the choices it owns in InitializeResult.Choices; the host checks a selection of them (smith add --with react,vitest,bootstrap) and turns it into a stack. What a choice writes (dependencies, tasks, files) stays with the plugin: the host never sees it.",
678
+ "type": "object",
679
+ "properties": {
680
+ "axis": {
681
+ "type": "string",
682
+ "description": "Axis is the question the choice answers: \"framework\", \"test\", \"style\", \"lint\". The plugins agree on the name; the host only compares it."
683
+ },
684
+ "name": {
685
+ "type": "string",
686
+ "description": "Name identifies the choice in a selection and is unique among the choices of all loaded plugins."
687
+ },
688
+ "description": {
689
+ "type": "string"
690
+ },
691
+ "language": {
692
+ "type": "string",
693
+ "description": "Language is the language plugin the choice applies to (\"node\")."
694
+ },
695
+ "roles": {
696
+ "type": "array",
697
+ "items": {
698
+ "$ref": "#/$defs/Role"
699
+ },
700
+ "description": "Roles are the roles the choice's plugin plays in a stack built from it: scaffold (it writes the project) and/or toolchain (it joins the toolchain). Empty means it only adds to what a plugin of the selection already writes, as a style does."
701
+ },
702
+ "needs": {
703
+ "type": "array",
704
+ "items": {
705
+ "type": "string"
706
+ },
707
+ "description": "Needs are axes that the selection must have a choice on. An axis the person leaves out is filled with the default of the axis."
708
+ },
709
+ "default": {
710
+ "type": "boolean",
711
+ "description": "Default marks the choice the host adds when a selection needs its axis and has no choice on it."
712
+ },
713
+ "many": {
714
+ "type": "boolean",
715
+ "description": "Many allows the choice next to other choices of its axis that are Many too. By default a selection has one choice per axis."
716
+ },
717
+ "requires": {
718
+ "type": "array",
719
+ "items": {
720
+ "type": "string"
721
+ },
722
+ "description": "Requires are choices that must be in the selection."
723
+ },
724
+ "conflicts": {
725
+ "type": "array",
726
+ "items": {
727
+ "type": "string"
728
+ },
729
+ "description": "Conflicts are choices that must not be in the selection."
730
+ }
731
+ },
732
+ "required": [
733
+ "axis",
734
+ "name",
735
+ "language"
736
+ ]
737
+ },
738
+ "Component": {
739
+ "description": "Component is one dependency of a project.",
740
+ "type": "object",
741
+ "properties": {
742
+ "name": {
743
+ "type": "string"
744
+ },
745
+ "version": {
746
+ "type": "string",
747
+ "description": "Version is the locked version, or the declared range when the project has no lockfile."
748
+ },
749
+ "purl": {
750
+ "type": "string",
751
+ "description": "PURL is the package URL (https://github.com/package-url/purl-spec)."
752
+ }
753
+ },
754
+ "required": [
755
+ "name",
756
+ "purl"
757
+ ]
758
+ },
759
+ "ComponentsParams": {
760
+ "description": "ComponentsParams asks a plugin for the components one of its projects ships: what a CycloneDX SBOM lists.",
761
+ "type": "object",
762
+ "properties": {
763
+ "project": {
764
+ "$ref": "#/$defs/Project"
765
+ }
766
+ },
767
+ "required": [
768
+ "project"
769
+ ]
770
+ },
771
+ "ComponentsResult": {
772
+ "description": "ComponentsResult lists the components, read from a lockfile where the ecosystem has one and from the manifest otherwise. An empty list is an answer: the project declares no dependencies.",
773
+ "type": "object",
774
+ "properties": {
775
+ "components": {
776
+ "type": "array",
777
+ "items": {
778
+ "$ref": "#/$defs/Component"
779
+ }
780
+ },
781
+ "source": {
782
+ "type": "string",
783
+ "description": "Source is the workspace-relative file the list was read from, \"\" when the project has none."
784
+ },
785
+ "purl": {
786
+ "type": "string",
787
+ "description": "PURL is the project's own package URL when it has a registry identity (without a version: the release supplies it); \"\" otherwise."
788
+ }
789
+ },
790
+ "required": [
791
+ "components"
792
+ ]
793
+ },
591
794
  "ContributeParams": {
592
795
  "description": "ContributeParams hands a plugin the projects it applies to: those whose stack includes it, or, for a workspace-wide convention, those of the languages it supports that smith.toml enables it for.",
593
796
  "type": "object",
@@ -646,6 +849,10 @@ export const protocolSchema = {
646
849
  "release": {
647
850
  "$ref": "#/$defs/ReleaseDefaults",
648
851
  "description": "Release is how the project's release files are made, for a project that releases through GitHub releases and leaves them out of its smith.toml [projects.<name>.release] table; what the table sets wins."
852
+ },
853
+ "facts": {
854
+ "type": "object",
855
+ "description": "Facts are the plugin's default facts for the project: typed values under namespaced keys ({\"source.roots\": [\"src\"]}) that other settings and plugins read through the merged Project.Facts. They are the lowest scope: a [facts] table in smith.toml wins over them, and two plugins that publish different values for one key are an error that names both."
649
856
  }
650
857
  }
651
858
  },
@@ -773,6 +980,114 @@ export const protocolSchema = {
773
980
  }
774
981
  }
775
982
  },
983
+ "DeriveParams": {
984
+ "description": "DeriveParams asks a plugin for the files it can derive from facts and the project graph. The plugin writes nothing: it answers with the content, and the host decides, from its ownership record, whether to write it.",
985
+ "type": "object",
986
+ "properties": {
987
+ "projects": {
988
+ "type": "array",
989
+ "items": {
990
+ "$ref": "#/$defs/Project"
991
+ },
992
+ "description": "Projects are the projects the plugin serves."
993
+ },
994
+ "graph": {
995
+ "type": "object",
996
+ "additionalProperties": {
997
+ "type": "array",
998
+ "items": {
999
+ "type": "string"
1000
+ }
1001
+ },
1002
+ "description": "Graph maps a project name to the names of the projects it depends on inside the workspace, sorted. The host resolved these edges: a plugin does not repeat that work."
1003
+ },
1004
+ "owned": {
1005
+ "type": "array",
1006
+ "items": {
1007
+ "type": "string"
1008
+ },
1009
+ "description": "Owned are the workspace-relative paths the host's ownership record says smith owns. A plugin proposes such a file again even when it is missing on disk, so deleting a derived file and deriving restores it. A file that is not owned and not on disk is not proposed."
1010
+ }
1011
+ },
1012
+ "required": [
1013
+ "projects"
1014
+ ]
1015
+ },
1016
+ "DeriveResult": {
1017
+ "description": "DeriveResult carries the files a plugin derives.",
1018
+ "type": "object",
1019
+ "properties": {
1020
+ "files": {
1021
+ "type": "array",
1022
+ "items": {
1023
+ "$ref": "#/$defs/DerivedFile"
1024
+ }
1025
+ },
1026
+ "diverging": {
1027
+ "type": "array",
1028
+ "items": {
1029
+ "$ref": "#/$defs/DivergentFile"
1030
+ },
1031
+ "description": "Diverging are files a renderer looked at and left alone because they differ from the shape their siblings share. They are not drift: the project owns them. The host shows them in `smith derive --explain` and in the doctor, and never writes them."
1032
+ }
1033
+ },
1034
+ "required": [
1035
+ "files"
1036
+ ]
1037
+ },
1038
+ "DerivedFile": {
1039
+ "description": "DerivedFile is the content one renderer derives for one file.",
1040
+ "type": "object",
1041
+ "properties": {
1042
+ "project": {
1043
+ "type": "string",
1044
+ "description": "Project is the project the file belongs to."
1045
+ },
1046
+ "path": {
1047
+ "type": "string",
1048
+ "description": "Path is workspace-relative, with forward slashes."
1049
+ },
1050
+ "mode": {
1051
+ "type": "string",
1052
+ "description": "Mode is DeriveFile, DeriveRegion or DeriveKeys."
1053
+ },
1054
+ "renderer": {
1055
+ "type": "string",
1056
+ "description": "Renderer is the stable id of the renderer, for example \"node.tsconfig.references\". It names the region markers."
1057
+ },
1058
+ "version": {
1059
+ "type": "integer",
1060
+ "description": "Version is the renderer's version. A new version means new output for the same inputs, so the host rewrites an unedited file."
1061
+ },
1062
+ "content": {
1063
+ "type": "string",
1064
+ "description": "Content is the whole file (DeriveFile) or the lines of the region (DeriveRegion), without the markers. Unused for DeriveKeys."
1065
+ },
1066
+ "comment": {
1067
+ "type": "string",
1068
+ "description": "Comment is the line comment leader of the file (\"//\", \"#\"): the host writes a generated header after it in a DeriveFile. Empty for a format without comments, then the file has no header."
1069
+ },
1070
+ "format": {
1071
+ "type": "string",
1072
+ "description": "Format is \"json\" or \"toml\" for DeriveKeys."
1073
+ },
1074
+ "table": {
1075
+ "type": "string",
1076
+ "description": "Table is the TOML table that holds the keys (\"tool.smith\"); required for \"toml\", unused for \"json\", whose keys are the top-level ones."
1077
+ },
1078
+ "keys": {
1079
+ "type": "object",
1080
+ "description": "Keys are the owned keys and their values (DeriveKeys). A TOML value is a string, a number, a boolean or an array of those."
1081
+ }
1082
+ },
1083
+ "required": [
1084
+ "project",
1085
+ "path",
1086
+ "mode",
1087
+ "renderer",
1088
+ "version"
1089
+ ]
1090
+ },
776
1091
  "DevcontainerFeature": {
777
1092
  "description": "DevcontainerFeature is a dev container feature and its options (https://containers.dev/features).",
778
1093
  "type": "object",
@@ -822,6 +1137,34 @@ export const protocolSchema = {
822
1137
  "projects"
823
1138
  ]
824
1139
  },
1140
+ "DivergentFile": {
1141
+ "description": "DivergentFile is a file a renderer does not derive because the project made it differ from its siblings.",
1142
+ "type": "object",
1143
+ "properties": {
1144
+ "project": {
1145
+ "type": "string",
1146
+ "description": "Project is the project the file belongs to."
1147
+ },
1148
+ "path": {
1149
+ "type": "string",
1150
+ "description": "Path is workspace-relative, with forward slashes."
1151
+ },
1152
+ "renderer": {
1153
+ "type": "string",
1154
+ "description": "Renderer is the stable id of the renderer that compared the file."
1155
+ },
1156
+ "detail": {
1157
+ "type": "string",
1158
+ "description": "Detail says in a sentence how the file differs from its siblings."
1159
+ }
1160
+ },
1161
+ "required": [
1162
+ "project",
1163
+ "path",
1164
+ "renderer",
1165
+ "detail"
1166
+ ]
1167
+ },
825
1168
  "DoctorParams": {
826
1169
  "description": "DoctorParams gives the plugin its projects to check.",
827
1170
  "type": "object",
@@ -1038,6 +1381,24 @@ export const protocolSchema = {
1038
1381
  "settings": {
1039
1382
  "type": "object",
1040
1383
  "description": "Settings are this plugin's own settings for the new project, from the recipe the project is created from (its settings.<plugin> table); the same table is later the plugin's entry in Project.Settings. Nil when there are none."
1384
+ },
1385
+ "facts": {
1386
+ "type": "object",
1387
+ "description": "Facts are the shared facts that hold for the new project, from smith.toml: [projects.<name>.facts] over [facts]. A key that is absent was set by no one: the plugin scaffolds its own default. Set by the host; read only."
1388
+ },
1389
+ "shape": {
1390
+ "type": "object",
1391
+ "additionalProperties": {
1392
+ "type": "string"
1393
+ },
1394
+ "description": "Shape is the dialect the new project should have: the facts (see Project.Shape) that most projects of its kind and language agree on, as the host counted them. A plugin applies the keys it follows (see InitializeResult.Follows) and ignores the rest. Nil when there is nothing to follow, or the person asked for a fresh project."
1395
+ },
1396
+ "choices": {
1397
+ "type": "array",
1398
+ "items": {
1399
+ "type": "string"
1400
+ },
1401
+ "description": "Choices are the choices (see Choice) the new project is built from, the stack's Choices, in the order they apply: a choice comes after those it requires. A plugin applies the choices it owns and ignores the rest. Absent when the stack is not a selection. Set by the host; read only."
1041
1402
  }
1042
1403
  },
1043
1404
  "required": [
@@ -1174,6 +1535,20 @@ export const protocolSchema = {
1174
1535
  "$ref": "#/$defs/ProjectKind"
1175
1536
  },
1176
1537
  "description": "Kinds are the project kinds the plugin can scaffold with generate. Absent or empty means all three (app, lib and package): what every plugin that says nothing does. A plugin that scaffolds only some, such as an app, lists them, so the host offers and the conformance suite asks for just those."
1538
+ },
1539
+ "follows": {
1540
+ "type": "array",
1541
+ "items": {
1542
+ "type": "string"
1543
+ },
1544
+ "description": "Follows are the shape keys (see Project.Shape) the plugin applies when it generates a project with GenerateParams.Shape. Where siblings split on a key, the host asks the person only for a key that a plugin of the stack follows. Absent means the plugin follows none."
1545
+ },
1546
+ "choices": {
1547
+ "type": "array",
1548
+ "items": {
1549
+ "$ref": "#/$defs/Choice"
1550
+ },
1551
+ "description": "Choices are the options on an axis (framework, test, style) the plugin owns. The host lists them (smith choices) and checks a selection of them (smith add --with). Absent means the plugin owns none."
1177
1552
  }
1178
1553
  },
1179
1554
  "required": [
@@ -1418,6 +1793,13 @@ export const protocolSchema = {
1418
1793
  },
1419
1794
  "description": "Settings are plugins' settings for this project from smith.toml, by plugin name: [projects.<project>.settings.<name>] (what a recipe's settings became when it created the project), and for a convention its [conventions.<name>] settings and the project's [projects.<project>.<name>] on top. Set by the host; a plugin reads its own entry."
1420
1795
  },
1796
+ "shape": {
1797
+ "type": "object",
1798
+ "additionalProperties": {
1799
+ "type": "string"
1800
+ },
1801
+ "description": "Shape is the dialect of the project: named facts about how it is made (\"build.tool\": \"esbuild\", \"test.runner\": \"@swc/jest\", \"exports.target\": \"dist\"), each a short string, read from the manifest and files the plugin already reads in discovery. The host counts the shapes of the projects of one kind and language to let a new project follow what they do, and knows no key itself. Nil when the plugin does not describe shapes."
1802
+ },
1421
1803
  "registries": {
1422
1804
  "$ref": "#/$defs/Registries",
1423
1805
  "description": "Registries are the private registries the project installs from and publishes to, from smith.toml [registries]; nil means the public ones. Set by the host."
@@ -1425,6 +1807,10 @@ export const protocolSchema = {
1425
1807
  "releaseAssets": {
1426
1808
  "$ref": "#/$defs/AssetSpec",
1427
1809
  "description": "ReleaseAssets is what an app's release builds, from smith.toml [projects.<project>.release]: the platforms and file names, so a plugin can check (doctor) and prepare (CI) for them. Nil for a project that has no release files. Set by the host."
1810
+ },
1811
+ "facts": {
1812
+ "type": "object",
1813
+ "description": "Facts are the shared facts that hold for the project, merged by the host, narrowest scope first: [projects.<name>.facts], then [facts], then the defaults plugins published through Contribution.Facts. A key that is absent was set by no one: the plugin uses its own default. A request made before every plugin has contributed carries the smith.toml facts and the defaults published so far. Set by the host; read only."
1428
1814
  }
1429
1815
  },
1430
1816
  "required": [
@@ -1474,6 +1860,10 @@ export const protocolSchema = {
1474
1860
  },
1475
1861
  "branch": {
1476
1862
  "type": "string"
1863
+ },
1864
+ "tag": {
1865
+ "type": "string",
1866
+ "description": "Tag is the name of the release channel the version belongs to (npm's dist-tag \"beta\"). When set, the plugin publishes under it and leaves the registry's stable pointer where it is, or ignores it when its registry has no such pointer. Only sent to a plugin with the channels capability."
1477
1867
  }
1478
1868
  },
1479
1869
  "required": [
@@ -1853,6 +2243,13 @@ export const protocolSchema = {
1853
2243
  "items": {
1854
2244
  "type": "string"
1855
2245
  }
2246
+ },
2247
+ "choices": {
2248
+ "type": "array",
2249
+ "items": {
2250
+ "type": "string"
2251
+ },
2252
+ "description": "Choices make the stack a saved selection (see Choice). When they are set, the host derives the language, the scaffold and the toolchain from them, and Toolchain lists only the plugins to add after those. The plugins of the stack get the choices in GenerateParams.Choices."
1856
2253
  }
1857
2254
  },
1858
2255
  "required": [
@@ -38,6 +38,33 @@ export declare const ERROR_CODES: {
38
38
  readonly RequestCancelled: -32800;
39
39
  };
40
40
  export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
41
+ /** APISurfaceParams asks a plugin for the public API names of one project. */
42
+ export interface APISurfaceParams {
43
+ project: Project;
44
+ /**
45
+ * Ref is the git revision to read the project at (the tag of its last release). Empty means the
46
+ * working tree.
47
+ */
48
+ ref?: string;
49
+ }
50
+ /**
51
+ * APISurfaceResult is the public API of a project: the names its entry points export. A host
52
+ * compares the names of two revisions; it never reads their meaning, so a plugin chooses its own
53
+ * spelling and keeps it stable.
54
+ */
55
+ export interface APISurfaceResult {
56
+ /**
57
+ * Names are the exported names, sorted. A name an entry point other than the main one exports is
58
+ * prefixed with that entry point and a colon ("./cli:run"). An empty list is an answer: the
59
+ * project exports nothing.
60
+ */
61
+ names: string[];
62
+ /**
63
+ * Unreadable says why the API could not be listed (no entry point, no source at the revision).
64
+ * When set, Names is empty and the host skips the project without a warning.
65
+ */
66
+ unreadable?: string;
67
+ }
41
68
  /**
42
69
  * ActionStep is a provider-native step: a GitHub action or an Azure task, with its inputs in the
43
70
  * order they are written.
@@ -160,6 +187,23 @@ export interface Capabilities {
160
187
  * request-cancelled error. A host never sends the notification to a plugin without it.
161
188
  */
162
189
  cancel?: boolean;
190
+ /** Components lists a project's dependencies for its SBOM. */
191
+ components?: boolean;
192
+ /** Derive says which files the plugin derives from facts and the project graph (smith derive). */
193
+ derive?: boolean;
194
+ /**
195
+ * APISurface lists the names a project exports, at a git revision, so a release can warn when the
196
+ * commits ask for a smaller bump than the API change needs.
197
+ */
198
+ apiSurface?: boolean;
199
+ /**
200
+ * Channels says a prerelease version the plugin publishes never takes the place of the stable
201
+ * one: the plugin publishes under PublishParams.Tag, a release channel's name, without moving the
202
+ * registry's stable pointer (npm's latest), or its registry orders a prerelease below the release
203
+ * (PyPI, NuGet) and needs no tag. A host does not publish a package of a release channel with a
204
+ * plugin that lacks it.
205
+ */
206
+ channels?: boolean;
163
207
  }
164
208
  /**
165
209
  * ChangeKindParams asks a plugin what changing a project's kind edits. The plugin writes nothing:
@@ -203,6 +247,78 @@ export interface ChangeKindResult {
203
247
  */
204
248
  warnings?: string[];
205
249
  }
250
+ /**
251
+ * Choice is one option on an axis of a project's make-up: a framework, a test runner, a style
252
+ * library. A plugin publishes the choices it owns in InitializeResult.Choices; the host checks a
253
+ * selection of them (smith add --with react,vitest,bootstrap) and turns it into a stack. What a
254
+ * choice writes (dependencies, tasks, files) stays with the plugin: the host never sees it.
255
+ */
256
+ export interface Choice {
257
+ /**
258
+ * Axis is the question the choice answers: "framework", "test", "style", "lint". The plugins
259
+ * agree on the name; the host only compares it.
260
+ */
261
+ axis: string;
262
+ /**
263
+ * Name identifies the choice in a selection and is unique among the choices of all loaded
264
+ * plugins.
265
+ */
266
+ name: string;
267
+ description?: string;
268
+ /** Language is the language plugin the choice applies to ("node"). */
269
+ language: string;
270
+ /**
271
+ * Roles are the roles the choice's plugin plays in a stack built from it: scaffold (it writes the
272
+ * project) and/or toolchain (it joins the toolchain). Empty means it only adds to what a plugin
273
+ * of the selection already writes, as a style does.
274
+ */
275
+ roles?: Role[];
276
+ /**
277
+ * Needs are axes that the selection must have a choice on. An axis the person leaves out is
278
+ * filled with the default of the axis.
279
+ */
280
+ needs?: string[];
281
+ /** Default marks the choice the host adds when a selection needs its axis and has no choice on it. */
282
+ default?: boolean;
283
+ /**
284
+ * Many allows the choice next to other choices of its axis that are Many too. By default a
285
+ * selection has one choice per axis.
286
+ */
287
+ many?: boolean;
288
+ /** Requires are choices that must be in the selection. */
289
+ requires?: string[];
290
+ /** Conflicts are choices that must not be in the selection. */
291
+ conflicts?: string[];
292
+ }
293
+ /** Component is one dependency of a project. */
294
+ export interface Component {
295
+ name: string;
296
+ /** Version is the locked version, or the declared range when the project has no lockfile. */
297
+ version?: string;
298
+ /** PURL is the package URL (https://github.com/package-url/purl-spec). */
299
+ purl: string;
300
+ }
301
+ /**
302
+ * ComponentsParams asks a plugin for the components one of its projects ships: what a CycloneDX
303
+ * SBOM lists.
304
+ */
305
+ export interface ComponentsParams {
306
+ project: Project;
307
+ }
308
+ /**
309
+ * ComponentsResult lists the components, read from a lockfile where the ecosystem has one and from
310
+ * the manifest otherwise. An empty list is an answer: the project declares no dependencies.
311
+ */
312
+ export interface ComponentsResult {
313
+ components: Component[];
314
+ /** Source is the workspace-relative file the list was read from, "" when the project has none. */
315
+ source?: string;
316
+ /**
317
+ * PURL is the project's own package URL when it has a registry identity (without a version: the
318
+ * release supplies it); "" otherwise.
319
+ */
320
+ purl?: string;
321
+ }
206
322
  /**
207
323
  * ContributeParams hands a plugin the projects it applies to: those whose stack includes it, or,
208
324
  * for a workspace-wide convention, those of the languages it supports that smith.toml enables it
@@ -235,6 +351,13 @@ export interface Contribution {
235
351
  * sets wins.
236
352
  */
237
353
  release?: ReleaseDefaults;
354
+ /**
355
+ * Facts are the plugin's default facts for the project: typed values under namespaced keys
356
+ * ({"source.roots": ["src"]}) that other settings and plugins read through the merged
357
+ * Project.Facts. They are the lowest scope: a [facts] table in smith.toml wins over them, and two
358
+ * plugins that publish different values for one key are an error that names both.
359
+ */
360
+ facts?: Record<string, unknown>;
238
361
  }
239
362
  /** Credential is a secret the release phase needs. */
240
363
  export interface Credential {
@@ -296,6 +419,77 @@ export interface DependencyResult {
296
419
  files?: string[];
297
420
  notes?: string[];
298
421
  }
422
+ /**
423
+ * DeriveParams asks a plugin for the files it can derive from facts and the project graph. The
424
+ * plugin writes nothing: it answers with the content, and the host decides, from its ownership
425
+ * record, whether to write it.
426
+ */
427
+ export interface DeriveParams {
428
+ /** Projects are the projects the plugin serves. */
429
+ projects: Project[];
430
+ /**
431
+ * Graph maps a project name to the names of the projects it depends on inside the workspace,
432
+ * sorted. The host resolved these edges: a plugin does not repeat that work.
433
+ */
434
+ graph?: Record<string, string[]>;
435
+ /**
436
+ * Owned are the workspace-relative paths the host's ownership record says smith owns. A plugin
437
+ * proposes such a file again even when it is missing on disk, so deleting a derived file and
438
+ * deriving restores it. A file that is not owned and not on disk is not proposed.
439
+ */
440
+ owned?: string[];
441
+ }
442
+ /** DeriveResult carries the files a plugin derives. */
443
+ export interface DeriveResult {
444
+ files: DerivedFile[];
445
+ /**
446
+ * Diverging are files a renderer looked at and left alone because they differ from the shape
447
+ * their siblings share. They are not drift: the project owns them. The host shows them in `smith
448
+ * derive --explain` and in the doctor, and never writes them.
449
+ */
450
+ diverging?: DivergentFile[];
451
+ }
452
+ /** DerivedFile is the content one renderer derives for one file. */
453
+ export interface DerivedFile {
454
+ /** Project is the project the file belongs to. */
455
+ project: string;
456
+ /** Path is workspace-relative, with forward slashes. */
457
+ path: string;
458
+ /** Mode is DeriveFile, DeriveRegion or DeriveKeys. */
459
+ mode: string;
460
+ /**
461
+ * Renderer is the stable id of the renderer, for example "node.tsconfig.references". It names the
462
+ * region markers.
463
+ */
464
+ renderer: string;
465
+ /**
466
+ * Version is the renderer's version. A new version means new output for the same inputs, so the
467
+ * host rewrites an unedited file.
468
+ */
469
+ version: number;
470
+ /**
471
+ * Content is the whole file (DeriveFile) or the lines of the region (DeriveRegion), without the
472
+ * markers. Unused for DeriveKeys.
473
+ */
474
+ content?: string;
475
+ /**
476
+ * Comment is the line comment leader of the file ("//", "#"): the host writes a generated header
477
+ * after it in a DeriveFile. Empty for a format without comments, then the file has no header.
478
+ */
479
+ comment?: string;
480
+ /** Format is "json" or "toml" for DeriveKeys. */
481
+ format?: string;
482
+ /**
483
+ * Table is the TOML table that holds the keys ("tool.smith"); required for "toml", unused for
484
+ * "json", whose keys are the top-level ones.
485
+ */
486
+ table?: string;
487
+ /**
488
+ * Keys are the owned keys and their values (DeriveKeys). A TOML value is a string, a number, a
489
+ * boolean or an array of those.
490
+ */
491
+ keys?: Record<string, unknown>;
492
+ }
299
493
  /** DevcontainerFeature is a dev container feature and its options (https://containers.dev/features). */
300
494
  export interface DevcontainerFeature {
301
495
  /** ID is the feature reference, e.g. "ghcr.io/devcontainers/features/go:1". */
@@ -316,6 +510,20 @@ export interface DiscoverParams {
316
510
  export interface DiscoverResult {
317
511
  projects: Project[];
318
512
  }
513
+ /**
514
+ * DivergentFile is a file a renderer does not derive because the project made it differ from its
515
+ * siblings.
516
+ */
517
+ export interface DivergentFile {
518
+ /** Project is the project the file belongs to. */
519
+ project: string;
520
+ /** Path is workspace-relative, with forward slashes. */
521
+ path: string;
522
+ /** Renderer is the stable id of the renderer that compared the file. */
523
+ renderer: string;
524
+ /** Detail says in a sentence how the file differs from its siblings. */
525
+ detail: string;
526
+ }
319
527
  /** DoctorParams gives the plugin its projects to check. */
320
528
  export interface DoctorParams {
321
529
  projects: Project[];
@@ -433,6 +641,25 @@ export interface GenerateParams {
433
641
  * Project.Settings. Nil when there are none.
434
642
  */
435
643
  settings?: Record<string, unknown>;
644
+ /**
645
+ * Facts are the shared facts that hold for the new project, from smith.toml:
646
+ * [projects.<name>.facts] over [facts]. A key that is absent was set by no one: the plugin
647
+ * scaffolds its own default. Set by the host; read only.
648
+ */
649
+ facts?: Record<string, unknown>;
650
+ /**
651
+ * Shape is the dialect the new project should have: the facts (see Project.Shape) that most
652
+ * projects of its kind and language agree on, as the host counted them. A plugin applies the keys
653
+ * it follows (see InitializeResult.Follows) and ignores the rest. Nil when there is nothing to
654
+ * follow, or the person asked for a fresh project.
655
+ */
656
+ shape?: Record<string, string>;
657
+ /**
658
+ * Choices are the choices (see Choice) the new project is built from, the stack's Choices, in the
659
+ * order they apply: a choice comes after those it requires. A plugin applies the choices it owns
660
+ * and ignores the rest. Absent when the stack is not a selection. Set by the host; read only.
661
+ */
662
+ choices?: string[];
436
663
  }
437
664
  /** GenerateResult lists what the plugin wrote, workspace-relative. */
438
665
  export interface GenerateResult {
@@ -504,6 +731,18 @@ export interface InitializeResult {
504
731
  * just those.
505
732
  */
506
733
  kinds?: ProjectKind[];
734
+ /**
735
+ * Follows are the shape keys (see Project.Shape) the plugin applies when it generates a project
736
+ * with GenerateParams.Shape. Where siblings split on a key, the host asks the person only for a
737
+ * key that a plugin of the stack follows. Absent means the plugin follows none.
738
+ */
739
+ follows?: string[];
740
+ /**
741
+ * Choices are the options on an axis (framework, test, style) the plugin owns. The host lists
742
+ * them (smith choices) and checks a selection of them (smith add --with). Absent means the plugin
743
+ * owns none.
744
+ */
745
+ choices?: Choice[];
507
746
  }
508
747
  /**
509
748
  * Input is one input of an ActionStep. Values are written as quoted YAML strings, except true and
@@ -663,6 +902,14 @@ export interface Project {
663
902
  * [projects.<project>.<name>] on top. Set by the host; a plugin reads its own entry.
664
903
  */
665
904
  settings?: Record<string, Record<string, unknown>>;
905
+ /**
906
+ * Shape is the dialect of the project: named facts about how it is made ("build.tool": "esbuild",
907
+ * "test.runner": "@swc/jest", "exports.target": "dist"), each a short string, read from the
908
+ * manifest and files the plugin already reads in discovery. The host counts the shapes of the
909
+ * projects of one kind and language to let a new project follow what they do, and knows no key
910
+ * itself. Nil when the plugin does not describe shapes.
911
+ */
912
+ shape?: Record<string, string>;
666
913
  /**
667
914
  * Registries are the private registries the project installs from and publishes to, from
668
915
  * smith.toml [registries]; nil means the public ones. Set by the host.
@@ -674,6 +921,14 @@ export interface Project {
674
921
  * a project that has no release files. Set by the host.
675
922
  */
676
923
  releaseAssets?: AssetSpec;
924
+ /**
925
+ * Facts are the shared facts that hold for the project, merged by the host, narrowest scope
926
+ * first: [projects.<name>.facts], then [facts], then the defaults plugins published through
927
+ * Contribution.Facts. A key that is absent was set by no one: the plugin uses its own default. A
928
+ * request made before every plugin has contributed carries the smith.toml facts and the defaults
929
+ * published so far. Set by the host; read only.
930
+ */
931
+ facts?: Record<string, unknown>;
677
932
  }
678
933
  /**
679
934
  * ProjectKind is the universal kind vocabulary.
@@ -711,6 +966,13 @@ export interface PublishParams {
711
966
  */
712
967
  commit?: string;
713
968
  branch?: string;
969
+ /**
970
+ * Tag is the name of the release channel the version belongs to (npm's dist-tag "beta"). When
971
+ * set, the plugin publishes under it and leaves the registry's stable pointer where it is, or
972
+ * ignores it when its registry has no such pointer. Only sent to a plugin with the channels
973
+ * capability.
974
+ */
975
+ tag?: string;
714
976
  }
715
977
  /**
716
978
  * PublishResult reports what happened. Skipped with a reason is a success: an already-published
@@ -900,6 +1162,12 @@ export interface Stack {
900
1162
  /** Scaffold generates new projects; empty means the language plugin does. */
901
1163
  scaffold?: string;
902
1164
  deploy?: string[];
1165
+ /**
1166
+ * Choices make the stack a saved selection (see Choice). When they are set, the host derives the
1167
+ * language, the scaffold and the toolchain from them, and Toolchain lists only the plugins to add
1168
+ * after those. The plugins of the stack get the choices in GenerateParams.Choices.
1169
+ */
1170
+ choices?: string[];
903
1171
  }
904
1172
  /** Step is one command a CI phase runs. */
905
1173
  export interface Step {
@@ -1156,6 +1424,35 @@ export interface ProtocolMethods {
1156
1424
  result: LatestResult;
1157
1425
  capability: 'latest';
1158
1426
  };
1427
+ /**
1428
+ * Asks which components a project ships (its locked dependencies, read from a lockfile where the
1429
+ * ecosystem has one and from the manifest otherwise), as package URLs, for the SBOM a release
1430
+ * attaches.
1431
+ */
1432
+ 'sbom/components': {
1433
+ params: ComponentsParams;
1434
+ result: ComponentsResult;
1435
+ capability: 'components';
1436
+ };
1437
+ /**
1438
+ * Asks which files a plugin derives from facts and the project graph (a tsconfig's references, a
1439
+ * test config's display name). The plugin writes nothing: it answers with the content, and the
1440
+ * host rewrites a file only when its ownership record allows.
1441
+ */
1442
+ derive: {
1443
+ params: DeriveParams;
1444
+ result: DeriveResult;
1445
+ capability: 'derive';
1446
+ };
1447
+ /**
1448
+ * Asks for the public API names of a project, at the working tree or at a git revision (its last
1449
+ * release tag), so a release can compare them and say what bump the change needs.
1450
+ */
1451
+ 'api/surface': {
1452
+ params: APISurfaceParams;
1453
+ result: APISurfaceResult;
1454
+ capability: 'apiSurface';
1455
+ };
1159
1456
  }
1160
1457
  export type MethodName = keyof ProtocolMethods;
1161
1458
  export type ParamsOf<M extends MethodName> = ProtocolMethods[M]['params'];
package/serve/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { serve, serveStreams } from './serve.use-case.ts';
2
2
  export { capabilitiesOf, implementsMethod } from './capabilities-of.algorithm.ts';
3
- export type { Awaitable, Cancellable, CIContributor, Contributor, DeclarationEditor, DependencyManager, Discoverer, DoctorContributor, Generator, KindChanger, LatestReader, Plugin, PluginDescription, Publisher, RegistryReader, Remover, SmithPlugin, Versioner, } from './plugin.contract.ts';
3
+ export type { APISurfaceReader, Awaitable, Cancellable, CIContributor, ComponentLister, Contributor, DeclarationEditor, DependencyManager, Deriver, Discoverer, DoctorContributor, Generator, KindChanger, LatestReader, Plugin, PluginDescription, Publisher, RegistryReader, Remover, SmithPlugin, Versioner, } from './plugin.contract.ts';
4
4
  export type { FrameSink, ServeStreams } from './protocol-streams.contract.ts';
@@ -23,4 +23,7 @@ export const PLUGIN_MEMBERS = {
23
23
  'dependencies/declared': { member: 'declared', requires: ['declared', 'rewrite'] },
24
24
  'dependencies/rewrite': { member: 'rewrite', requires: ['declared', 'rewrite'] },
25
25
  'dependencies/latest': { member: 'latest', requires: ['latest'] },
26
+ 'sbom/components': { member: 'components', requires: ['components'] },
27
+ 'derive': { member: 'derive', requires: ['derive'] },
28
+ 'api/surface': { member: 'apiSurface', requires: ['apiSurface'] },
26
29
  };
@@ -1,4 +1,4 @@
1
- import type { ChangeKindParams, ChangeKindResult, CIParams, CIResult, ContributeParams, ContributeResult, DeclaredParams, DeclaredResult, DependencyResult, DiscoverParams, DiscoverResult, DoctorParams, DoctorResult, GenerateParams, GenerateResult, InitializeParams, InitializeResult, InstallParams, LatestParams, LatestResult, PublishedParams, PublishedResult, PublishParams, PublishResult, RemoveParams, RemoveResult, RewriteParams, UninstallParams, VersionParams, VersionResult } from '../protocol/index.ts';
1
+ import type { DeriveParams, DeriveResult, APISurfaceParams, APISurfaceResult, ChangeKindParams, ChangeKindResult, CIParams, CIResult, ComponentsParams, ComponentsResult, ContributeParams, ContributeResult, DeclaredParams, DeclaredResult, DependencyResult, DiscoverParams, DiscoverResult, DoctorParams, DoctorResult, GenerateParams, GenerateResult, InitializeParams, InitializeResult, InstallParams, LatestParams, LatestResult, PublishedParams, PublishedResult, PublishParams, PublishResult, RemoveParams, RemoveResult, RewriteParams, UninstallParams, VersionParams, VersionResult } from '../protocol/index.ts';
2
2
  /** A result now or later: every method of a plugin may be `async`. */
3
3
  export type Awaitable<T> = T | Promise<T>;
4
4
  /** What a plugin says about itself. The serve loop fills in `protocolVersion` and `capabilities`. */
@@ -44,6 +44,18 @@ export interface Remover {
44
44
  export interface KindChanger {
45
45
  changeKind: (params: ChangeKindParams, signal: AbortSignal) => Awaitable<ChangeKindResult>;
46
46
  }
47
+ /** `sbom/components`: the components a project ships, for the SBOM a release attaches. */
48
+ export interface ComponentLister {
49
+ components: (params: ComponentsParams, signal: AbortSignal) => Awaitable<ComponentsResult>;
50
+ }
51
+ /** `derive`: the files the plugin derives from facts and the project graph (smith derive). */
52
+ export interface Deriver {
53
+ derive: (params: DeriveParams, signal: AbortSignal) => Awaitable<DeriveResult>;
54
+ }
55
+ /** `api/surface`: the public API names of a project at a git revision, for the release's version check. */
56
+ export interface APISurfaceReader {
57
+ apiSurface: (params: APISurfaceParams, signal: AbortSignal) => Awaitable<APISurfaceResult>;
58
+ }
47
59
  /** `dependencies/install` and `dependencies/uninstall`: both, or the capability is not advertised. */
48
60
  export interface DependencyManager {
49
61
  install: (params: InstallParams, signal: AbortSignal) => Awaitable<DependencyResult>;
@@ -81,4 +93,4 @@ export interface Cancellable {
81
93
  * from `cancellable`. How many requests it can work on at once is declared in
82
94
  * `describe`'s `concurrency`: omit it for one at a time, in order.
83
95
  */
84
- export type SmithPlugin = Plugin & Partial<Discoverer & Generator & Versioner & Publisher & RegistryReader & CIContributor & DoctorContributor & Remover & KindChanger & DependencyManager & DeclarationEditor & LatestReader & Contributor & Cancellable>;
96
+ export type SmithPlugin = Plugin & Partial<Discoverer & Generator & Versioner & Publisher & RegistryReader & CIContributor & DoctorContributor & Remover & KindChanger & ComponentLister & Deriver & APISurfaceReader & DependencyManager & DeclarationEditor & LatestReader & Contributor & Cancellable>;