process-graph 1.1.1 → 1.3.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.
Files changed (50) hide show
  1. package/README.md +22 -19
  2. package/dist/bin.js +4 -3
  3. package/dist/cli.d.ts +100 -14
  4. package/dist/cli.js +3 -2
  5. package/dist/{control-aDk9560d.js → control-DiygoEhm.js} +163 -1
  6. package/dist/data-file-BVMxkTug.js +95 -0
  7. package/dist/dist-81kTKnMq.js +3 -0
  8. package/dist/{dist-HLlW8vgT.js → dist-BI01yxFA.js} +256 -103
  9. package/dist/{dist-BpCBmnMl.js → dist-arkejVz2.js} +2 -112
  10. package/dist/{graphql-C1j5wQFz.d.ts → graphql-BE7xm5dB.d.ts} +3 -2
  11. package/dist/{index-DricKcyl.d.ts → index-9Au8pAmP.d.ts} +4 -4
  12. package/dist/{index-CrM5qWUA.d.ts → index-BC4tmAuQ.d.ts} +2 -2
  13. package/dist/{index-DhbvDgK4.d.ts → index-CR8xUK3S.d.ts} +388 -580
  14. package/dist/{index-CoA6vXqe.d.ts → index-D06bcdfw.d.ts} +1 -1
  15. package/dist/{index-CCfe0csl.d.ts → index-D4CnOhNf.d.ts} +34 -3
  16. package/dist/{index-CmZOswkx.d.ts → index-Dvz_I_uQ.d.ts} +17 -8
  17. package/dist/{index-CptUHq6F.d.ts → index-hri2a13-.d.ts} +1 -1
  18. package/dist/index.d.ts +4 -4
  19. package/dist/index.js +5 -5
  20. package/dist/layout.d.ts +1 -1
  21. package/dist/{parse-CuipC_2K.js → parse-C1Q1zsGt.js} +1 -1
  22. package/dist/{queries-CCexJAE2.js → queries-C8kIDRiY.js} +2 -93
  23. package/dist/react-flow.d.ts +5 -5
  24. package/dist/react-flow.js +3 -3
  25. package/dist/react.d.ts +1 -1
  26. package/dist/react.js +2 -2
  27. package/dist/{router-D4gtTlXm.js → router-Dk91O_9p.js} +27 -2
  28. package/dist/{schema-CVnS6HiX.js → schema-BPT8fmfa.js} +2 -2
  29. package/dist/{selection-BW_xXWOy.js → selection-iet5RAlP.js} +1 -1
  30. package/dist/source/github/node.d.ts +1 -1
  31. package/dist/source/github.d.ts +2 -2
  32. package/dist/source/github.js +1 -1
  33. package/dist/{source-DRDpEA8Q.js → source-D51N7K3A.js} +23 -3
  34. package/dist/store/node.d.ts +27 -2
  35. package/dist/store/node.js +2 -2
  36. package/dist/store/wasm/worker.js +2 -2
  37. package/dist/store/wasm.d.ts +1 -1
  38. package/dist/{store-BcR70CaO.js → store-DKzvLGgl.js} +252 -37
  39. package/dist/store.d.ts +115 -3
  40. package/dist/store.js +43 -9
  41. package/dist/theme-values-BJJhAkUn-ORyRUrPf.js +114 -0
  42. package/dist/theme-values-BybZ-cle.d.ts +116 -0
  43. package/dist/ui/theme.d.ts +2 -0
  44. package/dist/ui/theme.js +2 -0
  45. package/dist/ui.d.ts +6 -120
  46. package/dist/ui.js +2 -1
  47. package/dist/version-Dagrkvwt.js +5 -0
  48. package/package.json +28 -23
  49. package/dist/dist-IjrgWhHQ.js +0 -3
  50. package/dist/open-CyRXJ4bt.js +0 -29
