yarramate 0.10.0 → 0.12.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.
@@ -6,6 +6,7 @@ import { createHash, randomUUID } from 'node:crypto';
6
6
  import Ajv2020Module from 'ajv/dist/2020.js';
7
7
  import { isMap, isSeq, parseDocument } from 'yaml';
8
8
  import { isMainModule, resolveCliWorkspaceSources, versionResult, } from '../cli-support.js';
9
+ import { deriveChangedSubjects } from '../changed.js';
9
10
  import { compileWorkspace } from '../compiler.js';
10
11
  import { adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
11
12
  import { locateSourcePath } from '../source-document.js';
@@ -78,7 +79,7 @@ const usage = 'Usage:\n' +
78
79
  ' yarramate-likec4 check <likec4-project.yaml> [--json] <workspace-or-source...>\n' +
79
80
  ' yarramate-likec4 export <projection.yaml> <mapping.yaml> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
80
81
  ' yarramate-likec4 export-project [--check] <projection.yaml> <mapping.yaml> <output-dir> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
81
- ' yarramate-likec4 export-project [--check] <likec4-project.yaml> <output-dir> <workspace-or-source...>\n';
82
+ ' yarramate-likec4 export-project [--check] <likec4-project.yaml> <output-dir> [--changed <git-range>] <workspace-or-source...>\n';
82
83
  const diagnosticJson = (diagnostics) => `${JSON.stringify({
83
84
  format: 'yarramate/likec4-diagnostic-result/v1',
84
85
  diagnostics,
@@ -528,6 +529,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
528
529
  let invalidOptions = false;
529
530
  let kindMappingPath;
530
531
  let comparison;
532
+ let changedRange;
531
533
  const sourcePaths = [];
532
534
  for (let index = 0; index < sourceOptions.length; index += 1) {
533
535
  const option = sourceOptions[index];
@@ -549,6 +551,19 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
549
551
  }
550
552
  continue;
551
553
  }
554
+ if (option === '--changed') {
555
+ const value = sourceOptions[index + 1];
556
+ if (value === undefined ||
557
+ value.startsWith('-') ||
558
+ changedRange !== undefined) {
559
+ invalidOptions = true;
560
+ }
561
+ else {
562
+ changedRange = value;
563
+ index += 1;
564
+ }
565
+ continue;
566
+ }
552
567
  if (option === '--compare') {
553
568
  const from = sourceOptions[index + 1];
554
569
  const to = sourceOptions[index + 2];
@@ -581,6 +596,8 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
581
596
  (command === 'export-project' && outputDirectory === undefined) ||
582
597
  (projectDefinitionMode &&
583
598
  (kindMappingPath !== undefined || comparison !== undefined)) ||
599
+ (changedRange !== undefined &&
600
+ !(projectDefinitionMode && command === 'export-project')) ||
584
601
  invalidOptions ||
585
602
  sourcePaths.length === 0 ||
586
603
  sourcePaths.some((argument) => argument.startsWith('-'))) {
@@ -695,6 +712,9 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
695
712
  ? [
696
713
  {
697
714
  ...(view.id === undefined ? {} : { id: view.id }),
715
+ ...(view.folder === undefined
716
+ ? {}
717
+ : { folder: view.folder }),
698
718
  prepared,
699
719
  ...(view.compare === undefined
700
720
  ? {}
@@ -886,7 +906,32 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
886
906
  }
887
907
  renderedViewIds.add(renderedId);
888
908
  }
889
- const exported = exportLikeC4Project(loadedProject.document.value, successfulViews);
909
+ let gitChange;
910
+ if (changedRange !== undefined) {
911
+ const modelDocuments = sources.flatMap((candidate) => {
912
+ const parsed = parseDocument(candidate.source);
913
+ if (parsed.get('format') !== 'yarramate/v1')
914
+ return [];
915
+ const documentId = parsed.get('id');
916
+ return typeof documentId === 'string'
917
+ ? [{ ...candidate, documentId }]
918
+ : [];
919
+ });
920
+ const derived = deriveChangedSubjects(cwd, changedRange, modelDocuments);
921
+ if (!derived.ok) {
922
+ return { exitCode: 2, stdout: '', stderr: `${derived.message}\n` };
923
+ }
924
+ const all = [
925
+ ...derived.changed.concepts,
926
+ ...derived.changed.relationships,
927
+ ];
928
+ gitChange = {
929
+ range: derived.changed.range,
930
+ added: derived.changed.added,
931
+ modified: all.filter((id) => !derived.changed.added.includes(id)),
932
+ };
933
+ }
934
+ const exported = exportLikeC4Project(loadedProject.document.value, successfulViews, gitChange === undefined ? {} : { gitChange });
890
935
  if (!exported.ok) {
891
936
  return {
892
937
  exitCode: 1,
@@ -21,5 +21,13 @@ export type LikeC4ExportResult = {
21
21
  };
22
22
  export interface LikeC4ExportOptions {
23
23
  readonly comparison?: StateComparison;
24
+ /** Git-derived review overlay (ADR 0066): subjects new or changed in a
25
+ * ref range carry metadata and view styling; nothing is authored. */
26
+ readonly gitChange?: GitChangeOverlay;
27
+ }
28
+ export interface GitChangeOverlay {
29
+ readonly range: string;
30
+ readonly added: readonly string[];
31
+ readonly modified: readonly string[];
24
32
  }
25
33
  export declare function exportLikeC4(projection: ProjectionResult, mapping: AdapterMapping, kindMapping?: LikeC4KindMapping, options?: LikeC4ExportOptions): LikeC4ExportResult;
@@ -62,6 +62,13 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
62
62
  const concepts = projection.subjects
63
63
  .filter(({ type }) => type === 'concept')
64
64
  .sort((left, right) => left.id.localeCompare(right.id));
65
+ const gitChangeOf = (id) => options.gitChange === undefined
66
+ ? undefined
67
+ : options.gitChange.added.includes(id)
68
+ ? 'new'
69
+ : options.gitChange.modified.includes(id)
70
+ ? 'changed'
71
+ : undefined;
65
72
  const comparisonChange = new Map(options.comparison === undefined
66
73
  ? []
67
74
  : [
@@ -121,6 +128,7 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
121
128
  ['yarramateId', concept.id],
122
129
  ['yarramateKind', semanticKind],
123
130
  ['yarramateChange', comparisonChange.get(concept.id)],
131
+ ['yarramateGitChange', gitChangeOf(concept.id)],
124
132
  [
125
133
  'status',
126
134
  valueFor(projection.claims, concept.id, 'yarramate/lifecycle/status'),
@@ -168,6 +176,7 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
168
176
  ['yarramateId', relationship.id],
169
177
  ['yarramateKind', structural.predicate],
170
178
  ['yarramateChange', comparisonChange.get(relationship.id)],
179
+ ['yarramateGitChange', gitChangeOf(relationship.id)],
171
180
  [
172
181
  'status',
173
182
  valueFor(projection.claims, relationship.id, 'yarramate/lifecycle/status'),
@@ -212,6 +221,18 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
212
221
  return ` style ${external} { color red; border dashed }`;
213
222
  }
214
223
  return ` style ${external} { color gray }`;
224
+ })), ...(options.gitChange === undefined
225
+ ? []
226
+ : concepts.flatMap((concept) => {
227
+ const external = externalByNative.get(concept.id);
228
+ const change = gitChangeOf(concept.id);
229
+ if (change === 'new') {
230
+ return [` style ${external} { color green }`];
231
+ }
232
+ if (change === 'changed') {
233
+ return [` style ${external} { color amber }`];
234
+ }
235
+ return [];
215
236
  })), ' autoLayout LeftRight', ' }', '}', '');
216
237
  return { ok: true, source: lines.join('\n') };
217
238
  }
@@ -1,6 +1,6 @@
1
1
  import type { WorkspaceSource } from '../compiler.js';
2
2
  import type { LikeC4PreparationResult } from './likec4-prepare.js';
3
- import { type LikeC4ExportResult } from './likec4-export.js';
3
+ import { type GitChangeOverlay, type LikeC4ExportResult } from './likec4-export.js';
4
4
  export interface LikeC4ProjectDefinition {
5
5
  readonly format: 'yarramate/likec4-project/v1';
6
6
  readonly id: string;
@@ -11,6 +11,7 @@ export interface LikeC4ProjectDefinition {
11
11
  readonly views: ReadonlyArray<{
12
12
  readonly id?: string;
13
13
  readonly projection: string;
14
+ readonly folder?: string;
14
15
  readonly compare?: {
15
16
  readonly from: string;
16
17
  readonly to: string;
@@ -40,6 +41,7 @@ export interface LikeC4Deployment {
40
41
  export declare const loadLikeC4ProjectDefinition: (source: WorkspaceSource) => import("../source-document.js").SourceDocumentResult<LikeC4ProjectDefinition>;
41
42
  export interface PreparedLikeC4ProjectView {
42
43
  readonly id?: string;
44
+ readonly folder?: string;
43
45
  readonly prepared: Extract<LikeC4PreparationResult, {
44
46
  readonly ok: true;
45
47
  }>;
@@ -55,4 +57,7 @@ export interface PreparedLikeC4ProjectView {
55
57
  };
56
58
  readonly deployment?: LikeC4Deployment;
57
59
  }
58
- export declare function exportLikeC4Project(project: LikeC4ProjectDefinition, views: readonly PreparedLikeC4ProjectView[]): LikeC4ExportResult;
60
+ export interface LikeC4ProjectExportOptions {
61
+ readonly gitChange?: GitChangeOverlay;
62
+ }
63
+ export declare function exportLikeC4Project(project: LikeC4ProjectDefinition, views: readonly PreparedLikeC4ProjectView[], options?: LikeC4ProjectExportOptions): LikeC4ExportResult;
@@ -8,6 +8,15 @@ const Ajv2020 = Ajv2020Module.default;
8
8
  const validateLikeC4Project = new Ajv2020({ allErrors: true }).compile(likeC4ProjectSchema);
9
9
  export const loadLikeC4ProjectDefinition = (source) => loadSourceDocument(source, validateLikeC4Project, 'LikeC4 project');
10
10
  const quote = (value) => `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`;
11
+ // A folder files the view in LikeC4's sidebar tree: the title becomes a
12
+ // path ('Folder / Title') whose last segment is the displayed title (ADR
13
+ // 0067). A view without a declared title still needs a title line to
14
+ // carry the path, so the view id stands in as the leaf.
15
+ const titleLines = (folder, title, viewId) => folder === undefined
16
+ ? title === undefined
17
+ ? []
18
+ : [` title ${quote(title)}`]
19
+ : [` title ${quote(`${folder} / ${title ?? viewId}`)}`];
11
20
  const unionProjection = (project, views) => ({
12
21
  format: 'yarramate/projection-result/v1',
13
22
  projection: `${project.id}@${project.version}`,
@@ -28,7 +37,7 @@ const unionProjection = (project, views) => ({
28
37
  ...new Map(views.flatMap(({ prepared }) => prepared.projection.claims.map((claim) => [claim.id, claim]))).values(),
29
38
  ].sort((left, right) => left.id.localeCompare(right.id)),
30
39
  });
31
- const viewBody = ({ id, prepared, dynamic, deployment, }) => {
40
+ const viewBody = ({ id, folder, prepared, dynamic, deployment, }, gitChange) => {
32
41
  const source = prepared.source;
33
42
  const startToken = '\nviews {\n';
34
43
  const start = source.indexOf(startToken);
@@ -58,9 +67,7 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
58
67
  const viewId = id ?? prepared.projection.projection.split('@')[0];
59
68
  return [
60
69
  ` deployment view ${viewId} {`,
61
- ...(prepared.projection.presentation?.title === undefined
62
- ? []
63
- : [` title ${quote(prepared.projection.presentation.title)}`]),
70
+ ...titleLines(folder, prepared.projection.presentation?.title, viewId),
64
71
  ...(prepared.projection.presentation?.description === undefined
65
72
  ? []
66
73
  : [
@@ -105,9 +112,7 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
105
112
  const viewId = id ?? prepared.projection.projection.split('@')[0];
106
113
  return [
107
114
  ` dynamic view ${viewId} {`,
108
- ...(prepared.projection.presentation?.title === undefined
109
- ? []
110
- : [` title ${quote(prepared.projection.presentation.title)}`]),
115
+ ...titleLines(folder, prepared.projection.presentation?.title, viewId),
111
116
  ...(prepared.projection.presentation?.description === undefined
112
117
  ? []
113
118
  : [
@@ -117,15 +122,40 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
117
122
  ' }',
118
123
  ].join('\n');
119
124
  }
125
+ const overlayRules = gitChange === undefined
126
+ ? []
127
+ : prepared.projection.subjects
128
+ .filter(({ type }) => type === 'concept')
129
+ .flatMap(({ id: nativeId }) => {
130
+ const external = externalByNative.get(nativeId);
131
+ if (external === undefined)
132
+ return [];
133
+ if (gitChange.added.includes(nativeId)) {
134
+ return [` style ${external} { color green }`];
135
+ }
136
+ if (gitChange.modified.includes(nativeId)) {
137
+ return [` style ${external} { color amber }`];
138
+ }
139
+ return [];
140
+ })
141
+ .sort();
120
142
  const membershipRules = [
121
143
  includeRule,
122
144
  ' exclude * -> *',
123
145
  ...relationshipRules,
146
+ ...overlayRules,
124
147
  ].join('\n');
125
- return source
148
+ const viewId = id ?? prepared.projection.projection.split('@')[0];
149
+ const body = source
126
150
  .slice(start + startToken.length, end)
127
- .replace(/^ view [A-Za-z_][A-Za-z0-9_-]* \{/, ` view ${id ?? prepared.projection.projection.split('@')[0]} {`)
151
+ .replace(/^ view [A-Za-z_][A-Za-z0-9_-]* \{/, ` view ${viewId} {`)
128
152
  .replace(' include *', membershipRules);
153
+ if (folder === undefined)
154
+ return body;
155
+ const title = prepared.projection.presentation?.title;
156
+ return title === undefined
157
+ ? body.replace(` view ${viewId} {`, ` view ${viewId} {\n${titleLines(folder, undefined, viewId).join('\n')}`)
158
+ : body.replace(` title ${quote(title)}`, titleLines(folder, title, viewId).join('\n'));
129
159
  };
130
160
  const deploymentBody = ({ deployment, prepared, }) => {
131
161
  if (deployment === undefined)
@@ -168,12 +198,48 @@ const deploymentBody = ({ deployment, prepared, }) => {
168
198
  '}',
169
199
  ].join('\n');
170
200
  };
171
- export function exportLikeC4Project(project, views) {
201
+ // The synthetic review view (ADR 0066): every changed subject with its
202
+ // highlight, and a description that doubles as the legend.
203
+ const reviewChangesView = (views, gitChange) => {
204
+ const externalByNative = new Map(views.flatMap((view) => view.prepared.subjectMapping.mappings
205
+ .filter(({ type }) => type === 'concept')
206
+ .map(({ native, external }) => [native, external])));
207
+ const changedConcepts = [
208
+ ...new Set([...gitChange.added, ...gitChange.modified].flatMap((nativeId) => {
209
+ const external = externalByNative.get(nativeId);
210
+ return external === undefined ? [] : [external];
211
+ })),
212
+ ].sort();
213
+ if (changedConcepts.length === 0)
214
+ return undefined;
215
+ const changedRelationships = [
216
+ ...new Set([...gitChange.added, ...gitChange.modified].filter((nativeId) => !externalByNative.has(nativeId))),
217
+ ].sort();
218
+ const styles = changedConcepts.map((external) => {
219
+ const nativeId = [...externalByNative.entries()].find(([, candidate]) => candidate === external)[0];
220
+ return gitChange.added.includes(nativeId)
221
+ ? ` style ${external} { color green }`
222
+ : ` style ${external} { color amber }`;
223
+ });
224
+ return [
225
+ ' view review-changes {',
226
+ ` title ${quote(`Review: ${gitChange.range}`)}`,
227
+ ` description ${quote(`Subjects touched in ${gitChange.range}. Legend: green = new, amber = changed; connected context unhighlighted. Derived from git - nothing here is authored (ADR 0066).`)}`,
228
+ ` include ${changedConcepts.join(', ')}`,
229
+ ...changedRelationships.map((id) => ` include * -> * where metadata.yarramateId is '${id}'`),
230
+ ...styles,
231
+ ' autoLayout LeftRight',
232
+ ' }',
233
+ ].join('\n');
234
+ };
235
+ export function exportLikeC4Project(project, views, options = {}) {
172
236
  const first = views[0];
173
237
  if (first === undefined) {
174
238
  throw new Error('LikeC4 project requires at least one view');
175
239
  }
176
- const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping);
240
+ const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping, options.gitChange === undefined
241
+ ? {}
242
+ : { gitChange: options.gitChange });
177
243
  if (!model.ok)
178
244
  return model;
179
245
  const startToken = '\nviews {\n';
@@ -182,9 +248,13 @@ export function exportLikeC4Project(project, views) {
182
248
  const rendered = deploymentBody(view);
183
249
  return rendered === undefined ? [] : [rendered];
184
250
  });
185
- const renderedViews = views.map(viewBody);
251
+ const renderedViews = views.map((view) => viewBody(view, options.gitChange));
252
+ const review = options.gitChange === undefined
253
+ ? undefined
254
+ : reviewChangesView(views, options.gitChange);
255
+ const allViews = review === undefined ? renderedViews : [...renderedViews, review];
186
256
  return {
187
257
  ok: true,
188
- source: `${model.source.slice(0, modelEnd)}${deployments.length === 0 ? '' : `\n${deployments.join('\n')}\n`}\nviews {\n${renderedViews.join('\n')}\n}\n`,
258
+ source: `${model.source.slice(0, modelEnd)}${deployments.length === 0 ? '' : `\n${deployments.join('\n')}\n`}\nviews {\n${allViews.join('\n')}\n}\n`,
189
259
  };
190
260
  }
package/dist/changed.d.ts CHANGED
@@ -2,6 +2,9 @@ export interface ChangedSubjects {
2
2
  readonly range: string;
3
3
  readonly concepts: readonly string[];
4
4
  readonly relationships: readonly string[];
5
+ /** Subjects whose entire declaration is new in the range (subset of
6
+ * concepts + relationships); the rest changed in place. */
7
+ readonly added: readonly string[];
5
8
  }
6
9
  export type ChangedResult = {
7
10
  readonly ok: true;
package/dist/changed.js CHANGED
@@ -52,16 +52,21 @@ const itemSpans = (source) => {
52
52
  }
53
53
  return spans;
54
54
  };
55
- // New-side line ranges from `git diff --unified=0`. A pure deletion has a
56
- // zero count; it still touches the position it collapsed onto.
57
55
  const changedLineRanges = (diff) => {
58
- const ranges = [];
59
- for (const match of diff.matchAll(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gm)) {
60
- const start = Number(match[1]);
61
- const count = match[2] === undefined ? 1 : Number(match[2]);
62
- ranges.push(count === 0 ? [Math.max(start, 1), Math.max(start, 1)] : [start, start + count - 1]);
56
+ const touched = [];
57
+ const inserted = [];
58
+ for (const match of diff.matchAll(/^@@ -\d+(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gm)) {
59
+ const oldCount = match[1] === undefined ? 1 : Number(match[1]);
60
+ const start = Number(match[2]);
61
+ const count = match[3] === undefined ? 1 : Number(match[3]);
62
+ const range = count === 0
63
+ ? [Math.max(start, 1), Math.max(start, 1)]
64
+ : [start, start + count - 1];
65
+ touched.push(range);
66
+ if (oldCount === 0 && count > 0)
67
+ inserted.push(range);
63
68
  }
64
- return ranges;
69
+ return { touched, inserted };
65
70
  };
66
71
  export function deriveChangedSubjects(cwd, range, documents) {
67
72
  const probe = spawnSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
@@ -75,6 +80,7 @@ export function deriveChangedSubjects(cwd, range, documents) {
75
80
  }
76
81
  const concepts = new Set();
77
82
  const relationships = new Set();
83
+ const added = new Set();
78
84
  for (const document of documents) {
79
85
  const diffed = spawnSync('git', ['-C', cwd, 'diff', '--unified=0', range, '--', document.path], { encoding: 'utf8' });
80
86
  if (diffed.status !== 0) {
@@ -84,13 +90,13 @@ export function deriveChangedSubjects(cwd, range, documents) {
84
90
  };
85
91
  }
86
92
  const ranges = changedLineRanges(diffed.stdout ?? '');
87
- if (ranges.length === 0)
93
+ if (ranges.touched.length === 0)
88
94
  continue;
89
95
  const spans = itemSpans(document.source);
90
96
  for (const span of spans) {
91
97
  if (span.collection === 'states')
92
98
  continue;
93
- const touched = ranges.some(([from, to]) => from <= span.endLine && to >= span.startLine);
99
+ const touched = ranges.touched.some(([from, to]) => from <= span.endLine && to >= span.startLine);
94
100
  if (!touched)
95
101
  continue;
96
102
  const qualified = `${document.documentId}#${span.id}`;
@@ -98,6 +104,9 @@ export function deriveChangedSubjects(cwd, range, documents) {
98
104
  concepts.add(qualified);
99
105
  else
100
106
  relationships.add(qualified);
107
+ const whollyInserted = ranges.inserted.some(([from, to]) => from <= span.startLine && to >= span.endLine);
108
+ if (whollyInserted)
109
+ added.add(qualified);
101
110
  }
102
111
  }
103
112
  return {
@@ -106,6 +115,7 @@ export function deriveChangedSubjects(cwd, range, documents) {
106
115
  range,
107
116
  concepts: [...concepts].sort(),
108
117
  relationships: [...relationships].sort(),
118
+ added: [...added].sort(),
109
119
  },
110
120
  };
111
121
  }
@@ -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 <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--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>] [--catalogue <catalogue.yaml>] [--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>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\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 likec4 <likec4-project.yaml> <output-dir> <workspace.yaml>\n";
10
+ export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--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>] [--catalogue <catalogue.yaml>] [--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>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\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 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;
@@ -23,7 +23,7 @@ export const versionResult = (binary) => ({
23
23
  stdout: `${binary} ${packageVersion}\n`,
24
24
  stderr: '',
25
25
  });
26
- export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--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>] [--catalogue <catalogue.yaml>] [--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>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\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 likec4 <likec4-project.yaml> <output-dir> <workspace.yaml>\n';
26
+ export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--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>] [--catalogue <catalogue.yaml>] [--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>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\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 likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
27
27
  export const diagnosticJson = (diagnostics) => `${JSON.stringify({
28
28
  format: 'yarramate/diagnostic-result/v1',
29
29
  diagnostics,
@@ -91,6 +91,7 @@ export function runExportCommand(options, cwd) {
91
91
  parsed.json) {
92
92
  return { exitCode: 2, stdout: '', stderr: usage };
93
93
  }
94
+ const changedArguments = parsed.changed === undefined ? [] : ['--changed', parsed.changed];
94
95
  if (!existsSync(likec4AdapterEntry)) {
95
96
  return {
96
97
  exitCode: 2,
@@ -105,6 +106,7 @@ export function runExportCommand(options, cwd) {
105
106
  projectDefinition,
106
107
  outputDirectory,
107
108
  workspacePath,
109
+ ...changedArguments,
108
110
  ], { cwd, encoding: 'utf8' });
109
111
  const exitCode = delegated.status === 0 ? 0 : delegated.status === 1 ? 1 : 2;
110
112
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarramate",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Tool-neutral semantic architecture engine and guided methodology",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -231,6 +231,13 @@
231
231
  "type": "string",
232
232
  "minLength": 1
233
233
  }
234
+ },
235
+ "added": {
236
+ "type": "array",
237
+ "items": {
238
+ "type": "string",
239
+ "minLength": 1
240
+ }
234
241
  }
235
242
  },
236
243
  "additionalProperties": false
@@ -72,6 +72,11 @@
72
72
  "projection": {
73
73
  "$ref": "#/$defs/path"
74
74
  },
75
+ "folder": {
76
+ "type": "string",
77
+ "description": "Sidebar folder for the rendered view, emitted as a LikeC4 title path ('Folder / Title'). Nest with '/' separators; folders appear in the order their first view appears in the views list.",
78
+ "pattern": "^[^/]+(?:/[^/]+)*$"
79
+ },
75
80
  "compare": {
76
81
  "$ref": "#/$defs/comparison"
77
82
  },