yarramate 1.5.0 → 1.7.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 (49) hide show
  1. package/dist/adapters/visual/session-server.js +27 -1
  2. package/dist/adapters/visual/workspace-model.d.ts +16 -3
  3. package/dist/adapters/visual/workspace-model.js +12 -5
  4. package/dist/ask-command.js +25 -16
  5. package/dist/catalogue-sources.d.ts +20 -0
  6. package/dist/catalogue-sources.js +39 -0
  7. package/dist/check-command.js +38 -9
  8. package/dist/cli-support.d.ts +8 -1
  9. package/dist/cli-support.js +5 -1
  10. package/dist/cli.d.ts +10 -0
  11. package/dist/cli.js +17 -1
  12. package/dist/design-command.js +21 -9
  13. package/dist/export-command.js +41 -5
  14. package/dist/import-command.d.ts +16 -0
  15. package/dist/import-command.js +156 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/interrogate-command.d.ts +82 -1
  19. package/dist/interrogate-command.js +195 -29
  20. package/dist/interrogation-entry.d.ts +1 -1
  21. package/dist/interrogation-entry.js +1 -1
  22. package/dist/projection.d.ts +47 -0
  23. package/dist/projection.js +126 -0
  24. package/dist/visual-app-lib/editor.js +26771 -26713
  25. package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +16 -3
  26. package/dist/visual-app-lib/types/interrogate-command.d.ts +82 -1
  27. package/dist/visual-app-lib/types/projection.d.ts +47 -0
  28. package/dist/visual-app-lib/types/workspace.d.ts +15 -0
  29. package/dist/workbook-entry.d.ts +16 -0
  30. package/dist/workbook-entry.js +16 -0
  31. package/dist/workbook-import-entry.d.ts +30 -0
  32. package/dist/workbook-import-entry.js +30 -0
  33. package/dist/workbook-merge.d.ts +77 -0
  34. package/dist/workbook-merge.js +108 -0
  35. package/dist/workbook-operations.d.ts +9 -0
  36. package/dist/workbook-operations.js +139 -0
  37. package/dist/workbook-read.d.ts +43 -0
  38. package/dist/workbook-read.js +314 -0
  39. package/dist/workbook-xlsx.d.ts +54 -0
  40. package/dist/workbook-xlsx.js +228 -0
  41. package/dist/workbook.d.ts +32 -0
  42. package/dist/workbook.js +308 -0
  43. package/dist/workspace.d.ts +15 -0
  44. package/dist/workspace.js +1 -0
  45. package/docs/CONSUMING-YARRAMATE.md +127 -0
  46. package/package.json +9 -1
  47. package/schema/yarramate-interrogation-report.schema.json +9 -0
  48. package/schema/yarramate-question-catalogue.schema.json +2 -3
  49. package/schema/yarramate-workspace.schema.json +4 -0
@@ -1,6 +1,7 @@
1
1
  import Ajv2020Import from 'ajv/dist/2020.js';
2
2
  import { isDeclaredNonGoal } from './brief.js';
3
3
  import { loadSourceDocument } from './source-document.js';
4
+ import { similarity } from './subject-identity.js';
4
5
  import projectionSchema from '../schema/yarramate-projection.schema.json' with { type: 'json'
5
6
  };
6
7
  const ajv2020Module = Ajv2020Import;
@@ -182,6 +183,131 @@ const conceptSelector = (graph, projection, profileContext) => {
182
183
  },
183
184
  };
184
185
  };
