yarramate 1.31.0 → 1.33.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 (76) hide show
  1. package/catalogues/core-enrichment.yaml +70 -0
  2. package/dist/adapters/likec4-export.d.ts +3 -0
  3. package/dist/adapters/likec4-export.js +4 -1
  4. package/dist/adapters/likec4-project-export.d.ts +10 -1
  5. package/dist/adapters/likec4-project-export.js +8 -3
  6. package/dist/adapters/likec4-project.d.ts +3 -0
  7. package/dist/adapters/likec4-project.js +4 -3
  8. package/dist/adapters/mcp-cli.js +17 -4
  9. package/dist/branding.d.ts +86 -0
  10. package/dist/branding.js +80 -0
  11. package/dist/brief.js +4 -1
  12. package/dist/cli-support.d.ts +1 -1
  13. package/dist/cli-support.js +1 -1
  14. package/dist/compiler.js +70 -50
  15. package/dist/export-command.js +30 -4
  16. package/dist/graph-projection.d.ts +12 -0
  17. package/dist/graph-projection.js +7 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/projection.d.ts +5 -0
  21. package/dist/projection.js +4 -0
  22. package/dist/relationship-reading.d.ts +12 -1
  23. package/dist/relationship-reading.js +28 -3
  24. package/dist/responsibility-kinds.d.ts +23 -0
  25. package/dist/responsibility-kinds.js +43 -0
  26. package/dist/responsibility.d.ts +99 -0
  27. package/dist/responsibility.js +239 -0
  28. package/dist/schema-validators.generated.js +37 -25
  29. package/dist/shipped-catalogue.generated.d.ts +1 -1
  30. package/dist/shipped-catalogue.generated.js +1 -1
  31. package/dist/shipped-profile.d.ts +21 -0
  32. package/dist/shipped-profile.js +33 -0
  33. package/dist/tools/export.d.ts +11 -0
  34. package/dist/tools/export.js +28 -1
  35. package/dist/tools/table.d.ts +32 -3
  36. package/dist/tools/table.js +96 -36
  37. package/dist/tools/workspace.d.ts +6 -0
  38. package/dist/tools-entry.d.ts +5 -2
  39. package/dist/tools-entry.js +5 -2
  40. package/dist/visual-app/assets/elk-worker.min-B0rP1gRk.js +22 -0
  41. package/dist/visual-app/assets/elk.bundled-NyuRbCog.js +24 -0
  42. package/dist/visual-app/assets/index-Bg4LA2rt.js +373 -0
  43. package/dist/visual-app/assets/index-DYTKTQsG.css +1 -0
  44. package/dist/visual-app/index.html +2 -2
  45. package/dist/visual-app-lib/editor.js +31979 -31096
  46. package/dist/visual-app-lib/styles.css +1 -1
  47. package/dist/visual-app-lib/types/branding.d.ts +86 -0
  48. package/dist/visual-app-lib/types/graph-projection.d.ts +12 -0
  49. package/dist/visual-app-lib/types/projection.d.ts +5 -0
  50. package/dist/visual-app-lib/types/relationship-reading.d.ts +12 -1
  51. package/dist/visual-app-lib/types/responsibility-kinds.d.ts +23 -0
  52. package/dist/visual-app-lib/types/shipped-catalogue.generated.d.ts +1 -1
  53. package/dist/visual-app-lib/types/shipped-profile.d.ts +21 -0
  54. package/dist/visual-app-lib/types/tools/workspace.d.ts +6 -0
  55. package/dist/visual-app-lib/types/visual-app/App.d.ts +4 -1
  56. package/dist/visual-app-lib/types/visual-app/elk-layout.d.ts +2 -0
  57. package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +10 -1
  58. package/dist/visual-app-lib/types/visual-app/mount.d.ts +10 -0
  59. package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +3 -2
  60. package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +3 -1
  61. package/dist/visual-app-lib/types/visual-app/subject-form.d.ts +5 -0
  62. package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +4 -1
  63. package/dist/workbook-entry.d.ts +1 -0
  64. package/dist/workbook-entry.js +1 -0
  65. package/dist/workbook.d.ts +3 -2
  66. package/dist/workbook.js +19 -4
  67. package/docs/CONSUMING-YARRAMATE.md +56 -1
  68. package/package.json +10 -9
  69. package/schema/yarramate-projection.schema.json +4 -0
  70. package/schema/yarramate-responsibility.schema.json +123 -0
  71. package/schema/yarramate-visual-graph.schema.json +8 -0
  72. package/skills/yarramate-architecture/SKILL.md +1 -0
  73. package/dist/visual-app/assets/elk-worker.min-D8OVqK8T.js +0 -22
  74. package/dist/visual-app/assets/elk.bundled-JlCCXIin.js +0 -24
  75. package/dist/visual-app/assets/index-BSwETsaq.js +0 -373
  76. package/dist/visual-app/assets/index-CZSChZa6.css +0 -1
