@arki/dot 0.1.0 → 0.1.2

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 (52) hide show
  1. package/README.md +36 -31
  2. package/dist/define-app.d.ts +58 -16
  3. package/dist/define-app.d.ts.map +1 -1
  4. package/dist/define-app.js +23 -13
  5. package/dist/define-app.js.map +1 -1
  6. package/dist/index.d.ts +7 -5
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +6 -4
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel/app-instance.d.ts +3 -3
  11. package/dist/kernel/app-instance.d.ts.map +1 -1
  12. package/dist/kernel/app-instance.js +265 -159
  13. package/dist/kernel/app-instance.js.map +1 -1
  14. package/dist/lifecycle.d.ts +11 -9
  15. package/dist/lifecycle.d.ts.map +1 -1
  16. package/dist/lifecycle.js +15 -9
  17. package/dist/lifecycle.js.map +1 -1
  18. package/dist/pip-contract.d.ts +254 -149
  19. package/dist/pip-contract.d.ts.map +1 -1
  20. package/dist/pip-contract.js +185 -41
  21. package/dist/pip-contract.js.map +1 -1
  22. package/dist/pip.d.ts +9 -12
  23. package/dist/pip.d.ts.map +1 -1
  24. package/dist/pip.js +8 -11
  25. package/dist/pip.js.map +1 -1
  26. package/dist/test-harness.d.ts +13 -10
  27. package/dist/test-harness.d.ts.map +1 -1
  28. package/dist/test-harness.js +12 -12
  29. package/dist/test-harness.js.map +1 -1
  30. package/package.json +10 -4
  31. package/src/cli/discover.ts +223 -0
  32. package/src/cli/error-codes.ts +74 -0
  33. package/src/cli/files.ts +120 -0
  34. package/src/cli/index.ts +539 -0
  35. package/src/cli/json.ts +49 -0
  36. package/src/cli/new.ts +420 -0
  37. package/src/cli/observability-probe.ts +51 -0
  38. package/src/cli/render-doctor.ts +199 -0
  39. package/src/cli/render-explain.ts +161 -0
  40. package/src/define-app.ts +310 -0
  41. package/src/diagnostics.ts +91 -0
  42. package/src/index.ts +89 -0
  43. package/src/kernel/app-instance.ts +1341 -0
  44. package/src/kernel/otel.ts +265 -0
  45. package/src/lifecycle-observer.ts +100 -0
  46. package/src/lifecycle.ts +121 -0
  47. package/src/manifest.ts +94 -0
  48. package/src/pip-contract.ts +477 -0
  49. package/src/pip.ts +84 -0
  50. package/src/test-harness.ts +72 -0
  51. package/src/timeline.ts +137 -0
  52. package/templates/app-minimal/AGENTS.md.tmpl +2 -2
