yarramate 0.13.0 → 0.14.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.
- package/README.md +2 -0
- package/dist/adapter-mapping.js +1 -2
- package/dist/adapters/likec4-cli.js +2 -4
- package/dist/adapters/likec4-kind-mapping.js +1 -2
- package/dist/adapters/likec4-project.js +1 -2
- package/dist/apply-command.js +159 -11
- package/dist/ask-command.js +150 -15
- package/dist/brief.d.ts +1 -0
- package/dist/brief.js +3 -1
- package/dist/cli-support.d.ts +2 -2
- package/dist/cli-support.js +2 -3
- package/dist/compiler.js +2 -4
- package/dist/core-contract.js +1 -2
- package/dist/evidence.js +1 -2
- package/dist/interrogate-command.js +1 -2
- package/dist/profile.d.ts +3 -3
- package/dist/projection.js +1 -2
- package/dist/source-document.d.ts +1 -1
- package/dist/workspace.js +1 -2
- package/package.json +4 -4
- package/schema/yarramate-apply-result.schema.json +6 -2
- package/schema/yarramate-ask-result.schema.json +52 -0
- package/schema/yarramate-operations.schema.json +52 -0
- package/skills/yarramate-architecture/references/native-authoring.md +6 -2
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://github.com/yarrasys/yarramate/actions/workflows/codeql.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
+
Product site: **[yarramate.dev](https://yarramate.dev)**
|
|
9
|
+
|
|
8
10
|
YarraMate keeps your coding agents' architecture context correct. Declare
|
|
9
11
|
the design once, as a checked model in git; agents receive prose rendered
|
|
10
12
|
from it — bounded briefs and open design questions — and the CLI
|
package/dist/adapter-mapping.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
3
|
-
import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.json' with {
|
|
4
|
-
type: 'json'
|
|
3
|
+
import adapterMappingSchema from '../schema/yarramate-adapter-mapping.schema.json' with { type: 'json'
|
|
5
4
|
};
|
|
6
5
|
const Ajv2020 = Ajv2020Module.default;
|
|
7
6
|
const validateSchema = new Ajv2020({ allErrors: true }).compile(adapterMappingSchema);
|
|
@@ -12,11 +12,9 @@ import { adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } f
|
|
|
12
12
|
import { locateSourcePath } from '../source-document.js';
|
|
13
13
|
import { prepareLikeC4Export, } from './likec4-prepare.js';
|
|
14
14
|
import { exportLikeC4Project, loadLikeC4ProjectDefinition, } from './likec4-project.js';
|
|
15
|
-
import generatedProjectSchema from '../../schema/yarramate-likec4-generated-project.schema.json' with {
|
|
16
|
-
type: 'json'
|
|
15
|
+
import generatedProjectSchema from '../../schema/yarramate-likec4-generated-project.schema.json' with { type: 'json'
|
|
17
16
|
};
|
|
18
|
-
import generatedProjectV2Schema from '../../schema/yarramate-likec4-generated-project-v2.schema.json' with {
|
|
19
|
-
type: 'json'
|
|
17
|
+
import generatedProjectV2Schema from '../../schema/yarramate-likec4-generated-project-v2.schema.json' with { type: 'json'
|
|
20
18
|
};
|
|
21
19
|
const Ajv2020 = Ajv2020Module.default;
|
|
22
20
|
const validateGeneratedProjectMarker = new Ajv2020({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from '../source-document.js';
|
|
3
|
-
import kindMappingSchema from '../../schema/yarramate-likec4-kind-mapping.schema.json' with {
|
|
4
|
-
type: 'json'
|
|
3
|
+
import kindMappingSchema from '../../schema/yarramate-likec4-kind-mapping.schema.json' with { type: 'json'
|
|
5
4
|
};
|
|
6
5
|
const Ajv2020 = Ajv2020Module.default;
|
|
7
6
|
const validateSchema = new Ajv2020({ allErrors: true }).compile(kindMappingSchema);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { loadSourceDocument } from '../source-document.js';
|
|
3
3
|
import { exportLikeC4, } from './likec4-export.js';
|
|
4
|
-
import likeC4ProjectSchema from '../../schema/yarramate-likec4-project.schema.json' with {
|
|
5
|
-
type: 'json'
|
|
4
|
+
import likeC4ProjectSchema from '../../schema/yarramate-likec4-project.schema.json' with { type: 'json'
|
|
6
5
|
};
|
|
7
6
|
const Ajv2020 = Ajv2020Module.default;
|
|
8
7
|
const validateLikeC4Project = new Ajv2020({ allErrors: true }).compile(likeC4ProjectSchema);
|
package/dist/apply-command.js
CHANGED
|
@@ -6,8 +6,7 @@ import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
|
6
6
|
import { compileWorkspace } from './compiler.js';
|
|
7
7
|
import { loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
8
8
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
9
|
-
import operationsSchema from '../schema/yarramate-operations.schema.json' with {
|
|
10
|
-
type: 'json'
|
|
9
|
+
import operationsSchema from '../schema/yarramate-operations.schema.json' with { type: 'json'
|
|
11
10
|
};
|
|
12
11
|
const Ajv2020 = Ajv2020Module.default;
|
|
13
12
|
const validateOperations = new Ajv2020({ allErrors: true }).compile(operationsSchema);
|
|
@@ -114,12 +113,15 @@ const itemMap = (source, collection, id) => {
|
|
|
114
113
|
const pair = pairFor(root, collection);
|
|
115
114
|
if (pair === undefined || !isSeq(pair.value))
|
|
116
115
|
return undefined;
|
|
117
|
-
const
|
|
116
|
+
const sequence = pair.value;
|
|
117
|
+
const found = sequence.items.find((candidate) => isMap(candidate) &&
|
|
118
118
|
candidate.items.some((field) => isScalar(field.key) &&
|
|
119
119
|
field.key.value === 'id' &&
|
|
120
120
|
isScalar(field.value) &&
|
|
121
121
|
field.value.value === id));
|
|
122
|
-
return found === undefined
|
|
122
|
+
return found === undefined
|
|
123
|
+
? undefined
|
|
124
|
+
: { map: found, sequence };
|
|
123
125
|
};
|
|
124
126
|
// The indent item fields sit at, read off the item's own first field.
|
|
125
127
|
const fieldIndentOf = (source, map) => indentAt(source, nodeRange(map.items[0].key)[0]);
|
|
@@ -208,6 +210,40 @@ const removeField = (source, map, key) => {
|
|
|
208
210
|
: nodeRange(pair.value)[2];
|
|
209
211
|
return splice(source, start, lineEndAfter(source, valueEnd), '');
|
|
210
212
|
};
|
|
213
|
+
// Whole-subject deletion (#123): remove the exact authored item range,
|
|
214
|
+
// marker line included. Unlike field removal — where line deletion on a
|
|
215
|
+
// flow item would silently take the whole item (the 0.8.1 regression) —
|
|
216
|
+
// deleting the whole item is precisely the intent here, so flow-style
|
|
217
|
+
// items in a block sequence share the line-based path. An item inside a
|
|
218
|
+
// flow collection rewrites the collection value instead, and removing
|
|
219
|
+
// the last item leaves an explicit empty collection: the document
|
|
220
|
+
// schema requires the key.
|
|
221
|
+
const removeCollectionItem = (source, collection, id) => {
|
|
222
|
+
const { map, sequence } = itemMap(source, collection, id);
|
|
223
|
+
if (sequence.flow) {
|
|
224
|
+
const remaining = sequence.toJSON().filter((item) => item.id !== id);
|
|
225
|
+
const [start, valueEnd] = nodeRange(sequence);
|
|
226
|
+
return splice(source, start, valueEnd, remaining.length === 0
|
|
227
|
+
? '[]'
|
|
228
|
+
: stringify(remaining, {
|
|
229
|
+
collectionStyle: 'flow',
|
|
230
|
+
lineWidth: 0,
|
|
231
|
+
}).trimEnd());
|
|
232
|
+
}
|
|
233
|
+
// Node ranges may extend past the trailing newline to the next line
|
|
234
|
+
// start; afterContentLine anchors the removal on the item's own last
|
|
235
|
+
// content line.
|
|
236
|
+
const end = afterContentLine(source, nodeRange(map)[2]);
|
|
237
|
+
if (sequence.items.length === 1) {
|
|
238
|
+
let valueStart = nodeRange(sequence)[0];
|
|
239
|
+
while (valueStart > 0 &&
|
|
240
|
+
(source[valueStart - 1] === ' ' || source[valueStart - 1] === '\n')) {
|
|
241
|
+
valueStart -= 1;
|
|
242
|
+
}
|
|
243
|
+
return splice(source, valueStart, end, ' []\n');
|
|
244
|
+
}
|
|
245
|
+
return splice(source, lineStartOf(source, nodeRange(map)[0]), end, '');
|
|
246
|
+
};
|
|
211
247
|
// ---------------------------------------------------------------------------
|
|
212
248
|
export function runApplyCommand(options, cwd) {
|
|
213
249
|
const json = options.includes('--json');
|
|
@@ -253,16 +289,20 @@ export function runApplyCommand(options, cwd) {
|
|
|
253
289
|
addedRelationships: 0,
|
|
254
290
|
updatedConcepts: 0,
|
|
255
291
|
updatedRelationships: 0,
|
|
292
|
+
deletedConcepts: 0,
|
|
293
|
+
deletedRelationships: 0,
|
|
256
294
|
};
|
|
295
|
+
const deletions = [];
|
|
296
|
+
const locateOperation = (index, message) => ({
|
|
297
|
+
severity: 'error',
|
|
298
|
+
code: 'YM912',
|
|
299
|
+
message,
|
|
300
|
+
...locateSourcePath(operationsPath, yaml, lineCounter, ['operations', index, 'document'], `/operations/${index}/document`),
|
|
301
|
+
});
|
|
257
302
|
for (const [index, operation] of operations.entries()) {
|
|
258
303
|
const absolute = resolve(cwd, operation.document);
|
|
259
304
|
const manifestPath = workspaceDocuments.get(absolute);
|
|
260
|
-
const locate = (message) => (
|
|
261
|
-
severity: 'error',
|
|
262
|
-
code: 'YM912',
|
|
263
|
-
message,
|
|
264
|
-
...locateSourcePath(operationsPath, yaml, lineCounter, ['operations', index, 'document'], `/operations/${index}/document`),
|
|
265
|
-
});
|
|
305
|
+
const locate = (message) => locateOperation(index, message);
|
|
266
306
|
if (manifestPath === undefined) {
|
|
267
307
|
return failed([
|
|
268
308
|
locate(`Operation ${index} targets "${operation.document}", which is not a document of workspace "${workspace.id}"`),
|
|
@@ -280,6 +320,26 @@ export function runApplyCommand(options, cwd) {
|
|
|
280
320
|
source = appendCollectionItem(source, 'relationships', operation.relationship);
|
|
281
321
|
counts.addedRelationships += 1;
|
|
282
322
|
}
|
|
323
|
+
else if (operation.op === 'delete-concept' ||
|
|
324
|
+
operation.op === 'delete-relationship') {
|
|
325
|
+
const collection = operation.op === 'delete-concept' ? 'concepts' : 'relationships';
|
|
326
|
+
const id = operation.op === 'delete-concept'
|
|
327
|
+
? operation.concept.id
|
|
328
|
+
: operation.relationship.id;
|
|
329
|
+
if (itemMap(source, collection, id) === undefined) {
|
|
330
|
+
return failed([
|
|
331
|
+
locate(`Operation ${index} deletes "${id}", which does not exist in ${operation.document}`),
|
|
332
|
+
]);
|
|
333
|
+
}
|
|
334
|
+
source = removeCollectionItem(source, collection, id);
|
|
335
|
+
deletions.push({ index, absolute, id });
|
|
336
|
+
if (operation.op === 'delete-concept') {
|
|
337
|
+
counts.deletedConcepts += 1;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
counts.deletedRelationships += 1;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
283
343
|
else {
|
|
284
344
|
const collection = operation.op === 'update-concept' ? 'concepts' : 'relationships';
|
|
285
345
|
const payload = (operation.op === 'update-concept'
|
|
@@ -334,6 +394,92 @@ export function runApplyCommand(options, cwd) {
|
|
|
334
394
|
}
|
|
335
395
|
candidates.set(absolute, source);
|
|
336
396
|
}
|
|
397
|
+
// Reference integrity for deletes (#123), evaluated against the
|
|
398
|
+
// post-batch state: stage everything first, then look, so a concept
|
|
399
|
+
// deleted together with its referring relationships in one batch
|
|
400
|
+
// succeeds while a target anything still points at rejects the
|
|
401
|
+
// whole batch. Referring sites are relationship endpoints, owner,
|
|
402
|
+
// constraint and identified references; projection selectors are
|
|
403
|
+
// deliberately unchecked — they tolerate no-match by design. The
|
|
404
|
+
// compile gate below stays the backstop.
|
|
405
|
+
if (deletions.length > 0) {
|
|
406
|
+
const staged = workspace.documents.map((path) => {
|
|
407
|
+
const absolute = resolve(cwd, path);
|
|
408
|
+
return {
|
|
409
|
+
absolute,
|
|
410
|
+
value: parseDocument(candidates.get(absolute) ?? readFileSync(absolute, 'utf8')).toJSON(),
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
const qualify = (documentId, reference) => reference.includes('#') ? reference : `${documentId}#${reference}`;
|
|
414
|
+
const referrers = staged.flatMap(({ value }) => {
|
|
415
|
+
const documentId = value?.id;
|
|
416
|
+
if (documentId === undefined)
|
|
417
|
+
return [];
|
|
418
|
+
const sites = [];
|
|
419
|
+
for (const concept of value?.concepts ?? []) {
|
|
420
|
+
const subject = `${documentId}#${concept.id}`;
|
|
421
|
+
if (concept.owner !== undefined) {
|
|
422
|
+
sites.push({
|
|
423
|
+
ref: qualify(documentId, concept.owner),
|
|
424
|
+
subject,
|
|
425
|
+
field: 'owner',
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
for (const constraint of concept.constraints ?? []) {
|
|
429
|
+
sites.push({
|
|
430
|
+
ref: qualify(documentId, constraint.ref),
|
|
431
|
+
subject,
|
|
432
|
+
field: 'constraints',
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
for (const reference of concept.references ?? []) {
|
|
436
|
+
sites.push({
|
|
437
|
+
ref: qualify(documentId, reference.ref),
|
|
438
|
+
subject,
|
|
439
|
+
field: 'references',
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const relationship of value?.relationships ?? []) {
|
|
444
|
+
const subject = `${documentId}#${relationship.id}`;
|
|
445
|
+
for (const endpoint of ['from', 'to']) {
|
|
446
|
+
const reference = relationship[endpoint];
|
|
447
|
+
if (reference !== undefined) {
|
|
448
|
+
sites.push({
|
|
449
|
+
ref: qualify(documentId, reference),
|
|
450
|
+
subject,
|
|
451
|
+
field: endpoint,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const reference of relationship.references ?? []) {
|
|
456
|
+
sites.push({
|
|
457
|
+
ref: qualify(documentId, reference.ref),
|
|
458
|
+
subject,
|
|
459
|
+
field: 'references',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return sites;
|
|
464
|
+
});
|
|
465
|
+
const documentIds = new Map(staged.map(({ absolute, value }) => [absolute, value?.id]));
|
|
466
|
+
const violations = deletions.flatMap((deletion) => {
|
|
467
|
+
const documentId = documentIds.get(deletion.absolute);
|
|
468
|
+
if (documentId === undefined)
|
|
469
|
+
return [];
|
|
470
|
+
const target = `${documentId}#${deletion.id}`;
|
|
471
|
+
const referring = referrers.filter((site) => site.ref === target);
|
|
472
|
+
if (referring.length === 0)
|
|
473
|
+
return [];
|
|
474
|
+
return [
|
|
475
|
+
locateOperation(deletion.index, `Operation ${deletion.index} deletes "${deletion.id}", which is still referenced by ${referring
|
|
476
|
+
.map((site) => `"${site.subject}" (${site.field})`)
|
|
477
|
+
.join(', ')}`),
|
|
478
|
+
];
|
|
479
|
+
});
|
|
480
|
+
if (violations.length > 0)
|
|
481
|
+
return failed(violations);
|
|
482
|
+
}
|
|
337
483
|
// The atomic gate: the whole candidate workspace must compile before a
|
|
338
484
|
// single byte is written; any diagnostic rejects the entire batch.
|
|
339
485
|
const compilation = compileWorkspace([...workspace.profiles, ...workspace.documents].map((path) => {
|
|
@@ -366,7 +512,9 @@ export function runApplyCommand(options, cwd) {
|
|
|
366
512
|
const applied = counts.addedConcepts +
|
|
367
513
|
counts.addedRelationships +
|
|
368
514
|
counts.updatedConcepts +
|
|
369
|
-
counts.updatedRelationships
|
|
515
|
+
counts.updatedRelationships +
|
|
516
|
+
counts.deletedConcepts +
|
|
517
|
+
counts.deletedRelationships;
|
|
370
518
|
return {
|
|
371
519
|
exitCode: 0,
|
|
372
520
|
stdout: `Applied ${applied} operation${applied === 1 ? '' : 's'} to ${touched.join(', ')}\n`,
|
package/dist/ask-command.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { parseDocument } from 'yaml';
|
|
5
5
|
import { compareArchitectureStates, } from './architecture-state.js';
|
|
6
|
-
import { renderBrief } from './brief.js';
|
|
6
|
+
import { coreLocalKind, renderBrief } from './brief.js';
|
|
7
7
|
import { deriveChangedSubjects } from './changed.js';
|
|
8
8
|
import { runCheckCommand } from './check-command.js';
|
|
9
9
|
import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
@@ -105,18 +105,118 @@ const plannedLines = (subjects) => {
|
|
|
105
105
|
return ` ${subject.id.padEnd(width)} ${clauses.join('; ')}`;
|
|
106
106
|
});
|
|
107
107
|
};
|
|
108
|
+
// On a dense graph, hub seeds make 1-hop connected expansion reach most
|
|
109
|
+
// of the model (a focused ask on a 248-concept model reached 243). The
|
|
110
|
+
// cap keeps each seed's most material neighbours and announces the rest
|
|
111
|
+
// (ADR 0070); --neighbours overrides it, 0 lifts it.
|
|
112
|
+
const defaultNeighbourCap = 12;
|
|
113
|
+
const motivationKindIds = new Set(conceptKinds
|
|
114
|
+
.filter(({ layer }) => layer === 'motivation')
|
|
115
|
+
.map(({ id }) => id));
|
|
116
|
+
// Neighbours rank by the same reading the brief ranks paragraphs with
|
|
117
|
+
// under a budget (ADR 0042/0055): motivation first, then planned,
|
|
118
|
+
// current, retired. Ties break on seed affinity (a neighbour touching
|
|
119
|
+
// more seeds is more material to the slice), then id — deterministic.
|
|
120
|
+
const materialityRank = (graph, profileContext, id) => {
|
|
121
|
+
const kind = claimValue(graph.claims, id, 'yarramate/concept/kind');
|
|
122
|
+
const core = kind === undefined
|
|
123
|
+
? undefined
|
|
124
|
+
: coreLocalKind(kind, profileContext?.conceptKindLineages);
|
|
125
|
+
if (core !== undefined && motivationKindIds.has(core))
|
|
126
|
+
return 0;
|
|
127
|
+
const status = claimValue(graph.claims, id, 'yarramate/lifecycle/status');
|
|
128
|
+
return status === 'planned' ? 1 : status === 'retired' ? 3 : 2;
|
|
129
|
+
};
|
|
108
130
|
// The one-hop connected neighbourhood every slice and advice mode uses:
|
|
109
131
|
// the same machinery context --subject exposed, now seeded by matching.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
// The capped result is always a subset of the uncapped one — the cap
|
|
133
|
+
// drops neighbours and their edges, never seeds, and never adds edges
|
|
134
|
+
// the connected expansion would not have selected.
|
|
135
|
+
const sliceProjection = (graph, seeds, title, profileContext, neighbourCap) => {
|
|
136
|
+
const full = evaluateProjection(graph, {
|
|
137
|
+
format: 'yarramate/projection/v1',
|
|
138
|
+
id: 'ask-slice',
|
|
139
|
+
version: '0.0',
|
|
140
|
+
query: { subjects: [...seeds], relationships: 'connected' },
|
|
141
|
+
presentation: {
|
|
142
|
+
title,
|
|
143
|
+
description: `Connected neighbourhood of ${seeds.join(', ')}`,
|
|
144
|
+
},
|
|
145
|
+
}, profileContext);
|
|
146
|
+
if (neighbourCap === 0)
|
|
147
|
+
return { result: full };
|
|
148
|
+
const seedSet = new Set(seeds);
|
|
149
|
+
const relationshipIds = new Set(full.subjects
|
|
150
|
+
.filter(({ type }) => type === 'relationship')
|
|
151
|
+
.map(({ id }) => id));
|
|
152
|
+
const endpointsById = new Map();
|
|
153
|
+
for (const claim of full.claims) {
|
|
154
|
+
if (relationshipIds.has(claim.id) && 'ref' in claim.object) {
|
|
155
|
+
endpointsById.set(claim.id, [claim.subject, claim.object.ref]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const neighboursOf = new Map(seeds.map((seed) => [seed, []]));
|
|
159
|
+
const affinity = new Map();
|
|
160
|
+
for (const [from, to] of endpointsById.values()) {
|
|
161
|
+
for (const [seed, neighbour] of [
|
|
162
|
+
[from, to],
|
|
163
|
+
[to, from],
|
|
164
|
+
]) {
|
|
165
|
+
if (!seedSet.has(seed) || seedSet.has(neighbour))
|
|
166
|
+
continue;
|
|
167
|
+
const list = neighboursOf.get(seed);
|
|
168
|
+
if (list !== undefined && !list.includes(neighbour)) {
|
|
169
|
+
list.push(neighbour);
|
|
170
|
+
affinity.set(neighbour, (affinity.get(neighbour) ?? 0) + 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const keptNeighbours = new Set();
|
|
175
|
+
for (const list of neighboursOf.values()) {
|
|
176
|
+
const ordered = [...list].sort((left, right) => materialityRank(graph, profileContext, left) -
|
|
177
|
+
materialityRank(graph, profileContext, right) ||
|
|
178
|
+
(affinity.get(right) ?? 0) - (affinity.get(left) ?? 0) ||
|
|
179
|
+
left.localeCompare(right));
|
|
180
|
+
for (const neighbour of ordered.slice(0, neighbourCap)) {
|
|
181
|
+
keptNeighbours.add(neighbour);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const allNeighbours = new Set([...neighboursOf.values()].flat());
|
|
185
|
+
const omitted = allNeighbours.size - keptNeighbours.size;
|
|
186
|
+
if (omitted === 0)
|
|
187
|
+
return { result: full };
|
|
188
|
+
const keptConcepts = new Set([...seedSet, ...keptNeighbours]);
|
|
189
|
+
const keptRelationships = new Set([...endpointsById]
|
|
190
|
+
.filter(([, [from, to]]) => keptConcepts.has(from) && keptConcepts.has(to))
|
|
191
|
+
.map(([id]) => id));
|
|
192
|
+
const keptSubjects = new Set([...keptConcepts, ...keptRelationships]);
|
|
193
|
+
const keptDocuments = new Set([...keptConcepts].map((id) => id.slice(0, id.indexOf('#'))));
|
|
194
|
+
const result = {
|
|
195
|
+
...full,
|
|
196
|
+
documents: full.documents.filter(({ id }) => keptDocuments.has(id)),
|
|
197
|
+
subjects: full.subjects.filter(({ id }) => keptSubjects.has(id)),
|
|
198
|
+
claims: full.claims.filter((claim) => (keptConcepts.has(claim.subject) && !relationshipIds.has(claim.id)) ||
|
|
199
|
+
keptRelationships.has(claim.subject) ||
|
|
200
|
+
keptRelationships.has(claim.id)),
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
result,
|
|
204
|
+
neighbourhood: {
|
|
205
|
+
cap: neighbourCap,
|
|
206
|
+
kept: keptNeighbours.size,
|
|
207
|
+
omitted,
|
|
208
|
+
omittedBySeed: seeds.flatMap((seed) => {
|
|
209
|
+
const dropped = (neighboursOf.get(seed) ?? []).filter((neighbour) => !keptNeighbours.has(neighbour)).length;
|
|
210
|
+
return dropped > 0 ? [{ seed, omitted: dropped }] : [];
|
|
211
|
+
}),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
// The honesty line, in the budgeted-ladder voice (ADR 0042): what was
|
|
216
|
+
// dropped is named, and so is the way to widen.
|
|
217
|
+
const neighbourhoodLine = (neighbourhood) => `[neighbours ${neighbourhood.cap}: ${neighbourhood.omitted} of ` +
|
|
218
|
+
`${neighbourhood.kept + neighbourhood.omitted} neighbours omitted — ` +
|
|
219
|
+
`raise --neighbours or pass --neighbours 0 for the full neighbourhood]`;
|
|
120
220
|
export function runAskCommand(options, cwd) {
|
|
121
221
|
let json = false;
|
|
122
222
|
let subjects = false;
|
|
@@ -128,6 +228,7 @@ export function runAskCommand(options, cwd) {
|
|
|
128
228
|
let compare;
|
|
129
229
|
let changed;
|
|
130
230
|
let budget;
|
|
231
|
+
let neighbours;
|
|
131
232
|
let kindFilter;
|
|
132
233
|
let statusFilter;
|
|
133
234
|
let cataloguePath;
|
|
@@ -177,6 +278,7 @@ export function runAskCommand(options, cwd) {
|
|
|
177
278
|
continue;
|
|
178
279
|
}
|
|
179
280
|
if (option === '--budget' ||
|
|
281
|
+
option === '--neighbours' ||
|
|
180
282
|
option === '--kind' ||
|
|
181
283
|
option === '--status' ||
|
|
182
284
|
option === '--catalogue' ||
|
|
@@ -191,6 +293,12 @@ export function runAskCommand(options, cwd) {
|
|
|
191
293
|
}
|
|
192
294
|
budget = Number(value);
|
|
193
295
|
}
|
|
296
|
+
else if (option === '--neighbours') {
|
|
297
|
+
if (neighbours !== undefined || !/^(0|[1-9][0-9]*)$/.test(value)) {
|
|
298
|
+
return { exitCode: 2, stdout: '', stderr: usage };
|
|
299
|
+
}
|
|
300
|
+
neighbours = Number(value);
|
|
301
|
+
}
|
|
194
302
|
else if (option === '--changed') {
|
|
195
303
|
if (changed !== undefined) {
|
|
196
304
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
@@ -241,6 +349,7 @@ export function runAskCommand(options, cwd) {
|
|
|
241
349
|
advise ||
|
|
242
350
|
changed !== undefined ||
|
|
243
351
|
budget !== undefined ||
|
|
352
|
+
neighbours !== undefined ||
|
|
244
353
|
query.length === 0)) ||
|
|
245
354
|
(query.length > 0 && exclusiveModes > 0) ||
|
|
246
355
|
(changed !== undefined &&
|
|
@@ -248,7 +357,11 @@ export function runAskCommand(options, cwd) {
|
|
|
248
357
|
((kindFilter !== undefined || statusFilter !== undefined) && !subjects) ||
|
|
249
358
|
(cataloguePath !== undefined && !open && !advise) ||
|
|
250
359
|
(budget !== undefined &&
|
|
251
|
-
(json || (query.length === 0 && !advise && changed === undefined)))
|
|
360
|
+
(json || (query.length === 0 && !advise && changed === undefined))) ||
|
|
361
|
+
(neighbours !== undefined &&
|
|
362
|
+
query.length === 0 &&
|
|
363
|
+
!advise &&
|
|
364
|
+
changed === undefined)) {
|
|
252
365
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
253
366
|
}
|
|
254
367
|
try {
|
|
@@ -615,7 +728,7 @@ export function runAskCommand(options, cwd) {
|
|
|
615
728
|
projections: workspace.projections.length,
|
|
616
729
|
uncovered,
|
|
617
730
|
};
|
|
618
|
-
const evaluated = sliceProjection(graph, seeds, `Review slice ${changed}`, compilation.profileContext);
|
|
731
|
+
const { result: evaluated, neighbourhood } = sliceProjection(graph, seeds, `Review slice ${changed}`, compilation.profileContext, neighbours ?? defaultNeighbourCap);
|
|
619
732
|
const result = {
|
|
620
733
|
format: 'yarramate/ask-result/v1',
|
|
621
734
|
workspace: workspace.id,
|
|
@@ -624,6 +737,7 @@ export function runAskCommand(options, cwd) {
|
|
|
624
737
|
seeds,
|
|
625
738
|
changed: derived.changed,
|
|
626
739
|
coverage,
|
|
740
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
627
741
|
result: evaluated,
|
|
628
742
|
};
|
|
629
743
|
if (changedIds.length === 0) {
|
|
@@ -637,6 +751,9 @@ export function runAskCommand(options, cwd) {
|
|
|
637
751
|
`${plural(derived.changed.relationships.length, 'relationship')} changed (workspace ${workspace.id})`,
|
|
638
752
|
'',
|
|
639
753
|
rendered.trimEnd(),
|
|
754
|
+
...(neighbourhood === undefined
|
|
755
|
+
? []
|
|
756
|
+
: ['', neighbourhoodLine(neighbourhood)]),
|
|
640
757
|
'',
|
|
641
758
|
uncovered.length === 0
|
|
642
759
|
? `Review coverage: every changed subject appears in at least one of the ${coverage.projections} authored projections.`
|
|
@@ -657,6 +774,17 @@ export function runAskCommand(options, cwd) {
|
|
|
657
774
|
: undefined;
|
|
658
775
|
if (projectionCandidate !== undefined &&
|
|
659
776
|
parseDocument(readFileSync(projectionCandidate, 'utf8')).get('format') === 'yarramate/projection/v1') {
|
|
777
|
+
// A projection file defines its own query; there is no seeded
|
|
778
|
+
// expansion to cap, so an explicit --neighbours is a contradiction
|
|
779
|
+
// rather than something to ignore silently.
|
|
780
|
+
if (neighbours !== undefined) {
|
|
781
|
+
return {
|
|
782
|
+
exitCode: 2,
|
|
783
|
+
stdout: '',
|
|
784
|
+
stderr: `--neighbours applies to seeded slices; ${soleTerm} is a ` +
|
|
785
|
+
'projection that defines its own query\n',
|
|
786
|
+
};
|
|
787
|
+
}
|
|
660
788
|
const loaded = loadProjection({
|
|
661
789
|
path: soleTerm,
|
|
662
790
|
source: readFileSync(projectionCandidate, 'utf8'),
|
|
@@ -772,7 +900,7 @@ export function runAskCommand(options, cwd) {
|
|
|
772
900
|
lines.push('', note);
|
|
773
901
|
return emit(result, `${lines.join('\n')}\n`);
|
|
774
902
|
}
|
|
775
|
-
const evaluated = sliceProjection(graph, resolution.seeds, topic, compilation.profileContext);
|
|
903
|
+
const { result: evaluated, neighbourhood } = sliceProjection(graph, resolution.seeds, topic, compilation.profileContext, neighbours ?? defaultNeighbourCap);
|
|
776
904
|
if (!advise) {
|
|
777
905
|
const result = {
|
|
778
906
|
format: 'yarramate/ask-result/v1',
|
|
@@ -782,6 +910,7 @@ export function runAskCommand(options, cwd) {
|
|
|
782
910
|
topic,
|
|
783
911
|
seeds: resolution.seeds,
|
|
784
912
|
matched: resolution.matched,
|
|
913
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
785
914
|
result: evaluated,
|
|
786
915
|
};
|
|
787
916
|
const rendered = budget === undefined
|
|
@@ -794,7 +923,9 @@ export function runAskCommand(options, cwd) {
|
|
|
794
923
|
: '') +
|
|
795
924
|
`: ${resolution.seeds.join(', ')}\n\n`
|
|
796
925
|
: '';
|
|
797
|
-
return emit(result,
|
|
926
|
+
return emit(result, neighbourhood === undefined
|
|
927
|
+
? `${header}${rendered}`
|
|
928
|
+
: `${header}${rendered.trimEnd()}\n\n${neighbourhoodLine(neighbourhood)}\n`);
|
|
798
929
|
}
|
|
799
930
|
// --advise: the expert composition. The engine assembles ground
|
|
800
931
|
// truth — slice, open questions, drift — and stops; the reading and
|
|
@@ -874,6 +1005,7 @@ export function runAskCommand(options, cwd) {
|
|
|
874
1005
|
seeds: resolution.seeds,
|
|
875
1006
|
matched: resolution.matched,
|
|
876
1007
|
slice: brief,
|
|
1008
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
877
1009
|
openQuestions,
|
|
878
1010
|
...(reconciliation === undefined ? {} : { reconciliation }),
|
|
879
1011
|
};
|
|
@@ -884,6 +1016,9 @@ export function runAskCommand(options, cwd) {
|
|
|
884
1016
|
'== Model slice ==',
|
|
885
1017
|
'',
|
|
886
1018
|
brief.trimEnd(),
|
|
1019
|
+
...(neighbourhood === undefined
|
|
1020
|
+
? []
|
|
1021
|
+
: ['', neighbourhoodLine(neighbourhood)]),
|
|
887
1022
|
'',
|
|
888
1023
|
'== Open questions touching this slice ==',
|
|
889
1024
|
];
|
package/dist/brief.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ResolvedProfileContext } from './compiler.js';
|
|
2
2
|
import type { ProjectionResult } from './projection.js';
|
|
3
|
+
export declare const coreLocalKind: (kind: string, lineages: ReadonlyMap<string, readonly string[]> | undefined) => string | undefined;
|
|
3
4
|
export declare function renderBrief(result: ProjectionResult, profileContext?: ResolvedProfileContext, budgetTokens?: number): string;
|
package/dist/brief.js
CHANGED
|
@@ -14,7 +14,9 @@ const claimReferences = (claims, subject, predicate) => claims.flatMap((claim) =
|
|
|
14
14
|
: []);
|
|
15
15
|
// Resolve a qualified kind to its nearest core-profile local id through
|
|
16
16
|
// declared lineage, mirroring how `next` orients relationships (ADR 0048).
|
|
17
|
-
|
|
17
|
+
// Exported so ask's neighbour cap ranks neighbours by the same reading
|
|
18
|
+
// the brief ranks paragraphs with (ADR 0070).
|
|
19
|
+
export const coreLocalKind = (kind, lineages) => {
|
|
18
20
|
for (const candidate of [kind, ...(lineages?.get(kind) ?? [])]) {
|
|
19
21
|
const separator = candidate.indexOf('#');
|
|
20
22
|
if (separator !== -1 && candidate.startsWith('yarramate/core@')) {
|
package/dist/cli-support.d.ts
CHANGED
|
@@ -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> --where \"<free text>\" | <document-id>#<local-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>] [--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";
|
|
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>] [--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>\" | <document-id>#<local-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>\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;
|
|
@@ -18,7 +18,7 @@ export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counte
|
|
|
18
18
|
readonly observations: number;
|
|
19
19
|
readonly contradicted: number;
|
|
20
20
|
}) => string;
|
|
21
|
-
export declare const humanDiagnostics: (diagnostics: readonly Pick<Diagnostic,
|
|
21
|
+
export declare const humanDiagnostics: (diagnostics: readonly Pick<Diagnostic, 'path' | 'line' | 'column' | 'code' | 'message'>[]) => string;
|
|
22
22
|
export declare const sortDiagnostics: <T extends Diagnostic>(diagnostics: readonly T[]) => T[];
|
|
23
23
|
export declare const resolveCliWorkspaceSources: (paths: readonly string[], cwd: string, options?: {
|
|
24
24
|
readonly includeAdapterMappings?: boolean;
|
package/dist/cli-support.js
CHANGED
|
@@ -3,8 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { parseDocument } from 'yaml';
|
|
5
5
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
6
|
-
import packageManifest from '../package.json' with {
|
|
7
|
-
type: 'json'
|
|
6
|
+
import packageManifest from '../package.json' with { type: 'json'
|
|
8
7
|
};
|
|
9
8
|
export const isMainModule = (moduleUrl, entrypoint) => {
|
|
10
9
|
if (entrypoint === undefined)
|
|
@@ -23,7 +22,7 @@ export const versionResult = (binary) => ({
|
|
|
23
22
|
stdout: `${binary} ${packageVersion}\n`,
|
|
24
23
|
stderr: '',
|
|
25
24
|
});
|
|
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> --where "<free text>" | <document-id>#<local-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>] [--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';
|
|
25
|
+
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>] [--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>" | <document-id>#<local-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>\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
26
|
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
28
27
|
format: 'yarramate/diagnostic-result/v1',
|
|
29
28
|
diagnostics,
|
package/dist/compiler.js
CHANGED
|
@@ -2,11 +2,9 @@ import Ajv2020Module from 'ajv/dist/2020.js';
|
|
|
2
2
|
import { LineCounter, parseDocument } from 'yaml';
|
|
3
3
|
import { conceptKinds, relationshipPolicies, } from './profile.js';
|
|
4
4
|
import { closestCandidate, describeSchemaViolation, } from './source-document.js';
|
|
5
|
-
import documentSchema from '../schema/yarramate-document.schema.json' with {
|
|
6
|
-
type: 'json'
|
|
5
|
+
import documentSchema from '../schema/yarramate-document.schema.json' with { type: 'json'
|
|
7
6
|
};
|
|
8
|
-
import profileSchema from '../schema/yarramate-profile.schema.json' with {
|
|
9
|
-
type: 'json'
|
|
7
|
+
import profileSchema from '../schema/yarramate-profile.schema.json' with { type: 'json'
|
|
10
8
|
};
|
|
11
9
|
const coreProfile = 'yarramate/core@0.1';
|
|
12
10
|
const Ajv2020 = Ajv2020Module.default;
|
package/dist/core-contract.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { LineCounter, parseDocument } from 'yaml';
|
|
3
3
|
import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
4
|
-
import coreContractSchema from '../schema/yarramate-core-contract.schema.json' with {
|
|
5
|
-
type: 'json'
|
|
4
|
+
import coreContractSchema from '../schema/yarramate-core-contract.schema.json' with { type: 'json'
|
|
6
5
|
};
|
|
7
6
|
const Ajv2020 = Ajv2020Module.default;
|
|
8
7
|
const validateCoreContract = new Ajv2020({ allErrors: true }).compile(coreContractSchema);
|
package/dist/evidence.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { diagnosticOrder, loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
3
|
-
import evidenceSchema from '../schema/yarramate-evidence.schema.json' with {
|
|
4
|
-
type: 'json'
|
|
3
|
+
import evidenceSchema from '../schema/yarramate-evidence.schema.json' with { type: 'json'
|
|
5
4
|
};
|
|
6
5
|
const Ajv2020 = Ajv2020Module.default;
|
|
7
6
|
const validateEvidenceSchema = new Ajv2020({ allErrors: true }).compile(evidenceSchema);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { loadSourceDocument, locateSourcePath, } from './source-document.js';
|
|
3
|
-
import catalogueSchema from '../schema/yarramate-question-catalogue.schema.json' with {
|
|
4
|
-
type: 'json'
|
|
3
|
+
import catalogueSchema from '../schema/yarramate-question-catalogue.schema.json' with { type: 'json'
|
|
5
4
|
};
|
|
6
5
|
const Ajv2020 = Ajv2020Module.default;
|
|
7
6
|
const validateCatalogue = new Ajv2020({ allErrors: true }).compile(catalogueSchema);
|
package/dist/profile.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const layers: readonly [
|
|
2
|
-
export declare const aspects: readonly [
|
|
1
|
+
export declare const layers: readonly ['motivation', 'strategy', 'business', 'application', 'technology', 'physical', 'implementation', 'composite'];
|
|
2
|
+
export declare const aspects: readonly ['motivation', 'active-structure', 'behavior', 'passive-structure', 'composite'];
|
|
3
3
|
export type Layer = (typeof layers)[number];
|
|
4
4
|
export type Aspect = (typeof aspects)[number];
|
|
5
5
|
export interface ConceptKind {
|
|
@@ -14,7 +14,7 @@ export interface ConceptKind {
|
|
|
14
14
|
readonly inspiredBy: string;
|
|
15
15
|
}
|
|
16
16
|
export declare const conceptKinds: readonly ConceptKind[];
|
|
17
|
-
export declare const relationshipKinds: readonly [
|
|
17
|
+
export declare const relationshipKinds: readonly ['composition', 'aggregation', 'assignment', 'realization', 'serving', 'access', 'influence', 'association', 'triggering', 'flow', 'specialization'];
|
|
18
18
|
export type RelationshipKind = (typeof relationshipKinds)[number];
|
|
19
19
|
export interface RelationshipPolicy {
|
|
20
20
|
readonly id: RelationshipKind;
|
package/dist/projection.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
2
2
|
import { loadSourceDocument } from './source-document.js';
|
|
3
|
-
import projectionSchema from '../schema/yarramate-projection.schema.json' with {
|
|
4
|
-
type: 'json'
|
|
3
|
+
import projectionSchema from '../schema/yarramate-projection.schema.json' with { type: 'json'
|
|
5
4
|
};
|
|
6
5
|
const Ajv2020 = Ajv2020Module.default;
|
|
7
6
|
const validateProjection = new Ajv2020({ allErrors: true }).compile(projectionSchema);
|
|
@@ -20,7 +20,7 @@ export type SourceDocumentResult<T> = {
|
|
|
20
20
|
readonly diagnostics: readonly Diagnostic[];
|
|
21
21
|
};
|
|
22
22
|
export declare const diagnosticOrder: (left: Diagnostic, right: Diagnostic) => number;
|
|
23
|
-
export declare const describeSchemaViolation: (error: Pick<ErrorObject,
|
|
23
|
+
export declare const describeSchemaViolation: (error: Pick<ErrorObject, 'keyword' | 'message' | 'params'>) => string;
|
|
24
24
|
export declare const closestCandidate: (value: string, candidates: Iterable<string>) => string | undefined;
|
|
25
25
|
export declare function locateSourcePath(sourcePath: string, yaml: ReturnType<typeof parseDocument>, lineCounter: LineCounter, yamlPath: readonly (string | number)[], pointer: string): SourceLocation;
|
|
26
26
|
export declare function loadSourceDocument<T>(source: WorkspaceSource, validate: ValidateFunction, schemaLabel: string): SourceDocumentResult<T>;
|
package/dist/workspace.js
CHANGED
|
@@ -2,8 +2,7 @@ import { globSync, realpathSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, isAbsolute, relative, resolve, sep, } from 'node:path';
|
|
3
3
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
4
4
|
import { diagnosticOrder, loadSourceDocument, } from './source-document.js';
|
|
5
|
-
import workspaceSchema from '../schema/yarramate-workspace.schema.json' with {
|
|
6
|
-
type: 'json'
|
|
5
|
+
import workspaceSchema from '../schema/yarramate-workspace.schema.json' with { type: 'json'
|
|
7
6
|
};
|
|
8
7
|
const Ajv2020 = Ajv2020Module.default;
|
|
9
8
|
const validateWorkspace = new Ajv2020({ allErrors: true }).compile(workspaceSchema);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yarramate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Tool-neutral semantic architecture engine and guided methodology",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/yarrasys/yarramate.git"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://
|
|
10
|
+
"homepage": "https://yarramate.dev",
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/yarrasys/yarramate/issues"
|
|
13
13
|
},
|
|
@@ -108,9 +108,9 @@
|
|
|
108
108
|
"yaml": "^2.8.1"
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@types/node": "^
|
|
111
|
+
"@types/node": "^26.1.2",
|
|
112
112
|
"likec4": "^1.59.2",
|
|
113
|
-
"typescript": "^
|
|
113
|
+
"typescript": "^7.0.2",
|
|
114
114
|
"vitest": "^4.1.10"
|
|
115
115
|
}
|
|
116
116
|
}
|
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
"addedConcepts",
|
|
16
16
|
"addedRelationships",
|
|
17
17
|
"updatedConcepts",
|
|
18
|
-
"updatedRelationships"
|
|
18
|
+
"updatedRelationships",
|
|
19
|
+
"deletedConcepts",
|
|
20
|
+
"deletedRelationships"
|
|
19
21
|
],
|
|
20
22
|
"properties": {
|
|
21
23
|
"addedConcepts": { "type": "integer", "minimum": 0 },
|
|
22
24
|
"addedRelationships": { "type": "integer", "minimum": 0 },
|
|
23
25
|
"updatedConcepts": { "type": "integer", "minimum": 0 },
|
|
24
|
-
"updatedRelationships": { "type": "integer", "minimum": 0 }
|
|
26
|
+
"updatedRelationships": { "type": "integer", "minimum": 0 },
|
|
27
|
+
"deletedConcepts": { "type": "integer", "minimum": 0 },
|
|
28
|
+
"deletedRelationships": { "type": "integer", "minimum": 0 }
|
|
25
29
|
}
|
|
26
30
|
},
|
|
27
31
|
"documents": {
|
|
@@ -263,6 +263,9 @@
|
|
|
263
263
|
}
|
|
264
264
|
},
|
|
265
265
|
"additionalProperties": false
|
|
266
|
+
},
|
|
267
|
+
"neighbourhood": {
|
|
268
|
+
"$ref": "#/$defs/neighbourhoodOmission"
|
|
266
269
|
}
|
|
267
270
|
},
|
|
268
271
|
"additionalProperties": false
|
|
@@ -302,6 +305,9 @@
|
|
|
302
305
|
"type": "string",
|
|
303
306
|
"minLength": 1
|
|
304
307
|
},
|
|
308
|
+
"neighbourhood": {
|
|
309
|
+
"$ref": "#/$defs/neighbourhoodOmission"
|
|
310
|
+
},
|
|
305
311
|
"openQuestions": {
|
|
306
312
|
"type": "array",
|
|
307
313
|
"items": {
|
|
@@ -707,6 +713,52 @@
|
|
|
707
713
|
}
|
|
708
714
|
],
|
|
709
715
|
"$defs": {
|
|
716
|
+
"neighbourhoodOmission": {
|
|
717
|
+
"description": "Present only when the per-seed neighbour cap dropped part of the 1-hop expansion (ADR 0070): the cap in force, neighbours kept and omitted in total, and the per-seed omission counts. Absent means the slice is the complete connected neighbourhood.",
|
|
718
|
+
"type": "object",
|
|
719
|
+
"required": [
|
|
720
|
+
"cap",
|
|
721
|
+
"kept",
|
|
722
|
+
"omitted",
|
|
723
|
+
"omittedBySeed"
|
|
724
|
+
],
|
|
725
|
+
"properties": {
|
|
726
|
+
"cap": {
|
|
727
|
+
"type": "integer",
|
|
728
|
+
"minimum": 1
|
|
729
|
+
},
|
|
730
|
+
"kept": {
|
|
731
|
+
"type": "integer",
|
|
732
|
+
"minimum": 0
|
|
733
|
+
},
|
|
734
|
+
"omitted": {
|
|
735
|
+
"type": "integer",
|
|
736
|
+
"minimum": 1
|
|
737
|
+
},
|
|
738
|
+
"omittedBySeed": {
|
|
739
|
+
"type": "array",
|
|
740
|
+
"items": {
|
|
741
|
+
"type": "object",
|
|
742
|
+
"required": [
|
|
743
|
+
"seed",
|
|
744
|
+
"omitted"
|
|
745
|
+
],
|
|
746
|
+
"properties": {
|
|
747
|
+
"seed": {
|
|
748
|
+
"type": "string",
|
|
749
|
+
"minLength": 1
|
|
750
|
+
},
|
|
751
|
+
"omitted": {
|
|
752
|
+
"type": "integer",
|
|
753
|
+
"minimum": 1
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
"additionalProperties": false
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
"additionalProperties": false
|
|
761
|
+
},
|
|
710
762
|
"conceptEntry": {
|
|
711
763
|
"type": "object",
|
|
712
764
|
"required": [
|
|
@@ -170,6 +170,18 @@
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
},
|
|
173
|
+
"deleteTarget": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"additionalProperties": false,
|
|
176
|
+
"required": [
|
|
177
|
+
"id"
|
|
178
|
+
],
|
|
179
|
+
"properties": {
|
|
180
|
+
"id": {
|
|
181
|
+
"$ref": "#/$defs/nonEmptyText"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
173
185
|
"operation": {
|
|
174
186
|
"oneOf": [
|
|
175
187
|
{
|
|
@@ -327,6 +339,46 @@
|
|
|
327
339
|
}
|
|
328
340
|
}
|
|
329
341
|
}
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"type": "object",
|
|
345
|
+
"additionalProperties": false,
|
|
346
|
+
"required": [
|
|
347
|
+
"op",
|
|
348
|
+
"document",
|
|
349
|
+
"concept"
|
|
350
|
+
],
|
|
351
|
+
"properties": {
|
|
352
|
+
"op": {
|
|
353
|
+
"const": "delete-concept"
|
|
354
|
+
},
|
|
355
|
+
"document": {
|
|
356
|
+
"$ref": "#/$defs/nonEmptyText"
|
|
357
|
+
},
|
|
358
|
+
"concept": {
|
|
359
|
+
"$ref": "#/$defs/deleteTarget"
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"type": "object",
|
|
365
|
+
"additionalProperties": false,
|
|
366
|
+
"required": [
|
|
367
|
+
"op",
|
|
368
|
+
"document",
|
|
369
|
+
"relationship"
|
|
370
|
+
],
|
|
371
|
+
"properties": {
|
|
372
|
+
"op": {
|
|
373
|
+
"const": "delete-relationship"
|
|
374
|
+
},
|
|
375
|
+
"document": {
|
|
376
|
+
"$ref": "#/$defs/nonEmptyText"
|
|
377
|
+
},
|
|
378
|
+
"relationship": {
|
|
379
|
+
"$ref": "#/$defs/deleteTarget"
|
|
380
|
+
}
|
|
381
|
+
}
|
|
330
382
|
}
|
|
331
383
|
]
|
|
332
384
|
}
|
|
@@ -168,8 +168,12 @@ yarramate apply operations.yaml workspace.yaml
|
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
The whole candidate workspace must compile or nothing is written.
|
|
171
|
-
Update operations enrich
|
|
172
|
-
|
|
171
|
+
Update operations enrich by default — scalars replace, lists append — and
|
|
172
|
+
retract explicitly with `remove: [<field> ...]`. Whole subjects leave
|
|
173
|
+
through `delete-concept` / `delete-relationship` (payload: the `id` only),
|
|
174
|
+
rejected while anything still references the target; delete the referring
|
|
175
|
+
relationships in the same batch. To descope, retire (`status: retired`)
|
|
176
|
+
instead — delete only when the history itself is noise.
|
|
173
177
|
|
|
174
178
|
## Ownership and constraints
|
|
175
179
|
|