@@ -830,6 +830,76 @@ questions:
830
830
  Add an owner reference to an accountable actor (evidence such as
831
831
  CODEOWNERS may propose it; a human confirms accountability).
832
832
 
833
+ - id: responsible-missing
834
+ wave: business
835
+ since: "1.33"
836
+ scope: subject
837
+ subjects:
838
+ kinds:
839
+ - yarramate/core@0.1#businessService
840
+ - yarramate/core@0.1#applicationService
841
+ - yarramate/core@0.1#applicationComponent
842
+ - yarramate/core@0.1#capability
843
+ trigger:
844
+ - condition: missing-linkage
845
+ kinds:
846
+ - yarramate/policy@0.2#responsible
847
+ direction: incoming
848
+ counterpartKinds:
849
+ - yarramate/core@0.1#businessActor
850
+ - yarramate/core@0.1#businessRole
851
+ - yarramate/core@0.1#businessCollaboration
852
+ - yarramate/core@0.1#stakeholder
853
+ question: >-
854
+ Who is responsible for {subject.name}?
855
+ askPlain: >-
856
+ Who actually builds, runs or delivers "{subject.name}" day to day?
857
+ materiality: >-
858
+ Accountability says whose desk a failure lands on; responsibility says
859
+ whose hands are on it. A subject with nobody responsible has nobody to
860
+ hand the work to, and a RACI row with no R is a gap the matrix reports.
861
+ authority: either
862
+ resolution: >-
863
+ Add a `responsible` relationship from the actor, role or collaboration
864
+ that does the work to the subject (a support contract or CODEOWNERS may
865
+ propose it; a person confirms it).
866
+ - id: role-idle
867
+ wave: business
868
+ since: "1.33"
869
+ scope: subject
870
+ subjects:
871
+ kinds:
872
+ - yarramate/core@0.1#businessActor
873
+ - yarramate/core@0.1#businessRole
874
+ - yarramate/core@0.1#businessCollaboration
875
+ trigger:
876
+ - condition: missing-reference
877
+ predicate: yarramate/ownership/owner
878
+ direction: incoming
879
+ - condition: missing-relationship
880
+ kinds:
881
+ - yarramate/policy@0.2#responsible
882
+ - yarramate/policy@0.2#consulted
883
+ - yarramate/policy@0.2#informed
884
+ direction: outgoing
885
+ - condition: missing-relationship
886
+ kinds:
887
+ - yarramate/core@0.1#serving
888
+ direction: incoming
889
+ question: >-
890
+ What does {subject.name} answer for?
891
+ askPlain: >-
892
+ "{subject.name}" is in the model but owns nothing, is responsible for
893
+ nothing, and is neither consulted nor informed about anything. What is
894
+ their part, or should they go?
895
+ materiality: >-
896
+ A person with no letter is either missing the edges a delivery depends
897
+ on or a name that outlived its role. A served actor is a consumer, not
898
+ a responsibility holder, and is not asked.
899
+ authority: human
900
+ resolution: >-
901
+ Give the person a letter (an owner claim on what they answer for, or a
902
+ responsible, consulted or informed relationship) or retire the subject.
833
903
  - id: actor-unassigned
834
904
  wave: business
835
905
  since: "0.1"
@@ -1,4 +1,5 @@
1
1
  import { type AdapterMapping } from '../adapter-mapping.js';
2
+ import { type Branding } from '../branding.js';
2
3
  import type { StateComparison } from '../architecture-state.js';
3
4
  import type { ProjectionResult } from '../projection.js';
4
5
  import type { LikeC4KindMapping } from './likec4-kind-mapping.js';
@@ -21,6 +22,8 @@ export type LikeC4ExportResult = {
21
22
  };
22
23
  export interface LikeC4ExportOptions {
23
24
  readonly comparison?: StateComparison;
25
+ /** The banner names the host's product (#546, ADR 0158); absent, the engine. */
26
+ readonly branding?: Branding;
24
27
  /** Git-derived review overlay (ADR 0066): subjects new or changed in a
25
28
  * ref range carry metadata and view styling; nothing is authored. */
26
29
  readonly gitChange?: GitChangeOverlay;
@@ -1,4 +1,5 @@
1
1
  import { adapterMappingEntryLocation, adapterMappingLocation, } from '../adapter-mapping.js';
2
+ import { resolveBranding } from '../branding.js';
2
3
  import { diagnosticOrder } from '../source-document.js';
3
4
  const identifier = /^[A-Za-z_][A-Za-z0-9_-]*$/;
4
5
  const valueFor = (claims, subject, predicate) => {
@@ -114,8 +115,10 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
114
115
  diagnostics: diagnostics.sort(diagnosticOrder),
115
116
  };
116
117
  }
