yarramate 0.23.0 → 1.0.0

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.
Files changed (159) hide show
  1. package/README.md +13 -4
  2. package/catalogues/core-enrichment.yaml +79 -55
  3. package/dist/adapter-mapping.js +6 -1
  4. package/dist/adapters/likec4-cli.js +36 -7
  5. package/dist/adapters/visual/client.d.ts +2 -2
  6. package/dist/adapters/visual/client.js +79 -10
  7. package/dist/adapters/visual/protocol-contract.d.ts +124 -10
  8. package/dist/adapters/visual/protocol-contract.js +12 -1
  9. package/dist/adapters/visual/protocol.d.ts +63 -0
  10. package/dist/adapters/visual/protocol.js +96 -5
  11. package/dist/adapters/visual/session-server.d.ts +1 -6
  12. package/dist/adapters/visual/session-server.js +243 -143
  13. package/dist/adapters/visual/session-store.d.ts +9 -0
  14. package/dist/adapters/visual/session-store.js +14 -5
  15. package/dist/adapters/visual/view-identity.d.ts +135 -0
  16. package/dist/adapters/visual/view-identity.js +200 -0
  17. package/dist/adapters/visual/wire.d.ts +19 -4
  18. package/dist/adapters/visual/workspace-model.d.ts +116 -0
  19. package/dist/adapters/visual/workspace-model.js +212 -0
  20. package/dist/adapters/visual-cli.d.ts +1 -1
  21. package/dist/adapters/visual-cli.js +30 -21
  22. package/dist/apply-cli.d.ts +2 -0
  23. package/dist/apply-cli.js +83 -0
  24. package/dist/apply-command.d.ts +84 -3
  25. package/dist/apply-command.js +218 -109
  26. package/dist/archimate-relationships.generated.d.ts +22 -0
  27. package/dist/archimate-relationships.generated.js +156 -0
  28. package/dist/ask-command.js +18 -6
  29. package/dist/attestation-staleness.js +2 -2
  30. package/dist/changed.js +1 -1
  31. package/dist/check-command.js +13 -5
  32. package/dist/cli-support.d.ts +1 -1
  33. package/dist/cli-support.js +1 -1
  34. package/dist/cli.js +1 -1
  35. package/dist/compiler.d.ts +31 -3
  36. package/dist/compiler.js +321 -81
  37. package/dist/concept-drafting.d.ts +27 -0
  38. package/dist/concept-drafting.js +75 -0
  39. package/dist/core-contract.js +6 -1
  40. package/dist/deletion-drafting.d.ts +52 -0
  41. package/dist/deletion-drafting.js +100 -0
  42. package/dist/evidence.js +6 -1
  43. package/dist/graph-projection.d.ts +14 -0
  44. package/dist/graph-projection.js +4 -0
  45. package/dist/index.d.ts +8 -1
  46. package/dist/index.js +8 -1
  47. package/dist/interrogate-command.js +63 -17
  48. package/dist/kind-label.d.ts +4 -3
  49. package/dist/kind-label.js +4 -3
  50. package/dist/nesting.d.ts +15 -0
  51. package/dist/nesting.js +2 -0
  52. package/dist/notation/archimate.d.ts +1 -1
  53. package/dist/notation/archimate.js +108 -9
  54. package/dist/profile.d.ts +9 -12
  55. package/dist/profile.js +17 -26
  56. package/dist/projection.d.ts +62 -3
  57. package/dist/projection.js +127 -43
  58. package/dist/relationship-drafting.d.ts +51 -0
  59. package/dist/relationship-drafting.js +92 -0
  60. package/dist/relationship-matrix.d.ts +14 -0
  61. package/dist/relationship-matrix.js +74 -0
  62. package/dist/shipped-profile.d.ts +1 -1
  63. package/dist/shipped-profile.js +19 -5
  64. package/dist/source-store.d.ts +95 -0
  65. package/dist/source-store.js +170 -0
  66. package/dist/subject-references.js +1 -1
  67. package/dist/visual-app/assets/index-BLcpI8WN.js +394 -0
  68. package/dist/visual-app/assets/index-DLutIWES.css +1 -0
  69. package/dist/visual-app/index.html +2 -2
  70. package/dist/visual-app-lib/editor.d.ts +1 -0
  71. package/dist/visual-app-lib/editor.js +90968 -0
  72. package/dist/visual-app-lib/styles.css +2 -0
  73. package/dist/visual-app-lib/types/adapter-mapping.d.ts +40 -0
  74. package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +455 -0
  75. package/dist/visual-app-lib/types/adapters/visual/view-identity.d.ts +135 -0
  76. package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +141 -0
  77. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +116 -0
  78. package/dist/visual-app-lib/types/apply-command.d.ts +93 -0
  79. package/dist/visual-app-lib/types/archimate-relationships.generated.d.ts +22 -0
  80. package/dist/visual-app-lib/types/brief.d.ts +5 -0
  81. package/dist/visual-app-lib/types/compiler.d.ts +155 -0
  82. package/dist/visual-app-lib/types/concept-drafting.d.ts +27 -0
  83. package/dist/visual-app-lib/types/deletion-drafting.d.ts +52 -0
  84. package/dist/visual-app-lib/types/evidence.d.ts +62 -0
  85. package/dist/visual-app-lib/types/graph-claims.d.ts +18 -0
  86. package/dist/visual-app-lib/types/graph-projection.d.ts +77 -0
  87. package/dist/visual-app-lib/types/kind-label.d.ts +9 -0
  88. package/dist/visual-app-lib/types/nesting.d.ts +15 -0
  89. package/dist/visual-app-lib/types/notation/archimate.d.ts +71 -0
  90. package/dist/visual-app-lib/types/operations.d.ts +157 -0
  91. package/dist/visual-app-lib/types/profile.d.ts +47 -0
  92. package/dist/visual-app-lib/types/projection.d.ts +113 -0
  93. package/dist/visual-app-lib/types/relationship-drafting.d.ts +51 -0
  94. package/dist/visual-app-lib/types/relationship-matrix.d.ts +14 -0
  95. package/dist/visual-app-lib/types/shipped-profile.d.ts +2 -0
  96. package/dist/visual-app-lib/types/source-document.d.ts +36 -0
  97. package/dist/visual-app-lib/types/source-store.d.ts +95 -0
  98. package/dist/visual-app-lib/types/subject-references.d.ts +92 -0
  99. package/dist/visual-app-lib/types/visual-app/App.d.ts +15 -0
  100. package/dist/visual-app-lib/types/visual-app/badges.d.ts +7 -0
  101. package/dist/visual-app-lib/types/visual-app/changeset-tray.d.ts +106 -0
  102. package/dist/visual-app-lib/types/visual-app/confirm-dialog.d.ts +14 -0
  103. package/dist/visual-app-lib/types/visual-app/connection-panel.d.ts +23 -0
  104. package/dist/visual-app-lib/types/visual-app/context-menu-model.d.ts +187 -0
  105. package/dist/visual-app-lib/types/visual-app/context-menu.d.ts +24 -0
  106. package/dist/visual-app-lib/types/visual-app/describe-query.d.ts +9 -0
  107. package/dist/visual-app-lib/types/visual-app/editor-host.d.ts +70 -0
  108. package/dist/visual-app-lib/types/visual-app/faults.d.ts +26 -0
  109. package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +73 -0
  110. package/dist/visual-app-lib/types/visual-app/kind-icons.d.ts +3 -0
  111. package/dist/visual-app-lib/types/visual-app/local-host.d.ts +38 -0
  112. package/dist/visual-app-lib/types/visual-app/mount.d.ts +56 -0
  113. package/dist/visual-app-lib/types/visual-app/prompt-dialog.d.ts +24 -0
  114. package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +69 -0
  115. package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +142 -0
  116. package/dist/visual-app-lib/types/visual-app/quick-filter.d.ts +11 -0
  117. package/dist/visual-app-lib/types/visual-app/relationship-kind-options.d.ts +50 -0
  118. package/dist/visual-app-lib/types/visual-app/save-view.d.ts +86 -0
  119. package/dist/visual-app-lib/types/visual-app/section-stack.d.ts +59 -0
  120. package/dist/visual-app-lib/types/visual-app/session-client.d.ts +38 -0
  121. package/dist/visual-app-lib/types/visual-app/state.d.ts +328 -0
  122. package/dist/visual-app-lib/types/visual-app/subject-draft-panel.d.ts +22 -0
  123. package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +42 -0
  124. package/dist/visual-app-lib/types/visual-app/view-tree-model.d.ts +122 -0
  125. package/dist/visual-app-lib/types/visual-app/view-tree.d.ts +32 -0
  126. package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +268 -0
  127. package/dist/visual-app-lib/types/workspace.d.ts +29 -0
  128. package/dist/workspace.js +7 -1
  129. package/docs/CONSUMING-YARRAMATE.md +51 -2
  130. package/package.json +9 -2
  131. package/schema/yarramate-adapter-mapping.schema.json +1 -1
  132. package/schema/yarramate-ask-result.schema.json +24 -3
  133. package/schema/yarramate-check-result.schema.json +9 -0
  134. package/schema/yarramate-diagnostic-result.schema.json +9 -0
  135. package/schema/yarramate-document.schema.json +6 -0
  136. package/schema/yarramate-evidence-report.schema.json +1 -1
  137. package/schema/yarramate-evidence.schema.json +1 -1
  138. package/schema/yarramate-graph-v2.schema.json +34 -9
  139. package/schema/yarramate-likec4-generated-project-v2.schema.json +1 -1
  140. package/schema/yarramate-likec4-generated-project.schema.json +1 -1
  141. package/schema/yarramate-likec4-project.schema.json +1 -1
  142. package/schema/yarramate-projection-result.schema.json +3 -7
  143. package/schema/yarramate-projection.schema.json +16 -16
  144. package/schema/yarramate-reconciliation-report.schema.json +2 -2
  145. package/schema/yarramate-rtm.schema.json +1 -1
  146. package/schema/yarramate-state-comparison.schema.json +1 -1
  147. package/schema/yarramate-visual-diagnostic-result.schema.json +9 -0
  148. package/schema/yarramate-visual-event.schema.json +40 -41
  149. package/schema/yarramate-visual-graph.schema.json +8 -0
  150. package/schema/yarramate-visual-handoff.schema.json +3 -3
  151. package/schema/yarramate-visual-session-descriptor.schema.json +6 -6
  152. package/schema/yarramate-visual-session-started.schema.json +10 -9
  153. package/schema/yarramate-visual-status.schema.json +3 -3
  154. package/skills/yarramate-architecture/SKILL.md +3 -3
  155. package/skills/yarramate-architecture/references/modelling-patterns.md +23 -44
  156. package/skills/yarramate-architecture/references/native-authoring.md +32 -49
  157. package/skills/yarramate-architecture/references/visual-conversations.md +13 -10
  158. package/dist/visual-app/assets/index-CRd1Khma.js +0 -354
  159. package/dist/visual-app/assets/index-tZSHlU-a.css +0 -1
