@monosmith/plugin-sdk 0.6.0 → 2.0.0-beta.2

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.6.0",
3
+ "version": "2.0.0-beta.2",
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",
@@ -218,6 +218,28 @@ export const protocolSchema = {
218
218
  },
219
219
  "capability": "components",
220
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
221
243
  }
222
244
  },
223
245
  "x-errorCodes": [
@@ -298,6 +320,42 @@ export const protocolSchema = {
298
320
  }
299
321
  ],
300
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
+ },
301
359
  "ActionStep": {
302
360
  "description": "ActionStep is a provider-native step: a GitHub action or an Azure task, with its inputs in the order they are written.",
303
361
  "type": "object",
@@ -546,6 +604,18 @@ export const protocolSchema = {
546
604
  "components": {
547
605
  "type": "boolean",
548
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."
549
619
  }
550
620
  }
551
621
  },
@@ -603,6 +673,68 @@ export const protocolSchema = {
603
673
  }
604
674
  }
605
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
+ },
606
738
  "Component": {
607
739
  "description": "Component is one dependency of a project.",
608
740
  "type": "object",
@@ -717,6 +849,10 @@ export const protocolSchema = {
717
849
  "release": {
718
850
  "$ref": "#/$defs/ReleaseDefaults",
719
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."
720
856
  }
721
857
  }
722
858
  },
@@ -805,6 +941,28 @@ export const protocolSchema = {
805
941
  "projects"
806
942
  ]
807
943
  },
944
+ "DeclaredProject": {
945
+ "description": "DeclaredProject is a project smith.toml declares: a folder with no manifest.",
946
+ "type": "object",
947
+ "properties": {
948
+ "name": {
949
+ "type": "string",
950
+ "description": "Name is the project's name, the key of its [projects.<name>] table."
951
+ },
952
+ "root": {
953
+ "type": "string",
954
+ "description": "Root is the workspace-relative folder."
955
+ },
956
+ "kind": {
957
+ "$ref": "#/$defs/ProjectKind",
958
+ "description": "Kind is \"app\", \"lib\" or \"package\", or empty for the plugin's choice."
959
+ }
960
+ },
961
+ "required": [
962
+ "name",
963
+ "root"
964
+ ]
965
+ },
808
966
  "DeclaredResult": {
809
967
  "description": "DeclaredResult is every declaration, and the version installed of each name where the plugin can tell.",
810
968
  "type": "object",
@@ -844,6 +1002,114 @@ export const protocolSchema = {
844
1002
  }
845
1003
  }
846
1004
  },