118
+ const brand = resolveBranding(options.branding);
117
119
  const lines = [
118
- '// Generated by YarraMate. Edit the native documents, not this file.',
120
+ `// Generated by ${brand.productName}. Edit the native documents, not this file.`,
121
+ ...(brand.vendorLine === null ? [] : [`// ${brand.vendorLine}`]),
119
122
  'model {',
120
123
  ];
121
124
  for (const concept of concepts) {
@@ -1,4 +1,5 @@
1
1
  import type { WorkspaceSource } from '../compiler.js';
2
+ import { type Branding } from '../branding.js';
2
3
  import { type LikeC4PreparationDiagnostic } from './likec4-prepare.js';
3
4
  import type { GitChangeOverlay } from './likec4-export.js';
4
5
  import { type LikeC4ProjectDefinition, type PreparedLikeC4ProjectView } from './likec4-project.js';
@@ -26,6 +27,12 @@ export interface LikeC4ProjectExportInput {
26
27
  readonly requireMappedRelationships: boolean;
27
28
  /** The git-derived review overlay, when the CLI derived one. */
28
29
  readonly gitChange?: GitChangeOverlay;
30
+ /**
31
+ * The host's branding (#546, ADR 0158): the model banner and the project
32
+ * name in `likec4.config.json`. The marker file and its digests are
33
+ * machinery and keep their names.
34
+ */
35
+ readonly branding?: Branding;
29
36
  }
30
37
  export interface LikeC4ProjectExported {
31
38
  readonly ok: true;
@@ -38,6 +45,8 @@ export interface LikeC4ProjectExported {
38
45
  readonly kindMappingIdentity?: string;
39
46
  /** Every source that fed the export, for the marker's input digests. */
40
47
  readonly inputs: readonly WorkspaceSource[];
48
+ /** Echoed from the input so `generatedProjectFiles` names the project for it. */
49
+ readonly branding?: Branding;
41
50
  }
42
51
  export type LikeC4ProjectExportResult = LikeC4ProjectExported | {
43
52
  readonly ok: false;
@@ -62,7 +71,7 @@ export interface GeneratedProjectOwnership {
62
71
  }
63
72
  export declare const ownershipOf: (exported: LikeC4ProjectExported) => GeneratedProjectOwnership;
64
73
  export declare const inputDigestsOf: (inputs: readonly WorkspaceSource[]) => Readonly<Record<string, string>>;
65
- export declare const projectNameOf: (projectIdentity: string) => string;
74
+ export declare const projectNameOf: (projectIdentity: string, branding?: Branding) => string;
66
75
  /**
67
76
  * The four files a generated project holds, exactly as the CLI writes them
68
77
  * under its output directory: the config, the model, the specification the
@@ -1,4 +1,5 @@
1
1
  import { parseDocument } from 'yaml';
2
+ import { brandSlug, resolveBranding } from '../branding.js';
2
3
  import { sha256Hex } from '../digest.js';
3
4
  import { LIKEC4_SPECIFICATION_SOURCE } from '../likec4-specification.generated.js';
4
5
  import { locateSourcePath } from '../source-document.js';
@@ -165,7 +166,10 @@ export const exportLikeC4ProjectFromSources = (input) => {
165
166
  }
166
167
  renderedViewIds.add(renderedId);
167
168
  }
168
- const exported = exportLikeC4Project(definition, successfulViews, input.gitChange === undefined ? {} : { gitChange: input.gitChange });
169
+ const exported = exportLikeC4Project(definition, successfulViews, {
170
+ ...(input.gitChange === undefined ? {} : { gitChange: input.gitChange }),
171
+ ...(input.branding === undefined ? {} : { branding: input.branding }),
172
+ });
169
173
  if (!exported.ok)
170
174
  return { ok: false, diagnostics: exported.diagnostics };
171
175
  const first = successfulViews[0];
@@ -182,6 +186,7 @@ export const exportLikeC4ProjectFromSources = (input) => {
182
186
  kindMappingIdentity: `${first.prepared.kindMapping.id}@${first.prepared.kindMapping.version}`,
183
187
  }),
184
188
  inputs: [projectSource, ...sources, ...referencedSources.values()],
189
+ ...(input.branding === undefined ? {} : { branding: input.branding }),
185
190
  };
186
191
  };
187
192
  // ---------------------------------------------------------------------------
@@ -208,7 +213,7 @@ export const ownershipOf = (exported) => ({
208
213
  export const inputDigestsOf = (inputs) => Object.fromEntries(inputs
209
214
  .map(({ path, source }) => [path, sha256Hex(source)])
210
215
  .sort(([left], [right]) => left.localeCompare(right)));
211
- export const projectNameOf = (projectIdentity) => `yarramate-${projectIdentity}`.replaceAll(/[^A-Za-z0-9_-]/g, '-');
216
+ export const projectNameOf = (projectIdentity, branding) => `${brandSlug(resolveBranding(branding))}-${projectIdentity}`.replaceAll(/[^A-Za-z0-9_-]/g, '-');
212
217
  /**
213
218
  * The four files a generated project holds, exactly as the CLI writes them
214
219
  * under its output directory: the config, the model, the specification the
@@ -218,7 +223,7 @@ export const projectNameOf = (projectIdentity) => `yarramate-${projectIdentity}`
218
223
  export const generatedProjectFiles = (exported) => {
219
224
  const configSource = `${JSON.stringify({
220
225
  $schema: 'https://likec4.dev/schemas/config.json',
221
- name: projectNameOf(exported.projectIdentity),
226
+ name: projectNameOf(exported.projectIdentity, exported.branding),
222
227
  title: exported.project.title,
223
228
  }, null, 2)}\n`;
224
229
  const markerSource = `${JSON.stringify({
@@ -1,3 +1,4 @@
1
+ import type { Branding } from '../branding.js';
1
2
  import type { WorkspaceSource } from '../compiler.js';
2
3
  import type { LikeC4PreparationResult } from './likec4-prepare.js';
3
4
  import { type GitChangeOverlay, type LikeC4ExportResult } from './likec4-export.js';
@@ -59,5 +60,7 @@ export interface PreparedLikeC4ProjectView {
59
60
  }
60
61
  export interface LikeC4ProjectExportOptions {
61
62
  readonly gitChange?: GitChangeOverlay;
63
+ /** Names the product in the model banner (#546, ADR 0158). */
64
+ readonly branding?: Branding;
62
65
  }
63
66
  export declare function exportLikeC4Project(project: LikeC4ProjectDefinition, views: readonly PreparedLikeC4ProjectView[], options?: LikeC4ProjectExportOptions): LikeC4ExportResult;
@@ -236,9 +236,10 @@ export function exportLikeC4Project(project, views, options = {}) {
236
236
  if (first === undefined) {
237
237
  throw new Error('LikeC4 project requires at least one view');
238
238
  }
239
- const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping, options.gitChange === undefined
240
- ? {}
241
- : { gitChange: options.gitChange });
239
+ const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping, {
240
+ ...(options.gitChange === undefined ? {} : { gitChange: options.gitChange }),
241
+ ...(options.branding === undefined ? {} : { branding: options.branding }),
242
+ });
242
243
  if (!model.ok)
243
244
  return model;
244
245
  const startToken = '\nviews {\n';
@@ -7,7 +7,7 @@ import { isMainModule, packageVersion, versionResult, } from '../cli-support.js'
7
7
  import { runCli } from '../cli.js';
8
8
  import { createFileSystemStore } from '../source-store.js';
9
9
  import { loadWorkspaceManifest } from '../workspace.js';
10
- import { LOOP, STDIO_PROPERTIES, TOOL_CATALOGUE, runTool, } from '../tools/table.js';
10
+ import { STDIO_PROPERTIES, TOOL_CATALOGUE, instructionsFor, runTool, } from '../tools/table.js';
11
11
  /** The sentence the stdio rows add, because only here is there a `workspace` to name. */
12
12
  const WORKSPACE_SENTENCE = 'Every tool takes the same optional `workspace`; omit it to use the workspace this server was started with, or .yarramate/workspace.yaml under its working directory.';
13
13
  const CONVENTIONAL_WORKSPACE = join('.yarramate', 'workspace.yaml');
@@ -148,8 +148,8 @@ const runExport = (input, tool, root) => {
148
148
  stderr: '',
149
149
  };
150
150
  }