@@ -0,0 +1,137 @@
1
+ /**
2
+ * ASCII waterfall renderer for DOT lifecycle diagnostics.
3
+ *
4
+ * Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-pip
5
+ * duration chart suitable for printing in a terminal or embedding in a
6
+ * `dot doctor` text report. No colour, no Unicode beyond `█` so the
7
+ * output stays clean in log aggregators and CI.
8
+ *
9
+ * @example
10
+ * ```
11
+ * Timeline: my-app (state=booted)
12
+ * ─────────────────────────────────────────────────────────
13
+ * configure
14
+ * env 0.5ms █
15
+ * db 1.4ms █████
16
+ * kv 0.3ms █
17
+ *
18
+ * boot
19
+ * env 2.8ms █████████
20
+ * db 32.9ms ███████████████████████████████████████████
21
+ * kv 3.9ms ████████████
22
+ * ─────────────────────────────────────────────────────────
23
+ * ```
24
+ *
25
+ * @see packages/dot/docs/observability.md
26
+ */
27
+
28
+ import type { DotDiagnosticsSnapshot, LifecycleDiagnostic } from './diagnostics.js';
29
+ import type { DotLifecycleHook } from './lifecycle.js';
30
+
31
+ const PHASE_ORDER: readonly DotLifecycleHook[] = ['configure', 'boot', 'start', 'stop', 'dispose'];
32
+
33
+ const BAR_CHAR = '█';
34
+ const DEFAULT_BAR_WIDTH = 50;
35
+ const MIN_BAR_FOR_NONZERO = 1;
36
+
37
+ export type RenderTimelineOptions = {
38
+ /** Maximum bar width in characters (default `50`). */
39
+ readonly barWidth?: number;
40
+ /**
41
+ * When `true`, also emit any pip's diagnostic `issues[]` underneath
42
+ * the bar so failure reasons are visible in the timeline view.
43
+ * Default `true`.
44
+ */
45
+ readonly showIssues?: boolean;
46
+ };
47
+
48
+ /**
49
+ * Render the snapshot's lifecycle records as a per-phase waterfall.
50
+ * Pure — no IO. Caller is responsible for printing the result.
51
+ */
52
+ export function renderTimeline(snapshot: DotDiagnosticsSnapshot, opts: RenderTimelineOptions = {}): string {
53
+ const barWidth = opts.barWidth ?? DEFAULT_BAR_WIDTH;
54
+ const showIssues = opts.showIssues ?? true;
55
+
56
+ // Group lifecycle entries by phase, preserving execution order within
57
+ // each phase. The kernel pushes entries in execution order; we filter
58
+ // and re-order here to make the output deterministic when phases are
59
+ // interleaved (currently they aren't, but be defensive).
60
+ const byPhase = new Map<DotLifecycleHook, LifecycleDiagnostic[]>();
61
+ for (const entry of snapshot.lifecycle) {
62
+ const bucket = byPhase.get(entry.hook);
63
+ if (bucket) bucket.push(entry);
64
+ else byPhase.set(entry.hook, [entry]);
65
+ }
66
+
67
+ // Scale: the longest duration across all entries sets the full-width bar.
68
+ // Using a single global scale lets readers compare durations across
69
+ // phases at a glance ("boot.db is 10x configure.env").
70
+ let maxDuration = 0;
71
+ for (const entry of snapshot.lifecycle) {
72
+ const d = entry.durationMs ?? 0;
73
+ if (d > maxDuration) maxDuration = d;
74
+ }
75
+
76
+ const lines: string[] = [];
77
+ const title = `Timeline: ${snapshot.app.name} (state=${snapshot.app.state})`;
78
+ lines.push(title);
79
+ lines.push('─'.repeat(title.length));
80
+
81
+ // Layout: 2-space indent, 10-char pip column, 8-char duration column, then bar.
82
+ const PIP_COL = 12;
83
+ const DUR_COL = 8;
84
+ const indent = ' ';
85
+
86
+ let anyRendered = false;
87
+ for (const phase of PHASE_ORDER) {
88
+ const entries = byPhase.get(phase);
89
+ if (!entries || entries.length === 0) continue;
90
+ if (anyRendered) lines.push('');
91
+ anyRendered = true;
92
+ lines.push(phase);
93
+ for (const entry of entries) {
94
+ const durationMs = entry.durationMs ?? 0;
95
+ const bar = makeBar(durationMs, maxDuration, barWidth);
96
+ const failedMark = entry.state === 'failed' ? ' ✗' : '';
97
+ const pipCol = entry.pip.padEnd(PIP_COL);
98
+ const durCol = formatDuration(durationMs).padStart(DUR_COL);
99
+ lines.push(`${indent}${pipCol}${durCol} ${bar}${failedMark}`);
100
+ if (showIssues && entry.issues.length > 0) {
101
+ for (const issue of entry.issues) {
102
+ lines.push(`${indent} └─ ${issue.code}: ${issue.message}`);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ if (!anyRendered) {
109
+ lines.push('(no lifecycle entries — app has not run any phase yet)');
110
+ } else {
111
+ lines.push('─'.repeat(title.length));
112
+ }
113
+
114
+ return lines.join('\n');
115
+ }
116
+
117
+ /**
118
+ * Build the bar string. Zero-duration entries get a single block (so they
119
+ * are still visually present); otherwise width is proportional to the
120
+ * global max.
121
+ */
122
+ function makeBar(duration: number, max: number, width: number): string {
123
+ if (max <= 0) return '';
124
+ if (duration <= 0) return BAR_CHAR.repeat(MIN_BAR_FOR_NONZERO);
125
+ const cells = Math.max(MIN_BAR_FOR_NONZERO, Math.round((duration / max) * width));
126
+ return BAR_CHAR.repeat(cells);
127
+ }
128
+
129
+ /**
130
+ * Format a duration in milliseconds with one decimal place. Caps the
131
+ * decimal at sub-second values; values ≥ 1000ms render as integer ms
132
+ * to keep the column tight.
133
+ */
134
+ function formatDuration(ms: number): string {
135
+ if (ms >= 1000) return `${Math.round(ms)}ms`;
136
+ return `${ms.toFixed(1)}ms`;
137
+ }
@@ -36,8 +36,8 @@ command exits zero.
36
36
  app reads belong here.
37
37
  - Don't reach into `@arki/dot` internals (anything under
38
38
  `@arki/dot/kernel`, `@arki/dot/legacy`, or `dist/` subpaths). Use the
39
- public `defineApp` / `defineDotPip` API from `@arki/dot` and the
40
- `defineDotPip` symbol from `@arki/dot/pip`.
39
+ public `defineApp` / `pip` API from `@arki/dot` and the
40
+ `pip` / `service` / `rename` symbols from `@arki/dot/pip`.
41
41
 
42
42
  ## DOT artifact locations
43
43