yarramate 1.4.1 → 1.6.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/dist/adapters/visual/session-server.js +147 -45
- package/dist/ask-command.js +3 -3
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +17 -1
- package/dist/design-command.js +10 -6
- package/dist/export-command.js +41 -5
- package/dist/import-command.d.ts +16 -0
- package/dist/import-command.js +156 -0
- package/dist/interrogate-command.d.ts +1 -1
- package/dist/interrogate-command.js +103 -1
- package/dist/visual-app/assets/{index-yCHtmQUH.js → index-C42oLqXj.js} +2 -2
- package/dist/visual-app/index.html +1 -1
- package/dist/visual-app-lib/editor.js +26880 -26790
- package/dist/visual-app-lib/types/interrogate-command.d.ts +1 -1
- package/dist/workbook-entry.d.ts +16 -0
- package/dist/workbook-entry.js +16 -0
- package/dist/workbook-merge.d.ts +77 -0
- package/dist/workbook-merge.js +108 -0
- package/dist/workbook-operations.d.ts +9 -0
- package/dist/workbook-operations.js +139 -0
- package/dist/workbook-read.d.ts +43 -0
- package/dist/workbook-read.js +314 -0
- package/dist/workbook-xlsx.d.ts +54 -0
- package/dist/workbook-xlsx.js +228 -0
- package/dist/workbook.d.ts +32 -0
- package/dist/workbook.js +293 -0
- package/docs/CONSUMING-YARRAMATE.md +64 -0
- package/package.json +5 -1
|
@@ -501,8 +501,9 @@ export const startVisualServer = async (options) => {
|
|
|
501
501
|
* session instead of serving a stale graph.
|
|
502
502
|
*/
|
|
503
503
|
const workspaceSources = () => {
|
|
504
|
+
let reading = "";
|
|
504
505
|
try {
|
|
505
|
-
|
|
506
|
+
const paths = [
|
|
506
507
|
...resolvedWorkspace.profiles,
|
|
507
508
|
// Patterns are compiler input like profiles (#268). Without them a
|
|
508
509
|
// session recompiles a workspace the manifest does not describe: an
|
|
@@ -512,18 +513,46 @@ export const startVisualServer = async (options) => {
|
|
|
512
513
|
// rather than as a compile that failed.
|
|
513
514
|
...resolvedWorkspace.patterns,
|
|
514
515
|
...resolvedWorkspace.documents,
|
|
515
|
-
]
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
516
|
+
];
|
|
517
|
+
return {
|
|
518
|
+
ok: true,
|
|
519
|
+
sources: paths.map((path) => {
|
|
520
|
+
reading = path;
|
|
521
|
+
return {
|
|
522
|
+
path,
|
|
523
|
+
source: readFileSync(resolve(options.cwd, path), "utf8"),
|
|
524
|
+
};
|
|
525
|
+
}),
|
|
526
|
+
};
|
|
519
527
|
}
|
|
520
|
-
catch {
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
|
|
528
|
+
catch (cause) {
|
|
529
|
+
// Never an empty list. Compiling one SUCCEEDS - `compileWorkspaceResolved`
|
|
530
|
+
// over no sources returns an empty graph, not a failure - so swallowing
|
|
531
|
+
// the read error here made an unreadable workspace indistinguishable
|
|
532
|
+
// from an empty one, and the session then served "your model has nothing
|
|
533
|
+
// in it" as though it were an answer (#349). The older comment defended
|
|
534
|
+
// the empty list as belonging to a path "only trying to add detail";
|
|
535
|
+
// this feeds the single compile that produces what the browser draws.
|
|
536
|
+
return {
|
|
537
|
+
ok: false,
|
|
538
|
+
path: reading,
|
|
539
|
+
reason: cause instanceof Error ? cause.message : String(cause),
|
|
540
|
+
};
|
|
525
541
|
}
|
|
526
542
|
};
|
|
543
|
+
/**
|
|
544
|
+
* The sources, for attaching subjects to a diagnostic that already exists.
|
|
545
|
+
*
|
|
546
|
+
* Here an unreadable source really is nothing worth reporting: the caller is
|
|
547
|
+
* decorating a refusal it has already decided on, so a missing file costs a
|
|
548
|
+
* subject marker and nothing else. That was the whole of the old
|
|
549
|
+
* `workspaceSources` contract, and it stayed correct for exactly these two
|
|
550
|
+
* callers while being wrong for the compile (#349).
|
|
551
|
+
*/
|
|
552
|
+
const sourcesForDetail = () => {
|
|
553
|
+
const read = workspaceSources();
|
|
554
|
+
return read.ok ? read.sources : [];
|
|
555
|
+
};
|
|
527
556
|
/**
|
|
528
557
|
* The digest of every projection the session knows about, read now.
|
|
529
558
|
*
|
|
@@ -549,11 +578,22 @@ export const startVisualServer = async (options) => {
|
|
|
549
578
|
};
|
|
550
579
|
const recompileWorkspace = () => {
|
|
551
580
|
try {
|
|
552
|
-
const
|
|
581
|
+
const read = workspaceSources();
|
|
582
|
+
if (!read.ok) {
|
|
583
|
+
return {
|
|
584
|
+
ok: false,
|
|
585
|
+
diagnostics: [
|
|
586
|
+
serverDiagnostic("YMVS319", `Workspace source ${read.path} cannot be read, so the workspace was not recompiled; the last good model stays as it is: ${read.reason}`),
|
|
587
|
+
],
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const sources = read.sources;
|
|
553
591
|
const compiled = compileWorkspaceWithProfileContext(sources);
|
|
554
592
|
if (!compiled.ok) {
|
|
555
|
-
|
|
556
|
-
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
diagnostics: published(compiled.diagnostics, sources),
|
|
596
|
+
};
|
|
557
597
|
}
|
|
558
598
|
compiledWorkspace = {
|
|
559
599
|
graph: compiled.graph,
|
|
@@ -577,14 +617,36 @@ export const startVisualServer = async (options) => {
|
|
|
577
617
|
// replacing the identity the session started with.
|
|
578
618
|
views.splice(0, views.length, ...workspaceModel.views);
|
|
579
619
|
rendered = workspaceModel.model;
|
|
580
|
-
|
|
620
|
+
// Recovered: a browser connecting now is not handed a fault that has
|
|
621
|
+
// been fixed since. The connected ones clear it on the `model` frame
|
|
622
|
+
// this success is about to broadcast.
|
|
623
|
+
standingDiagnostics = [];
|
|
624
|
+
return { ok: true };
|
|
581
625
|
}
|
|
582
|
-
catch {
|
|
583
|
-
|
|
584
|
-
|
|
626
|
+
catch (cause) {
|
|
627
|
+
return {
|
|
628
|
+
ok: false,
|
|
629
|
+
diagnostics: [
|
|
630
|
+
serverDiagnostic("YMVS319", `Workspace could not be recompiled, so the last good model stays as it is: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
631
|
+
],
|
|
632
|
+
};
|
|
585
633
|
}
|
|
586
634
|
};
|
|
587
|
-
|
|
635
|
+
/**
|
|
636
|
+
* What the last recompile could not do, held for a browser that is not
|
|
637
|
+
* connected yet.
|
|
638
|
+
*
|
|
639
|
+
* The startup recompile runs before any socket exists, so a failure there
|
|
640
|
+
* has nobody to tell. Holding it means the first browser to arrive is told
|
|
641
|
+
* what every later one would have heard, rather than opening onto a session
|
|
642
|
+
* that looks well and answers nothing.
|
|
643
|
+
*/
|
|
644
|
+
let standingDiagnostics = [];
|
|
645
|
+
{
|
|
646
|
+
const started = recompileWorkspace();
|
|
647
|
+
if (!started.ok)
|
|
648
|
+
standingDiagnostics = started.diagnostics;
|
|
649
|
+
}
|
|
588
650
|
const filterMatchedIds = (query) => compiledWorkspace === undefined
|
|
589
651
|
? []
|
|
590
652
|
: matchedIdsOf(compiledWorkspace.graph, query, compiledWorkspace.profileContext);
|
|
@@ -851,6 +913,36 @@ export const startVisualServer = async (options) => {
|
|
|
851
913
|
for (const socket of connections.values())
|
|
852
914
|
sendFrame(socket, frame);
|
|
853
915
|
};
|
|
916
|
+
/**
|
|
917
|
+
* Tells the browser the workspace no longer compiles, and what said so.
|
|
918
|
+
*
|
|
919
|
+
* Journalled as an ordinary `diagnostic` response rather than a new frame
|
|
920
|
+
* kind, because that is the one the browser already renders - `Faults`
|
|
921
|
+
* reads `state.diagnostics`, which a `model` frame clears, so a recovered
|
|
922
|
+
* recompile clears the banner without anyone clearing it (#349). Held in
|
|
923
|
+
* `standingDiagnostics` too, for the browser that has not connected yet.
|
|
924
|
+
*/
|
|
925
|
+
const reportRecompileFailure = async (diagnostics, eventId = drawHex(16)) => {
|
|
926
|
+
standingDiagnostics = diagnostics;
|
|
927
|
+
const response = {
|
|
928
|
+
format: "yarramate/visual-response/v1",
|
|
929
|
+
sessionId,
|
|
930
|
+
responseId: drawHex(16),
|
|
931
|
+
eventId,
|
|
932
|
+
type: "diagnostic",
|
|
933
|
+
timestamp: stamp(),
|
|
934
|
+
payload: { diagnostics },
|
|
935
|
+
};
|
|
936
|
+
const appended = await appendVisualResponse(paths, response);
|
|
937
|
+
if (appended.ok) {
|
|
938
|
+
transcriptBytes = appended.transcriptBytes;
|
|
939
|
+
recordResponse(response);
|
|
940
|
+
broadcast({ kind: "response", response });
|
|
941
|
+
}
|
|
942
|
+
else if (appended.freeze !== undefined) {
|
|
943
|
+
freeze(appended.freeze);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
854
946
|
const idleDelivery = () => ({
|
|
855
947
|
waiting: true,
|
|
856
948
|
lastSequence,
|
|
@@ -1172,8 +1264,17 @@ export const startVisualServer = async (options) => {
|
|
|
1172
1264
|
kind: "apply-result",
|
|
1173
1265
|
result: { ok: false, diagnostics: refused },
|
|
1174
1266
|
});
|
|
1175
|
-
|
|
1267
|
+
const refreshed = recompileWorkspace();
|
|
1268
|
+
if (refreshed.ok) {
|
|
1176
1269
|
broadcast({ kind: "model", model: rendered, views });
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
// Nothing at all reached the browser here before (#349): a refused
|
|
1273
|
+
// apply whose refresh also failed left the reviewer with a
|
|
1274
|
+
// refusal about their rows and no word that the workspace itself
|
|
1275
|
+
// had stopped compiling.
|
|
1276
|
+
await reportRecompileFailure(refreshed.diagnostics);
|
|
1277
|
+
}
|
|
1177
1278
|
return;
|
|
1178
1279
|
}
|
|
1179
1280
|
const operationsSource = stringify({
|
|
@@ -1193,7 +1294,7 @@ export const startVisualServer = async (options) => {
|
|
|
1193
1294
|
kind: "apply-result",
|
|
1194
1295
|
result: {
|
|
1195
1296
|
ok: false,
|
|
1196
|
-
diagnostics: published(loadedWorkspace.diagnostics,
|
|
1297
|
+
diagnostics: published(loadedWorkspace.diagnostics, sourcesForDetail()),
|
|
1197
1298
|
},
|
|
1198
1299
|
});
|
|
1199
1300
|
return;
|
|
@@ -1232,7 +1333,7 @@ export const startVisualServer = async (options) => {
|
|
|
1232
1333
|
kind: "apply-result",
|
|
1233
1334
|
result: {
|
|
1234
1335
|
ok: false,
|
|
1235
|
-
diagnostics: published(outcome.diagnostics,
|
|
1336
|
+
diagnostics: published(outcome.diagnostics, sourcesForDetail()),
|
|
1236
1337
|
},
|
|
1237
1338
|
});
|
|
1238
1339
|
return;
|
|
@@ -1249,7 +1350,8 @@ export const startVisualServer = async (options) => {
|
|
|
1249
1350
|
kind: "apply-result",
|
|
1250
1351
|
result: { ok: true, result: outcome.result },
|
|
1251
1352
|
});
|
|
1252
|
-
|
|
1353
|
+
const recompiled = recompileWorkspace();
|
|
1354
|
+
if (recompiled.ok) {
|
|
1253
1355
|
broadcast({ kind: "model", model: rendered, views });
|
|
1254
1356
|
return;
|
|
1255
1357
|
}
|
|
@@ -1257,29 +1359,13 @@ export const startVisualServer = async (options) => {
|
|
|
1257
1359
|
// landed but produced a document Core itself can no longer parse.
|
|
1258
1360
|
// The freeze alone only tells the browser input is refused, not why
|
|
1259
1361
|
// the graph on screen has gone stale, so a diagnostic response is
|
|
1260
|
-
// journaled alongside it
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
timestamp: stamp(),
|
|
1268
|
-
payload: {
|
|
1269
|
-
diagnostics: [
|
|
1270
|
-
serverDiagnostic("YMVS310", "Workspace failed to recompile after a landed changeset"),
|
|
1271
|
-
],
|
|
1272
|
-
},
|
|
1273
|
-
};
|
|
1274
|
-
const appendedResponse = await appendVisualResponse(paths, diagnosticResponse);
|
|
1275
|
-
if (appendedResponse.ok) {
|
|
1276
|
-
transcriptBytes = appendedResponse.transcriptBytes;
|
|
1277
|
-
recordResponse(diagnosticResponse);
|
|
1278
|
-
broadcast({ kind: "response", response: diagnosticResponse });
|
|
1279
|
-
}
|
|
1280
|
-
else if (appendedResponse.freeze !== undefined) {
|
|
1281
|
-
freeze(appendedResponse.freeze);
|
|
1282
|
-
}
|
|
1362
|
+
// journaled alongside it - and it carries the compiler's OWN
|
|
1363
|
+
// diagnostics, because YMVS310 by itself names no document, no code
|
|
1364
|
+
// and no line (#349).
|
|
1365
|
+
await reportRecompileFailure([
|
|
1366
|
+
serverDiagnostic("YMVS310", "Workspace failed to recompile after a landed changeset"),
|
|
1367
|
+
...recompiled.diagnostics,
|
|
1368
|
+
], event.eventId);
|
|
1283
1369
|
freeze("recompile-failed");
|
|
1284
1370
|
return;
|
|
1285
1371
|
}
|
|
@@ -1431,6 +1517,22 @@ export const startVisualServer = async (options) => {
|
|
|
1431
1517
|
return;
|
|
1432
1518
|
}
|
|
1433
1519
|
sendFrame(socket, { kind: "ready", snapshot: snapshot() });
|
|
1520
|
+
// A recompile that failed before this socket existed - the one at
|
|
1521
|
+
// startup, most often - has had nobody to tell until now (#349).
|
|
1522
|
+
if (standingDiagnostics.length > 0) {
|
|
1523
|
+
sendFrame(socket, {
|
|
1524
|
+
kind: "response",
|
|
1525
|
+
response: {
|
|
1526
|
+
format: "yarramate/visual-response/v1",
|
|
1527
|
+
sessionId,
|
|
1528
|
+
responseId: drawHex(16),
|
|
1529
|
+
eventId: drawHex(16),
|
|
1530
|
+
type: "diagnostic",
|
|
1531
|
+
timestamp: stamp(),
|
|
1532
|
+
payload: { diagnostics: standingDiagnostics },
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1434
1536
|
});
|
|
1435
1537
|
};
|
|
1436
1538
|
// ------------------------------------------------------------- agent routes
|
package/dist/ask-command.js
CHANGED
|
@@ -471,7 +471,7 @@ export function runAskCommand(options, cwd) {
|
|
|
471
471
|
const loadedCatalogue = loadQuestionCatalogue({
|
|
472
472
|
path: shippedCataloguePath,
|
|
473
473
|
source: readFileSync(shippedCataloguePath, 'utf8'),
|
|
474
|
-
});
|
|
474
|
+
}, compilation.profileContext);
|
|
475
475
|
if (!loadedCatalogue.ok)
|
|
476
476
|
return failed(loadedCatalogue.diagnostics);
|
|
477
477
|
const report = evaluateCatalogue(loadedCatalogue.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations));
|
|
@@ -701,7 +701,7 @@ export function runAskCommand(options, cwd) {
|
|
|
701
701
|
const loadedCatalogue = loadQuestionCatalogue({
|
|
702
702
|
path: cataloguePath ?? resolvedCataloguePath,
|
|
703
703
|
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
704
|
-
});
|
|
704
|
+
}, compilation.profileContext);
|
|
705
705
|
if (!loadedCatalogue.ok)
|
|
706
706
|
return failed(loadedCatalogue.diagnostics);
|
|
707
707
|
// The evidence overlay rides along for the one condition that
|
|
@@ -996,7 +996,7 @@ export function runAskCommand(options, cwd) {
|
|
|
996
996
|
const loadedCatalogue = loadQuestionCatalogue({
|
|
997
997
|
path: cataloguePath ?? resolvedCataloguePath,
|
|
998
998
|
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
999
|
-
});
|
|
999
|
+
}, compilation.profileContext);
|
|
1000
1000
|
if (!loadedCatalogue.ok)
|
|
1001
1001
|
return failed(loadedCatalogue.diagnostics);
|
|
1002
1002
|
// Loaded ahead of evaluation so the overlay feeds the one condition
|
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 <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
|
|
10
|
+
export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where \"<free text>\" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
|
|
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;
|
package/dist/cli-support.js
CHANGED
|
@@ -22,7 +22,7 @@ export const versionResult = (binary) => ({
|
|
|
22
22
|
stdout: `${binary} ${packageVersion}\n`,
|
|
23
23
|
stderr: '',
|
|
24
24
|
});
|
|
25
|
-
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
|
|
25
|
+
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <subject-id>] [--catalogue <catalogue.yaml>] [--facilitate] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <subject-id> ... | <projection.yaml> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--neighbours <n>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --where "<free text>" | <subject-id> ... [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--neighbours <n>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml> [--json]\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export rtm <workspace.yaml> --out <directory>\n yarramate export xlsx <projection.yaml> <workspace.yaml> --out <file>\n yarramate import xlsx <workbook.xlsx> <workspace.yaml> [--json]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
|
|
26
26
|
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
27
27
|
format: 'yarramate/diagnostic-result/v1',
|
|
28
28
|
diagnostics,
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,3 +3,13 @@ import { type CliResult } from './cli-support.js';
|
|
|
3
3
|
export type { CliResult } from './cli-support.js';
|
|
4
4
|
export declare const deriveInitId: (directory: string) => string;
|
|
5
5
|
export declare function runCli(args: readonly string[], cwd?: string): CliResult;
|
|
6
|
+
/**
|
|
7
|
+
* Every verb, including the one that cannot be synchronous.
|
|
8
|
+
*
|
|
9
|
+
* `import xlsx` has to inflate a workbook, and the only inflater available
|
|
10
|
+
* everywhere this runs is `DecompressionStream`, which is async. Widening
|
|
11
|
+
* `runCli` to return a promise would change the type every one of its callers
|
|
12
|
+
* reads - the readers half of the rule in CONTRIBUTING.md - so the async verb
|
|
13
|
+
* gets its own entry and `runCli` keeps its signature.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runCliAsync(args: readonly string[], cwd?: string): Promise<CliResult>;
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { compileWorkspace } from './compiler.js';
|
|
|
5
5
|
import { diagnosticJson, isMainModule, usage, versionResult, } from './cli-support.js';
|
|
6
6
|
import { runAskCommand } from './ask-command.js';
|
|
7
7
|
import { runCheckCommand } from './check-command.js';
|
|
8
|
+
import { runImportCommand } from './import-command.js';
|
|
8
9
|
import { runExportCommand } from './export-command.js';
|
|
9
10
|
import { runApplyCommand } from './apply-cli.js';
|
|
10
11
|
import { runDesignCommand } from './design-command.js';
|
|
@@ -227,8 +228,23 @@ export function runCli(args, cwd = process.cwd()) {
|
|
|
227
228
|
}
|
|
228
229
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
229
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Every verb, including the one that cannot be synchronous.
|
|
233
|
+
*
|
|
234
|
+
* `import xlsx` has to inflate a workbook, and the only inflater available
|
|
235
|
+
* everywhere this runs is `DecompressionStream`, which is async. Widening
|
|
236
|
+
* `runCli` to return a promise would change the type every one of its callers
|
|
237
|
+
* reads - the readers half of the rule in CONTRIBUTING.md - so the async verb
|
|
238
|
+
* gets its own entry and `runCli` keeps its signature.
|
|
239
|
+
*/
|
|
240
|
+
export async function runCliAsync(args, cwd = process.cwd()) {
|
|
241
|
+
const [command, ...options] = args;
|
|
242
|
+
if (command === 'import')
|
|
243
|
+
return runImportCommand(options, cwd);
|
|
244
|
+
return runCli(args, cwd);
|
|
245
|
+
}
|
|
230
246
|
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
231
|
-
const result =
|
|
247
|
+
const result = await runCliAsync(process.argv.slice(2));
|
|
232
248
|
process.stdout.write(result.stdout);
|
|
233
249
|
process.stderr.write(result.stderr);
|
|
234
250
|
process.exitCode = result.exitCode;
|
package/dist/design-command.js
CHANGED
|
@@ -191,12 +191,10 @@ export function runDesignCommand(options, cwd) {
|
|
|
191
191
|
const resolvedCataloguePath = cataloguePath === undefined
|
|
192
192
|
? shippedCataloguePath
|
|
193
193
|
: resolve(cwd, cataloguePath);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (!loadedCatalogue.ok)
|
|
199
|
-
return failed(loadedCatalogue.diagnostics);
|
|
194
|
+
// Compiled BEFORE the catalogue loads, so the catalogue can be checked
|
|
195
|
+
// against the vocabulary its kinds are written against (#351). A
|
|
196
|
+
// catalogue naming a kind its own profile does not have loads clean
|
|
197
|
+
// otherwise, and the question it names is dead on arrival.
|
|
200
198
|
const compilation = compileWorkspaceWithProfileContext([
|
|
201
199
|
...workspace.profiles,
|
|
202
200
|
...workspace.patterns,
|
|
@@ -207,6 +205,12 @@ export function runDesignCommand(options, cwd) {
|
|
|
207
205
|
})));
|
|
208
206
|
if (!compilation.ok)
|
|
209
207
|
return failed(compilation.diagnostics);
|
|
208
|
+
const loadedCatalogue = loadQuestionCatalogue({
|
|
209
|
+
path: cataloguePath ?? resolvedCataloguePath,
|
|
210
|
+
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
211
|
+
}, compilation.profileContext);
|
|
212
|
+
if (!loadedCatalogue.ok)
|
|
213
|
+
return failed(loadedCatalogue.diagnostics);
|
|
210
214
|
// The evidence overlay rides along for the one condition that reads
|
|
211
215
|
// it (unchallenged-evidence). A workspace declaring no evidence
|
|
212
216
|
// passes an empty overlay — known to be empty, which keeps that
|
package/dist/export-command.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { parseDocument } from 'yaml';
|
|
6
7
|
import { renderBrief } from './brief.js';
|
|
7
8
|
import { deriveChangedSubjects } from './changed.js';
|
|
8
|
-
import { humanDiagnostics, usage } from './cli-support.js';
|
|
9
|
+
import { humanDiagnostics, packageVersion, usage, } from './cli-support.js';
|
|
9
10
|
import { compileWorkspaceWithProfileContext, } from './compiler.js';
|
|
10
11
|
import { serializeSemanticGraph } from './graph.js';
|
|
11
12
|
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
12
13
|
import { evaluateProjection, loadProjection, renderProjectionMarkdown, } from './projection.js';
|
|
13
14
|
import { buildRtm, renderRtmMarkdown } from './rtm.js';
|
|
15
|
+
import { workbookFrom } from './workbook.js';
|
|
14
16
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
15
17
|
// The adapter stays a separate process behind the verb: the core never
|
|
16
18
|
// imports adapter code (the adapter-runtime-dependency exclusion), it
|
|
@@ -74,7 +76,7 @@ const parseExportOptions = (options) => {
|
|
|
74
76
|
export function runExportCommand(options, cwd) {
|
|
75
77
|
const [kind, ...rest] = options;
|
|
76
78
|
if (kind === undefined ||
|
|
77
|
-
!['graph', 'markdown', 'briefs', 'rtm', 'likec4'].includes(kind)) {
|
|
79
|
+
!['graph', 'markdown', 'briefs', 'rtm', 'likec4', 'xlsx'].includes(kind)) {
|
|
78
80
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
79
81
|
}
|
|
80
82
|
const parsed = parseExportOptions(rest);
|
|
@@ -128,7 +130,8 @@ export function runExportCommand(options, cwd) {
|
|
|
128
130
|
parsed.json ||
|
|
129
131
|
(usesChanged && (kind === 'graph' || kind === 'rtm')) ||
|
|
130
132
|
(parsed.budget !== undefined && kind !== 'briefs') ||
|
|
131
|
-
((kind === 'briefs' || kind === 'rtm'
|
|
133
|
+
((kind === 'briefs' || kind === 'rtm' || kind === 'xlsx') &&
|
|
134
|
+
parsed.out === undefined)) {
|
|
132
135
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
133
136
|
}
|
|
134
137
|
try {
|
|
@@ -149,14 +152,17 @@ export function runExportCommand(options, cwd) {
|
|
|
149
152
|
if (!loadedWorkspace.ok)
|
|
150
153
|
return failed(loadedWorkspace.diagnostics);
|
|
151
154
|
const workspace = loadedWorkspace.workspace;
|
|
152
|
-
|
|
155
|
+
// Named rather than inlined so the workbook can pin its digests against
|
|
156
|
+
// exactly the bytes that compiled, the way a visual commit does (#355).
|
|
157
|
+
const sources = [
|
|
153
158
|
...workspace.profiles,
|
|
154
159
|
...workspace.patterns,
|
|
155
160
|
...workspace.documents,
|
|
156
161
|
].map((path) => ({
|
|
157
162
|
path,
|
|
158
163
|
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
159
|
-
}))
|
|
164
|
+
}));
|
|
165
|
+
const compilation = compileWorkspaceWithProfileContext(sources);
|
|
160
166
|
if (!compilation.ok)
|
|
161
167
|
return failed(compilation.diagnostics);
|
|
162
168
|
if (kind === 'rtm') {
|
|
@@ -246,6 +252,36 @@ export function runExportCommand(options, cwd) {
|
|
|
246
252
|
return failed(loadedProjection.diagnostics);
|
|
247
253
|
result = evaluateProjection(compilation.graph, loadedProjection.projection, compilation.profileContext);
|
|
248
254
|
}
|
|
255
|
+
if (kind === 'xlsx') {
|
|
256
|
+
// A workbook an architect can work in (#355). It takes a PROJECTION,
|
|
257
|
+
// like markdown and briefs do, which is what gives it version selection
|
|
258
|
+
// for free: a projection query already has a `states` facet, so
|
|
259
|
+
// "export the target state" is an existing capability rather than a
|
|
260
|
+
// flag competing with it.
|
|
261
|
+
const bytes = workbookFrom(result, {
|
|
262
|
+
workspace: workspace.id,
|
|
263
|
+
yarramateVersion: packageVersion,
|
|
264
|
+
sourceDigests: Object.fromEntries(sources.map(({ path, source }) => [
|
|
265
|
+
path,
|
|
266
|
+
createHash('sha256').update(source, 'utf8').digest('hex'),
|
|
267
|
+
])),
|
|
268
|
+
conceptKinds: [
|
|
269
|
+
...compilation.profileContext.conceptKindLineages.keys(),
|
|
270
|
+
].sort(),
|
|
271
|
+
relationshipKinds: [
|
|
272
|
+
...compilation.profileContext.relationshipKindLineages.keys(),
|
|
273
|
+
].sort(),
|
|
274
|
+
statuses: ['planned', 'current', 'retired'],
|
|
275
|
+
});
|
|
276
|
+
const outPath = resolve(cwd, parsed.out);
|
|
277
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
278
|
+
writeFileSync(outPath, bytes);
|
|
279
|
+
return {
|
|
280
|
+
exitCode: 0,
|
|
281
|
+
stdout: `Wrote workbook to ${parsed.out}\n`,
|
|
282
|
+
stderr: '',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
249
285
|
if (kind === 'markdown') {
|
|
250
286
|
const rendered = renderProjectionMarkdown(result, compilation.profileContext);
|
|
251
287
|
if (parsed.out === undefined) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CliResult } from './cli-support.js';
|
|
2
|
+
/**
|
|
3
|
+
* `yarramate import xlsx <file> <workspace.yaml>` (#355, ADR 0127).
|
|
4
|
+
*
|
|
5
|
+
* The workbook carries its own ancestor, so this is a three-way merge rather
|
|
6
|
+
* than an overwrite: the author's edits are measured against `~Baseline`, the
|
|
7
|
+
* repository's drift is measured against the same, and only a field both moved
|
|
8
|
+
* is refused. Everything the author changed that the repository left alone
|
|
9
|
+
* merges cleanly, which is what makes a week-long workbook cycle usable.
|
|
10
|
+
*
|
|
11
|
+
* Edits land as `yarramate/operations/v1` through `apply`, so untouched YAML
|
|
12
|
+
* keeps its comments, key order and formatting, and the whole import passes
|
|
13
|
+
* the atomic compile gate. A workbook that would produce an uncompilable model
|
|
14
|
+
* is refused whole rather than half written.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runImportCommand(options: readonly string[], cwd: string): Promise<CliResult>;
|