process-graph 1.3.0 → 1.4.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.
@@ -1,19 +1,21 @@
1
1
  import { a as validateOverlay, d as overlayStatuses, f as participantKinds } from "./schema-BPT8fmfa.js";
2
2
  import { t as validate } from "./validate-DF3mQZww.js";
3
- import { n as parseModel, t as ModelParseError } from "./parse-C1Q1zsGt.js";
3
+ import { f as terminalJson, h as parseModel, m as ModelParseError, n as viewText, p as terminalText } from "./view-DTeNUhGG.js";
4
4
  import { c as deriveStatuses, f as toStateMachine, g as handoffs, h as toFlowchart, i as readySet, n as criticalPath, p as toSwimlane, r as impact, s as explainStatus, t as blockedBy, u as toSequence } from "./queries-C8kIDRiY.js";
5
5
  import { S as UnknownNodeError, f as layeredFacts, n as illegalCycles, u as factsFrom, x as CycleError } from "./control-DiygoEhm.js";
6
- import { l as assembleSync, t as createGitHubIssuesSource, u as createGraphSourceRouter } from "./source-D51N7K3A.js";
6
+ import { r as filterStates, s as orderFields } from "./schema-DVAttrJg.js";
7
+ import { l as createGraphSourceRouter, t as createGitHubIssuesSource } from "./source-lTKshktI.js";
7
8
  import { t as GraphSourceError } from "./errors-BZe5WzCn.js";
8
9
  import { t as githubTokenFromEnvironment } from "./node-AchI1T-K.js";
9
10
  import { g as emptyModel } from "./store-DKzvLGgl.js";
10
11
  import { t as createGraphStoreRouter } from "./router-Dk91O_9p.js";
11
12
  import { i as openNodeGraphStore, t as prepareDataFile } from "./data-file-BVMxkTug.js";
13
+ import { a as scopesBySource, i as InvalidScopeError, n as UnknownSourceError, r as syncScopes, t as InterruptedSyncError } from "./sync-CkaJO3OQ.js";
12
14
  import { t as exitCodes } from "./exit-codes-0ultCqdS.js";
13
15
  import { ORPCError, createRouterClient } from "@orpc/server";
14
16
  import { readFileSync } from "node:fs";
15
17
  import { join } from "node:path";
16
- import { Argument, Command, CommanderError, Option } from "commander";
18
+ import { Argument, Command, CommanderError, InvalidArgumentError, Option } from "commander";
17
19
  import { readFile } from "node:fs/promises";
18
20
  import { homedir } from "node:os";
19
21
  //#region ../graph-cli/dist/errors.js
@@ -417,6 +419,150 @@ const why = async (session, id) => {
417
419
  chain
418
420
  };
419
421
  };
422
+ /**
423
+ * An element's direct sub-tasks, from the `child-of` relations a sync keeps, as the store's hierarchy query answers.
424
+ * @param session - The store and model.
425
+ * @param id - The element asked about.
426
+ * @throws CliError with `notFound` when the model or the element does not exist.
427
+ */
428
+ const children = async (session, id) => {
429
+ const { model } = await loadRun(session);
430
+ const element = requireElement(model, id);
431
+ const found = await session.store.v1.query.children({
432
+ modelId: session.modelId,
433
+ element: id
434
+ }, callOptions(session.signal));
435
+ return {
436
+ element: {
437
+ id,
438
+ label: labelOf(element)
439
+ },
440
+ children: summarise(model, found.children)
441
+ };
442
+ };
443
+ /**
444
+ * The elements an element is a sub-task of, nearest first, from the `child-of` relations a sync keeps, as the store's hierarchy query answers.
445
+ * @param session - The store and model.
446
+ * @param id - The element asked about.
447
+ * @throws CliError with `notFound` when the model or the element does not exist.
448
+ */
449
+ const ancestors = async (session, id) => {
450
+ const { model } = await loadRun(session);
451
+ const element = requireElement(model, id);
452
+ const found = await session.store.v1.query.ancestors({
453
+ modelId: session.modelId,
454
+ element: id
455
+ }, callOptions(session.signal));
456
+ return {
457
+ element: {
458
+ id,
459
+ label: labelOf(element)
460
+ },
461
+ ancestors: found.ancestors.map((link) => ({
462
+ id: link.element,
463
+ label: labelOf(requireElement(model, link.element)),
464
+ distance: link.distance
465
+ }))
466
+ };
467
+ };
468
+ //#endregion
469
+ //#region ../graph-cli/dist/sync.js
470
+ /** The short direction names the command line takes for an order's direction. */
471
+ const directionNames = {
472
+ asc: "ascending",
473
+ desc: "descending"
474
+ };
475
+ /**
476
+ * Thrown by `sync` when a source fails part way. Its exit status and message are the source's failure, classified as `failureOf` classifies it; its cause is the error underneath that failure, such as the token port's own error; and what to run next is data, in `resume` and `remaining`, never text.
477
+ */
478
+ var SyncInterruptedError = class extends CliError {
479
+ resume;
480
+ remaining;
481
+ saved;
482
+ /**
483
+ * @param exitCode - The exit status the source's failure maps to.
484
+ * @param message - The source's failure.
485
+ * @param interruption - Where the sync carries on.
486
+ * @param options - The underlying error, kept as the cause.
487
+ */
488
+ constructor(exitCode, message, interruption, options) {
489
+ super(exitCode, message, options);
490
+ this.name = "SyncInterruptedError";
491
+ this.resume = interruption.resume;
492
+ this.remaining = interruption.remaining;
493
+ this.saved = interruption.saved;
494
+ }
495
+ };
496
+ /** A usage error for a scope that names no source or a source nothing reads, or nothing for any other error. */
497
+ const usageError = (error) => {
498
+ if (error instanceof InvalidScopeError) return new CliError(exitCodes.usage, error.message, { cause: error });
499
+ if (error instanceof UnknownSourceError) return new CliError(exitCodes.usage, `no source reads "${error.prefix}" scopes`, { cause: error });
500
+ };
501
+ /**
502
+ * Reads every page of the scopes from their sources and merges each into the session's model as it is read, creating the model when the store has none: participants, bound activities, transitions, relations (sub-issues, assignees, repository membership) and run state. Records already in the model are replaced by identifier and the rest are kept, so syncing again refreshes what changed.
503
+ *
504
+ * When a source fails part way, the pages read before the failure are already written, and the error carries where to carry on: the request that resumes the failing source after the cursor of its last page read, and the scopes of the sources not reached.
505
+ * @param session - The store and model.
506
+ * @param sources - The source for each identifier prefix.
507
+ * @param request - The scopes, a cursor to resume after, and a filter.
508
+ * @returns What was written, and the gaps the sources reported.
509
+ * @throws CliError with `usage` for a scope with no source prefix, a prefix no source serves, or `after` with scopes of more than one source; and SyncInterruptedError, with the source's failure and where to carry on, when a source fails part way.
510
+ */
511
+ const sync = async (session, sources, request) => {
512
+ const { scopes, after, filter } = request;
513
+ let groups;
514
+ try {
515
+ groups = scopesBySource(scopes);
516
+ } catch (error) {
517
+ throw usageError(error) ?? error;
518
+ }
519
+ const [first] = groups;
520
+ if (after !== void 0 && groups.length > 1) throw new CliError(exitCodes.usage, "--after resumes one source's sync: give only that source's scopes");
521
+ const resume = after === void 0 || first === void 0 ? void 0 : {
522
+ modelId: session.modelId,
523
+ scopes,
524
+ ...filter === void 0 ? {} : { filter },
525
+ source: first.prefix,
526
+ after
527
+ };
528
+ try {
529
+ const result = await syncScopes({
530
+ store: session.store,
531
+ sources,
532
+ modelId: session.modelId,
533
+ scopes,
534
+ filter,
535
+ resume,
536
+ signal: session.signal
537
+ });
538
+ return {
539
+ modelId: session.modelId,
540
+ revision: result.revision,
541
+ participants: result.participants,
542
+ activities: result.activities,
543
+ transitions: result.transitions,
544
+ relations: result.relations,
545
+ gaps: result.gaps
546
+ };
547
+ } catch (error) {
548
+ const usage = usageError(error);
549
+ if (usage !== void 0) throw usage;
550
+ if (!(error instanceof InterruptedSyncError)) throw error;
551
+ const failure = failureOf(error.cause);
552
+ const { resume: from, remaining, saved } = error;
553
+ const failing = groups.find((group) => group.prefix === from.source);
554
+ if (failing === void 0) throw new Error(`unreachable: the interrupted source "${from.source}" is one of the sync's own`, { cause: error });
555
+ throw new SyncInterruptedError(failure.exitCode, failure.message, {
556
+ resume: {
557
+ scopes: failing.scopes,
558
+ ...from.after === void 0 ? {} : { after: from.after },
559
+ ...filter === void 0 ? {} : { filter }
560
+ },
561
+ remaining,
562
+ saved
563
+ }, { cause: underlyingCause(error.cause) });
564
+ }
565
+ };
420
566
  //#endregion
