yarramate 0.12.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 +17 -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 +252 -17
- 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 +167 -1
- package/schema/yarramate-operations.schema.json +52 -0
- package/skills/yarramate-architecture/SKILL.md +4 -1
- 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
|
|
@@ -17,6 +19,20 @@ whose gaps are found deterministically.
|
|
|
17
19
|
> YarraMate is pre-release software. Interfaces may evolve before the first
|
|
18
20
|
> stable release.
|
|
19
21
|
|
|
22
|
+
## Proven across harnesses
|
|
23
|
+
|
|
24
|
+
The design bet is that the model — not the session — is the state, so any
|
|
25
|
+
agent in any harness can resume the work cold. We tested that adversarially
|
|
26
|
+
on a real product: a Claude Code session worked the design interview all
|
|
27
|
+
day, then an OpenAI Codex session — no shared context, the tool never
|
|
28
|
+
named — resumed it from a ten-line pointer file and the published CLI. It
|
|
29
|
+
answered 63 open design questions, filed two genuine defect reports, and in
|
|
30
|
+
a later session reported that the model "was not merely documentation" — it
|
|
31
|
+
caught an approval-path regression before the release shipped.
|
|
32
|
+
|
|
33
|
+
The full story, with every commit, PR, and release attached:
|
|
34
|
+
[The model is the handover](docs/CASE-STUDY-CROSS-HARNESS.md).
|
|
35
|
+
|
|
20
36
|
## Why YarraMate?
|
|
21
37
|
|
|
22
38
|
Architecture documents often drift away from implementation or become tied to
|
|
@@ -134,6 +150,7 @@ node dist/cli.js ask .yarramate/workspace.yaml
|
|
|
134
150
|
node dist/cli.js ask .yarramate/workspace.yaml "free text about the model"
|
|
135
151
|
node dist/cli.js ask .yarramate/workspace.yaml --subjects
|
|
136
152
|
node dist/cli.js ask .yarramate/workspace.yaml --advise "a design question"
|
|
153
|
+
node dist/cli.js ask .yarramate/workspace.yaml --where "compiler"
|
|
137
154
|
node dist/cli.js check .yarramate/workspace.yaml --json
|
|
138
155
|
node dist/cli.js reconcile .yarramate/workspace.yaml
|
|
139
156
|
node dist/cli.js export graph .yarramate/workspace.yaml
|
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,12 +3,12 @@ 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';
|
|
10
10
|
import { compileWorkspaceWithProfileContext, } from './compiler.js';
|
|
11
|
-
import { evaluateEvidenceWorkspace, loadEvidence } from './evidence.js';
|
|
11
|
+
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
12
12
|
import { evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, } from './interrogate-command.js';
|
|
13
13
|
import { buildNextSubjects, coverageClause, } from './next-command.js';
|
|
14
14
|
import { conceptKinds, relationshipPolicies, } from './profile.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;
|
|
@@ -124,9 +224,11 @@ export function runAskCommand(options, cwd) {
|
|
|
124
224
|
let open = false;
|
|
125
225
|
let kinds = false;
|
|
126
226
|
let advise = false;
|
|
227
|
+
let where = false;
|
|
127
228
|
let compare;
|
|
128
229
|
let changed;
|
|
129
230
|
let budget;
|
|
231
|
+
let neighbours;
|
|
130
232
|
let kindFilter;
|
|
131
233
|
let statusFilter;
|
|
132
234
|
let cataloguePath;
|
|
@@ -157,6 +259,10 @@ export function runAskCommand(options, cwd) {
|
|
|
157
259
|
advise = true;
|
|
158
260
|
continue;
|
|
159
261
|
}
|
|
262
|
+
if (option === '--where') {
|
|
263
|
+
where = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
160
266
|
if (option === '--compare') {
|
|
161
267
|
const from = options[index + 1];
|
|
162
268
|
const to = options[index + 2];
|
|
@@ -172,6 +278,7 @@ export function runAskCommand(options, cwd) {
|
|
|
172
278
|
continue;
|
|
173
279
|
}
|
|
174
280
|
if (option === '--budget' ||
|
|
281
|
+
option === '--neighbours' ||
|
|
175
282
|
option === '--kind' ||
|
|
176
283
|
option === '--status' ||
|
|
177
284
|
option === '--catalogue' ||
|
|
@@ -186,6 +293,12 @@ export function runAskCommand(options, cwd) {
|
|
|
186
293
|
}
|
|
187
294
|
budget = Number(value);
|
|
188
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
|
+
}
|
|
189
302
|
else if (option === '--changed') {
|
|
190
303
|
if (changed !== undefined) {
|
|
191
304
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
@@ -231,13 +344,24 @@ export function runAskCommand(options, cwd) {
|
|
|
231
344
|
exclusiveModes > 1 ||
|
|
232
345
|
(advise && exclusiveModes > 0) ||
|
|
233
346
|
(advise && query.length === 0) ||
|
|
347
|
+
(where &&
|
|
348
|
+
(exclusiveModes > 0 ||
|
|
349
|
+
advise ||
|
|
350
|
+
changed !== undefined ||
|
|
351
|
+
budget !== undefined ||
|
|
352
|
+
neighbours !== undefined ||
|
|
353
|
+
query.length === 0)) ||
|
|
234
354
|
(query.length > 0 && exclusiveModes > 0) ||
|
|
235
355
|
(changed !== undefined &&
|
|
236
356
|
(query.length > 0 || exclusiveModes > 0 || advise)) ||
|
|
237
357
|
((kindFilter !== undefined || statusFilter !== undefined) && !subjects) ||
|
|
238
358
|
(cataloguePath !== undefined && !open && !advise) ||
|
|
239
359
|
(budget !== undefined &&
|
|
240
|
-
(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)) {
|
|
241
365
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
242
366
|
}
|
|
243
367
|
try {
|
|
@@ -604,7 +728,7 @@ export function runAskCommand(options, cwd) {
|
|
|
604
728
|
projections: workspace.projections.length,
|
|
605
729
|
uncovered,
|
|
606
730
|
};
|
|
607
|
-
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);
|
|
608
732
|
const result = {
|
|
609
733
|
format: 'yarramate/ask-result/v1',
|
|
610
734
|
workspace: workspace.id,
|
|
@@ -613,6 +737,7 @@ export function runAskCommand(options, cwd) {
|
|
|
613
737
|
seeds,
|
|
614
738
|
changed: derived.changed,
|
|
615
739
|
coverage,
|
|
740
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
616
741
|
result: evaluated,
|
|
617
742
|
};
|
|
618
743
|
if (changedIds.length === 0) {
|
|
@@ -626,6 +751,9 @@ export function runAskCommand(options, cwd) {
|
|
|
626
751
|
`${plural(derived.changed.relationships.length, 'relationship')} changed (workspace ${workspace.id})`,
|
|
627
752
|
'',
|
|
628
753
|
rendered.trimEnd(),
|
|
754
|
+
...(neighbourhood === undefined
|
|
755
|
+
? []
|
|
756
|
+
: ['', neighbourhoodLine(neighbourhood)]),
|
|
629
757
|
'',
|
|
630
758
|
uncovered.length === 0
|
|
631
759
|
? `Review coverage: every changed subject appears in at least one of the ${coverage.projections} authored projections.`
|
|
@@ -638,11 +766,25 @@ export function runAskCommand(options, cwd) {
|
|
|
638
766
|
// names a projection file is precise addressing; anything else runs
|
|
639
767
|
// through free-text seeding, where exact subject ids win.
|
|
640
768
|
const soleTerm = query.length === 1 ? query[0] : undefined;
|
|
641
|
-
const projectionCandidate = soleTerm !== undefined &&
|
|
769
|
+
const projectionCandidate = soleTerm !== undefined &&
|
|
770
|
+
!advise &&
|
|
771
|
+
!where &&
|
|
772
|
+
existsSync(resolve(cwd, soleTerm))
|
|
642
773
|
? resolve(cwd, soleTerm)
|
|
643
774
|
: undefined;
|
|
644
775
|
if (projectionCandidate !== undefined &&
|
|
645
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
|
+
}
|
|
646
788
|
const loaded = loadProjection({
|
|
647
789
|
path: soleTerm,
|
|
648
790
|
source: readFileSync(projectionCandidate, 'utf8'),
|
|
@@ -672,7 +814,93 @@ export function runAskCommand(options, cwd) {
|
|
|
672
814
|
`List the roster: yarramate ask ${workspacePath} --subjects\n`,
|
|
673
815
|
};
|
|
674
816
|
}
|
|
675
|
-
|
|
817
|
+
// --where: evidence-backed pointing (ADR 0068). Verified locations for
|
|
818
|
+
// the matched subjects, an explicit list of matched-but-unobserved
|
|
819
|
+
// subjects, and a hand-off note for everything outside the model —
|
|
820
|
+
// authority follows epistemic status, so the routing is stated in the
|
|
821
|
+
// output rather than assumed by the reader.
|
|
822
|
+
if (where) {
|
|
823
|
+
const evidenceDocuments = [];
|
|
824
|
+
for (const path of workspace.evidence) {
|
|
825
|
+
const loaded = loadEvidence({
|
|
826
|
+
path,
|
|
827
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
828
|
+
});
|
|
829
|
+
if (!loaded.ok)
|
|
830
|
+
return failed(loaded.diagnostics);
|
|
831
|
+
evidenceDocuments.push(loaded.evidence);
|
|
832
|
+
}
|
|
833
|
+
const subjectOf = (observation) => 'subject' in observation
|
|
834
|
+
? observation.subject
|
|
835
|
+
: (observation.claim.split('~')[0] ?? observation.claim);
|
|
836
|
+
// Subject- and claim-level observations often share a locator; the
|
|
837
|
+
// pointer is the same either way, so identical entries collapse.
|
|
838
|
+
const entriesBySeed = resolution.seeds.map((seed) => ({
|
|
839
|
+
subject: seed,
|
|
840
|
+
observations: [
|
|
841
|
+
...new Map(evidenceDocuments
|
|
842
|
+
.flatMap((document) => document.observations
|
|
843
|
+
.filter((observation) => subjectOf(observation) === seed)
|
|
844
|
+
.map((observation) => ({
|
|
845
|
+
uri: observation.evidence.uri,
|
|
846
|
+
result: observation.result,
|
|
847
|
+
provider: document.provider,
|
|
848
|
+
...(observation.evidence.message === undefined
|
|
849
|
+
? {}
|
|
850
|
+
: { message: observation.evidence.message }),
|
|
851
|
+
})))
|
|
852
|
+
.map((entry) => [
|
|
853
|
+
`${entry.uri}${entry.result}${entry.provider}${entry.message ?? ''}`,
|
|
854
|
+
entry,
|
|
855
|
+
])).values(),
|
|
856
|
+
].sort((left, right) => left.uri.localeCompare(right.uri) ||
|
|
857
|
+
left.result.localeCompare(right.result)),
|
|
858
|
+
}));
|
|
859
|
+
const located = entriesBySeed.filter(({ observations }) => observations.length > 0);
|
|
860
|
+
const unobserved = entriesBySeed
|
|
861
|
+
.filter(({ observations }) => observations.length === 0)
|
|
862
|
+
.map(({ subject }) => subject);
|
|
863
|
+
const note = workspace.evidence.length === 0
|
|
864
|
+
? 'This workspace declares no evidence overlay, so no location is verified. For code locations, use your search tools; author evidence observations to make locations verifiable.'
|
|
865
|
+
: 'Locations above are verified by evidence overlays. Subjects listed as unobserved are modeled but unlocated. For code outside the model, use your search tools or a code index.';
|
|
866
|
+
const result = {
|
|
867
|
+
format: 'yarramate/ask-result/v1',
|
|
868
|
+
workspace: workspace.id,
|
|
869
|
+
mode: 'where',
|
|
870
|
+
addressing: resolution.addressing,
|
|
871
|
+
topic,
|
|
872
|
+
seeds: resolution.seeds,
|
|
873
|
+
matched: resolution.matched,
|
|
874
|
+
located,
|
|
875
|
+
coverage: { unobserved, note },
|
|
876
|
+
};
|
|
877
|
+
const lines = [
|
|
878
|
+
`Where: "${topic}" — ${plural(resolution.matched, 'concept')} matched` +
|
|
879
|
+
(resolution.matched > resolution.seeds.length
|
|
880
|
+
? `, seeded from the top ${resolution.seeds.length}`
|
|
881
|
+
: '') +
|
|
882
|
+
`: ${resolution.seeds.join(', ')}`,
|
|
883
|
+
'',
|
|
884
|
+
];
|
|
885
|
+
for (const entry of located) {
|
|
886
|
+
lines.push(` ${entry.subject}`);
|
|
887
|
+
for (const observation of entry.observations) {
|
|
888
|
+
lines.push(` ${observation.result} ${observation.uri} (${observation.provider})`);
|
|
889
|
+
if (observation.message !== undefined) {
|
|
890
|
+
lines.push(` ${observation.message}`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (located.length === 0) {
|
|
895
|
+
lines.push(' no verified locations');
|
|
896
|
+
}
|
|
897
|
+
if (unobserved.length > 0) {
|
|
898
|
+
lines.push('', ` unobserved — modeled, no evidence: ${unobserved.join(', ')}`);
|
|
899
|
+
}
|
|
900
|
+
lines.push('', note);
|
|
901
|
+
return emit(result, `${lines.join('\n')}\n`);
|
|
902
|
+
}
|
|
903
|
+
const { result: evaluated, neighbourhood } = sliceProjection(graph, resolution.seeds, topic, compilation.profileContext, neighbours ?? defaultNeighbourCap);
|
|
676
904
|
if (!advise) {
|
|
677
905
|
const result = {
|
|
678
906
|
format: 'yarramate/ask-result/v1',
|
|
@@ -682,6 +910,7 @@ export function runAskCommand(options, cwd) {
|
|
|
682
910
|
topic,
|
|
683
911
|
seeds: resolution.seeds,
|
|
684
912
|
matched: resolution.matched,
|
|
913
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
685
914
|
result: evaluated,
|
|
686
915
|
};
|
|
687
916
|
const rendered = budget === undefined
|
|
@@ -694,7 +923,9 @@ export function runAskCommand(options, cwd) {
|
|
|
694
923
|
: '') +
|
|
695
924
|
`: ${resolution.seeds.join(', ')}\n\n`
|
|
696
925
|
: '';
|
|
697
|
-
return emit(result,
|
|
926
|
+
return emit(result, neighbourhood === undefined
|
|
927
|
+
? `${header}${rendered}`
|
|
928
|
+
: `${header}${rendered.trimEnd()}\n\n${neighbourhoodLine(neighbourhood)}\n`);
|
|
698
929
|
}
|
|
699
930
|
// --advise: the expert composition. The engine assembles ground
|
|
700
931
|
// truth — slice, open questions, drift — and stops; the reading and
|
|
@@ -774,6 +1005,7 @@ export function runAskCommand(options, cwd) {
|
|
|
774
1005
|
seeds: resolution.seeds,
|
|
775
1006
|
matched: resolution.matched,
|
|
776
1007
|
slice: brief,
|
|
1008
|
+
...(neighbourhood === undefined ? {} : { neighbourhood }),
|
|
777
1009
|
openQuestions,
|
|
778
1010
|
...(reconciliation === undefined ? {} : { reconciliation }),
|
|
779
1011
|
};
|
|
@@ -784,6 +1016,9 @@ export function runAskCommand(options, cwd) {
|
|
|
784
1016
|
'== Model slice ==',
|
|
785
1017
|
'',
|
|
786
1018
|
brief.trimEnd(),
|
|
1019
|
+
...(neighbourhood === undefined
|
|
1020
|
+
? []
|
|
1021
|
+
: ['', neighbourhoodLine(neighbourhood)]),
|
|
787
1022
|
'',
|
|
788
1023
|
'== Open questions touching this slice ==',
|
|
789
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> --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> --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": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://yarrasys.dev/schema/yarramate-ask-result.schema.json",
|
|
4
4
|
"title": "YarraMate ask result",
|
|
5
|
-
"description": "One envelope for every consumed-now read: orientation, roster, slice, advice, next, open questions, and state comparison, discriminated by mode.",
|
|
5
|
+
"description": "One envelope for every consumed-now read: orientation, roster, slice, advice, verified locations, next, open questions, and state comparison, discriminated by mode.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"format",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"roster",
|
|
24
24
|
"slice",
|
|
25
25
|
"advice",
|
|
26
|
+
"where",
|
|
26
27
|
"next",
|
|
27
28
|
"open",
|
|
28
29
|
"compare",
|
|
@@ -262,6 +263,9 @@
|
|
|
262
263
|
}
|
|
263
264
|
},
|
|
264
265
|
"additionalProperties": false
|
|
266
|
+
},
|
|
267
|
+
"neighbourhood": {
|
|
268
|
+
"$ref": "#/$defs/neighbourhoodOmission"
|
|
265
269
|
}
|
|
266
270
|
},
|
|
267
271
|
"additionalProperties": false
|
|
@@ -301,6 +305,9 @@
|
|
|
301
305
|
"type": "string",
|
|
302
306
|
"minLength": 1
|
|
303
307
|
},
|
|
308
|
+
"neighbourhood": {
|
|
309
|
+
"$ref": "#/$defs/neighbourhoodOmission"
|
|
310
|
+
},
|
|
304
311
|
"openQuestions": {
|
|
305
312
|
"type": "array",
|
|
306
313
|
"items": {
|
|
@@ -363,6 +370,119 @@
|
|
|
363
370
|
},
|
|
364
371
|
"additionalProperties": false
|
|
365
372
|
},
|
|
373
|
+
{
|
|
374
|
+
"type": "object",
|
|
375
|
+
"required": [
|
|
376
|
+
"mode",
|
|
377
|
+
"addressing",
|
|
378
|
+
"topic",
|
|
379
|
+
"seeds",
|
|
380
|
+
"matched",
|
|
381
|
+
"located",
|
|
382
|
+
"coverage"
|
|
383
|
+
],
|
|
384
|
+
"properties": {
|
|
385
|
+
"format": true,
|
|
386
|
+
"workspace": true,
|
|
387
|
+
"mode": {
|
|
388
|
+
"const": "where"
|
|
389
|
+
},
|
|
390
|
+
"addressing": {
|
|
391
|
+
"enum": [
|
|
392
|
+
"free-text",
|
|
393
|
+
"subjects"
|
|
394
|
+
]
|
|
395
|
+
},
|
|
396
|
+
"topic": {
|
|
397
|
+
"type": "string",
|
|
398
|
+
"minLength": 1
|
|
399
|
+
},
|
|
400
|
+
"seeds": {
|
|
401
|
+
"type": "array",
|
|
402
|
+
"items": {
|
|
403
|
+
"type": "string",
|
|
404
|
+
"minLength": 1
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
"matched": {
|
|
408
|
+
"type": "integer",
|
|
409
|
+
"minimum": 0
|
|
410
|
+
},
|
|
411
|
+
"located": {
|
|
412
|
+
"type": "array",
|
|
413
|
+
"items": {
|
|
414
|
+
"type": "object",
|
|
415
|
+
"required": [
|
|
416
|
+
"subject",
|
|
417
|
+
"observations"
|
|
418
|
+
],
|
|
419
|
+
"additionalProperties": false,
|
|
420
|
+
"properties": {
|
|
421
|
+
"subject": {
|
|
422
|
+
"type": "string",
|
|
423
|
+
"minLength": 1
|
|
424
|
+
},
|
|
425
|
+
"observations": {
|
|
426
|
+
"type": "array",
|
|
427
|
+
"minItems": 1,
|
|
428
|
+
"items": {
|
|
429
|
+
"type": "object",
|
|
430
|
+
"required": [
|
|
431
|
+
"uri",
|
|
432
|
+
"result",
|
|
433
|
+
"provider"
|
|
434
|
+
],
|
|
435
|
+
"additionalProperties": false,
|
|
436
|
+
"properties": {
|
|
437
|
+
"uri": {
|
|
438
|
+
"type": "string",
|
|
439
|
+
"minLength": 1
|
|
440
|
+
},
|
|
441
|
+
"result": {
|
|
442
|
+
"enum": [
|
|
443
|
+
"confirmed",
|
|
444
|
+
"contradicted",
|
|
445
|
+
"unknown",
|
|
446
|
+
"not-observed"
|
|
447
|
+
]
|
|
448
|
+
},
|
|
449
|
+
"provider": {
|
|
450
|
+
"type": "string",
|
|
451
|
+
"minLength": 1
|
|
452
|
+
},
|
|
453
|
+
"message": {
|
|
454
|
+
"type": "string",
|
|
455
|
+
"minLength": 1
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
"coverage": {
|
|
464
|
+
"type": "object",
|
|
465
|
+
"required": [
|
|
466
|
+
"unobserved",
|
|
467
|
+
"note"
|
|
468
|
+
],
|
|
469
|
+
"additionalProperties": false,
|
|
470
|
+
"properties": {
|
|
471
|
+
"unobserved": {
|
|
472
|
+
"type": "array",
|
|
473
|
+
"items": {
|
|
474
|
+
"type": "string",
|
|
475
|
+
"minLength": 1
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"note": {
|
|
479
|
+
"type": "string",
|
|
480
|
+
"minLength": 1
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
},
|
|
366
486
|
{
|
|
367
487
|
"type": "object",
|
|
368
488
|
"required": [
|
|
@@ -593,6 +713,52 @@
|
|
|
593
713
|
}
|
|
594
714
|
],
|
|
595
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
|
+
},
|
|
596
762
|
"conceptEntry": {
|
|
597
763
|
"type": "object",
|
|
598
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
|
}
|
|
@@ -33,7 +33,10 @@ design questions, and the backlog — planned subjects in dependency
|
|
|
33
33
|
order. `ask <workspace.yaml> --subjects` lists every concept;
|
|
34
34
|
`ask <workspace.yaml> --kinds` lists the declarable vocabulary;
|
|
35
35
|
`ask <workspace.yaml> "<free text>"` returns the model slice matching
|
|
36
|
-
your words.
|
|
36
|
+
your words; `ask <workspace.yaml> --where "<free text>"` returns the
|
|
37
|
+
evidence-verified code locations of matching subjects — prefer those
|
|
38
|
+
over searching when the subject is modeled, and use your own search
|
|
39
|
+
tools beyond the coverage boundary the output states.
|
|
37
40
|
|
|
38
41
|
- Existing implementation is the starting point: follow **Discover an
|
|
39
42
|
existing project**.
|
|
@@ -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
|
|