yarramate 0.5.0 → 0.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 (48) hide show
  1. package/README.md +39 -10
  2. package/catalogues/core-enrichment.yaml +700 -0
  3. package/dist/adapters/likec4-cli.js +179 -55
  4. package/dist/adapters/likec4-export.d.ts +1 -1
  5. package/dist/adapters/likec4-prepare.d.ts +6 -0
  6. package/dist/adapters/likec4-prepare.js +40 -2
  7. package/dist/adapters/mcp-cli.js +55 -49
  8. package/dist/apply-command.d.ts +2 -0
  9. package/dist/apply-command.js +199 -0
  10. package/dist/{new-command.d.ts → ask-command.d.ts} +1 -1
  11. package/dist/ask-command.js +729 -0
  12. package/dist/brief.d.ts +3 -0
  13. package/dist/brief.js +235 -0
  14. package/dist/check-command.js +70 -4
  15. package/dist/cli-support.d.ts +4 -1
  16. package/dist/cli-support.js +3 -2
  17. package/dist/cli.js +22 -586
  18. package/dist/compiler.js +45 -7
  19. package/dist/core-contract.d.ts +1 -1
  20. package/dist/{status-command.d.ts → design-command.d.ts} +1 -1
  21. package/dist/design-command.js +218 -0
  22. package/dist/export-command.d.ts +2 -0
  23. package/dist/export-command.js +228 -0
  24. package/dist/interrogate-command.d.ts +104 -0
  25. package/dist/interrogate-command.js +268 -0
  26. package/dist/next-command.d.ts +22 -0
  27. package/dist/next-command.js +167 -0
  28. package/dist/reconciliation.d.ts +2 -0
  29. package/dist/reconciliation.js +38 -0
  30. package/docs/CONSUMING-YARRAMATE.md +28 -21
  31. package/package.json +13 -10
  32. package/schema/yarramate-apply-result.schema.json +32 -0
  33. package/schema/yarramate-ask-result.schema.json +391 -0
  34. package/schema/yarramate-check-result.schema.json +12 -0
  35. package/schema/yarramate-core-contract.schema.json +5 -10
  36. package/schema/yarramate-design-step.schema.json +152 -0
  37. package/schema/yarramate-document.schema.json +76 -9
  38. package/schema/yarramate-interrogation-report.schema.json +89 -0
  39. package/schema/yarramate-likec4-generated-project-v2.schema.json +6 -0
  40. package/schema/yarramate-likec4-generated-project.schema.json +6 -0
  41. package/schema/yarramate-operations.schema.json +303 -0
  42. package/schema/yarramate-question-catalogue.schema.json +431 -0
  43. package/schema/yarramate-reconciliation-report.schema.json +8 -2
  44. package/skills/yarramate-architecture/SKILL.md +42 -17
  45. package/skills/yarramate-architecture/references/native-authoring.md +73 -17
  46. package/dist/new-command.js +0 -111
  47. package/dist/status-command.js +0 -171
  48. package/schema/yarramate-status-result.schema.json +0 -273
@@ -77,8 +77,8 @@ const usage = 'Usage:\n' +
77
77
  ' yarramate-likec4 check <projection.yaml> <mapping.yaml> [--json] [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
78
78
  ' yarramate-likec4 check <likec4-project.yaml> [--json] <workspace-or-source...>\n' +
79
79
  ' yarramate-likec4 export <projection.yaml> <mapping.yaml> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