1005
+ "DeriveParams": {
1006
+ "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.",
1007
+ "type": "object",
1008
+ "properties": {
1009
+ "projects": {
1010
+ "type": "array",
1011
+ "items": {
1012
+ "$ref": "#/$defs/Project"
1013
+ },
1014
+ "description": "Projects are the projects the plugin serves."
1015
+ },
1016
+ "graph": {
1017
+ "type": "object",
1018
+ "additionalProperties": {
1019
+ "type": "array",
1020
+ "items": {
1021
+ "type": "string"
1022
+ }
1023
+ },
1024
+ "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."
1025
+ },
1026
+ "owned": {
1027
+ "type": "array",
1028
+ "items": {
1029
+ "type": "string"
1030
+ },
1031
+ "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."
1032
+ }
1033
+ },
1034
+ "required": [
1035
+ "projects"
1036
+ ]
1037
+ },
1038
+ "DeriveResult": {
1039
+ "description": "DeriveResult carries the files a plugin derives.",
1040
+ "type": "object",
1041
+ "properties": {
1042
+ "files": {
1043
+ "type": "array",
1044
+ "items": {
1045
+ "$ref": "#/$defs/DerivedFile"
1046
+ }
1047
+ },
1048
+ "diverging": {
1049
+ "type": "array",
1050
+ "items": {
1051
+ "$ref": "#/$defs/DivergentFile"
1052
+ },
1053
+ "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."
1054
+ }
1055
+ },
1056
+ "required": [
1057
+ "files"
1058
+ ]
1059
+ },
1060
+ "DerivedFile": {
1061
+ "description": "DerivedFile is the content one renderer derives for one file.",
1062
+ "type": "object",
1063
+ "properties": {
1064
+ "project": {
1065
+ "type": "string",
1066
+ "description": "Project is the project the file belongs to."
1067
+ },
1068
+ "path": {
1069
+ "type": "string",
1070
+ "description": "Path is workspace-relative, with forward slashes."
1071
+ },
1072
+ "mode": {
1073
+ "type": "string",
1074
+ "description": "Mode is DeriveFile, DeriveRegion or DeriveKeys."
1075
+ },
1076
+ "renderer": {
1077
+ "type": "string",
1078
+ "description": "Renderer is the stable id of the renderer, for example \"node.tsconfig.references\". It names the region markers."
1079
+ },
1080
+ "version": {
1081
+ "type": "integer",
1082
+ "description": "Version is the renderer's version. A new version means new output for the same inputs, so the host rewrites an unedited file."
1083
+ },
1084
+ "content": {
1085
+ "type": "string",
1086
+ "description": "Content is the whole file (DeriveFile) or the lines of the region (DeriveRegion), without the markers. Unused for DeriveKeys."
1087
+ },
1088
+ "comment": {
1089
+ "type": "string",
1090
+ "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."
1091
+ },
1092
+ "format": {
1093
+ "type": "string",
1094
+ "description": "Format is \"json\" or \"toml\" for DeriveKeys."
1095
+ },
1096
+ "table": {
1097
+ "type": "string",
1098
+ "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."
1099
+ },
1100
+ "keys": {
1101
+ "type": "object",
1102
+ "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."
1103
+ }
1104
+ },
1105
+ "required": [
1106
+ "project",
1107
+ "path",
1108
+ "mode",
1109
+ "renderer",
1110
+ "version"
1111
+ ]
1112
+ },
847
1113
  "DevcontainerFeature": {
848
1114
  "description": "DevcontainerFeature is a dev container feature and its options (https://containers.dev/features).",
849
1115
  "type": "object",
@@ -872,6 +1138,13 @@ export const protocolSchema = {
872
1138
  "items": {
873
1139
  "type": "string"
874
1140
  }
1141
+ },
1142
+ "declared": {
1143
+ "type": "array",
1144
+ "items": {
1145
+ "$ref": "#/$defs/DeclaredProject"
1146
+ },
1147
+ "description": "Declared are the projects smith.toml declares for this plugin's language ([projects.<name>] with a root and a language): folders with no manifest of their own, such as a program that is a package of the Go module above it. The plugin describes each one in its result, or answers with an error saying why it cannot."
875
1148
  }
876
1149
  },
877
1150
  "required": [
@@ -893,6 +1166,34 @@ export const protocolSchema = {
893
1166
  "projects"
894
1167
  ]
895
1168
  },
1169
+ "DivergentFile": {
1170
+ "description": "DivergentFile is a file a renderer does not derive because the project made it differ from its siblings.",
1171
+ "type": "object",
1172
+ "properties": {
1173
+ "project": {
1174
+ "type": "string",
1175
+ "description": "Project is the project the file belongs to."
1176
+ },
1177
+ "path": {
1178
+ "type": "string",
1179
+ "description": "Path is workspace-relative, with forward slashes."
1180
+ },
1181
+ "renderer": {
1182
+ "type": "string",
1183
+ "description": "Renderer is the stable id of the renderer that compared the file."
1184
+ },
1185
+ "detail": {
1186
+ "type": "string",
1187
+ "description": "Detail says in a sentence how the file differs from its siblings."
1188
+ }
1189
+ },
1190
+ "required": [
1191
+ "project",
1192
+ "path",
1193
+ "renderer",
1194
+ "detail"
1195
+ ]
1196
+ },
896
1197
  "DoctorParams": {
897
1198
  "description": "DoctorParams gives the plugin its projects to check.",
898
1199
  "type": "object",
@@ -1109,6 +1410,24 @@ export const protocolSchema = {
1109
1410
  "settings": {
1110
1411
  "type": "object",
1111
1412
  "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."
1413
+ },
1414
+ "facts": {
1415
+ "type": "object",
1416
+ "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."
1417
+ },
1418
+ "shape": {
1419
+ "type": "object",
1420
+ "additionalProperties": {
1421
+ "type": "string"
1422
+ },
1423
+ "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."
1424
+ },
1425
+ "choices": {
1426
+ "type": "array",
1427
+ "items": {
1428
+ "type": "string"
1429
+ },
1430
+ "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."
1112
1431
  }
1113
1432
  },
1114
1433
  "required": [
@@ -1245,6 +1564,20 @@ export const protocolSchema = {
1245
1564
  "$ref": "#/$defs/ProjectKind"
1246
1565
  },
1247
1566
  "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."
1567
+ },
1568
+ "follows": {
1569
+ "type": "array",
1570
+ "items": {
1571
+ "type": "string"
1572
+ },
1573
+ "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."
1574
+ },
1575
+ "choices": {
1576
+ "type": "array",
1577
+ "items": {
1578
+ "$ref": "#/$defs/Choice"
1579
+ },
1580
+ "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."
1248
1581
  }
1249
1582
  },