421
567
  //#region ../graph-cli/dist/render.js
422
568
  /**
@@ -430,6 +576,10 @@ const renderReady = (result) => lines(elementRows(result.elements));
430
576
  const renderBlocked = (result) => lines(elementRows(result.blockedBy));
431
577
  /** `impact`: each element that depends on the one asked about. */
432
578
  const renderImpact = (result) => lines(elementRows(result.impacted));
579
+ /** `children`: each direct sub-task's identifier and label. */
580
+ const renderChildren = (result) => lines(elementRows(result.children));
581
+ /** `ancestors`: each element above the one asked about, nearest first, with its distance. */
582
+ const renderAncestors = (result) => lines(result.ancestors.map((link) => `${link.id}\t${link.label}\t${String(link.distance)}`));
433
583
  /** `path`: the critical path, one element per line in dependency order. */
434
584
  const renderPath = (result) => lines(elementRows(result.path));
435
585
  /** `cycles`: each illegal cycle as a closed path. */
@@ -481,7 +631,17 @@ const renderSetFact = (result) => lines([`set fact ${result.fact} to ${result.va
481
631
  /** `facts`: each recorded artefact as `artefact`, its id and `provided` or `absent`, then each named fact as `fact`, its name and its value, each sorted by id or name. */
482
632
  const renderFacts = (result) => lines([...Object.entries(result.facts.artefacts).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([id, provided]) => `artefact\t${id}\t${provided ? "provided" : "absent"}`), ...Object.entries(result.facts.values).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => `fact\t${name}\t${valueText(value)}`)]);
483
633
  /** `sync`: what was written, then each gap as its reason, target and message. */
484
- const renderSync = (result) => lines([`synced ${String(result.activities)} activities, ${String(result.transitions)} transitions and ${String(result.participants)} participants into "${result.modelId}" at revision ${String(result.revision)}`, ...result.gaps.map((gap) => `gap\t${gap.reason}\t${gap.target}\t${gap.message}`)]);
634
+ const renderSync = (result) => lines([`synced ${String(result.activities)} activities, ${String(result.transitions)} transitions, ${String(result.relations)} relations and ${String(result.participants)} participants into "${result.modelId}" at revision ${String(result.revision)}`, ...result.gaps.map((gap) => `gap\t${gap.reason}\t${gap.target}\t${gap.message}`)]);
635
+ /**
636
+ * The `sync` options that give a filter, as they would be typed again to resume under it, since a cursor is valid only under the filter it was issued with.
637
+ * @param filter - The filter, if any.
638
+ * @returns The options, each with a leading space, or the empty string.
639
+ */
640
+ const filterOptions = (filter) => [
641
+ ...(filter?.states ?? []).map((state) => ` --state ${state}`),
642
+ ...(filter?.labels ?? []).map((label) => ` --label ${label}`),
643
+ ...filter?.order === void 0 ? [] : [` --order ${filter.order.field} --direction ${filter.order.direction === directionNames.desc ? "desc" : "asc"}`]
644
+ ].join("");
485
645
  /**
486
646
  * A sync a source interrupted: the source's failure, whether the pages read before it were saved, and the commands that carry on, `resume with: sync <scopes> --after <cursor>` for the failing source and `then: sync <scopes>` for the sources not reached.
487
647
  * @param message - The source's failure.
@@ -493,333 +653,13 @@ const renderSyncInterruption = (message, interruption) => {
493
653
  return [
494
654
  message,
495
655
  ...saved ? ["the pages read before the failure are saved"] : [],
496
- `resume with: sync ${resume.scopes.join(" ")}${resume.after === void 0 ? "" : ` --after ${resume.after}`}`,
497
- ...remaining.length === 0 ? [] : [`then: sync ${remaining.join(" ")}`]
656
+ `resume with: sync ${resume.scopes.join(" ")}${resume.after === void 0 ? "" : ` --after ${resume.after}`}${filterOptions(resume.filter)}`,
657
+ ...remaining.length === 0 ? [] : [`then: sync ${remaining.join(" ")}${filterOptions(resume.filter)}`]
498
658
  ].join("\n");
499
659
  };
500
660
  /** `validate`: `valid`, or each issue as its code, its JSON pointer when it has one, and its message. */
501
661
  const renderValidate = (result) => result.valid ? lines(["valid"]) : lines(result.issues.map((issue) => issue.pointer === void 0 ? `${issue.code}\t${issue.message}` : `${issue.code}\t${issue.pointer === "" ? "/" : issue.pointer}\t${issue.message}`));
502
662
  //#endregion
503
- //#region ../graph-cli/dist/terminal-text.js
504
- /**
505
- * The characters that must never reach a terminal as they are, by Unicode general category: controls (`Cc`, where ESC starts the sequences that clear the screen, move the cursor or, through OSC 52, set the clipboard, and the C1 controls some terminals read as the same sequences), format characters (`Cf`, which holds every bidirectional control, the invisible operators, the soft hyphen, the interlinear annotation characters and the tag characters, all of which reorder, hide or change what is shown), the line and paragraph separators (`Zl`, `Zp`), and the unassigned and private-use code points (`Cn`, `Co`), which no font shows reliably. Text from a source (an issue title, a label, an actor's name) can carry any of them. A category, not a list of ranges, so a character Unicode adds to one of them is covered without a change here.
506
- *
507
- * Three are allowed: line feed and tab, which every text output uses, and the zero-width non-joiner and joiner (U+200C, U+200D), formats that emoji sequences (a family, a profession) and scripts such as Persian, Hindi and Malayalam need to be written correctly, and that cannot reorder or hide text on their own.
508
- */
509
- const unsafeCharacters = /[[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cn}\p{Co}]--[\n\t\u200C\u200D]]/gv;
510
- /** Code points are written in hexadecimal. */
511
- const hexadecimal = 16;
512
- /** A JSON `\u` escape always has four hexadecimal digits. */
513
- const jsonEscapeDigits = 4;
514
- /**
515
- * A character's code point.
516
- * @param character - One character, as the unsafe-character pattern matches it.
517
- * @returns Its code point.
518
- * @throws Error when given an empty string, which the pattern never matches.
519
- */
520
- const codePointOf = (character) => {
521
- const point = character.codePointAt(0);
522
- if (point === void 0) throw new Error("an unsafe character match was empty");
523
- return point;
524
- };
525
- /**
526
- * Text made safe to write to a terminal. Each unsafe character (see `unsafeCharacters`) is written as the visible escape `\u{<hex>}` of its code point, such as `\u{1b}` for ESC, `\u{202e}` for a right-to-left override or `\u{e0041}` for a tag character, so nothing is lost and nothing acts on the terminal. Line feed, tab, the zero-width joiners and every other character, accented letters, CJK, Arabic and emoji included, pass through. A literal `\u{...}` already in the text is left as it is, so the escaped form is for reading, not for reversing.
527
- * @param text - The text as written.
528
- * @returns The text with every unsafe character escaped.
529
- */
530
- const terminalText = (text) => text.replaceAll(unsafeCharacters, (character) => `\\u{${codePointOf(character).toString(hexadecimal)}}`);
531
- /**
532
- * A value as JSON that is also safe to write to a terminal. `JSON.stringify` escapes the C0 controls but writes every other unsafe character (see `unsafeCharacters`) as it is; this writes those as `\u` escapes too, a surrogate pair for a code point beyond the Basic Multilingual Plane, which leaves the JSON, and every value parsed from it, exactly the same.
533
- * @param value - The value to write.
534
- * @returns The JSON text, indented by two spaces and ending in a line break.
535
- */
536
- const terminalJson = (value) => `${JSON.stringify(value, null, 2).replaceAll(unsafeCharacters, (character) => character.split("").map((unit) => `\\u${unit.charCodeAt(0).toString(hexadecimal).padStart(jsonEscapeDigits, "0")}`).join(""))}\n`;
537
- //#endregion
538
- //#region ../graph-cli/dist/emit/text.js
539
- /**
540
- * The Mermaid entity code for a character that would end or change the meaning of a label: quotes, statement and edge-label delimiters, the entity marker itself and HTML.
541
- */
542
- const mermaidEntity = (character) => {
543
- switch (character) {
544
- case "#": return "#35;";
545
- case "\"": return "#quot;";
546
- case "<": return "#lt;";
547
- case ">": return "#gt;";
548
- case "&": return "#amp;";
549
- case ";": return "#59;";
550
- case "|": return "#124;";
551
- case ":": return "#58;";
552
- default: return character;
553
- }
554
- };
555
- /**
556
- * Text made safe to place inside any Mermaid label: a line break becomes a space, every control and invisible format character is written as its visible `\u{<hex>}` escape (see `terminalText`), and every character that Mermaid reads as syntax is written as its entity code, so a label from a source (an issue title, a repository name) can never break the diagram or act on the terminal it is printed to.
557
- * @param text - The label as written.
558
- * @returns The label as Mermaid source.
559
- */
560
- const mermaidText = (text) => terminalText(text.replaceAll(/[\r\n]/gu, " ")).replaceAll(/[#"<>&;|:]/gu, mermaidEntity);
561
- /**
562
- * A Graphviz DOT double-quoted string: line breaks written as DOT's own centred line break, every control and invisible format character as its visible `\u{<hex>}` escape (see `terminalText`), then backslashes, the escapes' included, and quotes escaped. Any identifier or label is valid DOT once quoted this way, and reads back as the escaped text.
563
- * @param text - The text as written.
564
- * @returns The quoted DOT string, quotes included.
565
- */
566
- const dotString = (text) => `"${text.split(/\r?\n/u).map((line) => terminalText(line).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")).join("\\n")}"`;
567
- //#endregion
568
- //#region ../graph-cli/dist/emit/mermaid.js
569
- /**
570
- * Model identifiers are free text (`github:org/repo#12`), which Mermaid does not accept as a node identifier, so every emitter names things by position in the view instead: a prefix and the index. The view's order is the model's order, so the same model always gives the same names.
571
- */
572
- const positionalIds = (prefix, ids) => new Map(ids.map((id, index) => [id, `${prefix}${String(index)}`]));
573
- /** Reads a name the emitter itself assigned, which exists for every identifier the view lists. */
574
- const nameIn = (names, id) => {
575
- const name = names.get(id);
576
- if (name === void 0) throw new Error(`"${id}" is not part of the view being emitted`);
577
- return name;
578
- };
579
- const indent$1 = " ";
580
- /** Each flowchart shape in Mermaid's syntax around a node name and quoted label: a stadium for a terminator, a rectangle for a process, a rhombus for a decision and a fork bar for a fork or join. */
581
- const mermaidShapes = {
582
- terminator: (name, label) => `${name}(["${label}"])`,
583
- process: (name, label) => `${name}["${label}"]`,
584
- decision: (name, label) => `${name}{"${label}"}`,
585
- bar: (name, label) => `${name}@{ shape: fork, label: "${label}" }`
586
- };
587
- const mermaidNode = (name, node) => mermaidShapes[node.shape](name, mermaidText(node.label));
588
- /** The edges of a flowchart, a hand-off (an edge whose ends are in different lanes) drawn thick. */
589
- const mermaidEdges = (view, names, isHandoff) => view.edges.map((edge) => {
590
- const arrow = isHandoff(edge.from, edge.to) ? "==>" : "-->";
591
- const label = edge.label === void 0 ? "" : `|"${mermaidText(edge.label)}"|`;
592
- return `${indent$1}${nameIn(names, edge.from)} ${arrow}${label} ${nameIn(names, edge.to)}`;
593
- });
594
- /**
595
- * A flowchart as a Mermaid `flowchart`, top to bottom. Nodes are declared in the view's order, then edges, each with its guard as the edge label.
596
- * @param view - The projected flowchart.
597
- * @returns Mermaid source, ending with a line break.
598
- */
599
- const flowchartToMermaid = (view) => {
600
- const names = positionalIds("n", view.nodes.map((node) => node.id));
601
- return `${[
602
- "flowchart TD",
603
- ...view.nodes.map((node) => `${indent$1}${mermaidNode(nameIn(names, node.id), node)}`),
604
- ...mermaidEdges(view, names, () => false)
605
- ].join("\n")}\n`;
606
- };
607
- /**
608
- * A swimlane as a Mermaid `flowchart`, left to right, with one subgraph per lane in the view's lane order (an empty lane is an empty subgraph, so it stays visible) and nodes with no actor outside every lane. Hand-offs, the edges that change lane, are drawn thick.
609
- * @param view - The projected swimlane.
610
- * @returns Mermaid source, ending with a line break.
611
- */
612
- const swimlaneToMermaid = (view) => {
613
- const names = positionalIds("n", view.nodes.map((node) => node.id));
614
- const nodes = new Map(view.nodes.map((node) => [node.id, node]));
615
- const laneOf = /* @__PURE__ */ new Map();
616
- view.lanes.forEach((lane, index) => {
617
- for (const id of lane.nodes) laneOf.set(id, index);
618
- });
619
- const declare = (id, depth) => {
620
- const node = nodes.get(id);
621
- if (node === void 0) throw new Error(`lane member "${id}" is not a node of the view`);
622
- return `${depth}${mermaidNode(nameIn(names, id), node)}`;
623
- };
624
- return `${[
625
- "flowchart LR",
626
- ...view.lanes.flatMap((lane, index) => [
627
- `${indent$1}subgraph lane${String(index)}["${mermaidText(lane.label)}"]`,
628
- ...lane.nodes.map((id) => declare(id, indent$1.repeat(2))),
629
- `${indent$1}end`
630
- ]),
631
- ...view.unassigned.map((id) => declare(id, indent$1)),
632
- ...mermaidEdges(view, names, (from, to) => {
633
- const fromLane = laneOf.get(from);
634
- const toLane = laneOf.get(to);
635
- return fromLane !== void 0 && toLane !== void 0 && fromLane !== toLane;
636
- })
637
- ].join("\n")}\n`;
638
- };
639
- /** A state machine transition's label: its event, its guard in brackets and its effect after a slash, each only when present. */
640
- const transitionLabel = (transition) => [
641
- transition.event,
642
- transition.guard === void 0 ? void 0 : `[${transition.guard}]`,
643
- transition.effect === void 0 ? void 0 : `/ ${transition.effect}`
644
- ].filter((part) => part !== void 0).join(" ");
645
- /**
646
- * A state machine as a Mermaid `stateDiagram-v2`. Initial and final pseudo-states are Mermaid's `[*]`; every other state is declared with its label. Each transition carries its event, guard and effect.
647
- * @param view - The projected state machine.
648
- * @returns Mermaid source, ending with a line break.
649
- */
650
- const stateMachineToMermaid = (view) => {
651
- const drawn = view.states.filter((state) => state.role === "state");
652
- const names = new Map([...positionalIds("s", drawn.map((state) => state.id)), ...view.states.filter((state) => state.role !== "state").map((state) => [state.id, "[*]"])]);
653
- return `${[
654
- "stateDiagram-v2",
655
- ...drawn.map((state) => `${indent$1}state "${mermaidText(state.label)}" as ${nameIn(names, state.id)}`),
656
- ...view.transitions.map((transition) => {
657
- const label = transitionLabel(transition);
658
- return `${indent$1}${nameIn(names, transition.source)} --> ${nameIn(names, transition.target)}${label === "" ? "" : ` : ${mermaidText(label)}`}`;
659
- })
660
- ].join("\n")}\n`;
661
- };
662
- /** The Mermaid keyword that separates one operand of a fragment from the next, for the operators with more than one operand. */
663
- const operandSeparator = (operator) => operator === "par" ? "and" : "else";
664
- /**
665
- * A sequence diagram as a Mermaid `sequenceDiagram`. Lifelines are declared in order of first appearance, labelled through `participantLabel`; messages follow in scenario order, wrapped in the fragments the projection drew (`alt` operands separated by `else`, `par` operands by `and`), each operand headed by its guard.
666
- * @param view - The projected sequence diagram.
667
- * @param participantLabel - The label of a lifeline's participant.
668
- * @returns Mermaid source, ending with a line break.
669
- */
670
- const sequenceToMermaid = (view, participantLabel) => {
671
- const names = positionalIds("p", view.lifelines);
672
- const items = (list, depth) => list.flatMap((item) => {
673
- const prefix = indent$1.repeat(depth);
674
- if (item.kind === "message") {
675
- const { message } = item;
676
- return [`${prefix}${nameIn(names, message.from)}->>${nameIn(names, message.to)}: ${mermaidText(message.label)}`];
677
- }
678
- return [...item.operands.flatMap((operand, index) => {
679
- const keyword = index === 0 ? item.operator : operandSeparator(item.operator);
680
- const guard = operand.guard === void 0 ? "" : ` ${mermaidText(operand.guard)}`;
681
- return [`${prefix}${keyword}${guard}`, ...items(operand.items, depth + 1)];
682
- }), `${prefix}end`];
683
- });
684
- return `${[
685
- "sequenceDiagram",
686
- ...view.lifelines.map((lifeline) => `${indent$1}participant ${nameIn(names, lifeline)} as ${mermaidText(participantLabel(lifeline))}`),
687
- ...items(view.items, 1)
688
- ].join("\n")}\n`;
689
- };
690
- //#endregion
691
- //#region ../graph-cli/dist/emit/dot.js
692
- const indent = " ";
693
- /** A DOT attribute list from name and value pairs, each value quoted; empty when there are none. */
694
- const attributes = (pairs) => pairs.length === 0 ? "" : ` [${pairs.map(([name, value]) => `${name}=${dotString(value)}`).join(", ")}]`;
695
- /** A flowchart node's DOT shape: an oval terminator, a box process, a diamond decision, and a filled bar for a fork or join. */
696
- const nodeShapes = {
697
- terminator: [["shape", "oval"]],
698
- process: [["shape", "box"]],
699
- decision: [["shape", "diamond"]],
700
- bar: [
701
- ["shape", "box"],
702
- ["style", "filled"],
703
- ["fillcolor", "black"],
704
- ["fontcolor", "white"]
705
- ]
706
- };
707
- const dotNode = (node, depth) => `${depth}${dotString(node.id)}${attributes([["label", node.label], ...nodeShapes[node.shape]])};`;
708
- /** The edges of a flowchart, a hand-off (an edge whose ends are in different lanes) drawn bold. */
709
- const dotEdges = (view, isHandoff) => view.edges.map((edge) => `${indent}${dotString(edge.from)} -> ${dotString(edge.to)}${attributes([...edge.label === void 0 ? [] : [["label", edge.label]], ...isHandoff(edge.from, edge.to) ? [["style", "bold"]] : []])};`);
710
- /**
711
- * A flowchart as a Graphviz `digraph`, top to bottom. Nodes are named by their model identifiers, so the DOT source reads back to the model directly.
712
- * @param view - The projected flowchart.
713
- * @returns DOT source, ending with a line break.
714
- */
715
- const flowchartToDot = (view) => `${[
716
- `digraph ${dotString("flowchart")} {`,
717
- `${indent}rankdir="TB";`,
718
- ...view.nodes.map((node) => dotNode(node, indent)),
719
- ...dotEdges(view, () => false),
720
- "}"
721
- ].join("\n")}\n`;
722
- /**
723
- * A swimlane as a Graphviz `digraph`, left to right, with one `cluster` subgraph per lane in the view's lane order and nodes with no actor outside every lane. An empty lane holds one invisible point so Graphviz still draws it. Hand-offs, the edges that change lane, are drawn bold.
724
- * @param view - The projected swimlane.
725
- * @returns DOT source, ending with a line break.
726
- */
727
- const swimlaneToDot = (view) => {
728
- const nodes = new Map(view.nodes.map((node) => [node.id, node]));
729
- const laneOf = /* @__PURE__ */ new Map();
730
- view.lanes.forEach((lane, index) => {
731
- for (const id of lane.nodes) laneOf.set(id, index);
732
- });
733
- const declare = (id, depth) => {
734
- const node = nodes.get(id);
735
- if (node === void 0) throw new Error(`lane member "${id}" is not a node of the view`);
736
- return dotNode(node, depth);
737
- };
738
- return `${[
739
- `digraph ${dotString("swimlane")} {`,
740
- `${indent}rankdir="LR";`,
741
- ...view.lanes.flatMap((lane, index) => {
742
- const cluster = `cluster_lane${String(index)}`;
743
- return [
744
- `${indent}subgraph ${dotString(cluster)} {`,
745
- `${indent.repeat(2)}label=${dotString(lane.label)};`,
746
- ...lane.nodes.length === 0 ? [`${indent.repeat(2)}${dotString(`${cluster}:empty`)}${attributes([["shape", "point"], ["style", "invis"]])};`] : lane.nodes.map((id) => declare(id, indent.repeat(2))),
747
- `${indent}}`
748
- ];
749
- }),
750
- ...view.unassigned.map((id) => declare(id, indent)),
751
- ...dotEdges(view, (from, to) => {
752
- const fromLane = laneOf.get(from);
753
- const toLane = laneOf.get(to);
754
- return fromLane !== void 0 && toLane !== void 0 && fromLane !== toLane;
755
- }),
756
- "}"
757
- ].join("\n")}\n`;
758
- };
759
- /** A state's DOT shape: a filled point for an initial pseudo-state, a double circle for a final one, a rounded box for a state. */
760
- const stateShape = (state) => {
761
- if (state.role === "initial") return [["label", ""], ["shape", "point"]];
762
- if (state.role === "final") return [["label", ""], ["shape", "doublecircle"]];
763
- return [
764
- ["label", state.label],
765
- ["shape", "box"],
766
- ["style", "rounded"]
767
- ];
768
- };
769
- /**
770
- * A state machine as a Graphviz `digraph`, left to right. Each transition is labelled with its event, guard and effect.
771
- * @param view - The projected state machine.
772
- * @returns DOT source, ending with a line break.
773
- */
774
- const stateMachineToDot = (view) => `${[
775
- `digraph ${dotString("state machine")} {`,
776
- `${indent}rankdir="LR";`,
777
- ...view.states.map((state) => `${indent}${dotString(state.id)}${attributes(stateShape(state))};`),
778
- ...view.transitions.map((transition) => {
779
- const label = transitionLabel(transition);
780
- return `${indent}${dotString(transition.source)} -> ${dotString(transition.target)}${attributes(label === "" ? [] : [["label", label]])};`;
781
- }),
782
- "}"
783
- ].join("\n")}\n`;
784
- const placeMessages = (items, fragments) => items.flatMap((item) => item.kind === "message" ? [{
785
- message: item.message,
786
- fragments
787
- }] : item.operands.flatMap((operand) => placeMessages(operand.items, [...fragments, operand.guard === void 0 ? item.operator : `${item.operator} [${operand.guard}]`])));
788
- /**
789
- * A sequence diagram as a Graphviz `digraph`. DOT has no sequence notation, so the diagram is drawn as a grid: a column per lifeline, headed by its participant's label, with one row per message in scenario order. Each message is an arrow across its row, numbered by its step and prefixed with the fragments around it (operator and guard, outermost first), since DOT cannot draw a box around arrows.
790
- * @param view - The projected sequence diagram.
791
- * @param participantLabel - The label of a lifeline's participant.
792
- * @returns DOT source, ending with a line break.
793
- */
794
- const sequenceToDot = (view, participantLabel) => {
795
- const columns = new Map(view.lifelines.map((lifeline, index) => [lifeline, index]));
796
- const column = (lifeline) => {
797
- const index = columns.get(lifeline);
798
- if (index === void 0) throw new Error(`"${lifeline}" is not a lifeline of the view`);
799
- return index;
800
- };
801
- const head = (index) => dotString(`p${String(index)}`);
802
- const cell = (index, row) => dotString(`p${String(index)}.${String(row)}`);
803
- const placed = placeMessages(view.items, []);
804
- const rows = placed.map((_, row) => row + 1);
805
- const pointAttributes = attributes([["shape", "point"], ["width", "0"]]);
806
- return `${[
807
- `digraph ${dotString("sequence")} {`,
808
- `${indent}rankdir="TB";`,
809
- ...view.lifelines.map((lifeline, index) => `${indent}${head(index)}${attributes([["label", participantLabel(lifeline)], ["shape", "box"]])};`),
810
- `${indent}{ rank="same"; ${view.lifelines.map((_, index) => `${head(index)};`).join(" ")} }`,
811
- ...view.lifelines.slice(1).map((_, index) => `${indent}${head(index)} -> ${head(index + 1)}${attributes([["style", "invis"]])};`),
812
- ...rows.flatMap((row) => [...view.lifelines.map((_, index) => `${indent}${cell(index, row)}${pointAttributes};`), `${indent}{ rank="same"; ${view.lifelines.map((_, index) => `${cell(index, row)};`).join(" ")} }`]),
813
- ...rows.length === 0 ? [] : view.lifelines.map((_, index) => `${indent}${[head(index), ...rows.map((row) => cell(index, row))].join(" -> ")}${attributes([["style", "dashed"], ["arrowhead", "none"]])};`),
814
- ...placed.map(({ message, fragments }, index) => {
815
- const row = index + 1;
816
- const text = fragments.length === 0 ? message.label : `${fragments.join(" / ")}: ${message.label}`;
817
- return `${indent}${cell(column(message.from), row)} -> ${cell(column(message.to), row)}${attributes([["label", `${String(row)}. ${text}`], ["constraint", "false"]])};`;
818
- }),
819
- "}"
820
- ].join("\n")}\n`;
821
- };
822
- //#endregion
823
663
  //#region ../graph-cli/dist/show.js
824
664
  /** The views `show` draws, as named on the command line. */
825
665
  const viewNames = [
@@ -828,7 +668,7 @@ const viewNames = [
828
668
  "state",
829
669
  "sequence"
830
670
  ];
831
- /** The formats `show` writes. */
671
+ /** The formats `show` writes, graph-export's, with JSON named first. */
832
672
  const formatNames = [
833
673
  "json",
834
674
  "mermaid",
@@ -875,22 +715,33 @@ const project = (model, request) => {
875
715
  projection: toSequence(model, scenarioOf(model, request.scenario))
876
716
  };
877
717
  };
718
+ /** A view as `show` names it, as the projection layer names its notation. */
719
+ const notationViewOf = (projected) => {
720
+ if (projected.view === "flowchart") return {
721
+ notation: "flowchart",
722
+ view: projected.projection
723
+ };
724
+ if (projected.view === "swimlane") return {
725
+ notation: "swimlane",
726
+ view: projected.projection
727
+ };
728
+ if (projected.view === "state") return {
729
+ notation: "stateMachine",
730
+ view: projected.projection
731
+ };
732
+ return {
733
+ notation: "sequence",
734
+ view: projected.projection
735
+ };
736
+ };
878
737
  /**
879
- * Writes a projected view as Mermaid or DOT source.
738
+ * Writes a projected view as Mermaid or DOT source, or as JSON, through graph-export's `viewText`, the writer the web app's downloads use too.
880
739
  * @param model - The model, for participant labels.
881
740
  * @param projected - The view.
882
- * @param format - `mermaid` or `dot`.
883
- * @returns The diagram source.
741
+ * @param format - `mermaid`, `dot` or `json`.
742
+ * @returns The text.
884
743
  */
885
- const emit = (model, projected, format) => {
886
- const labels = new Map(model.participants.map((participant) => [participant.id, participant.label]));
887
- const participantLabel = (id) => labels.get(id) ?? id;
888
- const mermaid = format === "mermaid";
889
- if (projected.view === "flowchart") return mermaid ? flowchartToMermaid(projected.projection) : flowchartToDot(projected.projection);
890
- if (projected.view === "swimlane") return mermaid ? swimlaneToMermaid(projected.projection) : swimlaneToDot(projected.projection);
891
- if (projected.view === "state") return mermaid ? stateMachineToMermaid(projected.projection) : stateMachineToDot(projected.projection);
892
- return mermaid ? sequenceToMermaid(projected.projection, participantLabel) : sequenceToDot(projected.projection, participantLabel);
893
- };
744
+ const emit = (model, projected, format) => viewText(model, notationViewOf(projected), format);
894
745
  /**
895
746
  * Draws the session's model as a view in a format.
896
747
  * @param session - The store and model.
@@ -899,7 +750,7 @@ const emit = (model, projected, format) => {
899
750
  const show = async (session, request) => {
900
751
  const { model } = await loadRun(session);
901
752
  const projected = project(model, request);
902
- const text = request.format === "json" ? `${JSON.stringify(projected.projection, null, 2)}\n` : emit(model, projected, request.format);
753
+ const text = emit(model, projected, request.format);
903
754
  return {
904
755
  ...projected,
905
756
  format: request.format,
@@ -907,130 +758,6 @@ const show = async (session, request) => {
907
758
  };
908
759
  };
909
760
  //#endregion
910
- //#region ../graph-cli/dist/sync.js
911
- /**
912
- * Thrown by `sync` when a source fails part way. Its exit status and message are the source's failure, classified as `failureOf` classifies it; its cause is the error underneath that failure, such as the token port's own error; and what to run next is data, in `resume` and `remaining`, never text.
913
- */
914
- var SyncInterruptedError = class extends CliError {
915
- resume;
916
- remaining;
917
- saved;
918
- /**
919
- * @param exitCode - The exit status the source's failure maps to.
920
- * @param message - The source's failure.
921
- * @param interruption - Where the sync carries on.
922
- * @param options - The underlying error, kept as the cause.
923
- */
924
- constructor(exitCode, message, interruption, options) {
925
- super(exitCode, message, options);
926
- this.name = "SyncInterruptedError";
927
- this.resume = interruption.resume;
928
- this.remaining = interruption.remaining;
929
- this.saved = interruption.saved;
930
- }
931
- };
932
- const scopeSeparator = ":";
933
- /** Groups scopes by their source prefix, keeping the order in which each prefix first appears. */
934
- const bySource = (scopes) => {
935
- const groups = /* @__PURE__ */ new Map();
936
- for (const scope of scopes) {
937
- const separator = scope.indexOf(scopeSeparator);
938
- if (separator <= 0) throw new CliError(exitCodes.usage, `"${scope}" is not a scope: write <source>:<scope>, such as github:owner/repo`);
939
- const prefix = scope.slice(0, separator);
940
- groups.set(prefix, [...groups.get(prefix) ?? [], scope]);
941
- }
942
- return [...groups].map(([prefix, grouped]) => ({
943
- prefix,
944
- scopes: grouped
945
- }));
946
- };
947
- /** Bound activities are the only elements a sync assembles, since a page's activities are bound by schema. */
948
- const boundActivities = (pages) => {
949
- const { model, overlay, gaps } = assembleSync(pages);
950
- const activities = model.elements.flatMap((element) => element.kind === "activity" && element.source !== void 0 ? [{
951
- ...element,
952
- source: element.source
953
- }] : []);
954
- return {
955
- items: {
956
- participants: model.participants,
957
- activities,
958
- transitions: model.transitions,
959
- overlay
960
- },
961
- gaps
962
- };
963
- };
964
- /**
965
- * Reads every page of the scopes from their sources and merges them into the session's model in one write, creating the model when the store has none. Records already in the model are replaced by identifier and the rest are kept, so syncing again refreshes what changed.
966
- *
967
- * When a source fails part way, the pages read before the failure are still written, and the error carries where to carry on: the request that resumes the failing source after the cursor of its last page read, and the scopes of the sources not reached.
968
- * @param session - The store and model.
969
- * @param sources - The source for each identifier prefix.
970
- * @param request - The scopes, and a cursor to resume after.
971
- * @returns What was written, and the gaps the sources reported.
972
- * @throws CliError with `usage` for a scope with no source prefix, a prefix no source serves, or `after` with scopes of more than one source; and SyncInterruptedError, with the source's failure and where to carry on, when a source fails part way.
973
- */
974
- const sync = async (session, sources, request) => {
975
- const groups = bySource(request.scopes);
976
- if (request.after !== void 0 && groups.length > 1) throw new CliError(exitCodes.usage, "--after resumes one source's sync: give only that source's scopes");
977
- const clients = groups.map((group) => {
978
- const client = sources(group.prefix);
979
- if (client === void 0) throw new CliError(exitCodes.usage, `no source reads "${group.prefix}" scopes`);
980
- return {
981
- ...group,
982
- client
983
- };
984
- });
985
- const pages = [];
986
- const write = async () => {
987
- const { items, gaps } = boundActivities(pages);
988
- const { revision } = await session.store.v1.items.upsert({
989
- modelId: session.modelId,
990
- items
991
- }, callOptions(session.signal));
992
- return {
993
- modelId: session.modelId,
994
- revision,
995
- participants: items.participants.length,
996
- activities: items.activities.length,
997
- transitions: items.transitions.length,
998
- gaps
999
- };
1000
- };
1001
- /** Reads the sources in order, each only after the one before has finished, since a source's failure must stop the ones after it. */
1002
- const readFrom = async (index) => {
1003
- const group = clients.at(index);
1004
- if (group === void 0) return;
1005
- let cursor = index === 0 ? request.after : void 0;
1006
- try {
1007
- const stream = await group.client.v1.sync.stream(cursor === void 0 ? { scope: group.scopes } : {
1008
- scope: group.scopes,
1009
- after: cursor
1010
- }, callOptions(session.signal));
1011
- for await (const page of stream) {
1012
- pages.push(page);
1013
- cursor = page.next;
1014
- }
1015
- } catch (error) {
1016
- const failure = failureOf(error);
1017
- const saved = pages.length > 0;
1018
- if (saved) await write();
1019
- throw new SyncInterruptedError(failure.exitCode, failure.message, {
1020
- resume: cursor === void 0 ? { scopes: group.scopes } : {
1021
- scopes: group.scopes,
1022
- after: cursor
1023
- },
1024
- remaining: clients.slice(index + 1).flatMap((next) => next.scopes),
1025
- saved
1026
- }, { cause: underlyingCause(error) });
1027
- }
1028
- await readFrom(index + 1);
1029
- };
1030
- await readFrom(0);
1031
- return write();
1032
- };
1033
- //#endregion
1034
761
  //#region ../graph-cli/dist/validate.js
1035
762
  /** A cycle that no decision closes, as an issue: status is undefined on it. */
1036
763
  const cycleIssues = (model) => illegalCycles(model).map((cycle) => ({
@@ -1112,6 +839,44 @@ const choice = (values, value, name) => {
1112
839
  if (!isOneOf(values, value)) throw new Error(`--${name} was not checked against its choices`);
1113
840
  return value;
1114
841
  };
842
+ /** The direction names `--direction` takes. */
843
+ const directions = Object.keys(directionNames);
844
+ const isDirection = (value) => isOneOf(directions, value);
845
+ /**
846
+ * A commander option parser that collects every occurrence of an option, in order, so the option can be repeated.
847
+ * @param value - This occurrence's value.
848
+ * @param previous - The values collected before it.
849
+ */
850
+ const collect = (value, previous) => [...previous ?? [], value];
851
+ /**
852
+ * A commander option parser that checks each value against its choices and collects every occurrence, in order. Commander's own `choices` replaces an option's parser, so the two cannot be combined.
853
+ * @param choices - The values allowed.
854
+ */
855
+ const collectChoice = (choices) => (value, previous) => {
856
+ if (!isOneOf(choices, value)) throw new InvalidArgumentError(`Allowed choices are ${choices.join(", ")}.`);
857
+ return [...previous ?? [], value];
858
+ };
859
+ /**
860
+ * Reads `sync`'s filter options into the source contract's filter, or nothing when none is given.
861
+ * @param options - The `sync` command's options.
862
+ * @returns The filter.
863
+ * @throws CliError with `usage` for `--direction` without `--order`.
864
+ */
865
+ const syncFilterOf = (options) => {
866
+ const { state, label, order, direction } = options;
867
+ const states = Array.isArray(state) ? state.filter((value) => isOneOf(filterStates, value)) : [];
868
+ const labels = Array.isArray(label) ? label.filter(isText) : [];
869
+ if (direction !== void 0 && order === void 0) throw new CliError(exitCodes.usage, "--direction orders by --order: give both");
870
+ const filter = {
871
+ ...states.length === 0 ? {} : { states },
872
+ ...labels.length === 0 ? {} : { labels },
873
+ ...order === void 0 ? {} : { order: {
874
+ field: choice(orderFields, order, "order"),
875
+ direction: directionNames[isDirection(direction) ? direction : "asc"]
876
+ } }
877
+ };
878
+ return Object.keys(filter).length === 0 ? void 0 : filter;
879
+ };
1115
880
  /** The word that forgets a named fact rather than giving it a value. */
1116
881
  const unknownValue = "unknown";
1117
882
  /** A number as JSON writes one, so a value that reads as a number is never also meant as text. */
@@ -1182,14 +947,16 @@ const runCli = async (args, ports, identity) => {
1182
947
  await opened.close();
1183
948
  }
1184
949
  };
1185
- program.command("sync").description("read the scopes from their sources and merge them into the model").argument("<scope...>", "source-qualified scopes, such as github:owner or github:owner/repo").option("--after <cursor>", "resume an interrupted sync after this cursor").action(async (scopes, options, command) => {
950
+ program.command("sync").description("read the scopes from their sources and merge them into the model").argument("<scope...>", "source-qualified scopes, such as github:owner or github:owner/repo").option("--after <cursor>", "resume an interrupted sync after this cursor").addOption(new Option("--state <state>", `list only work in this state, one of ${filterStates.join(", ")}; repeat for more than one`).argParser(collectChoice(filterStates))).addOption(new Option("--label <label>", "list only work carrying this label; repeat to keep work carrying any of them").argParser(collect)).addOption(new Option("--order <field>", "list the work in this order").choices(orderFields)).addOption(new Option("--direction <direction>", "the direction of --order; ascending when not given").choices(directions)).addHelpText("after", "\nA filter narrows what each scope lists, not the work it depends on. A source that cannot filter a scope, such as a GitHub Projects v2 board, refuses the sync. A cursor from --after is valid only with the filter it was issued under.").action(async (scopes, options, command) => {
1186
951
  const { after } = options;
952
+ const filter = syncFilterOf(options);
1187
953
  await withSession(async (session) => {
1188
954
  try {
1189
- const result = await sync(session, ports.source, typeof after === "string" ? {
955
+ const result = await sync(session, ports.source, {
1190
956
  scopes,
1191
- after
1192
- } : { scopes });
957
+ ...typeof after === "string" ? { after } : {},
958
+ ...filter === void 0 ? {} : { filter }
959
+ });
1193
960
  return {
1194
961
  json: result,
1195
962
  text: renderSync(result)
@@ -1227,6 +994,24 @@ const runCli = async (args, ports, identity) => {
1227
994
  };
1228
995
  })(command);
1229
996
  });
997
+ program.command("children").description("list an element's sub-tasks: the work a source reports as its children").argument("<id>", "the element").action(async (id, _options, command) => {
998
+ await withSession(async (session) => {
999
+ const result = await children(session, id);
1000
+ return {
1001
+ json: result,
1002
+ text: renderChildren(result)
1003
+ };
1004
+ })(command);
1005
+ });
1006
+ program.command("ancestors").description("list what an element is a sub-task of, nearest first, with how many levels up each is").argument("<id>", "the element").action(async (id, _options, command) => {
1007
+ await withSession(async (session) => {
1008
+ const result = await ancestors(session, id);
1009
+ return {
1010
+ json: result,
1011
+ text: renderAncestors(result)
1012
+ };
1013
+ })(command);
1014
+ });
1230
1015
  program.command("path").description("list the critical path: the longest chain of work still ahead").action(async (_options, command) => {
1231
1016
  await withSession(async (session) => {
1232
1017
  const result = await path(session);
@@ -1538,4 +1323,4 @@ const runProcess = async (identity) => {
1538
1323
  return runCli(process.argv.slice(2), nodePorts({ signal: controller.signal }), identity);
1539
1324
  };
1540
1325
  //#endregion
1541
- export { why as A, CliError as B, swimlaneToMermaid as C, impactOf as D, handoffsOf as E, setFact as F, setState as I, findRun as L, artefactStates as M, factsOf as N, path as O, setArtefact as P, loadRun as R, stateMachineToMermaid as S, cycles as T, failureOf as V, sequenceToDot as _, defaultModelId as a, flowchartToMermaid as b, validateStored as c, emit as d, formatNames as f, flowchartToDot as g, viewNames as h, runProcess as i, add as j, ready as k, SyncInterruptedError as l, show as m, nodePorts as n, runCli as o, project as p, packageVersion as r, validateDocument as s, defaultDataPath as t, sync as u, stateMachineToDot as v, blocked as w, sequenceToMermaid as x, swimlaneToDot as y, runFacts as z };
1326
+ export { setState as A, ready as C, factsOf as D, artefactStates as E, failureOf as F, loadRun as M, runFacts as N, setArtefact as O, CliError as P, path as S, add as T, blocked as _, defaultModelId as a, handoffsOf as b, validateStored as c, project as d, show as f, ancestors as g, sync as h, runProcess as i, findRun as j, setFact as k, emit as l, SyncInterruptedError as m, nodePorts as n, runCli as o, viewNames as p, packageVersion as r, validateDocument as s, defaultDataPath as t, formatNames as u, children as v, why as w, impactOf as x, cycles as y };