80
- ' yarramate-likec4 export-project <projection.yaml> <mapping.yaml> <output-dir> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
81
- ' yarramate-likec4 export-project <likec4-project.yaml> <output-dir> <workspace-or-source...>\n';
80
+ ' 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
82
  const diagnosticJson = (diagnostics) => `${JSON.stringify({
83
83
  format: 'yarramate/likec4-diagnostic-result/v1',
84
84
  diagnostics,
@@ -89,10 +89,13 @@ const checkJson = (ok, diagnostics) => `${JSON.stringify({
89
89
  diagnostics,
90
90
  }, null, 2)}\n`;
91
91
  const unmappedSubjectPreviewLength = 3;
92
- const summarizeUnmappedConcepts = (diagnostics) => {
93
- const unmapped = diagnostics.filter((diagnostic) => diagnostic.code === 'YMLC102');
92
+ const summarizeUnmappedSubjects = (diagnostics) => [
93
+ ['YMLC102', 'concepts'],
94
+ ['YMLC111', 'relationships'],
95
+ ].reduce((current, [code, noun]) => {
96
+ const unmapped = current.filter((diagnostic) => diagnostic.code === code);
94
97
  if (unmapped.length <= unmappedSubjectPreviewLength)
95
- return diagnostics;
98
+ return current;
96
99
  const preview = unmapped
97
100
  .slice(0, unmappedSubjectPreviewLength)
98
101
  .map((diagnostic) => 'subject' in diagnostic && diagnostic.subject !== undefined
@@ -101,21 +104,20 @@ const summarizeUnmappedConcepts = (diagnostics) => {
101
104
  .join(', ');
102
105
  const summary = {
103
106
  ...unmapped[0],
104
- message: `${unmapped.length} projected concepts have no LikeC4 mapping ` +
107
+ message: `${unmapped.length} projected ${noun} have no LikeC4 mapping ` +
105
108
  `(first: ${preview}); run "yarramate-likec4 map --sync" to add ` +
106
109
  'the missing mappings',
107
110
  };
108
111
  let summarized = false;
109
- return diagnostics.flatMap((diagnostic) => {
110
- if (diagnostic.code !== 'YMLC102')
112
+ return current.flatMap((diagnostic) => {
113
+ if (diagnostic.code !== code)
111
114
  return [diagnostic];
112
115
  if (summarized)
113
116
  return [];
114
117
  summarized = true;
115
118
  return [summary];
116
119
  });
117
- };
118
- const sameJson = (left, right) => JSON.stringify(left) === JSON.stringify(right);
120
+ }, diagnostics);
119
121
  const lowerCamel = (value) => value.replaceAll(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
120
122
  const runLikeC4MapSync = (args, cwd) => {
121
123
  const sync = args[0];
@@ -290,6 +292,33 @@ const runLikeC4MapSync = (args, cwd) => {
290
292
  return { exitCode: 2, stdout: '', stderr: `${message}\n` };
291
293
  }
292
294
  };
295
+ // Ownership fields never gate updates: a marker of either format version
296
+ // plus intact output digests proves the directory is entirely
297
+ // machine-generated, so regenerating after the inputs changed loses nothing.
298
+ const readGeneratedProjectMarker = (markerPath) => {
299
+ let marker;
300
+ try {
301
+ marker = JSON.parse(readFileSync(markerPath, 'utf8'));
302
+ }
303
+ catch {
304
+ return undefined;
305
+ }
306
+ return validateGeneratedProjectMarker(marker) ||
307
+ validateGeneratedProjectV2Marker(marker)
308
+ ? marker
309
+ : undefined;
310
+ };
311
+ const changedGeneratedFile = (projectPath, marker) => {
312
+ // Markers written before output digests existed are accepted once and
313
+ // upgraded on regeneration.
314
+ if (marker['digests'] === undefined)
315
+ return undefined;
316
+ const digests = marker['digests'];
317
+ return generatedFileNames.find((file) => {
318
+ const path = resolve(projectPath, file);
319
+ return (!existsSync(path) || sha256(readFileSync(path)) !== digests[file]);
320
+ });
321
+ };
293
322
  const publishGeneratedProject = (cwd, outputDirectory, input) => {
294
323
  const projectPath = resolve(cwd, outputDirectory);
295
324
  const markerPath = resolve(projectPath, 'yarramate.generated.json');
@@ -319,37 +348,21 @@ const publishGeneratedProject = (cwd, outputDirectory, input) => {
319
348
  stderr: `Generated project contains an unsafe file: ${unsafeFile}\n`,
320
349
  };
321
350
  }
322
- let marker;
323
- try {
324
- marker = JSON.parse(readFileSync(markerPath, 'utf8'));
325
- }
326
- catch {
327
- marker = undefined;
328
- }
329
- if (!input.validateMarker(marker) ||
330
- typeof marker !== 'object' ||
331
- marker === null ||
332
- Object.entries(input.ownership).some(([key, value]) => !sameJson(marker[key], value))) {
351
+ const marker = readGeneratedProjectMarker(markerPath);
352
+ if (marker === undefined) {
333
353
  return {
334
354
  exitCode: 2,
335
355
  stdout: '',
336
- stderr: `Output directory already exists: ${projectPath}\n`,
356
+ stderr: `Output directory exists but is not a YarraMate-generated project: ${projectPath}\n`,
337
357
  };
338
358
  }
339
- if ('digests' in marker && marker.digests !== undefined) {
340
- const digests = marker.digests;
341
- const changedFile = generatedFileNames.find((file) => {
342
- const path = resolve(projectPath, file);
343
- return (!existsSync(path) ||
344
- sha256(readFileSync(path)) !== digests[file]);
345
- });
346
- if (changedFile !== undefined) {
347
- return {
348
- exitCode: 2,
349
- stdout: '',
350
- stderr: `Generated project file has changed: ${resolve(projectPath, changedFile)}\n`,
351
- };
352
- }
359
+ const changedFile = changedGeneratedFile(projectPath, marker);
360
+ if (changedFile !== undefined) {
361
+ return {
362
+ exitCode: 2,
363
+ stdout: '',
364
+ stderr: `Generated project file has changed: ${resolve(projectPath, changedFile)}\n`,
365
+ };
353
366
  }
354
367
  }
355
368
  else {
@@ -373,6 +386,7 @@ const publishGeneratedProject = (cwd, outputDirectory, input) => {
373
386
  'model.likec4': sha256(input.modelSource),
374
387
  'specification.likec4': sha256(specificationSource),
375
388
  },
389
+ inputDigests: input.inputDigests,
376
390
  }, null, 2)}\n`;