@@ -0,0 +1,212 @@
1
+ import { stringify } from "yaml";
2
+ import { loadProjection } from "../../projection.js";
3
+ import { withDiagnosticSubjects } from "../../compiler.js";
4
+ import { posixDirectoryOf } from "../../apply-command.js";
5
+ import { projectGraphForCanvas } from "../../graph-projection.js";
6
+ import { DEFAULT_PROJECTION_DIRECTORY } from "./view-identity.js";
7
+ import { evaluateProjection, explainProjection } from "../../projection.js";
8
+ import { kindLabelOf } from "../../kind-label.js";
9
+ /**
10
+ * What a workspace looks like to the editor, however the editor is being run
11
+ * (#252).
12
+ *
13
+ * These were the session server's, and the session server is one host now: an
14
+ * embedder mounting the editor over its own store computes the same model, the
15
+ * same vocabulary and the same view counts, and two definitions of any of them
16
+ * would be two answers to the same question. So the arithmetic lives here and
17
+ * the orchestration - which bytes to read, which digests to pin, what a layout
18
+ * sidecar says - stays with whoever owns those.
19
+ *
20
+ * Browser-safe: no `node:`, no filesystem, no session. Everything is a pure
21
+ * function of a compile result.
22
+ */
23
+ /**
24
+ * The kinds a browser may offer, each with the core kind it descends from.
25
+ *
26
+ * `[0]` is the nearest declared ancestor, the same reading
27
+ * `projectGraphForCanvas` gives an authored subject its `coreKindLabel`. A kind
28
+ * with no lineage IS a core kind and stands for itself. Without the core label
29
+ * an editor can read a palette but cannot judge it: the ArchiMate table is
30
+ * keyed on core kinds, so offering an extension kind unchecked puts a `YM404`
31
+ * one click away.
32
+ */
33
+ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
34
+ id,
35
+ label: kindLabelOf(id),
36
+ coreLabel: kindLabelOf(lineages.get(id)?.[0] ?? id),
37
+ }));
38
+ /**
39
+ * How many SUBJECTS a query matches, which is not the size of its match set.
40
+ *
41
+ * A `SemanticGraph`'s subjects are concepts and relationships together, so the
42
+ * match set returns both — right for narrowing a canvas that draws edges as
43
+ * well as nodes, and wrong for a number sitting beside a view called its
44
+ * subject count. A view over three components with two relationships between
45
+ * them would read as five, and the reviewer counting boxes would find three.
46
+ */
47
+ export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
48
+ /**
49
+ * Rebuilds the shared editor workspace from one successful compile.
50
+ *
51
+ * The caller owns metadata which cannot be inferred from a graph (authority,
52
+ * source revisions, layouts, and initial view); this helper owns all derived
53
+ * canvas, vocabulary, and view-count arithmetic.
54
+ */
55
+ export const renderedWorkspaceOf = (compiled, views, metadata) => {
56
+ const refreshedViews = views.map((view) => ({
57
+ ...view,
58
+ subjectCount: conceptCountOf(compiled.graph, view.query, compiled.profileContext),
59
+ }));
60
+ return {
61
+ model: {
62
+ ...metadata,
63
+ graph: projectGraphForCanvas(compiled.graph, compiled.profileContext),
64
+ vocabulary: {
65
+ conceptKinds: kindOptionsOf(compiled.profileContext.conceptKindLineages),
66
+ relationshipKinds: kindOptionsOf(compiled.profileContext.relationshipKindLineages),
67
+ },
68
+ },
69
+ views: refreshedViews,
70
+ };
71
+ };
72
+ /** Every subject a query draws, concepts and relationships alike. */
73
+ export const matchedIdsOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.map(({ id }) => id);
74
+ /** Every concept a query dropped, and the facet that dropped it (#248). */
75
+ export const exclusionsOf = (graph, query, profileContext) => explainProjection(graph, adHoc(query), profileContext);
76
+ /**
77
+ * A query on its own is not a projection, and every evaluator here wants one.
78
+ * The id is a placeholder that never reaches a document.
79
+ */
80
+ const adHoc = (query) => ({
81
+ format: "yarramate/projection/v1",
82
+ id: "ad-hoc",
83
+ version: "0",
84
+ query,
85
+ });
86
+ /**
87
+ * One saved view, as the rail reads it. `subjectCount` is the caller's,
88
+ * because counting needs a compiled graph and a session builds its first list
89
+ * before it has one.
90
+ */
91
+ export const viewSummaryOf = (projection, path, subjectCount = 0) => ({
92
+ id: projection.id,
93
+ title: projection.presentation?.title ?? projection.id,
94
+ description: projection.presentation?.description ?? "",
95
+ query: projection.query,
96
+ presentation: projection.presentation,
97
+ path,
98
+ subjectCount,
99
+ });
100
+ /**
101
+ * Applies a landed view batch to a host-owned list.
102
+ *
103
+ * Writes are read back through the callback, so a malformed or otherwise
104
+ * unreadable landed document cannot become a fabricated summary.
105
+ */
106
+ export const adoptLandedViews = (views, operations, readSummary) => {
107
+ const next = [...views];
108
+ for (const operation of operations) {
109
+ const at = next.findIndex((view) => view.path === operation.path);
110
+ if (operation.op === "delete-view") {
111
+ if (at !== -1)
112
+ next.splice(at, 1);
113
+ continue;
114
+ }
115
+ const summary = readSummary(operation.path);
116
+ if (summary === undefined)
117
+ continue;
118
+ if (at === -1)
119
+ next.push(summary);
120
+ else
121
+ next[at] = summary;
122
+ }
123
+ return next;
124
+ };
125
+ /**
126
+ * The document a diagnostic the runtime minted itself points at. Not a file:
127
+ * these are about the session rather than about anything the workspace holds.
128
+ */
129
+ export const VISUAL_SERVER_DOCUMENT = "visual-session-server";
130
+ export const published = (diagnostics, sources) => withDiagnosticSubjects(diagnostics, sources);
131
+ export const serverDiagnostic = (code, message, pointer = "/") => ({
132
+ severity: "error",
133
+ code,
134
+ message,
135
+ path: VISUAL_SERVER_DOCUMENT,
136
+ pointer,
137
+ line: 1,
138
+ column: 1,
139
+ });
140
+ /**
141
+ * Whether the workspace would load a projection written at this path.
142
+ *
143
+ * This is a DIRECTORY check, not a pattern match, and deliberately so: ADR
144
+ * 0100 recorded that the manifest's pattern dialect has never been named - it
145
+ * is whatever `globSync` happens to support, undocumented and untested past
146
+ * one wildcard - so a matcher written here would be inventing the dialect
147
+ * rather than honouring it. A directory that already holds a projection the
148
+ * manifest resolved is a directory the manifest demonstrably reaches.
149
+ *
150
+ * A workspace with no projections at all has nothing to demonstrate, so the
151
+ * default directory is allowed: refusing there would make the first view in a
152
+ * fresh workspace impossible to create.
153
+ */
154
+ export const projectionDirectoryIsCovered = (path, workspace) => {
155
+ if (workspace.projections.length === 0) {
156
+ return posixDirectoryOf(path) === DEFAULT_PROJECTION_DIRECTORY;
157
+ }
158
+ const covered = new Set(workspace.projections.map(posixDirectoryOf));
159
+ return covered.has(posixDirectoryOf(path));
160
+ };
161
+ /**
162
+ * Turns staged view operations into pending writes, refusing before anything
163
+ * is written (ADR 0103).
164
+ *
165
+ * A `write-view` is validated through the same `loadProjection` the CLI's own
166
+ * projection writes go through, so a document the schema would reject never
167
+ * reaches the store. A `delete-view` names a revision, which is what makes a
168
+ * removal refusable rather than a silent success on a file someone else
169
+ * already changed.
170
+ */
171
+ export const planViewWrites = (operations, workspace, store) => {
172
+ const writes = [];
173
+ const diagnostics = [];
174
+ for (const operation of operations) {
175
+ const held = store.read(operation.path);
176
+ if (operation.op === "delete-view") {
177
+ if (held === undefined) {
178
+ diagnostics.push(serverDiagnostic("YMVS314", `View "${operation.path}" is already gone`));
179
+ continue;
180
+ }
181
+ writes.push({
182
+ path: operation.path,
183
+ source: null,
184
+ expected: held.revision,
185
+ });
186
+ continue;
187
+ }
188
+ if (held === undefined &&
189
+ !projectionDirectoryIsCovered(operation.path, workspace)) {
190
+ // A projection no pattern reaches is a file the workspace never loads,
191
+ // which is worse than a refusal because nothing later says so (ADR 0043).
192
+ diagnostics.push(serverDiagnostic("YMVS315", `The workspace manifest covers no projection in "${posixDirectoryOf(operation.path)}", so a view saved there would never load`));
193
+ continue;
194
+ }
195
+ const source = stringify(operation.projection);
196
+ const loaded = loadProjection({ path: operation.path, source });
197
+ if (!loaded.ok) {
198
+ // The composed document is the only source these are about, so they are
199
+ // published against it rather than against the workspace.
200
+ diagnostics.push(...published(loaded.diagnostics, [{ path: operation.path, source }]));
201
+ continue;
202
+ }
203
+ writes.push({
204
+ path: operation.path,
205
+ source,
206
+ expected: held?.revision ?? null,
207
+ });
208
+ }
209
+ return diagnostics.length > 0
210
+ ? { ok: false, diagnostics }
211
+ : { ok: true, writes };
212
+ };
@@ -30,7 +30,7 @@ export interface VisualStartIo {
30
30
  * Serves one visual session in the foreground.
31
31
  *
32
32
  * The request is validated before any filesystem or network effect, so a bad
33
- * document costs nothing. Exactly one public `visual-session-started/v1` line is
33
+ * document costs nothing. Exactly one public `visual-session-started/v2` line is
34
34
  * published before the command blocks — it names the private descriptor but
35
35
  * never carries the agent capability, which lives only in that mode 0600 file.
36
36
  * The command then holds the session open until the runtime closes it, whether
@@ -10,11 +10,14 @@ export const visualUsage = 'Usage:\n' +
10
10
  ' yarramate-visual request [--view <id>] [--title <text>]\n' +
11
11
  ' [--description <text>] [--chat]\n' +
12
12
  ' yarramate-visual start <request.json>\n' +
13
- ' yarramate-visual wait <descriptor.json> [--after <sequence>]\n' +
14
- ' yarramate-visual respond <descriptor.json> <response.json>\n' +
15
- ' yarramate-visual status <descriptor.json>\n' +
16
- ' yarramate-visual recover <descriptor.json> [--transcript]\n' +
17
- ' yarramate-visual stop <descriptor.json> [--transcript]\n' +
13
+ ' yarramate-visual wait <descriptor-uri> [--after <sequence>]\n' +
14
+ ' yarramate-visual respond <descriptor-uri> <response.json>\n' +
15
+ ' yarramate-visual status <descriptor-uri>\n' +
16
+ ' yarramate-visual recover <descriptor-uri> [--transcript]\n' +
17
+ ' yarramate-visual stop <descriptor-uri> [--transcript]\n' +
18
+ '\n' +
19
+ '<descriptor-uri> is the `descriptorPath` file: URI `start` published,\n' +
20
+ 'copied back verbatim. It is never resolved against the working directory.\n' +
18
21
  ' yarramate-visual --version\n';
19
22
  /**
20
23
  * Where a repository keeps its live sessions, one directory per session. It sits
@@ -126,11 +129,11 @@ const requestCliCommand = (rest, cwd) => {
126
129
  return refusalResult(built.diagnostics);
127
130
  return { exitCode: 0, stdout: documentLine(built.request), stderr: '' };
128
131
  };
129
- const waitCliCommand = async (descriptorPath, rest, cwd) => {
132
+ const waitCliCommand = async (descriptorUri, rest) => {
130
133
  const after = sequenceFlag(rest);
131
134
  if (after === undefined)
132
135
  return usageResult;
133
- const descriptor = await readVisualSessionDescriptor(descriptorPath, cwd);
136
+ const descriptor = await readVisualSessionDescriptor(descriptorUri);
134
137
  if (!descriptor.ok)
135
138
  return refusalResult(descriptor.diagnostics);
136
139
  const delivery = await waitForVisualEvent(descriptor.value, after);
@@ -144,13 +147,16 @@ const waitCliCommand = async (descriptorPath, rest, cwd) => {
144
147
  stderr: '',
145
148
  };
146
149
  };
147
- const respondCliCommand = async (descriptorPath, rest, cwd) => {
150
+ const respondCliCommand = async (descriptorUri, rest, cwd) => {
148
151
  const [responsePath, ...trailing] = rest;
149
152
  if (responsePath === undefined || trailing.length > 0)
150
153
  return usageResult;
151
- const descriptor = await readVisualSessionDescriptor(descriptorPath, cwd);
154
+ const descriptor = await readVisualSessionDescriptor(descriptorUri);
152
155
  if (!descriptor.ok)
153
156
  return refusalResult(descriptor.diagnostics);
157
+ // The response document is the agent's own file, named however the agent
158
+ // likes: it is not a protocol path field, so it stays a native path
159
+ // resolved against `cwd`.
154
160
  const document = await readVisualJsonDocument(responsePath, cwd);
155
161
  if (!document.ok)
156
162
  return refusalResult(document.diagnostics);
@@ -164,10 +170,10 @@ const respondCliCommand = async (descriptorPath, rest, cwd) => {
164
170
  return refusalResult(accepted.diagnostics);
165
171
  return { exitCode: 0, stdout: documentLine(accepted.value), stderr: '' };
166
172
  };
167
- const statusCliCommand = async (descriptorPath, rest, cwd) => {
173
+ const statusCliCommand = async (descriptorUri, rest) => {
168
174
  if (rest.length > 0)
169
175
  return usageResult;
170
- const descriptor = await readVisualSessionDescriptor(descriptorPath, cwd);
176
+ const descriptor = await readVisualSessionDescriptor(descriptorUri);
171
177
  if (!descriptor.ok)
172
178
  return refusalResult(descriptor.diagnostics);
173
179
  const status = await fetchVisualStatus(descriptor.value);
@@ -175,11 +181,11 @@ const statusCliCommand = async (descriptorPath, rest, cwd) => {
175
181
  return refusalResult(status.diagnostics);
176
182
  return { exitCode: 0, stdout: documentLine(status.value), stderr: '' };
177
183
  };
178
- const recoverCliCommand = async (descriptorPath, rest, cwd) => {
184
+ const recoverCliCommand = async (descriptorUri, rest) => {
179
185
  const includeTranscript = transcriptFlag(rest);
180
186
  if (includeTranscript === undefined)
181
187
  return usageResult;
182
- const descriptor = await readVisualSessionDescriptor(descriptorPath, cwd);
188
+ const descriptor = await readVisualSessionDescriptor(descriptorUri);
183
189
  if (!descriptor.ok)
184
190
  return refusalResult(descriptor.diagnostics);
185
191
  const handoff = await recoverVisualSessionClient(descriptor.value, includeTranscript);
@@ -201,13 +207,13 @@ const recoverCliCommand = async (descriptorPath, rest, cwd) => {
201
207
  * descriptor that did not parse, and a corrupt, redirected, or foreign
202
208
  * descriptor fails exactly as before.
203
209
  */
204
- const stopCliCommand = async (descriptorPath, rest, cwd) => {
210
+ const stopCliCommand = async (descriptorUri, rest) => {
205
211
  const includeTranscript = transcriptFlag(rest);
206
212
  if (includeTranscript === undefined)
207
213
  return usageResult;
208
- const descriptor = await readVisualSessionDescriptor(descriptorPath, cwd);
214
+ const descriptor = await readVisualSessionDescriptor(descriptorUri);
209
215
  if (!descriptor.ok) {
210
- return (await visualSessionAlreadyStopped(descriptorPath, cwd))
216
+ return (await visualSessionAlreadyStopped(descriptorUri))
211
217
  ? { exitCode: 0, stdout: '', stderr: '' }
212
218
  : refusalResult(descriptor.diagnostics);
213
219
  }
@@ -236,17 +242,20 @@ export async function runVisualClientCli(args, cwd = process.cwd()) {
236
242
  return requestCliCommand(args.slice(1), cwd);
237
243
  if (descriptor === undefined)
238
244
  return usageResult;
245
+ // `cwd` reaches only the commands that still read a native path of their
246
+ // own. The descriptor argument is a `file:` URI and is never resolved
247
+ // against anything.
239
248
  switch (command) {
240
249
  case 'wait':
241
- return waitCliCommand(descriptor, rest, cwd);
250
+ return waitCliCommand(descriptor, rest);
242
251
  case 'respond':
243
252
  return respondCliCommand(descriptor, rest, cwd);
244
253
  case 'status':
245
- return statusCliCommand(descriptor, rest, cwd);
254
+ return statusCliCommand(descriptor, rest);
246
255
  case 'recover':
247
- return recoverCliCommand(descriptor, rest, cwd);
256
+ return recoverCliCommand(descriptor, rest);
248
257
  case 'stop':
249
- return stopCliCommand(descriptor, rest, cwd);
258
+ return stopCliCommand(descriptor, rest);
250
259
  default:
251
260
  return usageResult;
252
261
  }
@@ -259,7 +268,7 @@ const processIo = {
259
268
  * Serves one visual session in the foreground.
260
269
  *
261
270
  * The request is validated before any filesystem or network effect, so a bad
262
- * document costs nothing. Exactly one public `visual-session-started/v1` line is
271
+ * document costs nothing. Exactly one public `visual-session-started/v2` line is
263
272
  * published before the command blocks — it names the private descriptor but
264
273
  * never carries the agent capability, which lives only in that mode 0600 file.
265
274
  * The command then holds the session open until the runtime closes it, whether
@@ -0,0 +1,2 @@
1
+ import type { CliResult } from './cli-support.js';
2
+ export declare function runApplyCommand(options: readonly string[], cwd: string): CliResult;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The `apply` command, and the only part of applying a batch that touches a
3
+ * filesystem.
4
+ *
5
+ * It lives apart from `./apply-command.js` for the reason `./nesting.js` and
6
+ * `./kind-label.js` state for their own values: the engine half is reachable
7
+ * from a browser now (#252), and a top-level `node:fs` import is a bundle
8
+ * failure whether or not the function that uses it is ever called. Core reads
9
+ * nothing (ADR 0100) - this is the caller that reads for it.
10
+ */
11
+ import { readFileSync } from 'node:fs';
12
+ import { resolve } from 'node:path';
13
+ import { parseDocument } from 'yaml';
14
+ import { landOperations, posixDirectoryOf } from './apply-command.js';
15
+ import { createFileSystemStore } from './source-store.js';
16
+ import { loadWorkspaceManifest } from './workspace.js';
17
+ import { diagnosticJson, humanDiagnostics, usage } from './cli-support.js';
18
+ export function runApplyCommand(options, cwd) {
19
+ const json = options.includes('--json');
20
+ const rest = options.filter((option) => option !== '--json');
21
+ const [operationsPath, workspacePath] = rest;
22
+ if (rest.length !== 2 ||
23
+ operationsPath === undefined ||
24
+ workspacePath === undefined ||
25
+ rest.some((option) => option.startsWith('-'))) {
26
+ return { exitCode: 2, stdout: '', stderr: usage };
27
+ }
28
+ try {
29
+ const manifestSource = readFileSync(resolve(cwd, workspacePath), 'utf8');
30
+ if (parseDocument(manifestSource).get('format') !== 'yarramate/workspace/v1') {
31
+ return {
32
+ exitCode: 2,
33
+ stdout: '',
34
+ stderr: 'apply requires an explicit workspace manifest (yarramate/workspace/v1)\n',
35
+ };
36
+ }
37
+ const operationsSource = readFileSync(resolve(cwd, operationsPath), 'utf8');
38
+ const loadedWorkspace = loadWorkspaceManifest({ path: workspacePath, source: manifestSource }, cwd);
39
+ if (!loadedWorkspace.ok) {
40
+ return {
41
+ exitCode: 1,
42
+ stdout: json
43
+ ? diagnosticJson(loadedWorkspace.diagnostics)
44
+ : humanDiagnostics(loadedWorkspace.diagnostics),
45
+ stderr: '',
46
+ };
47
+ }
48
+ const outcome = landOperations(createFileSystemStore(cwd), {
49
+ workspace: loadedWorkspace.workspace,
50
+ operations: { path: operationsPath, source: operationsSource },
51
+ manifestDirectory: posixDirectoryOf(workspacePath),
52
+ });
53
+ if (!outcome.ok) {
54
+ return {
55
+ exitCode: 1,
56
+ stdout: json
57
+ ? diagnosticJson(outcome.diagnostics)
58
+ : humanDiagnostics(outcome.diagnostics),
59
+ stderr: '',
60
+ };
61
+ }
62
+ const { result } = outcome;
63
+ if (json) {
64
+ return {
65
+ exitCode: 0,
66
+ stdout: `${JSON.stringify(result, null, 2)}\n`,
67
+ stderr: '',
68
+ };
69
+ }
70
+ // Every counter, summed by iteration rather than by hand, so a new
71
+ // operation kind cannot silently report zero work.
72
+ const applied = Object.values(result.applied).reduce((total, count) => total + count, 0);
73
+ return {
74
+ exitCode: 0,
75
+ stdout: `Applied ${applied} operation${applied === 1 ? '' : 's'} to ${result.documents.join(', ')}\n`,
76
+ stderr: '',
77
+ };
78
+ }
79
+ catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ return { exitCode: 2, stdout: '', stderr: `${message}\n` };
82
+ }
83
+ }
@@ -1,12 +1,93 @@
1
- import { type CliResult } from './cli-support.js';
2
1
  import { type Diagnostic, type WorkspaceSource } from './compiler.js';
2
+ import type { ResolvedWorkspace } from './workspace.js';
3
+ import { type PendingWrite, type SourceStore, type WriteConflict } from './source-store.js';
4
+ /**
5
+ * The directory part of a workspace path, in the `/`-separated terms a
6
+ * manifest is written in rather than the platform's.
7
+ *
8
+ * Both separators by hand rather than `node:path`'s `sep`: this module is
9
+ * reachable from a browser (#252), where there is no platform separator to
10
+ * ask about, and a manifest path is `/`-separated wherever it is read.
11
+ */
12
+ export declare const posixDirectoryOf: (path: string) => string;
3
13
  import type { YarramateApplyResult } from './operations.js';
4
14
  export type ApplyOutcome = {
5
15
  readonly ok: true;
16
+ /**
17
+ * The documents this batch changed, for the caller to write. Core
18
+ * returns them rather than writing them, so the store's comparison is
19
+ * the last thing that happens before bytes land (ADR 0100).
20
+ */
21
+ readonly sources: readonly WorkspaceSource[];
6
22
  readonly result: YarramateApplyResult;
7
23
  } | {
8
24
  readonly ok: false;
9
25
  readonly diagnostics: readonly Diagnostic[];
10
26
  };
11
- export declare const applyOperations: (operations: WorkspaceSource, workspace: WorkspaceSource, cwd: string) => ApplyOutcome;
12
- export declare function runApplyCommand(options: readonly string[], cwd: string): CliResult;
27
+ /**
28
+ * Everything `applyOperations` is allowed to know. The workspace is already
29
+ * resolved and the sources are already read, because Core does not reach for
30
+ * either (ADR 0100).
31
+ */
32
+ export interface ApplyInput {
33
+ /** The workspace this batch is addressed to, already resolved. */
34
+ readonly workspace: ResolvedWorkspace;
35
+ /** Every source that workspace resolves to, keyed by its manifest path. */
36
+ readonly sources: readonly WorkspaceSource[];
37
+ /** The operations document itself. */
38
+ readonly operations: WorkspaceSource;
39
+ /**
40
+ * Where the manifest sits relative to the paths in `workspace`, so an
41
+ * operation may address a document the way the manifest names it as well as
42
+ * the way the workspace lists it (#216). String arithmetic, not a lookup.
43
+ */
44
+ readonly manifestDirectory: string;
45
+ }
46
+ export declare const applyOperations: (input: ApplyInput) => ApplyOutcome;
47
+ export type PlannedOperations = {
48
+ readonly ok: true;
49
+ readonly outcome: ApplyOutcome & {
50
+ readonly ok: true;
51
+ };
52
+ /** What this batch would write, unwritten. Empty when it changes nothing. */
53
+ readonly writes: readonly PendingWrite[];
54
+ } | {
55
+ readonly ok: false;
56
+ readonly outcome: ApplyOutcome & {
57
+ readonly ok: false;
58
+ };
59
+ };
60
+ /**
61
+ * Reads a workspace through a store and applies a batch, stopping short of
62
+ * writing (ADR 0100).
63
+ *
64
+ * Separated from {@link landOperations} because a caller may have writes of
65
+ * its own to land in the same batch: the visual runtime commits a projection
66
+ * beside the model it belongs to, and one `writeAll` is what makes the two
67
+ * arrive together or not at all (ADR 0103). A caller with nothing to add wants
68
+ * `landOperations` and should not see this.
69
+ */
70
+ export declare const planOperations: (store: SourceStore, input: {
71
+ readonly workspace: ResolvedWorkspace;
72
+ readonly operations: WorkspaceSource;
73
+ readonly manifestDirectory: string;
74
+ }) => PlannedOperations;
75
+ /**
76
+ * The diagnostics a refused `writeAll` becomes, in the workspace range
77
+ * (ADR 0100). Shared so a caller that merges writes of its own into the batch
78
+ * reports a conflict the same way the CLI does.
79
+ */
80
+ export declare const writeConflictDiagnostics: (conflicts: readonly WriteConflict[], operationsPath: string) => readonly Diagnostic[];
81
+ /**
82
+ * Reads a workspace through a store, applies a batch, and writes what changed
83
+ * back only if every document still holds what was read (ADR 0100).
84
+ *
85
+ * This is the composition Core deliberately does not perform: `applyOperations`
86
+ * is a pure function, and the comparison that decides whether a batch lands
87
+ * belongs to the store.
88
+ */
89
+ export declare const landOperations: (store: SourceStore, input: {
90
+ readonly workspace: ResolvedWorkspace;
91
+ readonly operations: WorkspaceSource;
92
+ readonly manifestDirectory: string;
93
+ }) => ApplyOutcome;