@@ -1,20 +1,21 @@
1
- import { a as validateOverlay, d as overlayStatuses, f as participantKinds } from "./schema-CVnS6HiX.js";
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-CuipC_2K.js";
4
- import { _ as toSwimlane, b as handoffs, c as deriveStatuses, g as toStateMachine, i as readySet, m as toSequence, n as criticalPath, r as impact, s as explainStatus, t as blockedBy, y as toFlowchart } from "./queries-CCexJAE2.js";
5
- import { f as CycleError, n as illegalCycles, p as UnknownNodeError } from "./control-aDk9560d.js";
6
- import { l as assembleSync, t as createGitHubIssuesSource, u as createGraphSourceRouter } from "./source-DRDpEA8Q.js";
3
+ import { n as parseModel, t as ModelParseError } from "./parse-C1Q1zsGt.js";
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
+ 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";
7
+ import { t as GraphSourceError } from "./errors-BZe5WzCn.js";
7
8
  import { t as githubTokenFromEnvironment } from "./node-AchI1T-K.js";
8
- import { f as emptyModel } from "./store-BcR70CaO.js";
9
- import { t as createGraphStoreRouter } from "./router-D4gtTlXm.js";
10
- import { t as openNodeGraphStore } from "./open-CyRXJ4bt.js";
9
+ import { g as emptyModel } from "./store-DKzvLGgl.js";
10
+ import { t as createGraphStoreRouter } from "./router-Dk91O_9p.js";
11
+ import { i as openNodeGraphStore, t as prepareDataFile } from "./data-file-BVMxkTug.js";
11
12
  import { t as exitCodes } from "./exit-codes-0ultCqdS.js";
12
13
  import { ORPCError, createRouterClient } from "@orpc/server";
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
13
16
  import { Argument, Command, CommanderError, Option } from "commander";
14
- import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
15
17
  import { readFile } from "node:fs/promises";
16
18
  import { homedir } from "node:os";
17
- import { dirname, join } from "node:path";
18
19
  //#region ../graph-cli/dist/errors.js
19
20
  /** A failure the command line reports with its own exit status and message, and nothing else. */