377
391
  publishFiles([
378
392
  {
@@ -397,6 +411,85 @@ const publishGeneratedProject = (cwd, outputDirectory, input) => {
397
411
  stderr: '',
398
412
  };
399
413
  };
414
+ // Freshness is a pure digest comparison between the marker and the would-be
415
+ // export; nothing on disk is written or semantically judged.
416
+ const checkGeneratedProject = (cwd, outputDirectory, input) => {
417
+ const projectPath = resolve(cwd, outputDirectory);
418
+ if (!existsSync(projectPath)) {
419
+ return {
420
+ exitCode: 1,
421
+ stdout: 'Generated LikeC4 output: absent\n',
422
+ stderr: '',
423
+ };
424
+ }
425
+ const marker = readGeneratedProjectMarker(resolve(projectPath, 'yarramate.generated.json'));
426
+ if (marker === undefined) {
427
+ return {
428
+ exitCode: 2,
429
+ stdout: '',
430
+ stderr: `Output directory exists but is not a YarraMate-generated project: ${projectPath}\n`,
431
+ };
432
+ }
433
+ const changedFile = changedGeneratedFile(projectPath, marker);
434
+ if (changedFile !== undefined) {
435
+ return {
436
+ exitCode: 1,
437
+ stdout: 'Generated LikeC4 output: modified\n' +
438
+ 'Reason:\n' +
439
+ `- generated file changed: ${resolve(projectPath, changedFile)}\n` +
440
+ 'Safe to regenerate: no\n',
441
+ stderr: '',
442
+ };
443
+ }
444
+ const recordedInputs = marker['inputDigests'];
445
+ const reasons = [];
446
+ if (recordedInputs === undefined) {
447
+ reasons.push('marker predates input digests');
448
+ }
449
+ else {
450
+ const inputPaths = [
451
+ ...new Set([
452
+ ...Object.keys(recordedInputs),
453
+ ...Object.keys(input.inputDigests),
454
+ ]),
455
+ ].sort();
456
+ for (const path of inputPaths) {
457
+ const recorded = recordedInputs[path];
458
+ const current = input.inputDigests[path];
459
+ if (recorded === undefined)
460
+ reasons.push(`input added: ${path}`);
461
+ else if (current === undefined) {
462
+ reasons.push(`input removed: ${path}`);
463
+ }
464
+ else if (recorded !== current) {
465
+ reasons.push(`input changed: ${path}`);
466
+ }
467
+ }
468
+ }
469
+ const digests = marker['digests'];
470
+ if (digests !== undefined &&
471
+ sha256(input.modelSource) !== digests['model.likec4']) {
472
+ reasons.push('model source changed');
473
+ }
474
+ if (reasons.length === 0) {
475
+ return {
476
+ exitCode: 0,
477
+ stdout: 'Generated LikeC4 output: fresh\n',
478
+ stderr: '',
479
+ };
480
+ }
481
+ return {
482
+ exitCode: 1,
483
+ stdout: 'Generated LikeC4 output: stale\n' +
484
+ 'Reason:\n' +
485
+ reasons.map((reason) => `- ${reason}\n`).join('') +
486
+ 'Safe to regenerate: yes\n',
487
+ stderr: '',
488
+ };
489
+ };
490
+ const inputDigestsOf = (inputs) => Object.fromEntries(inputs
491
+ .map(({ path, source }) => [path, sha256(source)])
492
+ .sort(([left], [right]) => left.localeCompare(right)));
400
493
  export function runLikeC4Cli(args, cwd = process.cwd()) {
401
494
  if (args[0] === '--version') {
402
495
  return versionResult('yarramate-likec4');
@@ -404,7 +497,10 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
404
497
  if (args[0] === 'map') {
405
498
  return runLikeC4MapSync(args.slice(1), cwd);
406
499
  }
407
- const [command, projectionPath, mappingPath, ...options] = args;
500
+ const checkFreshness = args[0] === 'export-project' && args.includes('--check');
501
+ const [command, projectionPath, mappingPath, ...options] = checkFreshness
502
+ ? args.filter((argument) => argument !== '--check')
503
+ : args;
408
504
  let projectDefinitionMode = false;
409
505
  if ((command === 'check' || command === 'export-project') &&
410
506
  projectionPath !== undefined) {
@@ -493,7 +589,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
493
589
  const diagnosticOutput = (diagnostics) => json
494
590
  ? checkJson(false, diagnostics)
495
591
  : diagnosticJson(command === 'check'
496
- ? summarizeUnmappedConcepts(diagnostics)
592
+ ? summarizeUnmappedSubjects(diagnostics)
497
593
  : diagnostics);
498
594
  try {
499
595
  const resolved = resolveCliWorkspaceSources(sourcePaths, cwd);
@@ -584,6 +680,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
584
680
  ? {}
585
681
  : { comparison: view.compare }),
586
682
  vocabulary: 'bundled',
683
+ requireMappedRelationships: command === 'check',
587
684
  }),
588
685
  }));
