omkit 0.1.6 → 0.1.8

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.
@@ -70,7 +70,13 @@ export declare class ExecutionTree {
70
70
  private drive;
71
71
  private finalize;
72
72
  private finalizeOnce;
73
- /** The end-of-run links + assert summary, to the terminal (files are already written). */
73
+ /**
74
+ * The end-of-run recap: the run folder, sibling log paths, and the assert tally.
75
+ * Shared verbatim between the terminal (via {@link printSummary}) and the tail of
76
+ * `main.log` (as the footer passed to `writeNodeLogs`), so both close the same way.
77
+ */
78
+ private summaryLines;
79
+ /** The recap to the terminal (files are already written). */
74
80
  private printSummary;
75
81
  private verdictOk;
76
82
  private nodeView;
@@ -243,7 +243,8 @@ export class ExecutionTree {
243
243
  const entries = this.store.entries();
244
244
  const at = (name) => path.join(this.folder.path(), name);
245
245
  const assertSummary = `⊨ ${this.assertionsPassed} passed · ⊭ ${this.assertionsFailed} failed`;
246
- await writeNodeLogs(flat, entries);
246
+ const summary = this.summaryLines(at, assertSummary);
247
+ await writeNodeLogs(flat, entries, summary);
247
248
  await writeRollup(at("events.log"), flat, entries, (e) => e.level === "event");
248
249
  await writeRollup(at("asserts.log"), flat, entries, (e) => e.level === "assert", assertSummary);
249
250
  await writeRollup(at("snapshots.log"), flat, entries, (e) => e.level === "snapshot");
@@ -255,19 +256,30 @@ export class ExecutionTree {
255
256
  process.off("unhandledRejection", this.onUnhandled);
256
257
  this.consoleCapture.uninstall();
257
258
  ExecutionTree.current = null;
258
- this.printSummary(at, assertSummary);
259
+ this.printSummary(summary);
259
260
  if (!this.verdictOk())
260
261
  process.exitCode = 1;
261
262
  }
262
- /** The end-of-run links + assert summary, to the terminal (files are already written). */
263
- printSummary(at, assertSummary) {
263
+ /**
264
+ * The end-of-run recap: the run folder, sibling log paths, and the assert tally.
265
+ * Shared verbatim between the terminal (via {@link printSummary}) and the tail of
266
+ * `main.log` (as the footer passed to `writeNodeLogs`), so both close the same way.
267
+ */
268
+ summaryLines(at, assertSummary) {
269
+ return [
270
+ `om → ${this.folder.path()}`,
271
+ ` main log → ${at("main.log")}`,
272
+ ` events → ${at("events.log")}`,
273
+ ` asserts → ${at("asserts.log")} ${assertSummary}`,
274
+ ` snapshots → ${at("snapshots.log")}`,
275
+ ];
276
+ }
277
+ /** The recap to the terminal (files are already written). */
278
+ printSummary(lines) {
264
279
  const term = this.originalLog;
265
280
  term("");
266
- term(`om → ${this.folder.path()}`);
267
- term(` main log → ${at("main.log")}`);
268
- term(` events → ${at("events.log")}`);
269
- term(` asserts → ${at("asserts.log")} ${assertSummary}`);
270
- term(` snapshots → ${at("snapshots.log")}`);
281
+ for (const line of lines)
282
+ term(line);
271
283
  }
272
284
  verdictOk() {
273
285
  return this.faults.length === 0;
@@ -3,6 +3,8 @@ import type { NodeView } from "./views.ts";
3
3
  /**
4
4
  * Writes each node's own human `.log`: a one-time `ActionRef` header, then that
5
5
  * node's entries (by `nodeId`) prefixed `[seq iso-ts path]`. Rows never repeat
6
- * name/tags — the header carries them.
6
+ * name/tags — the header carries them. The optional `footer` (the run's end-of-run
7
+ * recap — assert tally + sibling log paths) is appended to the **root** log only,
8
+ * so `main.log` closes with the same snippet shown live on the terminal.
7
9
  */
8
- export declare function writeNodeLogs(nodes: readonly NodeView[], entries: readonly LogEntry[]): Promise<void>;
10
+ export declare function writeNodeLogs(nodes: readonly NodeView[], entries: readonly LogEntry[], footer?: readonly string[]): Promise<void>;
@@ -5,19 +5,24 @@ import { entryLine, groupByNode } from "./entryLine.js";
5
5
  /**
6
6
  * Writes each node's own human `.log`: a one-time `ActionRef` header, then that
7
7
  * node's entries (by `nodeId`) prefixed `[seq iso-ts path]`. Rows never repeat
8
- * name/tags — the header carries them.
8
+ * name/tags — the header carries them. The optional `footer` (the run's end-of-run
9
+ * recap — assert tally + sibling log paths) is appended to the **root** log only,
10
+ * so `main.log` closes with the same snippet shown live on the terminal.
9
11
  */
10
- export async function writeNodeLogs(nodes, entries) {
12
+ export async function writeNodeLogs(nodes, entries, footer = []) {
11
13
  const byNode = groupByNode(entries, () => true);
12
14
  const refByNode = new Map(nodes.map((n) => [n.id, n]));
13
15
  const refOf = (id) => refByNode.get(id);
14
16
  await Promise.all(nodes.map(async (node) => {
15
17
  const lines = (byNode.get(node.id) ?? []).map((e) => entryLine(e, refOf));
16
- const body = [...header(node), "", ...lines, ""].join("\n");
18
+ const foot = node.parentId === null && footer.length ? ["", summaryRule, ...footer] : [];
19
+ const body = [...header(node), "", ...lines, ...foot, ""].join("\n");
17
20
  await mkdir(path.dirname(node.logFile), { recursive: true });
18
21
  await writeFile(node.logFile, body, "utf8");
19
22
  }));
20
23
  }
24
+ /** The divider that opens the root log's end-of-run recap (mirrors the header's `── legend ──`). */
25
+ const summaryRule = "── summary ──────────────────────────────────────────────────────────";
21
26
  /** The per-file metadata header: ref, defining script, args, start/finish times, status. */
22
27
  const header = (node) => [
23
28
  `# ${renderRef(node)}`,
@@ -27,7 +32,18 @@ const header = (node) => [
27
32
  `# started: ${stamp(node.startedAt)}`,
28
33
  `# finished: ${stamp(node.endedAt)} (${node.duration}ms)`,
29
34
  `# status: ${node.status}`,
35
+ ...(node.parentId === null ? legend : []), // the run's root log (main.log) only
30
36
  ].filter((line) => line !== null);
37
+ /** A compact reading guide, printed once on the root log so an agent can follow the run. */
38
+ const legend = [
39
+ "#",
40
+ "# ── legend ──────────────────────────────────────────────────────────",
41
+ "# [n] global sequence — the merge/order key across every .log",
42
+ "# indented rows a child action's milestones, bubbled up here",
43
+ "# name [tags] the action and its tags · defined … where its code lives",
44
+ "# launch · → ….log a child started — open that file for its full output",
45
+ "# ⚡ event ✓ done ok ⊘ cancelled ✗ failed ⊨/⊭ assert pass/fail 📸 snapshot ▪ output",
46
+ ];
31
47
  const stamp = (ms) => (ms ? new Date(ms).toISOString() : "-");
32
48
  const renderArgs = (args) => {
33
49
  try {
@@ -6,7 +6,11 @@ import { label } from "../../foundation/ActionRef.js";
6
6
  * the child by its {@link label} (name + tags) via `refOf`; everything else keeps its
7
7
  * `source` (proc name, `console`, …).
8
8
  */
9
- export const entryLine = (e, refOf) => `[${e.sequence}] ${bodyOf(e, refOf)}`;
9
+ export const entryLine = (e, refOf) => {
10
+ // Bubbled child rows are nested a level in — the whole row, `[seq]` included.
11
+ const indent = e.level === "child" ? " " : "";
12
+ return `${indent}[${e.sequence}] ${bodyOf(e, refOf)}`;
13
+ };
10
14
  const bodyOf = (e, refOf) => {
11
15
  switch (e.level) {
12
16
  case "event":
@@ -24,9 +28,9 @@ const bodyOf = (e, refOf) => {
24
28
  // output. (Cancellation no longer logs at this level — it's a clean stop.)
25
29
  return e.source === "error" ? `✗ ${e.message}` : `▪ ${e.message}`;
26
30
  case "child": {
27
- // A bubbled child milestone carries the child's id as `source`: name it with its
28
- // tags (`name [tags]`) so the parent timeline always shows them. The launch
29
- // pointer (`source === "launch"`) keeps its `→ id · logfile` message as-is.
31
+ // A bubbled child line (indented at the row level by `entryLine`). The launch pointer
32
+ // (`source === "launch"`) keeps its `→ id · logfile` message; a milestone names the
33
+ // child by its label (name + tags) via `refOf`.
30
34
  if (e.source === "launch")
31
35
  return `${e.source} · ${e.message}`;
32
36
  const ref = refOf?.(e.source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omkit",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A tiny runtime for developer workflows: typed actions that pass runtime capabilities and emit typed events, under one run and one shared log. The operational/runtime layer to tskb's knowledge layer.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",