yarramate 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/visual/session-server.js +27 -1
- package/dist/adapters/visual/workspace-model.d.ts +16 -3
- package/dist/adapters/visual/workspace-model.js +12 -5
- package/dist/ask-command.js +25 -16
- package/dist/catalogue-sources.d.ts +20 -0
- package/dist/catalogue-sources.js +39 -0
- package/dist/check-command.js +38 -9
- package/dist/cli-support.d.ts +8 -1
- package/dist/cli-support.js +5 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +17 -1
- package/dist/design-command.js +21 -9
- package/dist/export-command.js +41 -5
- package/dist/import-command.d.ts +16 -0
- package/dist/import-command.js +156 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/interrogate-command.d.ts +82 -1
- package/dist/interrogate-command.js +195 -29
- package/dist/interrogation-entry.d.ts +1 -1
- package/dist/interrogation-entry.js +1 -1
- package/dist/projection.d.ts +47 -0
- package/dist/projection.js +126 -0
- package/dist/visual-app-lib/editor.js +26771 -26713
- package/dist/visual-app-lib/types/adapters/visual/workspace-model.d.ts +16 -3
- package/dist/visual-app-lib/types/interrogate-command.d.ts +82 -1
- package/dist/visual-app-lib/types/projection.d.ts +47 -0
- package/dist/visual-app-lib/types/workspace.d.ts +15 -0
- package/dist/workbook-entry.d.ts +16 -0
- package/dist/workbook-entry.js +16 -0
- package/dist/workbook-import-entry.d.ts +30 -0
- package/dist/workbook-import-entry.js +30 -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 +308 -0
- package/dist/workspace.d.ts +15 -0
- package/dist/workspace.js +1 -0
- package/docs/CONSUMING-YARRAMATE.md +127 -0
- package/package.json +9 -1
- package/schema/yarramate-interrogation-report.schema.json +9 -0
- package/schema/yarramate-question-catalogue.schema.json +2 -3
- package/schema/yarramate-workspace.schema.json +4 -0
|
@@ -124,6 +124,28 @@ const shippedCatalogue = (() => {
|
|
|
124
124
|
return undefined;
|
|
125
125
|
}
|
|
126
126
|
})();
|
|
127
|
+
/**
|
|
128
|
+
* The shipped catalogue plus the ones the workspace carries (#345, ADR 0129).
|
|
129
|
+
*
|
|
130
|
+
* A catalogue that cannot be read is SKIPPED rather than failing the session,
|
|
131
|
+
* which is what the shipped one already did: an interrogation overlay is a
|
|
132
|
+
* garnish on a model, and losing a session over it would be worse than losing
|
|
133
|
+
* the questions. `check` is where a broken catalogue is refused.
|
|
134
|
+
*/
|
|
135
|
+
const catalogueSetFor = (workspace) => {
|
|
136
|
+
if (shippedCatalogue === undefined)
|
|
137
|
+
return [];
|
|
138
|
+
const carried = [];
|
|
139
|
+
for (const path of workspace.questions ?? []) {
|
|
140
|
+
try {
|
|
141
|
+
carried.push({ path, source: readFileSync(path, "utf8") });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Skipped, as above.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return [shippedCatalogue, ...carried];
|
|
148
|
+
};
|
|
127
149
|
/**
|
|
128
150
|
* Takes one session terminal, whatever caused it: a reviewer's End, a child
|
|
129
151
|
* that failed, a browser that never came back, a cancelling main agent, or a
|
|
@@ -612,7 +634,11 @@ export const startVisualServer = async (options) => {
|
|
|
612
634
|
// that. A staged view operation pins against these (ADR 0103), and a
|
|
613
635
|
// projection missing from the map is one the commit will create.
|
|
614
636
|
projectionDigests: projectionDigestsNow(),
|
|
615
|
-
|
|
637
|
+
// The shipped catalogue plus whatever this workspace carries (#345),
|
|
638
|
+
// so the pane asks the interview `design` asks over the same files.
|
|
639
|
+
// Read on each recompile rather than once per process, because a
|
|
640
|
+
// consultant authoring a question mid-session is the whole point.
|
|
641
|
+
}, catalogueSetFor(resolvedWorkspace));
|
|
616
642
|
// Closures below retain this array, so refresh its contents without
|
|
617
643
|
// replacing the identity the session started with.
|
|
618
644
|
views.splice(0, views.length, ...workspaceModel.views);
|
|
@@ -57,10 +57,20 @@ export interface DismissedQuestion {
|
|
|
57
57
|
export declare const interrogationOverlayOf: (compiled: {
|
|
58
58
|
readonly graph: SemanticGraph;
|
|
59
59
|
readonly profileContext: ResolvedProfileContext;
|
|
60
|
-
},
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
63
|
+
* A set rather than one document so the pane asks the same interview the
|
|
64
|
+
* CLI does over the same workspace: an editor showing fewer questions than
|
|
65
|
+
* `design` does over the same files is a disagreement with no symptom.
|
|
66
|
+
*/
|
|
67
|
+
catalogue: {
|
|
61
68
|
readonly path: string;
|
|
62
69
|
readonly source: string;
|
|
63
|
-
}
|
|
70
|
+
} | readonly {
|
|
71
|
+
readonly path: string;
|
|
72
|
+
readonly source: string;
|
|
73
|
+
}[],
|
|
64
74
|
/**
|
|
65
75
|
* What the host has already dealt with (#328). Evaluation is unchanged and
|
|
66
76
|
* the model is untouched: this decides only what the pane draws, because a
|
|
@@ -83,7 +93,10 @@ export declare const renderedWorkspaceOf: (compiled: {
|
|
|
83
93
|
}, views: readonly VisualViewSummary[], metadata: Omit<VisualRenderedModel, "graph" | "vocabulary" | "interrogation">, catalogue?: {
|
|
84
94
|
readonly path: string;
|
|
85
95
|
readonly source: string;
|
|
86
|
-
}
|
|
96
|
+
} | readonly {
|
|
97
|
+
readonly path: string;
|
|
98
|
+
readonly source: string;
|
|
99
|
+
}[], dismissed?: readonly DismissedQuestion[]) => {
|
|
87
100
|
readonly model: VisualRenderedModel;
|
|
88
101
|
readonly views: readonly VisualViewSummary[];
|
|
89
102
|
};
|
|
@@ -6,7 +6,7 @@ import { projectGraphForCanvas } from "../../graph-projection.js";
|
|
|
6
6
|
import { DEFAULT_PROJECTION_DIRECTORY } from "./view-identity.js";
|
|
7
7
|
import { evaluateProjection, explainProjection } from "../../projection.js";
|
|
8
8
|
import { kindLabelOf } from "../../kind-label.js";
|
|
9
|
-
import { evaluateCatalogue,
|
|
9
|
+
import { evaluateCatalogue, composeCatalogues, } from "../../interrogate-command.js";
|
|
10
10
|
/**
|
|
11
11
|
* What a workspace looks like to the editor, however the editor is being run
|
|
12
12
|
* (#252).
|
|
@@ -46,7 +46,14 @@ export const kindOptionsOf = (lineages) => [...lineages.keys()].map((id) => ({
|
|
|
46
46
|
* them would read as five, and the reviewer counting boxes would find three.
|
|
47
47
|
*/
|
|
48
48
|
export const conceptCountOf = (graph, query, profileContext) => evaluateProjection(graph, adHoc(query), profileContext).subjects.filter(({ type }) => type === "concept").length;
|
|
49
|
-
export const interrogationOverlayOf = (compiled,
|
|
49
|
+
export const interrogationOverlayOf = (compiled,
|
|
50
|
+
/**
|
|
51
|
+
* The catalogue, or the composed SET a workspace carries (#345, ADR 0129).
|
|
52
|
+
* A set rather than one document so the pane asks the same interview the
|
|
53
|
+
* CLI does over the same workspace: an editor showing fewer questions than
|
|
54
|
+
* `design` does over the same files is a disagreement with no symptom.
|
|
55
|
+
*/
|
|
56
|
+
catalogue,
|
|
50
57
|
/**
|
|
51
58
|
* What the host has already dealt with (#328). Evaluation is unchanged and
|
|
52
59
|
* the model is untouched: this decides only what the pane draws, because a
|
|
@@ -54,10 +61,10 @@ export const interrogationOverlayOf = (compiled, catalogue,
|
|
|
54
61
|
* a pane embedded in it.
|
|
55
62
|
*/
|
|
56
63
|
dismissed = []) => {
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
64
|
+
const composed = composeCatalogues(Array.isArray(catalogue) ? catalogue : [catalogue]);
|
|
65
|
+
if (!composed.ok)
|
|
59
66
|
return undefined;
|
|
60
|
-
const report = evaluateCatalogue(
|
|
67
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compiled.graph, compiled.profileContext, undefined, composed.composed.catalogues);
|
|
61
68
|
const dismissedEverywhere = new Set(dismissed
|
|
62
69
|
.filter(({ subject }) => subject === undefined)
|
|
63
70
|
.map(({ questionId }) => questionId));
|
package/dist/ask-command.js
CHANGED
|
@@ -9,7 +9,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
11
|
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
12
|
-
import {
|
|
12
|
+
import { catalogueSources } from './catalogue-sources.js';
|
|
13
|
+
import { evaluateCatalogue, composeCatalogues, renderInterrogationReport, } from './interrogate-command.js';
|
|
13
14
|
import { buildNextSubjects, coverageClause, } from './next-command.js';
|
|
14
15
|
import { conceptKinds, relationshipPolicies, } from './profile.js';
|
|
15
16
|
import { ARCHIMATE_RELATIONSHIPS_VERSION, CORE_CONCEPT_KIND_ORDER, PERMITTED_RELATIONSHIP_LETTERS, RELATIONSHIP_LETTERS, } from './archimate-relationships.generated.js';
|
|
@@ -468,13 +469,13 @@ export function runAskCommand(options, cwd) {
|
|
|
468
469
|
const planned = buildNextSubjects(wholeWorkspace, compilation.graph, compilation.profileContext, evaluation.reports);
|
|
469
470
|
const current = entries.filter(({ status }) => status === 'current');
|
|
470
471
|
const retired = entries.filter(({ status }) => status === 'retired');
|
|
471
|
-
const
|
|
472
|
+
const composed = composeCatalogues(catalogueSources({
|
|
472
473
|
path: shippedCataloguePath,
|
|
473
474
|
source: readFileSync(shippedCataloguePath, 'utf8'),
|
|
474
|
-
}, compilation.profileContext);
|
|
475
|
-
if (!
|
|
476
|
-
return failed(
|
|
477
|
-
const report = evaluateCatalogue(
|
|
475
|
+
}, workspace, cwd), compilation.profileContext);
|
|
476
|
+
if (!composed.ok)
|
|
477
|
+
return failed(composed.diagnostics);
|
|
478
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues);
|
|
478
479
|
const result = {
|
|
479
480
|
format: 'yarramate/ask-result/v1',
|
|
480
481
|
workspace: workspace.id,
|
|
@@ -698,12 +699,12 @@ export function runAskCommand(options, cwd) {
|
|
|
698
699
|
const resolvedCataloguePath = cataloguePath === undefined
|
|
699
700
|
? shippedCataloguePath
|
|
700
701
|
: resolve(cwd, cataloguePath);
|
|
701
|
-
const
|
|
702
|
+
const composed = composeCatalogues(catalogueSources({
|
|
702
703
|
path: cataloguePath ?? resolvedCataloguePath,
|
|
703
704
|
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
704
|
-
}, compilation.profileContext);
|
|
705
|
-
if (!
|
|
706
|
-
return failed(
|
|
705
|
+
}, workspace, cwd), compilation.profileContext);
|
|
706
|
+
if (!composed.ok)
|
|
707
|
+
return failed(composed.diagnostics);
|
|
707
708
|
// The evidence overlay rides along for the one condition that
|
|
708
709
|
// reads it (unchallenged-evidence); a workspace declaring no
|
|
709
710
|
// evidence passes an overlay known to be empty.
|
|
@@ -718,13 +719,21 @@ export function runAskCommand(options, cwd) {
|
|
|
718
719
|
evidenceObservations.push(...loaded.evidence.observations);
|
|
719
720
|
}
|
|
720
721
|
const report = {
|
|
721
|
-
...evaluateCatalogue(
|
|
722
|
+
...evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues),
|
|
722
723
|
workspace: workspace.id,
|
|
723
724
|
};
|
|
725
|
+
// Field by field, to fix key ORDER in the emitted JSON. Every optional
|
|
726
|
+
// field has to be threaded through explicitly, which is why `catalogues`
|
|
727
|
+
// is here: a copier like this drops a new field silently and the only
|
|
728
|
+
// symptom is an absent one, which reads as "did not apply" rather than
|
|
729
|
+
// as "was lost".
|
|
724
730
|
const ordered = {
|
|
725
731
|
format: report.format,
|
|
726
732
|
workspace: report.workspace,
|
|
727
733
|
catalogue: report.catalogue,
|
|
734
|
+
...(report.catalogues === undefined
|
|
735
|
+
? {}
|
|
736
|
+
: { catalogues: report.catalogues }),
|
|
728
737
|
semantics: report.semantics,
|
|
729
738
|
summary: report.summary,
|
|
730
739
|
waves: report.waves,
|
|
@@ -993,12 +1002,12 @@ export function runAskCommand(options, cwd) {
|
|
|
993
1002
|
const resolvedCataloguePath = cataloguePath === undefined
|
|
994
1003
|
? shippedCataloguePath
|
|
995
1004
|
: resolve(cwd, cataloguePath);
|
|
996
|
-
const
|
|
1005
|
+
const composed = composeCatalogues(catalogueSources({
|
|
997
1006
|
path: cataloguePath ?? resolvedCataloguePath,
|
|
998
1007
|
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
999
|
-
}, compilation.profileContext);
|
|
1000
|
-
if (!
|
|
1001
|
-
return failed(
|
|
1008
|
+
}, workspace, cwd), compilation.profileContext);
|
|
1009
|
+
if (!composed.ok)
|
|
1010
|
+
return failed(composed.diagnostics);
|
|
1002
1011
|
// Loaded ahead of evaluation so the overlay feeds the one condition
|
|
1003
1012
|
// that reads it (unchallenged-evidence), then reused for the
|
|
1004
1013
|
// reconciliation summary below.
|
|
@@ -1012,7 +1021,7 @@ export function runAskCommand(options, cwd) {
|
|
|
1012
1021
|
return failed(loaded.diagnostics);
|
|
1013
1022
|
evidenceDocuments.push(loaded.evidence);
|
|
1014
1023
|
}
|
|
1015
|
-
const report = evaluateCatalogue(
|
|
1024
|
+
const report = evaluateCatalogue(composed.composed.catalogue, graph, compilation.profileContext, evidenceDocuments.flatMap(({ observations }) => observations), composed.composed.catalogues);
|
|
1016
1025
|
const openQuestions = [];
|
|
1017
1026
|
for (const wave of report.waves) {
|
|
1018
1027
|
for (const question of wave.questions) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WorkspaceSource } from './compiler.js';
|
|
2
|
+
import type { ResolvedWorkspace } from './workspace.js';
|
|
3
|
+
export declare const shippedCataloguePath: string;
|
|
4
|
+
/** The base catalogue as a source, read from disk. */
|
|
5
|
+
export declare const shippedCatalogueSource: () => WorkspaceSource;
|
|
6
|
+
/**
|
|
7
|
+
* The catalogues a verb should compose: the base, then whatever the workspace
|
|
8
|
+
* carries (#345, ADR 0129).
|
|
9
|
+
*
|
|
10
|
+
* Built in ONE place because every verb that interviews has to make the same
|
|
11
|
+
* choice, and a verb that forgot the workspace half would silently ask fewer
|
|
12
|
+
* questions than the workspace declares - a failure with no symptom, which is
|
|
13
|
+
* the shape this repository keeps being bitten by.
|
|
14
|
+
*
|
|
15
|
+
* The base is REPLACED by `--catalogue` and ADDED TO by `questions:`. Those
|
|
16
|
+
* are different powers on purpose: a host controls the catalogue that is not
|
|
17
|
+
* in the workspace (#328), and a consultant adds to it mid-engagement without
|
|
18
|
+
* a product release.
|
|
19
|
+
*/
|
|
20
|
+
export declare const catalogueSources: (base: WorkspaceSource, workspace: Pick<ResolvedWorkspace, 'questions'>, cwd: string) => readonly WorkspaceSource[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
// The catalogue ships inside the package, versioned with it, and harnesses
|
|
5
|
+
// never pass catalogue paths. The relative hop works from both src/ (dev) and
|
|
6
|
+
// dist/ (shipped). Defined ONCE: `design`, `ask` and `check` all need it, and
|
|
7
|
+
// three copies of a path constant is three chances for them to disagree about
|
|
8
|
+
// which catalogue is the base.
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
export const shippedCataloguePath = join(here, '..', 'catalogues', 'core-enrichment.yaml');
|
|
11
|
+
/** The base catalogue as a source, read from disk. */
|
|
12
|
+
export const shippedCatalogueSource = () => ({
|
|
13
|
+
path: shippedCataloguePath,
|
|
14
|
+
source: readFileSync(shippedCataloguePath, 'utf8'),
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* The catalogues a verb should compose: the base, then whatever the workspace
|
|
18
|
+
* carries (#345, ADR 0129).
|
|
19
|
+
*
|
|
20
|
+
* Built in ONE place because every verb that interviews has to make the same
|
|
21
|
+
* choice, and a verb that forgot the workspace half would silently ask fewer
|
|
22
|
+
* questions than the workspace declares - a failure with no symptom, which is
|
|
23
|
+
* the shape this repository keeps being bitten by.
|
|
24
|
+
*
|
|
25
|
+
* The base is REPLACED by `--catalogue` and ADDED TO by `questions:`. Those
|
|
26
|
+
* are different powers on purpose: a host controls the catalogue that is not
|
|
27
|
+
* in the workspace (#328), and a consultant adds to it mid-engagement without
|
|
28
|
+
* a product release.
|
|
29
|
+
*/
|
|
30
|
+
export const catalogueSources = (base, workspace, cwd) => [
|
|
31
|
+
base,
|
|
32
|
+
// `?? []` because `questions` is optional on the published type: adding a
|
|
33
|
+
// required field to `ResolvedWorkspace` broke a consumer's production module
|
|
34
|
+
// once already.
|
|
35
|
+
...(workspace.questions ?? []).map((path) => ({
|
|
36
|
+
path,
|
|
37
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
38
|
+
})),
|
|
39
|
+
];
|
package/dist/check-command.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { catalogueSources, shippedCatalogueSource, } from './catalogue-sources.js';
|
|
3
|
+
import { composeCatalogues } from './interrogate-command.js';
|
|
2
4
|
import { resolve } from 'node:path';
|
|
3
5
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
4
6
|
import { parseDocument } from 'yaml';
|
|
5
7
|
import { loadAdapterMapping, validateAdapterMappings, } from './adapter-mapping.js';
|
|
6
8
|
import { checkResultJson, humanDiagnostics, resolveCliWorkspaceSources, sortDiagnostics, usage, } from './cli-support.js';
|
|
7
|
-
import {
|
|
9
|
+
import { compileWorkspaceWithProfileContext, withDiagnosticSubjects, } from './compiler.js';
|
|
8
10
|
import { checkCoreContract, loadCoreContract, } from './core-contract.js';
|
|
9
11
|
import { evaluateEvidenceWorkspace, loadEvidence, } from './evidence.js';
|
|
10
|
-
import { loadProjection } from './projection.js';
|
|
12
|
+
import { loadProjection, projectionReferenceDiagnostics, } from './projection.js';
|
|
11
13
|
import { reconcileEvidenceReports, } from './reconciliation.js';
|
|
12
14
|
// `.default ?? module`, not a bare `.default`: NodeNext sees the raw CJS
|
|
13
15
|
// `module.exports` and a bundler the unwrapped class. One shape for all of
|
|
@@ -138,13 +140,15 @@ export function runCheckCommand(options, cwd) {
|
|
|
138
140
|
: humanDiagnostics(contractDiagnostics);
|
|
139
141
|
return { exitCode: 1, stdout: output, stderr: '' };
|
|
140
142
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
const projectionSources = resolved.projections.map((path) => ({
|
|
144
|
+
path,
|
|
145
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
146
|
+
}));
|
|
147
|
+
const loadedProjections = projectionSources.map((source) => ({
|
|
148
|
+
source,
|
|
149
|
+
loaded: loadProjection(source),
|
|
147
150
|
}));
|
|
151
|
+
const projectionDiagnostics = sortDiagnostics(loadedProjections.flatMap(({ loaded }) => loaded.ok ? [] : loaded.diagnostics));
|
|
148
152
|
if (projectionDiagnostics.length > 0) {
|
|
149
153
|
const output = json
|
|
150
154
|
? checkResultJson(false, projectionDiagnostics)
|
|
@@ -177,7 +181,7 @@ export function runCheckCommand(options, cwd) {
|
|
|
177
181
|
: humanDiagnostics(mappingLoadDiagnostics);
|
|
178
182
|
return { exitCode: 1, stdout: output, stderr: '' };
|
|
179
183
|
}
|
|
180
|
-
const result =
|
|
184
|
+
const result = compileWorkspaceWithProfileContext(coreSources);
|
|
181
185
|
const mappingValidation = result.ok
|
|
182
186
|
? validateAdapterMappings(result.graph, loadedMappings.flatMap((loaded) => loaded.ok ? [loaded.mapping] : []))
|
|
183
187
|
: undefined;
|
|
@@ -190,9 +194,34 @@ export function runCheckCommand(options, cwd) {
|
|
|
190
194
|
const evidenceDiagnostics = evidenceEvaluation === undefined || evidenceEvaluation.ok
|
|
191
195
|
? []
|
|
192
196
|
: evidenceEvaluation.diagnostics;
|
|
197
|
+
// A catalogue the manifest declares is workspace content, so `check`
|
|
198
|
+
// refuses a broken one (#345, ADR 0129). Composed rather than checked one
|
|
199
|
+
// by one, because the refusals that matter are CROSS-catalogue: a wave
|
|
200
|
+
// declared twice, and a question naming a wave nothing in the set
|
|
201
|
+
// declares. Checking each file alone would miss both and would refuse the
|
|
202
|
+
// one thing the feature exists to allow, a question joining a wave another
|
|
203
|
+
// catalogue declared.
|
|
204
|
+
const catalogueDiagnostics = result.ok && resolved.questions.length > 0
|
|
205
|
+
? (() => {
|
|
206
|
+
const composed = composeCatalogues(catalogueSources(shippedCatalogueSource(), resolved, cwd), result.profileContext);
|
|
207
|
+
return composed.ok ? [] : composed.diagnostics;
|
|
208
|
+
})()
|
|
209
|
+
: [];
|
|
210
|
+
// A projection is a document, and a query holds references the same way a
|
|
211
|
+
// relationship does. Checked HERE rather than with the projection's own
|
|
212
|
+
// schema load above, because a reference can only be resolved against a
|
|
213
|
+
// model that compiled: reporting dangling names out of a workspace that
|
|
214
|
+
// does not build would bury the real failure under its consequences.
|
|
215
|
+
const referenceDiagnostics = result.ok
|
|
216
|
+
? loadedProjections.flatMap(({ source, loaded }) => loaded.ok
|
|
217
|
+
? projectionReferenceDiagnostics(source, loaded.projection, result.graph, result.profileContext)
|
|
218
|
+
: [])
|
|
219
|
+
: [];
|
|
193
220
|
const optionalDiagnostics = sortDiagnostics([
|
|
194
221
|
...mappingDiagnostics,
|
|
195
222
|
...evidenceDiagnostics,
|
|
223
|
+
...referenceDiagnostics,
|
|
224
|
+
...catalogueDiagnostics,
|
|
196
225
|
]);
|
|
197
226
|
const ok = result.ok && optionalDiagnostics.length === 0;
|
|
198
227
|
// Published results name the subject a diagnostic is about wherever its
|
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;
|
|
@@ -30,6 +30,13 @@ export declare const resolveCliWorkspaceSources: (paths: readonly string[], cwd:
|
|
|
30
30
|
readonly contracts: readonly string[];
|
|
31
31
|
/** Pattern documents, which ride in `paths` and are not documents. */
|
|
32
32
|
readonly patterns: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Question catalogues the workspace carries (#345). Handed over rather
|
|
35
|
+
* than resolved-and-dropped, which is the #268 failure recorded below:
|
|
36
|
+
* a category a manifest declares and no verb receives is ignored with
|
|
37
|
+
* no symptom.
|
|
38
|
+
*/
|
|
39
|
+
readonly questions: readonly string[];
|
|
33
40
|
} | {
|
|
34
41
|
readonly ok: false;
|
|
35
42
|
readonly diagnostics: readonly Diagnostic[];
|
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,
|
|
@@ -51,6 +51,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
|
|
|
51
51
|
evidence: [],
|
|
52
52
|
contracts: [],
|
|
53
53
|
patterns: [],
|
|
54
|
+
questions: [],
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
57
|
const manifestPath = paths[0];
|
|
@@ -62,6 +63,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
|
|
|
62
63
|
evidence: [],
|
|
63
64
|
contracts: [],
|
|
64
65
|
patterns: [],
|
|
66
|
+
questions: [],
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
const source = readFileSync(resolve(cwd, manifestPath), 'utf8');
|
|
@@ -73,6 +75,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
|
|
|
73
75
|
evidence: [],
|
|
74
76
|
contracts: [],
|
|
75
77
|
patterns: [],
|
|
78
|
+
questions: [],
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
81
|
const loaded = loadWorkspaceManifest({ path: manifestPath, source }, cwd);
|
|
@@ -99,6 +102,7 @@ export const resolveCliWorkspaceSources = (paths, cwd, options = {}) => {
|
|
|
99
102
|
evidence: loaded.workspace.evidence,
|
|
100
103
|
contracts: loaded.workspace.contracts,
|
|
101
104
|
patterns: loaded.workspace.patterns,
|
|
105
|
+
questions: loaded.workspace.questions ?? [],
|
|
102
106
|
}
|
|
103
107
|
: { ok: false, diagnostics: loaded.diagnostics };
|
|
104
108
|
};
|
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
|
@@ -5,7 +5,7 @@ import { parseDocument } from 'yaml';
|
|
|
5
5
|
import { diagnosticJson, humanDiagnostics, usage, } from './cli-support.js';
|
|
6
6
|
import { compileWorkspaceWithProfileContext, } from './compiler.js';
|
|
7
7
|
import { loadEvidence } from './evidence.js';
|
|
8
|
-
import {
|
|
8
|
+
import { composeCatalogues, evaluateCatalogue, renderQuestion, } from './interrogate-command.js';
|
|
9
9
|
import { evaluateProjection } from './projection.js';
|
|
10
10
|
import { renderBrief } from './brief.js';
|
|
11
11
|
import { loadWorkspaceManifest } from './workspace.js';
|
|
@@ -205,12 +205,23 @@ export function runDesignCommand(options, cwd) {
|
|
|
205
205
|
})));
|
|
206
206
|
if (!compilation.ok)
|
|
207
207
|
return failed(compilation.diagnostics);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
// The base, then whatever the workspace carries (#345, ADR 0129). The
|
|
209
|
+
// base is REPLACED by `--catalogue` and ADDED TO by `questions:`, which is
|
|
210
|
+
// what lets a consultant author a question mid-engagement with no product
|
|
211
|
+
// release while a host still controls the catalogue that is not in the
|
|
212
|
+
// workspace.
|
|
213
|
+
const composed = composeCatalogues([
|
|
214
|
+
{
|
|
215
|
+
path: cataloguePath ?? resolvedCataloguePath,
|
|
216
|
+
source: readFileSync(resolvedCataloguePath, 'utf8'),
|
|
217
|
+
},
|
|
218
|
+
...(workspace.questions ?? []).map((path) => ({
|
|
219
|
+
path,
|
|
220
|
+
source: readFileSync(resolve(cwd, path), 'utf8'),
|
|
221
|
+
})),
|
|
222
|
+
], compilation.profileContext);
|
|
223
|
+
if (!composed.ok)
|
|
224
|
+
return failed(composed.diagnostics);
|
|
214
225
|
// The evidence overlay rides along for the one condition that reads
|
|
215
226
|
// it (unchallenged-evidence). A workspace declaring no evidence
|
|
216
227
|
// passes an empty overlay — known to be empty, which keeps that
|
|
@@ -235,8 +246,9 @@ export function runDesignCommand(options, cwd) {
|
|
|
235
246
|
};
|
|
236
247
|
}
|
|
237
248
|
}
|
|
238
|
-
const report = evaluateCatalogue(
|
|
239
|
-
|
|
249
|
+
const report = evaluateCatalogue(composed.composed.catalogue, compilation.graph, compilation.profileContext, evidenceObservations, composed.composed.catalogues);
|
|
250
|
+
// Keyed by the QUALIFIED id, matching what the report now carries.
|
|
251
|
+
const askPlainById = new Map(composed.composed.catalogue.questions.flatMap((question) => question.askPlain === undefined
|
|
240
252
|
? []
|
|
241
253
|
: [[question.id, question.askPlain]]));
|
|
242
254
|
const step = selectStep(report, subjectFilter, askPlainById);
|
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) {
|