yarramate 1.6.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 +7 -0
- package/dist/cli-support.js +4 -0
- package/dist/design-command.js +21 -9
- 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-import-entry.d.ts +30 -0
- package/dist/workbook-import-entry.js +30 -0
- package/dist/workbook.js +15 -0
- package/dist/workspace.d.ts +15 -0
- package/dist/workspace.js +1 -0
- package/docs/CONSUMING-YARRAMATE.md +63 -0
- package/package.json +5 -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
|
@@ -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
|
@@ -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/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/index.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export { applyOperations, landOperations, posixDirectoryOf, type ApplyInput, typ
|
|
|
18
18
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
19
19
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
20
20
|
export { deletionBlockers, describeDeletion, draftDeletion, type DeletionBlocker, } from './deletion-drafting.js';
|
|
21
|
-
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
|
21
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, type CatalogueCondition, type CatalogueEvidenceObservation, type CatalogueLoadResult, type CatalogueQuestion, type CatalogueSelector, type InterrogationReport, type InterrogationSummary, type OpenSubject, type QuestionCatalogue, type ReportQuestion, type ReportWave, } from './interrogate-command.js';
|
package/dist/index.js
CHANGED
|
@@ -16,4 +16,4 @@ export { applyOperations, landOperations, posixDirectoryOf, } from './apply-comm
|
|
|
16
16
|
export { connectableKinds, draftRelationship, proposeRelationshipId, stagedSubjectIds, } from './relationship-drafting.js';
|
|
17
17
|
export { draftConcept, proposeConceptId } from './concept-drafting.js';
|
|
18
18
|
export { deletionBlockers, describeDeletion, draftDeletion, } from './deletion-drafting.js';
|
|
19
|
-
export { INTERROGATION_SEMANTICS_VERSION, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|
|
19
|
+
export { INTERROGATION_SEMANTICS_VERSION, composeCatalogues, qualifiedQuestionId, evaluateCatalogue, loadQuestionCatalogue, renderInterrogationReport, renderQuestion, } from './interrogate-command.js';
|
|
@@ -188,14 +188,29 @@ export interface InterrogationSummary {
|
|
|
188
188
|
export interface InterrogationReport {
|
|
189
189
|
readonly format: 'yarramate/interrogation-report/v1';
|
|
190
190
|
readonly workspace: string;
|
|
191
|
+
/** The BASE catalogue, `id@version`. Unchanged in shape by composition. */
|
|
191
192
|
readonly catalogue: string;
|
|
193
|
+
/**
|
|
194
|
+
* Every contributing catalogue as `id@version`, base first, when more than
|
|
195
|
+
* one contributed (#345, ADR 0129). Optional so that adding it breaks no
|
|
196
|
+
* constructor, and `catalogue` keeps its value shape so it breaks no reader.
|
|
197
|
+
*/
|
|
198
|
+
readonly catalogues?: readonly string[];
|
|
192
199
|
/** {@link INTERROGATION_SEMANTICS_VERSION} at the time of evaluation. */
|
|
193
200
|
readonly semantics: string;
|
|
194
201
|
readonly summary: InterrogationSummary;
|
|
195
202
|
readonly waves: readonly ReportWave[];
|
|
196
203
|
}
|
|
197
204
|
export declare const renderQuestion: (template: string, subjectId: string, subjectName: string | undefined, counterparts?: readonly string[]) => string;
|
|
198
|
-
export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[]
|
|
205
|
+
export declare function evaluateCatalogue(catalogue: QuestionCatalogue, graph: SemanticGraph, profileContext?: ResolvedProfileContext, evidence?: readonly CatalogueEvidenceObservation[],
|
|
206
|
+
/**
|
|
207
|
+
* Contributing catalogues, from {@link composeCatalogues}. Composition
|
|
208
|
+
* happens BEFORE evaluation and hands this function an ordinary catalogue,
|
|
209
|
+
* so the only thing evaluation learns about composition is what to name in
|
|
210
|
+
* the report. A fifth optional parameter rather than an options object,
|
|
211
|
+
* because this signature is published and a consumer already calls it.
|
|
212
|
+
*/
|
|
213
|
+
catalogues?: readonly string[]): Omit<InterrogationReport, 'workspace'>;
|
|
199
214
|
export type CatalogueLoadResult = {
|
|
200
215
|
readonly ok: true;
|
|
201
216
|
readonly catalogue: QuestionCatalogue;
|
|
@@ -203,5 +218,71 @@ export type CatalogueLoadResult = {
|
|
|
203
218
|
readonly ok: false;
|
|
204
219
|
readonly diagnostics: readonly Diagnostic[];
|
|
205
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* A catalogue is qualified on the way OUT, never in what an author writes.
|
|
223
|
+
*
|
|
224
|
+
* The authored schema keeps ids local (`^[a-z][a-z0-9-]*$`) and that does not
|
|
225
|
+
* change; a consultant writes `regulator-signoff`, not
|
|
226
|
+
* `consulting#regulator-signoff`. The engine qualifies when it composes, which
|
|
227
|
+
* is the only moment two catalogues can be confused for each other.
|
|
228
|
+
*
|
|
229
|
+
* NO VERSION, and that is the decision rather than an omission (ADR 0129).
|
|
230
|
+
* `core-enrichment` went 1.0 to 1.3 in a single day renaming nothing; a
|
|
231
|
+
* versioned identity would have stranded every stored dismissal in every
|
|
232
|
+
* adopter's database three times that day, for changes that removed no
|
|
233
|
+
* question. Versioned identity is safe for things that are AUTHORED - a
|
|
234
|
+
* document keeps naming the version it was written against and an author
|
|
235
|
+
* updates it deliberately - and unsafe for things that are STORED, because a
|
|
236
|
+
* row in someone's database has no author to update it.
|
|
237
|
+
*/
|
|
238
|
+
export declare const qualifiedQuestionId: (catalogueId: string, questionId: string) => string;
|
|
239
|
+
export interface ComposedCatalogue {
|
|
240
|
+
/**
|
|
241
|
+
* The composed catalogue, ready for `evaluateCatalogue`. Structurally a
|
|
242
|
+
* `QuestionCatalogue`, but its question ids are QUALIFIED, so it is a
|
|
243
|
+
* composition result rather than something an author could have written and
|
|
244
|
+
* must never be validated against the authored schema again.
|
|
245
|
+
*/
|
|
246
|
+
readonly catalogue: QuestionCatalogue;
|
|
247
|
+
/** Every contributing catalogue as `id@version`, base first. */
|
|
248
|
+
readonly catalogues: readonly string[];
|
|
249
|
+
}
|
|
250
|
+
export type CatalogueCompositionResult = {
|
|
251
|
+
readonly ok: true;
|
|
252
|
+
readonly composed: ComposedCatalogue;
|
|
253
|
+
} | {
|
|
254
|
+
readonly ok: false;
|
|
255
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Compose a base catalogue with the ones a workspace carries (#345, ADR 0129).
|
|
259
|
+
*
|
|
260
|
+
* ADDITIVE. `--catalogue` and `MountOptions.catalogue` replace the base; this
|
|
261
|
+
* adds to it, which is what lets a consultant author a question at any point
|
|
262
|
+
* in an engagement with no product release.
|
|
263
|
+
*
|
|
264
|
+
* A WAVE IS DECLARED EXACTLY ONCE across the resolved set, and any catalogue
|
|
265
|
+
* may contribute questions to a wave it did not declare. That one rule settles
|
|
266
|
+
* three questions composition would otherwise raise. Wave identity: a project
|
|
267
|
+
* catalogue joins a declared wave rather than colliding with it. Ordering:
|
|
268
|
+
* only a declaration places a wave, so the base's order is untouched and new
|
|
269
|
+
* waves append. And the `opensWhen` precedence ADR 0125 made load-bearing does
|
|
270
|
+
* not arise at all, because there is only ever one declarer to ask.
|
|
271
|
+
*
|
|
272
|
+
* QUALIFICATION IS A PROPERTY OF COMPOSITION, NOT OF EVALUATION. A caller
|
|
273
|
+
* that hands `evaluateCatalogue` a catalogue directly still gets local ids,
|
|
274
|
+
* exactly as before this existed. Route through here even for ONE catalogue -
|
|
275
|
+
* which is what every CLI verb does - and ids are qualified from the start, so
|
|
276
|
+
* they do not change later when a workspace first carries a question of its
|
|
277
|
+
* own. That transition is the one thing an adopter keying stored judgments on
|
|
278
|
+
* a question id must not experience, and composing unconditionally is how it
|
|
279
|
+
* is avoided.
|
|
280
|
+
*
|
|
281
|
+
* ID COLLISIONS DISSOLVE rather than being resolved. Two catalogues may both
|
|
282
|
+
* carry `outcome-missing`; qualified, they are two different questions. There
|
|
283
|
+
* is no merge rule, no last-wins and no refusal, because there is nothing to
|
|
284
|
+
* merge.
|
|
285
|
+
*/
|
|
286
|
+
export declare function composeCatalogues(sources: readonly WorkspaceSource[], profileContext?: ResolvedProfileContext): CatalogueCompositionResult;
|
|
206
287
|
export declare function loadQuestionCatalogue(catalogueSource: WorkspaceSource, profileContext?: ResolvedProfileContext): CatalogueLoadResult;
|
|
207
288
|
export declare function renderInterrogationReport(report: InterrogationReport): string;
|