186
+ /** Below this, a suggestion is noise rather than a hint. */
187
+ const SUGGESTION_THRESHOLD = 0.6;
188
+ /**
189
+ * Every value in a projection query that names nothing.
190
+ *
191
+ * A projection is a DOCUMENT, and a query holds references the same way a
192
+ * relationship does. YarraMate refuses a relationship pointing at a concept
193
+ * that does not exist; it did not refuse a query naming a state that does not
194
+ * exist, and the symptom is silent. `states: [target-stat]` selects no state,
195
+ * which selects no subject, which exports a clean empty artifact with exit 0.
196
+ * Someone hands that to a client.
197
+ *
198
+ * Checked at `check`, not at `export`, because the typo is in a file rather
199
+ * than in an invocation: CI catches it, and every verb over the same
200
+ * projection inherits the guard instead of each growing its own.
201
+ *
202
+ * ONLY FACETS WITH A CLOSED NAMESPACE ARE CHECKED. `statuses` and
203
+ * `excludeStatuses` are schema enums, refused upstream before this runs.
204
+ * Everything else names something: `owners` and `constraints` are REFS to
205
+ * concepts, which the compiler itself proves by refusing an unresolved owner
206
+ * with YM304, so their namespace is the subject list like `subjects`.
207
+ *
208
+ * Each namespace is derived the way the FILTER derives it, so the check cannot
209
+ * drift from what it guards: `documents` reads the same provenance the
210
+ * `documents` facet compares against, and `states` is the same
211
+ * `yarramate/state/type` scan `conceptSelector` runs.
212
+ *
213
+ * A kind whose profile is not loaded is DORMANT rather than wrong, the same
214
+ * distinction #351 drew for question catalogues, so the kind facets are
215
+ * checked only when a profile context is present.
216
+ *
217
+ * An empty RESULT is not reported here and must not be. A query whose every
218
+ * name resolves and which selects nothing is a real answer to a real question:
219
+ * a target state nobody has populated yet is empty, correctly.
220
+ */
221
+ export function unmatchedSelectors(graph, projection, profileContext) {
222
+ const query = projection.query;
223
+ const found = [];
224
+ const check = (facet, values, known) => {
225
+ if (values === undefined)
226
+ return;
227
+ for (const value of values) {
228
+ if (known.has(value))
229
+ continue;
230
+ let nearest;
231
+ let best = 0;
232
+ for (const candidate of known) {
233
+ const score = similarity(value, candidate);
234
+ if (score > best) {
235
+ best = score;
236
+ nearest = candidate;
237
+ }
238
+ }
239
+ found.push({
240
+ facet,
241
+ value,
242
+ ...(nearest !== undefined && best >= SUGGESTION_THRESHOLD
243
+ ? { nearest }
244
+ : {}),
245
+ });
246
+ }
247
+ };
248
+ const subjectIds = new Set(graph.subjects.map(({ id }) => id));
249
+ check('subjects', query.subjects, subjectIds);
250
+ check('exclude', query.exclude, subjectIds);
251
+ // Against the SUBJECT list rather than against owners currently in use: a
252
+ // team that owns nothing yet is a real concept and selecting it is a real
253
+ // question with an empty answer, which is exactly what must not be refused.
254
+ check('owners', query.owners, subjectIds);
255
+ check('constraints', query.constraints, subjectIds);
256
+ // The same provenance `documents` filters on: a subject id no longer carries
257
+ // the document that declared it, so both read it off the kind claim.
258
+ const documentIds = new Set();
259
+ for (const claim of graph.claims) {
260
+ if (claim.predicate !== 'yarramate/concept/kind')
261
+ continue;
262
+ const document = claim.source?.document;
263
+ if (typeof document === 'string')
264
+ documentIds.add(document);
265
+ }
266
+ check('documents', query.documents, documentIds);
267
+ check('states', query.states, new Set(graph.claims
268
+ .filter(({ predicate }) => predicate === 'yarramate/state/type')
269
+ .map(({ subject }) => subject)));
270
+ if (profileContext !== undefined) {
271
+ check('kinds', query.kinds, new Set(profileContext.conceptKindLineages.keys()));
272
+ check('relationshipKinds', query.relationshipKinds, new Set(profileContext.relationshipKindLineages.keys()));
273
+ check('layers', query.layers, new Set(profileContext.conceptKindLayers.values()));
274
+ }
275
+ return found;
276
+ }
277
+ /**
278
+ * Where a value sits in the projection source, so the diagnostic is clickable.
279
+ * A query value appears once in a list item, and finding it by text is enough:
280
+ * the alternative is threading a YAML CST through a check that only needs to
281
+ * point at a line.
282
+ */
283
+ const locate = (source, value) => {
284
+ const lines = source.split('\n');
285
+ for (let index = 0; index < lines.length; index += 1) {
286
+ const at = lines[index].indexOf(value);
287
+ if (at >= 0)
288
+ return { line: index + 1, column: at + 1 };
289
+ }
290
+ return { line: 1, column: 1 };
291
+ };
292
+ /**
293
+ * {@link unmatchedSelectors} as diagnostics, built HERE rather than at each
294
+ * caller so `check`, and anything that adopts this later, refuse in identical
295
+ * words with an identical code.
296
+ */
297
+ export function projectionReferenceDiagnostics(source, projection, graph, profileContext) {
298
+ return unmatchedSelectors(graph, projection, profileContext).map(({ facet, value, nearest }) => ({
299
+ severity: 'error',
300
+ code: 'YM921',
301
+ message: `Projection query \`${facet}\` names ${JSON.stringify(value)}, ` +
302
+ 'which this workspace does not have, so it can never match. ' +
303
+ (nearest === undefined
304
+ ? 'Check the spelling against the model.'
305
+ : `Did you mean ${JSON.stringify(nearest)}?`),
306
+ path: source.path,
307
+ pointer: `/query/${facet}`,
308
+ ...locate(source.source, value),
309
+ }));
310
+ }
185
311
  /**
186
312
  * Every concept a query leaves out, and the facet that left it out.
187
313
  *