1250
1583
  "required": [
@@ -1489,6 +1822,13 @@ export const protocolSchema = {
1489
1822
  },
1490
1823
  "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."
1491
1824
  },
1825
+ "shape": {
1826
+ "type": "object",
1827
+ "additionalProperties": {
1828
+ "type": "string"
1829
+ },
1830
+ "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."
1831
+ },
1492
1832
  "registries": {
1493
1833
  "$ref": "#/$defs/Registries",
1494
1834
  "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."
@@ -1496,6 +1836,10 @@ export const protocolSchema = {
1496
1836
  "releaseAssets": {
1497
1837
  "$ref": "#/$defs/AssetSpec",
1498
1838
  "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."
1839
+ },
1840
+ "facts": {
1841
+ "type": "object",
1842
+ "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."
1499
1843
  }
1500
1844
  },
1501
1845
  "required": [
@@ -1545,6 +1889,10 @@ export const protocolSchema = {
1545
1889
  },
1546
1890
  "branch": {
1547
1891
  "type": "string"
1892
+ },
1893
+ "tag": {
1894
+ "type": "string",
1895
+ "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."
1548
1896
  }
1549
1897
  },
1550
1898
  "required": [
@@ -1924,6 +2272,13 @@ export const protocolSchema = {
1924
2272
  "items": {
1925
2273
  "type": "string"
1926
2274
  }
2275
+ },
2276
+ "choices": {
2277
+ "type": "array",
2278
+ "items": {
2279
+ "type": "string"
2280
+ },
2281
+ "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."
1927
2282
  }
1928
2283
  },
1929
2284
  "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.
@@ -162,6 +189,21 @@ export interface Capabilities {
162
189
  cancel?: boolean;
163
190
  /** Components lists a project's dependencies for its SBOM. */
164
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;
165
207
  }
166
208
  /**
167
209
  * ChangeKindParams asks a plugin what changing a project's kind edits. The plugin writes nothing:
@@ -205,6 +247,49 @@ export interface ChangeKindResult {
205
247
  */
206
248
  warnings?: string[];
207
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
+ }
208
293
  /** Component is one dependency of a project. */