151
- // A text kind under `out`: markdown and graph are one file, rtm and
152
- // briefs a directory, as the CLI lays them out.
151
+ // A text kind under `out`: markdown and graph are one file, rtm,
152
+ // responsibility and briefs a directory, as the CLI lays them out.
153
153
  if (kind === 'markdown' || kind === 'graph') {
154
154
  mkdirSync(dirname(target), { recursive: true });
155
155
  writeFileSync(target, outcome.text, 'utf8');
@@ -172,6 +172,19 @@ const runExport = (input, tool, root) => {
172
172
  stderr: '',
173
173
  };
174
174
  }
175
+ if (kind === 'responsibility') {
176
+ const result = outcome.result;
177
+ mkdirSync(target, { recursive: true });
178
+ writeFileSync(join(target, 'RESPONSIBILITY.md'), outcome.text, 'utf8');
179
+ if (result !== undefined) {
180
+ writeFileSync(join(target, 'responsibility.json'), `${JSON.stringify(result.matrix, null, 2)}\n`, 'utf8');
181
+ }
182
+ return {
183
+ exitCode: 0,
184
+ stdout: `Wrote RESPONSIBILITY.md and responsibility.json to ${out}\n`,
185
+ stderr: '',
186
+ };
187
+ }
175
188
  if (kind === 'briefs') {
176
189
  const result = outcome.result;
177
190
  mkdirSync(target, { recursive: true });
@@ -236,7 +249,7 @@ export const handleRequest = (request, context = { cwd: process.cwd(), workspace
236
249
  protocolVersion: '2025-06-18',
237
250
  capabilities: { tools: {} },
238
251
  serverInfo: { name: 'yarramate', version: packageVersion },
239
- instructions: `The architecture record of a YarraMate workspace. The native documents in the repository are canonical; every read renders them, and yarramate_apply is the one write, the same atomic batch the CLI lands. ${LOOP} ${WORKSPACE_SENTENCE}`,
252
+ instructions: `${instructionsFor()} ${WORKSPACE_SENTENCE}`,
240
253
  });
241
254
  return;
242
255
  }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * White-labelling (#546, ADR 0158): one value, set once by a host, that
3
+ * names the product on every surface a consumer's users see - the editor
4
+ * shell, the tool list an agent reads, the workbook's cover sheet and the
5
+ * LikeC4 banner. Nothing else changes: record formats, diagnostic codes and
6
+ * the wire protocol are contracts, not branding, and stay `yarramate/...`
7
+ * and `YM...`. Absent, every surface reads exactly as it always has.
8
+ *
9
+ * Pure: no imports, so it sits under every entry (`yarramate/tools`,
10
+ * `yarramate/workbook`, `yarramate/visual-app`) without weight.
11
+ */
12
+ export type BrandingLogo =
13
+ /** Inline markup the host wrote; rendered as the host's own SVG. */
14
+ {
15
+ readonly svg: string;
16
+ }
17
+ /** An address the page fetches; rendered as an image. */
18
+ | {
19
+ readonly url: string;
20
+ };
21
+ export interface Branding {
22
+ /** "ApertureX", "Halcyon Architecture". Required: a brand without a name is not one. */
23
+ readonly productName: string;
24
+ /**
25
+ * For tight chrome: beside the logo in the brand mark, and in the
26
+ * authority line ("Checked Halcyon model"). Defaults to `productName`.
27
+ * `null` draws the logo alone, for a wordmark that already says the name.
28
+ */
29
+ readonly shortName?: string | null;
30
+ /** Beside the title in the editor shell. Nothing is drawn without one. */
31
+ readonly logo?: BrandingLogo;
32
+ /**
33
+ * One CSS colour for the chrome - the authority mark, the notices, the
34
+ * changeset controls - never the notation: what a node or an edge looks
35
+ * like is the style preset's, and a comparison's added/removed marks keep
36
+ * their own tokens.
37
+ */
38
+ readonly accent?: string;
39
+ /** Where the brand mark links. Without it the mark is text, not a link. */
40
+ readonly docsUrl?: string;
41
+ /**
42
+ * The line under the product name, on the editor strip, the workbook's
43
+ * cover sheet and the LikeC4 banner. Unset: "Powered by yarramate". A
44
+ * string replaces it; `null` removes it, and with it every other mention
45
+ * of the engine those surfaces make.
46
+ */
47
+ readonly vendorLine?: string | null;
48
+ /**
49
+ * The prefix of the tool names an agent reads: `acme` publishes
50
+ * `acme_design`, `acme_apply`, ... Default `yarramate`. An instruction
51
+ * written for `yarramate_design` will not find `acme_design`, so a host
52
+ * that re-prefixes owns its own agent instructions.
53
+ */
54
+ readonly toolPrefix?: string;
55
+ }
56
+ /** `Branding` with every default applied, which is what the surfaces read. */
57
+ export interface ResolvedBranding {
58
+ readonly productName: string;
59
+ /** `null` when the mark is the logo alone; the authority line then says `productName`. */
60
+ readonly shortName: string | null;
61
+ readonly logo?: BrandingLogo;
62
+ readonly accent?: string;
63
+ readonly docsUrl?: string;
64
+ /** `null` when there is none to show. */
65
+ readonly vendorLine: string | null;
66
+ readonly toolPrefix: string;
67
+ /** False is the unbranded product: every surface as it was before #546. */
68
+ readonly branded: boolean;
69
+ }
70
+ export declare const YARRAMATE_PRODUCT_NAME = "YarraMate";
71
+ export declare const YARRAMATE_TOOL_PREFIX = "yarramate";
72
+ export declare const DEFAULT_VENDOR_LINE = "Powered by yarramate";
73
+ /**
74
+ * Applies the defaults. `undefined` is the unbranded product. A value with
75
+ * a blank `productName` or a `toolPrefix` no MCP client could publish is a
76
+ * host's configuration error and throws, at mount or serve time, rather
77
+ * than reaching a person as a blank name or an agent as a broken tool list.
78
+ */
79
+ export declare const resolveBranding: (branding?: Branding) => ResolvedBranding;
80
+ /**
81
+ * The product name as an identifier segment: the LikeC4 project name is
82
+ * `<slug>-<project id>`, `yarramate-…` unbranded. Lower-case letters,
83
+ * digits and single hyphens; a name with nothing usable in it falls back
84
+ * to the engine's own slug rather than producing an empty segment.
85
+ */
86
+ export declare const brandSlug: (branding: ResolvedBranding) => string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * White-labelling (#546, ADR 0158): one value, set once by a host, that
3
+ * names the product on every surface a consumer's users see - the editor
4
+ * shell, the tool list an agent reads, the workbook's cover sheet and the
5
+ * LikeC4 banner. Nothing else changes: record formats, diagnostic codes and
6
+ * the wire protocol are contracts, not branding, and stay `yarramate/...`
7
+ * and `YM...`. Absent, every surface reads exactly as it always has.
8
+ *
9
+ * Pure: no imports, so it sits under every entry (`yarramate/tools`,
10
+ * `yarramate/workbook`, `yarramate/visual-app`) without weight.
11
+ */
12
+ export const YARRAMATE_PRODUCT_NAME = 'YarraMate';
13
+ export const YARRAMATE_TOOL_PREFIX = 'yarramate';
14
+ export const DEFAULT_VENDOR_LINE = 'Powered by yarramate';
15
+ /** What an MCP client accepts in a tool name, applied to the prefix alone. */
16
+ const TOOL_PREFIX = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
17
+ const UNBRANDED = {
18
+ productName: YARRAMATE_PRODUCT_NAME,
19
+ shortName: YARRAMATE_PRODUCT_NAME,
20
+ vendorLine: null,
21
+ toolPrefix: YARRAMATE_TOOL_PREFIX,
22
+ branded: false,
23
+ };
24
+ /**
25
+ * Applies the defaults. `undefined` is the unbranded product. A value with
26
+ * a blank `productName` or a `toolPrefix` no MCP client could publish is a
27
+ * host's configuration error and throws, at mount or serve time, rather
28
+ * than reaching a person as a blank name or an agent as a broken tool list.
29
+ */
30
+ export const resolveBranding = (branding) => {
31
+ if (branding === undefined)
32
+ return UNBRANDED;
33
+ const productName = branding.productName.trim();
34
+ if (productName === '') {
35
+ throw new TypeError('branding.productName must not be blank');
36
+ }
37
+ const toolPrefix = branding.toolPrefix ?? YARRAMATE_TOOL_PREFIX;
38
+ if (!TOOL_PREFIX.test(toolPrefix)) {
39
+ throw new TypeError(`branding.toolPrefix "${toolPrefix}" must be letters, digits and hyphens, starting with a letter or digit`);
40
+ }
41
+ const shortName = branding.shortName === null ? null : branding.shortName?.trim();
42
+ const vendorLine = branding.vendorLine === undefined
43
+ ? DEFAULT_VENDOR_LINE
44
+ : branding.vendorLine === null || branding.vendorLine.trim() === ''
45
+ ? null
46
+ : branding.vendorLine.trim();
47
+ return {
48
+ productName,
49
+ shortName: shortName === null
50
+ ? null
51
+ : shortName === undefined || shortName === ''
52
+ ? productName
53
+ : shortName,
54
+ ...(branding.logo === undefined ? {} : { logo: branding.logo }),
55
+ ...(branding.accent === undefined ? {} : { accent: branding.accent }),
56
+ ...(branding.docsUrl === undefined ? {} : { docsUrl: branding.docsUrl }),
57
+ vendorLine,
58
+ toolPrefix,
59
+ branded: true,
60
+ };
61
+ };
62
+ /**
63
+ * The product name as an identifier segment: the LikeC4 project name is
64
+ * `<slug>-<project id>`, `yarramate-…` unbranded. Lower-case letters,
65
+ * digits and single hyphens; a name with nothing usable in it falls back
66
+ * to the engine's own slug rather than producing an empty segment.
67
+ */
68
+ export const brandSlug = (branding) => {
69
+ if (!branding.branded)
70
+ return YARRAMATE_TOOL_PREFIX;
71
+ // Split on the runs and join what is left: linear, where a trim by
72
+ // `^-+|-+$` is polynomial on a name that is mostly hyphens (CodeQL
73
+ // js/polynomial-redos, the rule that bit #547's walker too).
74
+ const slug = (branding.shortName ?? branding.productName)
75
+ .toLowerCase()
76
+ .split(/[^a-z0-9]+/)
77
+ .filter((part) => part !== '')
78
+ .join('-');
79
+ return slug === '' ? YARRAMATE_TOOL_PREFIX : slug;
80
+ };
package/dist/brief.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { conceptKinds } from './profile.js';
2
- import { RELATIONSHIP_READING, humanizeKind } from './relationship-reading.js';
2
+ import { EXTENSION_READING, RELATIONSHIP_READING, humanizeKind } from './relationship-reading.js';
3
3
  const coreKindNames = new Map(conceptKinds.map(({ id, name }) => [id, name]));
4
4
  const motivationKindIds = new Set(conceptKinds
5
5
  .filter(({ layer }) => layer === 'motivation')
@@ -66,6 +66,9 @@ const relationshipPhrase = (coreKind, fallbackKind, mode, content) => {
66
66
  if (coreKind === 'flow') {
67
67
  return content === undefined ? 'flows to' : `sends ${content} to`;
68
68
  }
69
+ const extension = EXTENSION_READING[fallbackKind];
70
+ if (extension !== undefined)
71
+ return extension;
69
72
  const reading = coreKind === undefined ? undefined : RELATIONSHIP_READING[coreKind];
70
73
  return reading ?? humanizeKind(fallbackKind);
71
74
  };
@@ -7,7 +7,7 @@ export interface CliResult {
7
7
  export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
8
8
  export declare const packageVersion: string;
9
9
  export declare const versionResult: (binary: string) => CliResult;
10
- export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
10
+ export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
11
11
  export declare const diagnosticJson: (diagnostics: unknown) => string;
12
12
  export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
13
13
  readonly documents: number;
@@ -22,7 +22,7 @@ export const versionResult = (binary) => ({
22
22
  stdout: `${binary} ${packageVersion}\n`,
23
23
  stderr: '',
24
24
  });
25
- export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
25
+ export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json | --text]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export responsibility <projection.yaml> <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
26
26
  export const diagnosticJson = (diagnostics) => `${JSON.stringify({
27
27
  format: 'yarramate/diagnostic-result/v1',
28
28
  diagnostics,
package/dist/compiler.js CHANGED
@@ -3,7 +3,7 @@ import { conceptKinds, relationshipPolicies, } from './profile.js';
3
3
  import { isCoreConceptKindId, matrixEndpointAspects, permittedRelationshipKinds as tablePermittedKinds, } from './relationship-matrix.js';
4
4
  import { closestCandidate, describeSchemaViolation, } from './source-document.js';
5
5
  import { ATTESTATION_PREDICATE_PREFIX, attestationClaimValue } from './graph-claims.js';
6
- import { shippedPolicyIdentity, shippedPolicySource, } from './shipped-profile.js';
6
+ import { shippedProfileOf } from './shipped-profile.js';
7
7
  import { validateDocument, validateProfile, validatePattern } from './schema-validation.js';
8
8
  const coreProfile = 'yarramate/core@0.1';
9
9
  /**
@@ -371,58 +371,78 @@ function compileWorkspaceResolved(parsed) {
371
371
  const identity = `${value.id}@${value.version}`;
372
372
  pendingProfiles.push({ input, value, identity, positionFor });
373
373
  }
374
- const alreadyDeclaresPolicy = pendingProfiles.some(({ identity }) => identity === shippedPolicyIdentity);
375
- if (!alreadyDeclaresPolicy) {
376
- // This probe runs BEFORE the document gate that rejects a source whose
377
- // schema check failed, so it has to hold its own precondition: a source
378
- // that composes to anything but a mapping - an empty file, a comment-only
379
- // one, a bare scalar - selects no profile at all. It used to read
380
- // `.profile` through an `as` cast, which is what hid the null from the
381
- // typechecker, and an empty document crashed the whole compile with a
382
- // `TypeError` instead of the `YM201 must be object` its schema already
383
- // produces. Every other consumer of a parsed entry checks its diagnostics
384
- // first (the profile walk above, the pattern walk below); this one could
385
- // not, because it runs before that gate exists, so it narrows instead.
386
- const selected = documentInputs.some(({ entry }) => {
387
- const value = entry.value;
388
- return (typeof value === 'object' &&
389
- value !== null &&
390
- value.profile ===
391
- shippedPolicyIdentity);
392
- });
393
- const extended = pendingProfiles.some(({ value }) => value.extends === shippedPolicyIdentity);
394
- if (selected || extended) {
395
- const input = {
396
- path: 'yarramate:profile:yarramate/policy@0.1',
397
- source: shippedPolicySource,
398
- };
399
- const { entry, fresh } = parseWorkspaceSource(input);
400
- const value = entry.value;
401
- if (entry.schemaDiagnostics.length > 0) {
402
- profileDiagnostics.push(...entry.schemaDiagnostics);
403
- }
404
- else if (!validateProfile(value)) {
405
- for (const error of validateProfile.errors ?? []) {
406
- profileDiagnostics.push({
407
- severity: 'error',
408
- code: 'YM201',
409
- message: `Profile schema violation: ${describeSchemaViolation(error)}`,
410
- path: input.path,
411
- pointer: error.instancePath || '/',
412
- line: 1,
413
- column: 1,
414
- });
415
- }
416
- }
417
- else {
418
- pendingProfiles.push({
419
- input,
420
- value,
421
- identity: shippedPolicyIdentity,
422
- positionFor: positionReader(input.source, entry.positions, fresh),
374
+ // The shipped optional profiles (ADR 0095; policy@0.2 per ADR 0159). Each
375
+ // is injected when a document selects it or a profile extends it, and a
376
+ // shipped profile's own parent follows it in, so `extends:
377
+ // yarramate/policy@0.2` brings 0.1 along. A workspace file that declares
378
+ // the same identity wins; the shipped copy is not added beside it.
379
+ //
380
+ // This probe runs BEFORE the document gate that rejects a source whose
381
+ // schema check failed, so it has to hold its own precondition: a source
382
+ // that composes to anything but a mapping - an empty file, a comment-only
383
+ // one, a bare scalar - selects no profile at all. It used to read
384
+ // `.profile` through an `as` cast, which is what hid the null from the
385
+ // typechecker, and an empty document crashed the whole compile with a
386
+ // `TypeError` instead of the `YM201 must be object` its schema already
387
+ // produces. Every other consumer of a parsed entry checks its diagnostics
388
+ // first (the profile walk above, the pattern walk below); this one could
389
+ // not, because it runs before that gate exists, so it narrows instead.
390
+ const declaredIdentities = new Set(pendingProfiles.map(({ identity }) => identity));
391
+ const requestedIdentities = new Set();
392
+ for (const { entry } of documentInputs) {
393
+ const value = entry.value;
394
+ if (typeof value !== 'object' || value === null)
395
+ continue;
396
+ const profile = value.profile;
397
+ if (typeof profile === 'string')
398
+ requestedIdentities.add(profile);
399
+ }
400
+ for (const { value } of pendingProfiles)
401
+ requestedIdentities.add(value.extends);
402
+ const wantedShipped = [];
403
+ const want = (identity) => {
404
+ if (declaredIdentities.has(identity) || wantedShipped.includes(identity))
405
+ return;
406
+ const shipped = shippedProfileOf(identity);
407
+ if (shipped === undefined)
408
+ return;
409
+ wantedShipped.push(identity);
410
+ want(shipped.extends);
411
+ };
412
+ for (const identity of requestedIdentities)
413
+ want(identity);
414
+ for (const identity of wantedShipped) {
415
+ const shipped = shippedProfileOf(identity);
416
+ const input = {
417
+ path: `yarramate:profile:${identity}`,
418
+ source: shipped.source,
419
+ };
420
+ const { entry, fresh } = parseWorkspaceSource(input);
421
+ const value = entry.value;
422
+ if (entry.schemaDiagnostics.length > 0) {
423
+ profileDiagnostics.push(...entry.schemaDiagnostics);
424
+ }
425
+ else if (!validateProfile(value)) {
426
+ for (const error of validateProfile.errors ?? []) {
427
+ profileDiagnostics.push({
428
+ severity: 'error',
429
+ code: 'YM201',
430
+ message: `Profile schema violation: ${describeSchemaViolation(error)}`,
431
+ path: input.path,
432
+ pointer: error.instancePath || '/',
433
+ line: 1,
434
+ column: 1,
423
435
  });
424
436
  }
425
437
  }
438
+ else {
439
+ pendingProfiles.push({
440
+ input,
441
+ value,
442
+ identity,
443
+ positionFor: positionReader(input.source, entry.positions, fresh),
444
+ });
445
+ }
426
446
  }
427
447
  let unresolvedProfiles = pendingProfiles.sort((left, right) => left.identity.localeCompare(right.identity) ||
428
448
  left.input.path.localeCompare(right.input.path));