589
686
  const failed = preparedViews.find(({ prepared }) => !prepared.ok);
@@ -813,6 +910,17 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
813
910
  ? undefined
814
911
  : `${first.prepared.kindMapping.id}@${first.prepared.kindMapping.version}`;
815
912
  const projectIdentity = `${loadedProject.document.value.id}@${loadedProject.document.value.version}`;
913
+ const inputDigests = inputDigestsOf([
914
+ projectSource,
915
+ ...sources,
916
+ ...referencedSources.values(),
917
+ ]);
918
+ if (checkFreshness) {
919
+ return checkGeneratedProject(cwd, outputDirectory, {
920
+ modelSource: exported.source,
921
+ inputDigests,
922
+ });
923
+ }
816
924
  return publishGeneratedProject(cwd, outputDirectory, {
817
925
  projectName: `yarramate-${projectIdentity}`.replaceAll(/[^A-Za-z0-9_-]/g, '-'),
818
926
  title: loadedProject.document.value.title,
@@ -832,29 +940,33 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
832
940
  : { comparison }),
833
941
  })),
834
942
  },
835
- validateMarker: (value) => validateGeneratedProjectV2Marker(value),
943
+ inputDigests,
836
944
  });
837
945
  }
946
+ const projectionSource = {
947
+ path: projectionPath,
948
+ source: readFileSync(resolve(cwd, projectionPath), 'utf8'),
949
+ };
950
+ const mappingSource = {
951
+ path: mappingPath,
952
+ source: readFileSync(resolve(cwd, mappingPath), 'utf8'),
953
+ };
954
+ const kindMappingSource = kindMappingPath === undefined
955
+ ? undefined
956
+ : {
957
+ path: kindMappingPath,
958
+ source: readFileSync(resolve(cwd, kindMappingPath), 'utf8'),
959
+ };
838
960
  const prepared = prepareLikeC4Export({
839
961
  sources,
840
- projection: {
841
- path: projectionPath,
842
- source: readFileSync(resolve(cwd, projectionPath), 'utf8'),
843
- },
844
- subjectMapping: {
845
- path: mappingPath,
846
- source: readFileSync(resolve(cwd, mappingPath), 'utf8'),
847
- },
848
- ...(kindMappingPath === undefined
962
+ projection: projectionSource,
963
+ subjectMapping: mappingSource,
964
+ ...(kindMappingSource === undefined
849
965
  ? {}
850
- : {
851
- kindMapping: {
852
- path: kindMappingPath,
853
- source: readFileSync(resolve(cwd, kindMappingPath), 'utf8'),
854
- },
855
- }),
966
+ : { kindMapping: kindMappingSource }),
856
967
  ...(comparison === undefined ? {} : { comparison }),
857
968
  vocabulary: command === 'export' ? 'consumer' : 'bundled',
969
+ requireMappedRelationships: command === 'check',
858
970
  });