209
294
  export interface Component {
210
295
  name: string;
@@ -266,6 +351,13 @@ export interface Contribution {
266
351
  * sets wins.
267
352
  */
268
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>;
269
361
  }
270
362
  /** Credential is a secret the release phase needs. */
271
363
  export interface Credential {
@@ -314,6 +406,15 @@ export interface Declaration {
314
406
  export interface DeclaredParams {
315
407
  projects: Project[];
316
408
  }
409
+ /** DeclaredProject is a project smith.toml declares: a folder with no manifest. */
410
+ export interface DeclaredProject {
411
+ /** Name is the project's name, the key of its [projects.<name>] table. */
412
+ name: string;
413
+ /** Root is the workspace-relative folder. */
414
+ root: string;
415
+ /** Kind is "app", "lib" or "package", or empty for the plugin's choice. */
416
+ kind?: ProjectKind;
417
+ }
317
418
  /**
318
419
  * DeclaredResult is every declaration, and the version installed of each name where the plugin can
319
420
  * tell.
@@ -327,6 +428,77 @@ export interface DependencyResult {
327
428
  files?: string[];
328
429
  notes?: string[];
329
430
  }
431
+ /**
432
+ * DeriveParams asks a plugin for the files it can derive from facts and the project graph. The
433
+ * plugin writes nothing: it answers with the content, and the host decides, from its ownership
434
+ * record, whether to write it.
435
+ */
436
+ export interface DeriveParams {
437
+ /** Projects are the projects the plugin serves. */
438
+ projects: Project[];
439
+ /**
440
+ * Graph maps a project name to the names of the projects it depends on inside the workspace,
441
+ * sorted. The host resolved these edges: a plugin does not repeat that work.
442
+ */
443
+ graph?: Record<string, string[]>;
444
+ /**
445
+ * Owned are the workspace-relative paths the host's ownership record says smith owns. A plugin
446
+ * proposes such a file again even when it is missing on disk, so deleting a derived file and
447
+ * deriving restores it. A file that is not owned and not on disk is not proposed.
448
+ */
449
+ owned?: string[];
450
+ }
451
+ /** DeriveResult carries the files a plugin derives. */
452
+ export interface DeriveResult {
453
+ files: DerivedFile[];
454
+ /**
455
+ * Diverging are files a renderer looked at and left alone because they differ from the shape
456
+ * their siblings share. They are not drift: the project owns them. The host shows them in `smith
457
+ * derive --explain` and in the doctor, and never writes them.
458
+ */
459
+ diverging?: DivergentFile[];
460
+ }
461
+ /** DerivedFile is the content one renderer derives for one file. */
462
+ export interface DerivedFile {
463
+ /** Project is the project the file belongs to. */
464
+ project: string;
465
+ /** Path is workspace-relative, with forward slashes. */
466
+ path: string;
467
+ /** Mode is DeriveFile, DeriveRegion or DeriveKeys. */
468
+ mode: string;
469
+ /**
470
+ * Renderer is the stable id of the renderer, for example "node.tsconfig.references". It names the
471
+ * region markers.
472
+ */
473
+ renderer: string;
474
+ /**
475
+ * Version is the renderer's version. A new version means new output for the same inputs, so the
476
+ * host rewrites an unedited file.
477
+ */
478
+ version: number;
479
+ /**
480
+ * Content is the whole file (DeriveFile) or the lines of the region (DeriveRegion), without the
481
+ * markers. Unused for DeriveKeys.
482
+ */
483
+ content?: string;
484
+ /**
485
+ * Comment is the line comment leader of the file ("//", "#"): the host writes a generated header
486
+ * after it in a DeriveFile. Empty for a format without comments, then the file has no header.
487
+ */
488
+ comment?: string;
489
+ /** Format is "json" or "toml" for DeriveKeys. */
490
+ format?: string;
491
+ /**
492
+ * Table is the TOML table that holds the keys ("tool.smith"); required for "toml", unused for
493
+ * "json", whose keys are the top-level ones.
494
+ */
495
+ table?: string;
496
+ /**
497
+ * Keys are the owned keys and their values (DeriveKeys). A TOML value is a string, a number, a
498
+ * boolean or an array of those.
499
+ */
500
+ keys?: Record<string, unknown>;
501
+ }
330
502
  /** DevcontainerFeature is a dev container feature and its options (https://containers.dev/features). */
331
503
  export interface DevcontainerFeature {
332
504
  /** ID is the feature reference, e.g. "ghcr.io/devcontainers/features/go:1". */
@@ -339,6 +511,13 @@ export interface DevcontainerFeature {
339
511
  */
340
512
  export interface DiscoverParams {
341
513
  manifests: string[];
514
+ /**
515
+ * Declared are the projects smith.toml declares for this plugin's language ([projects.<name>]
516
+ * with a root and a language): folders with no manifest of their own, such as a program that is a
517
+ * package of the Go module above it. The plugin describes each one in its result, or answers with
518
+ * an error saying why it cannot.
519
+ */
520
+ declared?: DeclaredProject[];
342
521
  }
343
522
  /**
344
523
  * DiscoverResult describes every manifest that is a project of this plugin. A manifest the plugin
@@ -347,6 +526,20 @@ export interface DiscoverParams {
347
526
  export interface DiscoverResult {
348
527
  projects: Project[];
349
528
  }
529
+ /**
530
+ * DivergentFile is a file a renderer does not derive because the project made it differ from its
531
+ * siblings.
532
+ */
533
+ export interface DivergentFile {
534
+ /** Project is the project the file belongs to. */
535
+ project: string;
536
+ /** Path is workspace-relative, with forward slashes. */
537
+ path: string;
538
+ /** Renderer is the stable id of the renderer that compared the file. */
539
+ renderer: string;
540
+ /** Detail says in a sentence how the file differs from its siblings. */
541
+ detail: string;
542
+ }
350
543
  /** DoctorParams gives the plugin its projects to check. */
351
544
  export interface DoctorParams {
352
545
  projects: Project[];
@@ -464,6 +657,25 @@ export interface GenerateParams {
464
657
  * Project.Settings. Nil when there are none.
465
658
  */
466
659
  settings?: Record<string, unknown>;
660
+ /**
661
+ * Facts are the shared facts that hold for the new project, from smith.toml:
662
+ * [projects.<name>.facts] over [facts]. A key that is absent was set by no one: the plugin
663
+ * scaffolds its own default. Set by the host; read only.
664
+ */
665
+ facts?: Record<string, unknown>;
666
+ /**
667
+ * Shape is the dialect the new project should have: the facts (see Project.Shape) that most
668
+ * projects of its kind and language agree on, as the host counted them. A plugin applies the keys
669
+ * it follows (see InitializeResult.Follows) and ignores the rest. Nil when there is nothing to
670
+ * follow, or the person asked for a fresh project.
671
+ */
672
+ shape?: Record<string, string>;
673
+ /**
674
+ * Choices are the choices (see Choice) the new project is built from, the stack's Choices, in the
675
+ * order they apply: a choice comes after those it requires. A plugin applies the choices it owns
676
+ * and ignores the rest. Absent when the stack is not a selection. Set by the host; read only.
677
+ */
678
+ choices?: string[];
467
679
  }
468
680
  /** GenerateResult lists what the plugin wrote, workspace-relative. */
469
681
  export interface GenerateResult {
@@ -535,6 +747,18 @@ export interface InitializeResult {
535
747
  * just those.
536
748
  */
537
749
  kinds?: ProjectKind[];
750
+ /**
751
+ * Follows are the shape keys (see Project.Shape) the plugin applies when it generates a project
752
+ * with GenerateParams.Shape. Where siblings split on a key, the host asks the person only for a
753
+ * key that a plugin of the stack follows. Absent means the plugin follows none.
754
+ */
755
+ follows?: string[];
756
+ /**
757
+ * Choices are the options on an axis (framework, test, style) the plugin owns. The host lists
758
+ * them (smith choices) and checks a selection of them (smith add --with). Absent means the plugin
759
+ * owns none.
760
+ */
761
+ choices?: Choice[];
538
762
  }
539
763
  /**
540
764
  * Input is one input of an ActionStep. Values are written as quoted YAML strings, except true and
@@ -694,6 +918,14 @@ export interface Project {
694
918
  * [projects.<project>.<name>] on top. Set by the host; a plugin reads its own entry.
695
919
  */
696
920
  settings?: Record<string, Record<string, unknown>>;
921
+ /**
922
+ * Shape is the dialect of the project: named facts about how it is made ("build.tool": "esbuild",
923
+ * "test.runner": "@swc/jest", "exports.target": "dist"), each a short string, read from the
924
+ * manifest and files the plugin already reads in discovery. The host counts the shapes of the
925
+ * projects of one kind and language to let a new project follow what they do, and knows no key
926
+ * itself. Nil when the plugin does not describe shapes.
927
+ */
928
+ shape?: Record<string, string>;
697
929
  /**
698
930
  * Registries are the private registries the project installs from and publishes to, from
699
931
  * smith.toml [registries]; nil means the public ones. Set by the host.
@@ -705,6 +937,14 @@ export interface Project {
705
937
  * a project that has no release files. Set by the host.
706
938
  */
707
939
  releaseAssets?: AssetSpec;
940
+ /**
941
+ * Facts are the shared facts that hold for the project, merged by the host, narrowest scope
942
+ * first: [projects.<name>.facts], then [facts], then the defaults plugins published through
943
+ * Contribution.Facts. A key that is absent was set by no one: the plugin uses its own default. A
944
+ * request made before every plugin has contributed carries the smith.toml facts and the defaults
945
+ * published so far. Set by the host; read only.
946
+ */
947
+ facts?: Record<string, unknown>;
708
948
  }
709
949
  /**
710
950
  * ProjectKind is the universal kind vocabulary.
@@ -742,6 +982,13 @@ export interface PublishParams {
742
982
  */
743
983
  commit?: string;
744
984
  branch?: string;
985
+ /**
986
+ * Tag is the name of the release channel the version belongs to (npm's dist-tag "beta"). When
987
+ * set, the plugin publishes under it and leaves the registry's stable pointer where it is, or
988
+ * ignores it when its registry has no such pointer. Only sent to a plugin with the channels
989
+ * capability.
990
+ */
991
+ tag?: string;
745
992
  }
746
993
  /**
747
994
  * PublishResult reports what happened. Skipped with a reason is a success: an already-published
@@ -931,6 +1178,12 @@ export interface Stack {
931
1178
  /** Scaffold generates new projects; empty means the language plugin does. */
932
1179
  scaffold?: string;
933
1180
  deploy?: string[];
1181
+ /**
1182
+ * Choices make the stack a saved selection (see Choice). When they are set, the host derives the
1183
+ * language, the scaffold and the toolchain from them, and Toolchain lists only the plugins to add
1184
+ * after those. The plugins of the stack get the choices in GenerateParams.Choices.
1185
+ */
1186
+ choices?: string[];
934
1187
  }
935
1188
  /** Step is one command a CI phase runs. */
936
1189
  export interface Step {
@@ -1197,6 +1450,25 @@ export interface ProtocolMethods {
1197
1450
  result: ComponentsResult;
1198
1451
  capability: 'components';
1199
1452
  };
1453
+ /**
1454
+ * Asks which files a plugin derives from facts and the project graph (a tsconfig's references, a
1455
+ * test config's display name). The plugin writes nothing: it answers with the content, and the
1456
+ * host rewrites a file only when its ownership record allows.
1457
+ */
1458
+ derive: {
1459
+ params: DeriveParams;
1460
+ result: DeriveResult;
1461
+ capability: 'derive';
1462
+ };
1463
+ /**
1464
+ * Asks for the public API names of a project, at the working tree or at a git revision (its last
1465
+ * release tag), so a release can compare them and say what bump the change needs.
1466
+ */
1467
+ 'api/surface': {
1468
+ params: APISurfaceParams;
1469
+ result: APISurfaceResult;
1470
+ capability: 'apiSurface';
1471
+ };
1200
1472
  }
1201
1473
  export type MethodName = keyof ProtocolMethods;
1202
1474
  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, ComponentLister, 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';
@@ -24,4 +24,6 @@ export const PLUGIN_MEMBERS = {
24
24
  'dependencies/rewrite': { member: 'rewrite', requires: ['declared', 'rewrite'] },
25
25
  'dependencies/latest': { member: 'latest', requires: ['latest'] },
26
26
  'sbom/components': { member: 'components', requires: ['components'] },
27
+ 'derive': { member: 'derive', requires: ['derive'] },
28
+ 'api/surface': { member: 'apiSurface', requires: ['apiSurface'] },
27
29
  };
@@ -1,4 +1,4 @@
1
- import type { 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';
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`. */
@@ -48,6 +48,14 @@ export interface KindChanger {
48
48
  export interface ComponentLister {
49
49
  components: (params: ComponentsParams, signal: AbortSignal) => Awaitable<ComponentsResult>;
50
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
+ }
51
59
  /** `dependencies/install` and `dependencies/uninstall`: both, or the capability is not advertised. */
52
60
  export interface DependencyManager {
53
61
  install: (params: InstallParams, signal: AbortSignal) => Awaitable<DependencyResult>;
@@ -85,4 +93,4 @@ export interface Cancellable {
85
93
  * from `cancellable`. How many requests it can work on at once is declared in
86
94
  * `describe`'s `concurrency`: omit it for one at a time, in order.
87
95
  */
88
- export type SmithPlugin = Plugin & Partial<Discoverer & Generator & Versioner & Publisher & RegistryReader & CIContributor & DoctorContributor & Remover & KindChanger & ComponentLister & 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>;