20
21
  var CliError = class extends Error {
@@ -46,6 +47,16 @@ const issueLines = (data) => {
46
47
  return data.issues.flatMap((issue) => typeof issue === "object" && issue !== null && "code" in issue && "message" in issue && typeof issue.code === "string" && typeof issue.message === "string" ? [`${issue.code}: ${issue.message}`] : []);
47
48
  };
48
49
  /**
50
+ * The error underneath a source's failure, for a failure's cause. A source called in process through oRPC fails with an `ORPCError` whose cause is the `GraphSourceError` the source threw, whose own cause, when it has one, is what went wrong underneath, such as a token port's error. Those two layers say nothing more than the failure's message and exit status already do, so the innermost error is the one worth keeping.
51
+ * @param error - What the source call threw.
52
+ * @returns The `GraphSourceError`'s cause when it has one, otherwise that `GraphSourceError`; anything else as it is.
53
+ */
54
+ const underlyingCause = (error) => {
55
+ const sourceError = error instanceof ORPCError && error.cause instanceof GraphSourceError ? error.cause : error;
56
+ if (!(sourceError instanceof GraphSourceError)) return error;
57
+ return sourceError.cause === void 0 ? sourceError : sourceError.cause;
58
+ };
59
+ /**
49
60
  * Classifies anything a command threw into its exit status and the message for standard error. A commander usage error has already printed its own message, so it is reported with an empty one.
50
61
  * @param error - What was thrown.
51
62
  * @returns The exit status and message.
@@ -95,6 +106,13 @@ const failureOf = (error) => {
95
106
  //#endregion
96
107
  //#region ../graph-cli/dist/session.js
97
108
  /**
109
+ * The facts a run's reasoning reads: the model's stored fact record first, then the session's own facts for every key the record does not answer. What `set artefact` and `set fact` record therefore overrides a host's facts only where it says something.
110
+ * @param session - The session, whose facts the host supplied.
111
+ * @param run - The stored run.
112
+ * @returns The `Facts` port for graph-domain.
113
+ */
114
+ const runFacts = (session, run) => layeredFacts(factsFrom(run.facts), session.facts);
115
+ /**
98
116
  * The oRPC client options carrying the session's signal. The key is left out when there is no signal, since the options type does not accept an explicit `undefined`.
99
117
  * @param signal - The signal, if any.
100
118
  */
@@ -110,7 +128,8 @@ const findRun = async (session) => {
110
128
  return {
111
129
  revision: stored.revision,
112
130
  model: stored.model,
113
- overlay: stored.overlay
131
+ overlay: stored.overlay,
132
+ facts: stored.facts
114
133
  };
115
134
  } catch (error) {
116
135
  if (error instanceof ORPCError && error.code === "NOT_FOUND") return;
@@ -212,6 +231,74 @@ const setState = async (session, id, state) => {
212
231
  revision
213
232
  };
214
233
  };
234
+ /** What can be recorded about an artefact: `provided`, known not provided (`absent`), or `unknown`, which forgets whatever was recorded. */
235
+ const artefactStates = [
236
+ "provided",
237
+ "absent",
238
+ "unknown"
239
+ ];
240
+ /**
241
+ * Records whether an artefact of the session's model is provided, so a consumer of an input no activity produces can become ready, or forgets it with `unknown`.
242
+ * @param session - The store and model.
243
+ * @param id - The artefact.
244
+ * @param state - `provided`, `absent` or `unknown`.
245
+ * @returns The artefact, its new state and the revision the change took.
246
+ * @throws CliError with `notFound` when the model or the artefact does not exist.
247
+ */
248
+ const setArtefact = async (session, id, state) => {
249
+ const { model } = await loadRun(session);
250
+ if (!model.artefacts.some((artefact) => artefact.id === id)) throw new CliError(exitCodes.notFound, `no artefact "${id}" in the model`);
251
+ const options = callOptions(session.signal);
252
+ const { revision } = state === "unknown" ? await session.store.v1.facts.clear({
253
+ modelId: session.modelId,
254
+ facts: { artefacts: [id] }
255
+ }, options) : await session.store.v1.facts.set({
256
+ modelId: session.modelId,
257
+ facts: { artefacts: { [id]: state === "provided" } }
258
+ }, options);
259
+ return {
260
+ artefact: id,
261
+ state,
262
+ revision
263
+ };
264
+ };
265
+ /**
266
+ * Records a named fact's value for the session's model, so every condition reading that name evaluates against it, or forgets it with `unknown`, so those conditions are indeterminate again.
267
+ * @param session - The store and model.
268
+ * @param name - The fact's name, the key a condition's `reference` node gives.
269
+ * @param value - Its trilean value, or `unknown`.
270
+ * @returns The fact, its new value and the revision the change took.
271
+ * @throws CliError with `notFound` when the model does not exist.
272
+ */
273
+ const setFact = async (session, name, value) => {
274
+ await loadRun(session);
275
+ const options = callOptions(session.signal);
276
+ const { revision } = value === "unknown" ? await session.store.v1.facts.clear({
277
+ modelId: session.modelId,
278
+ facts: { values: [name] }
279
+ }, options) : await session.store.v1.facts.set({
280
+ modelId: session.modelId,
281
+ facts: { values: { [name]: value } }
282
+ }, options);
283
+ return {
284
+ fact: name,
285
+ value,
286
+ revision
287
+ };
288
+ };
289
+ /**
290
+ * Reads what is recorded about the session's model's run: which artefacts are provided or absent, and each named fact's value.
291
+ * @param session - The store and model.
292
+ * @returns The fact record and the model's revision.
293
+ * @throws CliError with `notFound` when the model does not exist.
294
+ */
295
+ const factsOf = async (session) => {
296
+ const { revision, facts } = await loadRun(session);
297
+ return {
298
+ revision,
299
+ facts
300
+ };
301
+ };
215
302
  //#endregion
216
303
  //#region ../graph-cli/dist/queries.js
217
304
  /** Each element counts as one step, so the critical path is the longest chain of elements. */
@@ -221,8 +308,9 @@ const stepWeight = 1;
221
308
  * @param session - The store, model and facts.
222
309
  */
223
310
  const ready = async (session) => {
224
- const { model, overlay } = await loadRun(session);
225
- return { elements: summarise(model, readySet(model, overlay, session.facts)) };
311
+ const run = await loadRun(session);
312
+ const { model, overlay } = run;
313
+ return { elements: summarise(model, readySet(model, overlay, runFacts(session, run))) };
226
314
  };
227
315
  const statusOf = (statuses, summary) => {
228
316
  const status = statuses.get(summary.id);
@@ -238,15 +326,17 @@ const statusOf = (statuses, summary) => {
238
326
  * @param id - The element asked about.
239
327
  */
240
328
  const blocked = async (session, id) => {
241
- const { model, overlay } = await loadRun(session);
329
+ const run = await loadRun(session);
330
+ const { model, overlay } = run;
331
+ const facts = runFacts(session, run);
242
332
  const element = requireElement(model, id);
243
- const statuses = deriveStatuses(model, overlay, session.facts);
333
+ const statuses = deriveStatuses(model, overlay, facts);
244
334
  return {
245
335
  element: statusOf(statuses, {
246
336
  id,
247
337
  label: labelOf(element)
248
338
  }),
249
- blockedBy: summarise(model, blockedBy(model, overlay, session.facts, id))
339
+ blockedBy: summarise(model, blockedBy(model, overlay, facts, id))
250
340
  };
251
341
  };
252
342
  /**
@@ -255,15 +345,17 @@ const blocked = async (session, id) => {
255
345
  * @param id - The element asked about.
256
346
  */
257
347
  const impactOf = async (session, id) => {
258
- const { model, overlay } = await loadRun(session);
348
+ const run = await loadRun(session);
349
+ const { model, overlay } = run;
350
+ const facts = runFacts(session, run);
259
351
  const element = requireElement(model, id);
260
- const statuses = deriveStatuses(model, overlay, session.facts);
352
+ const statuses = deriveStatuses(model, overlay, facts);
261
353
  return {
262
354
  element: statusOf(statuses, {
263
355
  id,
264
356
  label: labelOf(element)
265
357
  }),
266
- impacted: summarise(model, impact(model, overlay, session.facts, id))
358
+ impacted: summarise(model, impact(model, overlay, facts, id))
267
359
  };
268
360
  };
269
361
  /**
@@ -271,8 +363,9 @@ const impactOf = async (session, id) => {
271
363
  * @param session - The store, model and facts.
272
364
  */
273
365
  const path = async (session) => {
274
- const { model, overlay } = await loadRun(session);
275
- const found = criticalPath(model, overlay, session.facts, () => stepWeight);
366
+ const run = await loadRun(session);
367
+ const { model, overlay } = run;
368
+ const found = criticalPath(model, overlay, runFacts(session, run), () => stepWeight);
276
369
  return {
277
370
  path: summarise(model, found.path),
278
371
  weight: found.weight
@@ -300,9 +393,10 @@ const handoffsOf = async (session) => {
300
393
  * @param id - The element asked about.
301
394
  */
302
395
  const why = async (session, id) => {
303
- const { model, overlay } = await loadRun(session);
396
+ const run = await loadRun(session);
397
+ const { model, overlay } = run;
304
398
  requireElement(model, id);
305
- const explained = explainStatus(model, overlay, session.facts, id);
399
+ const explained = explainStatus(model, overlay, runFacts(session, run), id);
306
400
  const chain = explained.chain.map((link) => ({
307
401
  id: link.element,
308
402
  label: labelOf(requireElement(model, link.element)),
@@ -375,8 +469,34 @@ const renderWhy = (result) => {
375
469
  const renderAdded = (kind, id, written) => lines([`added ${kind} ${id} at revision ${String(written.revision)}`]);
376
470
  /** `set`: the element's new run state and the revision it took. */
377
471
  const renderSet = (result) => lines([`set ${result.element} to ${result.state} at revision ${String(result.revision)}`]);
472
+ /** A fact's value as the command line reads it back: a boolean or number as written, text as it is, and the other kinds as their JSON. */
473
+ const valueText = (value) => {
474
+ if (value.kind === "boolean" || value.kind === "number") return value.kind === "number" && value.unit !== void 0 ? JSON.stringify(value) : String(value.value);
475
+ return value.kind === "text" ? value.value : JSON.stringify(value);
476
+ };
477
+ /** `set artefact`: the artefact's new state and the revision it took. */
478
+ const renderSetArtefact = (result) => lines([`set artefact ${result.artefact} to ${result.state} at revision ${String(result.revision)}`]);
479
+ /** `set fact`: the fact's new value and the revision it took. */
480
+ const renderSetFact = (result) => lines([`set fact ${result.fact} to ${result.value === "unknown" ? "unknown" : valueText(result.value)} at revision ${String(result.revision)}`]);
481
+ /** `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
+ 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)}`)]);
378
483
  /** `sync`: what was written, then each gap as its reason, target and message. */
379
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}`)]);
485
+ /**
486
+ * 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
+ * @param message - The source's failure.
488
+ * @param interruption - Where the sync carries on.
489
+ * @returns The lines for standard error, without a final line break.
490
+ */
491
+ const renderSyncInterruption = (message, interruption) => {
492
+ const { resume, remaining, saved } = interruption;
493
+ return [
494
+ message,
495
+ ...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(" ")}`]
498
+ ].join("\n");
499
+ };
380
500
  /** `validate`: `valid`, or each issue as its code, its JSON pointer when it has one, and its message. */
381
501
  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}`));
382
502
  //#endregion
@@ -788,6 +908,27 @@ const show = async (session, request) => {
788
908
  };
789
909
  //#endregion
790
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
+ };
791
932
  const scopeSeparator = ":";
792
933
  /** Groups scopes by their source prefix, keeping the order in which each prefix first appears. */
793
934
  const bySource = (scopes) => {
@@ -823,12 +964,12 @@ const boundActivities = (pages) => {
823
964
  /**
824
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.
825
966
  *
826
- * When a source fails part way, the pages read before the failure are still written, and the error says how to resume: the failing source's scopes with `--after` and the cursor of its last page read.
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.
827
968
  * @param session - The store and model.
828
969
  * @param sources - The source for each identifier prefix.
829
970
  * @param request - The scopes, and a cursor to resume after.
830
971
  * @returns What was written, and the gaps the sources reported.
831
- * @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 with the source's failure, the resume command added, when a source refuses the sync.
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.
832
973
  */
833
974
  const sync = async (session, sources, request) => {
834
975
  const groups = bySource(request.scopes);
@@ -875,13 +1016,14 @@ const sync = async (session, sources, request) => {
875
1016
  const failure = failureOf(error);
876
1017
  const saved = pages.length > 0;
877
1018
  if (saved) await write();
878
- const later = clients.slice(index + 1).flatMap((next) => next.scopes);
879
- throw new CliError(failure.exitCode, [
880
- failure.message,
881
- ...saved ? ["the pages read before the failure are saved"] : [],
882
- `resume with: sync ${group.scopes.join(" ")}${cursor === void 0 ? "" : ` --after ${cursor}`}`,
883
- ...later.length === 0 ? [] : [`then: sync ${later.join(" ")}`]
884
- ].join("\n"), { cause: error });
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) });
885
1027
  }
886
1028
  await readFrom(index + 1);
887
1029
  };
@@ -962,12 +1104,42 @@ const readGlobals = (command, ports) => {
962
1104
  json: json === true
963
1105
  };
964
1106
  };
1107
+ /** Narrows a variadic option's values, which commander gives as an untyped array, to the strings they are. */
1108
+ const isText = (value) => typeof value === "string";
965
1109
  const isOneOf = (values, value) => values.some((candidate) => candidate === value);
966
1110
  /** Reads an option whose choices commander has already checked, narrowing it to its type. */
967
1111
  const choice = (values, value, name) => {
968
1112
  if (!isOneOf(values, value)) throw new Error(`--${name} was not checked against its choices`);
969
1113
  return value;
970
1114
  };
1115
+ /** The word that forgets a named fact rather than giving it a value. */
1116
+ const unknownValue = "unknown";
1117
+ /** A number as JSON writes one, so a value that reads as a number is never also meant as text. */
1118
+ const jsonNumber = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u;
1119
+ /**
1120
+ * Reads a fact's value from the command line: `true` and `false` are booleans, `unknown` forgets the fact, a JSON number is a number, and anything else is text; `--text` reads the value as text whatever it looks like.
1121
+ * @param value - The value as given.
1122
+ * @param asText - Whether `--text` was given.
1123
+ * @returns The trilean value, or `unknown`.
1124
+ */
1125
+ const factValueOf = (value, asText) => {
1126
+ if (asText) return {
1127
+ kind: "text",
1128
+ value
1129
+ };
1130
+ if (value === unknownValue) return unknownValue;
1131
+ if (value === "true" || value === "false") return {
1132
+ kind: "boolean",
1133
+ value: value === "true"
1134
+ };
1135
+ return jsonNumber.test(value) ? {
1136
+ kind: "number",
1137
+ value: Number(value)
1138
+ } : {
1139
+ kind: "text",
1140
+ value
1141
+ };
1142
+ };
971
1143
  /**
972
1144
  * Runs the command line with the given arguments, writing only through the ports, and resolves with the exit status. It never exits the process or reads its arguments itself, so a test or a host program such as Switchboard runs it as a function.
973
1145
  * @param args - The arguments after the program name, such as `["ready", "--json"]`.
@@ -1013,14 +1185,19 @@ const runCli = async (args, ports, identity) => {
1013
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) => {
1014
1186
  const { after } = options;
1015
1187
  await withSession(async (session) => {
1016
- const result = await sync(session, ports.source, typeof after === "string" ? {
1017
- scopes,
1018
- after
1019
- } : { scopes });
1020
- return {
1021
- json: result,
1022
- text: renderSync(result)
1023
- };
1188
+ try {
1189
+ const result = await sync(session, ports.source, typeof after === "string" ? {
1190
+ scopes,
1191
+ after
1192
+ } : { scopes });
1193
+ return {
1194
+ json: result,
1195
+ text: renderSync(result)
1196
+ };
1197
+ } catch (error) {
1198
+ if (error instanceof SyncInterruptedError) throw new CliError(error.exitCode, renderSyncInterruption(error.message, error), { cause: error });
1199
+ throw error;
1200
+ }
1024
1201
  })(command);
1025
1202
  });
1026
1203
  program.command("ready").description("list the work that can start now").action(async (_options, command) => {
@@ -1131,15 +1308,17 @@ const runCli = async (args, ports, identity) => {
1131
1308
  }
1132
1309
  }, "participant");
1133
1310
  });
1134
- addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").action(async (id, label, options, command) => {
1135
- const { actor } = options;
1311
+ addCommand.command("activity").description("add an activity: a step of work").argument("<id>", "its identifier").argument("<label>", "its label").option("--actor <participant>", "the role or system that does it").option("--consumes <artefact...>", "the artefacts it needs before it can start").option("--produces <artefact...>", "the artefacts it makes").action(async (id, label, options, command) => {
1312
+ const { actor, consumes, produces } = options;
1136
1313
  await added(command, {
1137
1314
  kind: "element",
1138
1315
  record: {
1139
1316
  kind: "activity",
1140
1317
  id,
1141
1318
  label,
1142
- ...typeof actor === "string" ? { actor } : {}
1319
+ ...typeof actor === "string" ? { actor } : {},
1320
+ ...Array.isArray(consumes) ? { consumes: consumes.filter(isText) } : {},
1321
+ ...Array.isArray(produces) ? { produces: produces.filter(isText) } : {}
1143
1322
  }
1144
1323
  }, "activity");
1145
1324
  });
@@ -1202,7 +1381,7 @@ const runCli = async (args, ports, identity) => {
1202
1381
  }
1203
1382
  }, "transition");
1204
1383
  });
1205
- program.command("set").description("set an element's run state").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
1384
+ const setCommand = program.command("set").description("set an element's run state; set artefact and set fact record what is known from outside the process").argument("<id>", "the element").addArgument(new Argument("<state>", "its run state").choices(overlayStatuses)).action(async (id, state, _options, command) => {
1206
1385
  const status = choice(overlayStatuses, state, "state");
1207
1386
  await withSession(async (session) => {
1208
1387
  const result = await setState(session, id, status);
@@ -1212,6 +1391,35 @@ const runCli = async (args, ports, identity) => {
1212
1391
  };
1213
1392
  })(command);
1214
1393
  });
1394
+ setCommand.command("artefact").description("record whether an artefact is provided, so work consuming it can start; unknown forgets it").argument("<id>", "the artefact").addArgument(new Argument("<state>", "whether it is provided").choices(artefactStates)).action(async (id, state, _options, command) => {
1395
+ const provision = choice(artefactStates, state, "state");
1396
+ await withSession(async (session) => {
1397
+ const result = await setArtefact(session, id, provision);
1398
+ return {
1399
+ json: result,
1400
+ text: renderSetArtefact(result)
1401
+ };
1402
+ })(command);
1403
+ });
1404
+ setCommand.command("fact").description("record the value of a named fact that decision conditions read; unknown forgets it").argument("<name>", "the fact's name, as a condition's reference gives it").argument("<value>", "true, false, unknown, a number, or any other text").option("--text", "read the value as text, even true, false or a number").action(async (name, value, options, command) => {
1405
+ const parsed = factValueOf(value, options.text === true);
1406
+ await withSession(async (session) => {
1407
+ const result = await setFact(session, name, parsed);
1408
+ return {
1409
+ json: result,
1410
+ text: renderSetFact(result)
1411
+ };
1412
+ })(command);
1413
+ });
1414
+ program.command("facts").description("list what is recorded about the run: each artefact provided or absent, and each named fact's value").action(async (_options, command) => {
1415
+ await withSession(async (session) => {
1416
+ const result = await factsOf(session);
1417
+ return {
1418
+ json: result,
1419
+ text: renderFacts(result)
1420
+ };
1421
+ })(command);
1422
+ });
1215
1423
  program.command("validate").description("check the stored model, or a model file, for problems; exits with the invalid model status when there are any").argument("[file]", "a model as JSON, checked instead of the stored model").action(async (file, _options, command) => {
1216
1424
  const outcome = (result) => ({
1217
1425
  json: result,
@@ -1233,61 +1441,6 @@ const runCli = async (args, ports, identity) => {
1233
1441
  }
1234
1442
  return exitCode;
1235
1443
  };
1236
- /** The permission bits of a file mode, without its type. */
1237
- const permissionBits = 511;
1238
- /** The bits that give the file's group or anyone else any access. */
1239
- const groupAndOtherBits = 63;
1240
- /** The SQLite files a store at `path` uses: the database and its write-ahead log and shared-memory index. */
1241
- const storeFiles = (path) => [
1242
- path,
1243
- `${path}-wal`,
1244
- `${path}-shm`
1245
- ];
1246
- /** Whether an error is the file system's report that a path does not exist, or already does. */
1247
- const hasCode = (error, code) => error instanceof Error && "code" in error && error.code === code;
1248
- /**
1249
- * Narrows a path's mode to `mode` when its group or others have any access.
1250
- * @param path - The path.
1251
- * @param mode - The private mode it should have.
1252
- * @returns Whether the mode was changed; `false` too when the path does not exist.
1253
- */
1254
- const tighten = (path, mode) => {
1255
- let current;
1256
- try {
1257
- current = statSync(path).mode & permissionBits;
1258
- } catch (error) {
1259
- if (hasCode(error, "ENOENT")) return false;
1260
- throw error;
1261
- }
1262
- if ((current & groupAndOtherBits) === 0) return false;
1263
- chmodSync(path, mode);
1264
- return true;
1265
- };
1266
- /**
1267
- * Makes a data file ready for SQLite to open. The directory is created when missing, private when it is the command line's own. The database file is created private when missing, before SQLite opens it, so the file and the sidecars SQLite derives from it are never readable by others, whatever the umask; a file that already exists is left alone unless the directory is the command line's own. In its own directory, an existing directory or store file that group or others can read is narrowed to the private mode.
1268
- * @param path - The database file.
1269
- * @param options - How much of the path the command line owns, and the platform.
1270
- * @returns One warning line naming what was narrowed, or nothing.
1271
- */
1272
- const prepareDataFile = (path, options) => {
1273
- const directory = dirname(path);
1274
- if (options.platform === "win32") {
1275
- mkdirSync(directory, { recursive: true });
1276
- return [];
1277
- }
1278
- mkdirSync(directory, options.ownsDirectory ? {
1279
- recursive: true,
1280
- mode: 448
1281
- } : { recursive: true });
1282
- try {
1283
- closeSync(openSync(path, "wx", 384));
1284
- } catch (error) {
1285
- if (!hasCode(error, "EEXIST")) throw error;
1286
- }
1287
- if (!options.ownsDirectory) return [];
1288
- const narrowed = [...tighten(directory, 448) ? [directory] : [], ...storeFiles(path).filter((file) => tighten(file, 384))];
1289
- return narrowed.length === 0 ? [] : [`made ${narrowed.join(", ")} readable by its owner only, since other users could read the synced work in it`];
1290
- };
1291
1444
  //#endregion
1292
1445
  //#region ../graph-cli/dist/node.js
1293
1446
  /** The variable that names the data file outright. */
@@ -1330,7 +1483,7 @@ const packageVersion = (manifest) => {
1330
1483
  return parsed.version;
1331
1484
  };
1332
1485
  /**
1333
- * The ports for a Node process: the SQLite store over `node:sqlite` (creating the data file's directory when needed), the GitHub issues source for `github:` scopes, the file system and the process's streams.
1486
+ * The ports for a Node process: the SQLite store over `node:sqlite` (creating the data file's directory when needed, private when the command line or the host owns it), the GitHub issues source for `github:` scopes, the file system and the process's streams.
1334
1487
  * @param options - Replacements for any of the defaults.
1335
1488
  * @returns The ports.
1336
1489
  */
@@ -1338,7 +1491,7 @@ const nodePorts = (options = {}) => {
1338
1491
  const env = options.env ?? process.env;
1339
1492
  const token = options.token ?? githubTokenFromEnvironment({ env });
1340
1493
  const home = options.home ?? homedir();
1341
- const ownDataPath = xdgDataPath(env, home);
1494
+ const ownDataPath = options.dataPath ?? xdgDataPath(env, home);
1342
1495
  return {
1343
1496
  stdout: options.stdout ?? ((text) => {
1344
1497
  process.stdout.write(text);
@@ -1346,7 +1499,7 @@ const nodePorts = (options = {}) => {
1346
1499
  stderr: options.stderr ?? ((text) => {
1347
1500
  process.stderr.write(text);
1348
1501
  }),
1349
- defaultDataPath: defaultDataPath(env, home),
1502
+ defaultDataPath: options.dataPath ?? defaultDataPath(env, home),
1350
1503
  openStore: (path) => {
1351
1504
  const warnings = path === inMemory ? [] : prepareDataFile(path, {
1352
1505
  ownsDirectory: path === ownDataPath,
@@ -1385,4 +1538,4 @@ const runProcess = async (identity) => {
1385
1538
  return runCli(process.argv.slice(2), nodePorts({ signal: controller.signal }), identity);
1386
1539
  };
1387
1540
  //#endregion
1388
- export { add as A, blocked as C, path as D, impactOf as E, failureOf as F, findRun as M, loadRun as N, ready as O, CliError as P, swimlaneToMermaid as S, handoffsOf as T, stateMachineToDot as _, defaultModelId as a, sequenceToMermaid as b, validateStored as c, formatNames as d, project as f, sequenceToDot as g, flowchartToDot as h, runProcess as i, setState as j, why as k, sync as l, viewNames as m, nodePorts as n, runCli as o, show as p, packageVersion as r, validateDocument as s, defaultDataPath as t, emit as u, swimlaneToDot as v, cycles as w, stateMachineToMermaid as x, flowchartToMermaid as y };
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 };