859
971
  if (!prepared.ok) {
860
972
  return {
@@ -881,6 +993,18 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
881
993
  ? undefined
882
994
  : `${prepared.kindMapping.id}@${prepared.kindMapping.version}`;
883
995
  const comparisonIdentity = comparison;
996
+ const inputDigests = inputDigestsOf([
997
+ projectionSource,
998
+ mappingSource,
999
+ ...(kindMappingSource === undefined ? [] : [kindMappingSource]),
1000
+ ...sources,
1001
+ ]);
1002
+ if (checkFreshness) {
1003
+ return checkGeneratedProject(cwd, outputDirectory, {
1004
+ modelSource: prepared.source,
1005
+ inputDigests,
1006
+ });
1007
+ }
884
1008
  return publishGeneratedProject(cwd, outputDirectory, {
885
1009
  projectName: ['yarramate', projectionIdentity]
886
1010
  .join('-')
@@ -899,7 +1023,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
899
1023
  ? {}
900
1024
  : { comparison: comparisonIdentity }),
901
1025
  },
902
- validateMarker: (value) => validateGeneratedProjectMarker(value),
1026
+ inputDigests,
903
1027
  });
904
1028
  }
905
1029
  catch (error) {
@@ -4,7 +4,7 @@ import type { ProjectionResult } from '../projection.js';
4
4
  import type { LikeC4KindMapping } from './likec4-kind-mapping.js';
5
5
  export interface LikeC4ExportDiagnostic {
6
6
  readonly severity: 'error';
7
- readonly code: 'YMLC101' | 'YMLC102' | 'YMLC103' | 'YMLC104' | 'YMLC105' | 'YMLC106' | 'YMLC107' | 'YMLC108' | 'YMLC109';
7
+ readonly code: 'YMLC101' | 'YMLC102' | 'YMLC103' | 'YMLC104' | 'YMLC105' | 'YMLC106' | 'YMLC107' | 'YMLC108' | 'YMLC109' | 'YMLC111';
8
8
  readonly message: string;
9
9
  readonly subject?: string;
10
10
  readonly path: string;
@@ -13,6 +13,12 @@ export interface LikeC4PreparationInput {
13
13
  readonly to: string;
14
14
  };
15
15
  readonly vocabulary: 'bundled' | 'consumer';
16
+ /**
17
+ * Gate mode. Rendering a relationship needs no external identity — views
18
+ * select it by metadata — but `map --sync` still writes one, so only a
19
+ * check answering "would sync change anything" requires the entry.
20
+ */
21
+ readonly requireMappedRelationships?: boolean;
16
22
  }
17
23
  export type LikeC4PreparationDiagnostic = Diagnostic | LikeC4ExportDiagnostic;
18
24
  export type LikeC4PreparationResult = {
@@ -70,6 +70,31 @@ const unsupportedBundledKinds = (projection, kindMapping) => {
70
70
  }
71
71
  return diagnostics.sort(diagnosticOrder);
72
72
  };
73
+ const unmappedProjectedRelationships = (projection, mapping) => {
74
+ const mapped = new Set(mapping.mappings
75
+ .filter(({ type }) => type === 'relationship')
76
+ .map(({ native }) => native));
77
+ return projection.subjects
78
+ .filter(({ type, id }) => type === 'relationship' && !mapped.has(id))
79
+ .flatMap(({ id }) => {
80
+ const source = projection.claims.find((claim) => claim.id === id)?.source;
81
+ return source === undefined
82
+ ? []
83
+ : [
84
+ {
85
+ severity: 'error',
86
+ code: 'YMLC111',
87
+ message: `Projected relationship "${id}" has no LikeC4 mapping`,
88
+ subject: id,
89
+ path: source.path,
90
+ pointer: source.pointer,
91
+ line: source.line,
92
+ column: source.column,
93
+ },
94
+ ];
95
+ })
96
+ .sort(diagnosticOrder);
97
+ };
73
98
  export function prepareLikeC4Export(input) {
74
99
  const compilation = compileWorkspaceWithProfileContext(input.sources);
75
100
  if (!compilation.ok)
@@ -137,11 +162,24 @@ export function prepareLikeC4Export(input) {
137
162
  if (diagnostics.length > 0)
138
163
  return { ok: false, diagnostics };
139
164
  }
165
+ const unmappedRelationships = input.requireMappedRelationships === true
166
+ ? unmappedProjectedRelationships(projectionResult, subjectMapping.mapping)
167
+ : [];
140
168
  const exported = exportLikeC4(projectionResult, subjectMapping.mapping, kindMapping?.mapping, comparison === undefined
141
169
  ? undefined
142
170
  : { comparison: comparison.comparison });
143
- if (!exported.ok)
144
- return exported;
171
+ if (!exported.ok) {
172
+ return {
173
+ ok: false,
174
+ diagnostics: [
175
+ ...exported.diagnostics,
176
+ ...unmappedRelationships,
177
+ ].sort(diagnosticOrder),
178
+ };
179
+ }
180
+ if (unmappedRelationships.length > 0) {
181
+ return { ok: false, diagnostics: unmappedRelationships };
182
+ }
145
183
  return {
146
184
  ok: true,
147
185
  source: exported.source,
@@ -10,14 +10,65 @@ const workspaceProperty = {
10
10
  };
11
11
  const tools = [
12
12
  {
13
- name: 'yarramate_status',
14
- description: 'One-call workspace orientation: check verdict, reconciliation summary, and a titled inventory of documents, states, projections, evidence, and contracts.',
13
+ name: 'yarramate_ask',
14
+ description: 'The consumed-now read surface. Without a query: orientation — check verdict, drift summary, open-question count, and the backlog in dependency order. With a query: free text matches concept ids, names, and descriptions and returns the connected slice; exact subject ids (document-id#local-id) and projection paths address precisely. Set mode for the roster (subjects), declarable vocabulary (kinds), build order (next), or the full open-questions report (open).',
15
15
  inputSchema: {
16
16
  type: 'object',
17
17
  required: ['workspace'],
18
- properties: workspaceProperty,
18
+ properties: {
19
+ ...workspaceProperty,
20
+ query: {
21
+ type: 'string',
22
+ description: 'Free text, a globally qualified subject id, or a projection path',
23
+ },
24
+ mode: {
25
+ type: 'string',
26
+ enum: ['subjects', 'kinds', 'next', 'open'],
27
+ description: 'Optional flag mode instead of a query: the filterable roster, the declarable kind vocabulary, dependency-ordered planned work, or the open-questions report',
28
+ },
29
+ budget: {
30
+ type: 'integer',
31
+ minimum: 1,
32
+ description: 'Approximate token budget for the compact slice rendering (query form only)',
33
+ },
34
+ },
35
+ },
36
+ arguments: (input) => {
37
+ const workspace = String(input.workspace);
38
+ if (typeof input.mode === 'string') {
39
+ return ['ask', workspace, `--${input.mode}`, '--json'];
40
+ }
41
+ if (typeof input.query === 'string' && input.query.length > 0) {
42
+ const budget = typeof input.budget === 'number'
43
+ ? ['--budget', String(input.budget)]
44
+ : ['--json'];
45
+ return ['ask', workspace, input.query, ...budget];
46
+ }
47
+ return ['ask', workspace, '--json'];
48
+ },
49
+ },
50
+ {
51
+ name: 'yarramate_design',
52
+ description: 'The design interview, one stateless step: the top open question with its subject slice, materiality, and progress. Read-only — answers land through the CLI apply command in the repository, not through this server.',
53
+ inputSchema: {
54
+ type: 'object',
55
+ required: ['workspace'],
56
+ properties: {
57
+ ...workspaceProperty,
58
+ subject: {
59
+ type: 'string',
60
+ description: 'Optional globally qualified subject id to narrow the interview',
61
+ },
62
+ },
19
63
  },
20
- arguments: (input) => ['status', String(input.workspace), '--json'],
64
+ arguments: (input) => [
65
+ 'design',
66
+ String(input.workspace),
67
+ ...(typeof input.subject === 'string'
68
+ ? ['--subject', input.subject]
69
+ : []),
70
+ '--json',
71
+ ],
21
72
  },
22
73
  {
23
74
  name: 'yarramate_check',
@@ -39,51 +90,6 @@ const tools = [
39
90
  },
40
91
  arguments: (input) => ['reconcile', String(input.workspace)],
41
92
  },
42
- {
43
- name: 'yarramate_context',
44
- description: 'Bounded architecture context. Provide either a projection path or one or more globally qualified subjects (document-id#local-id) for an ad-hoc connected neighbourhood. Optional token budget switches to a compact ranked rendering.',
45
- inputSchema: {
46
- type: 'object',
47
- required: ['workspace'],
48
- properties: {
49
- ...workspaceProperty,
50
- projection: {
51
- type: 'string',
52
- description: 'Path to an authored projection definition',
53
- },
54
- subjects: {
55
- type: 'array',
56
- items: { type: 'string' },
57
- description: 'Globally qualified subject identities for ad-hoc context',
58
- },
59
- budget: {
60
- type: 'integer',
61
- minimum: 1,
62
- description: 'Approximate token budget for the compact rendering',
63
- },
64
- },
65
- },
66
- arguments: (input) => {
67
- const budget = typeof input.budget === 'number'
68
- ? ['--budget', String(input.budget)]
69
- : [];
70
- if (typeof input.projection === 'string') {
71
- return [
72
- 'context',
73
- input.projection,
74
- String(input.workspace),
75
- ...budget,
76
- ];
77
- }
78
- const subjects = Array.isArray(input.subjects)
79
- ? input.subjects.flatMap((subject) => [
80
- '--subject',
81
- String(subject),
82
- ])
83
- : [];
84
- return ['context', ...subjects, String(input.workspace), ...budget];
85
- },
86
- },
87
93
  ];
88
94
  const respond = (id, result) => {
89
95
  process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
@@ -0,0 +1,2 @@
1
+ import { type CliResult } from './cli-support.js';
2
+ export declare function runApplyCommand(options: readonly string[], cwd: string): CliResult;