my-frontend-observer 0.7.0 → 0.8.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 (104) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +37 -6
  3. package/dist/cli.js +297 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/domain/diagnostics.d.ts +1 -1
  6. package/dist/domain/diagnostics.js +4 -0
  7. package/dist/domain/diagnostics.js.map +1 -1
  8. package/dist/domain/externalReferenceFidelity.d.ts +30 -0
  9. package/dist/domain/externalReferenceFidelity.js +7 -1
  10. package/dist/domain/externalReferenceFidelity.js.map +1 -1
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +3 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/viewer/assets/index-CN_yb9Uf.css +1 -0
  15. package/dist/viewer/assets/index-D0pn5vP_.js +9 -0
  16. package/dist/viewer/icons/icon-192.png +0 -0
  17. package/dist/viewer/icons/icon-512.png +0 -0
  18. package/dist/viewer/index.html +15 -0
  19. package/dist/viewer/manifest.webmanifest +1 -0
  20. package/dist/viewer/registerSW.js +1 -0
  21. package/dist/viewer/sw.js +1 -0
  22. package/dist/viewer/workbox-9c191d2f.js +1 -0
  23. package/dist/viewerServer/context.d.ts +48 -0
  24. package/dist/viewerServer/context.js +60 -0
  25. package/dist/viewerServer/context.js.map +1 -0
  26. package/dist/viewerServer/evidence/classify.d.ts +59 -0
  27. package/dist/viewerServer/evidence/classify.js +124 -0
  28. package/dist/viewerServer/evidence/classify.js.map +1 -0
  29. package/dist/viewerServer/evidence/comparisonView.d.ts +28 -0
  30. package/dist/viewerServer/evidence/comparisonView.js +43 -0
  31. package/dist/viewerServer/evidence/comparisonView.js.map +1 -0
  32. package/dist/viewerServer/evidence/contextSourceView.d.ts +25 -0
  33. package/dist/viewerServer/evidence/contextSourceView.js +20 -0
  34. package/dist/viewerServer/evidence/contextSourceView.js.map +1 -0
  35. package/dist/viewerServer/evidence/discovery.d.ts +31 -0
  36. package/dist/viewerServer/evidence/discovery.js +78 -0
  37. package/dist/viewerServer/evidence/discovery.js.map +1 -0
  38. package/dist/viewerServer/evidence/evaluationView.d.ts +32 -0
  39. package/dist/viewerServer/evidence/evaluationView.js +50 -0
  40. package/dist/viewerServer/evidence/evaluationView.js.map +1 -0
  41. package/dist/viewerServer/evidence/handles.d.ts +21 -0
  42. package/dist/viewerServer/evidence/handles.js +43 -0
  43. package/dist/viewerServer/evidence/handles.js.map +1 -0
  44. package/dist/viewerServer/evidence/index.d.ts +41 -0
  45. package/dist/viewerServer/evidence/index.js +80 -0
  46. package/dist/viewerServer/evidence/index.js.map +1 -0
  47. package/dist/viewerServer/evidence/limits.d.ts +27 -0
  48. package/dist/viewerServer/evidence/limits.js +28 -0
  49. package/dist/viewerServer/evidence/limits.js.map +1 -0
  50. package/dist/viewerServer/evidence/linkedEvidence.d.ts +43 -0
  51. package/dist/viewerServer/evidence/linkedEvidence.js +151 -0
  52. package/dist/viewerServer/evidence/linkedEvidence.js.map +1 -0
  53. package/dist/viewerServer/evidence/mediaResolver.d.ts +16 -0
  54. package/dist/viewerServer/evidence/mediaResolver.js +85 -0
  55. package/dist/viewerServer/evidence/mediaResolver.js.map +1 -0
  56. package/dist/viewerServer/evidence/observationView.d.ts +29 -0
  57. package/dist/viewerServer/evidence/observationView.js +46 -0
  58. package/dist/viewerServer/evidence/observationView.js.map +1 -0
  59. package/dist/viewerServer/evidence/pathSafety.d.ts +11 -0
  60. package/dist/viewerServer/evidence/pathSafety.js +31 -0
  61. package/dist/viewerServer/evidence/pathSafety.js.map +1 -0
  62. package/dist/viewerServer/evidence/projection.d.ts +54 -0
  63. package/dist/viewerServer/evidence/projection.js +152 -0
  64. package/dist/viewerServer/evidence/projection.js.map +1 -0
  65. package/dist/viewerServer/evidence/referenceView.d.ts +133 -0
  66. package/dist/viewerServer/evidence/referenceView.js +169 -0
  67. package/dist/viewerServer/evidence/referenceView.js.map +1 -0
  68. package/dist/viewerServer/httpServer.d.ts +25 -0
  69. package/dist/viewerServer/httpServer.js +381 -0
  70. package/dist/viewerServer/httpServer.js.map +1 -0
  71. package/dist/viewerServer/openBrowser.d.ts +7 -0
  72. package/dist/viewerServer/openBrowser.js +32 -0
  73. package/dist/viewerServer/openBrowser.js.map +1 -0
  74. package/dist/viewerServer/port.d.ts +16 -0
  75. package/dist/viewerServer/port.js +19 -0
  76. package/dist/viewerServer/port.js.map +1 -0
  77. package/dist/viewerServer/viewerService.d.ts +57 -0
  78. package/dist/viewerServer/viewerService.js +83 -0
  79. package/dist/viewerServer/viewerService.js.map +1 -0
  80. package/docs/ARCHITECTURE.md +683 -55
  81. package/docs/CI_CD.md +32 -0
  82. package/docs/COMMANDS.md +182 -2
  83. package/docs/CONTRACTS.md +13 -7
  84. package/docs/CURRENT_STATE.md +158 -18
  85. package/docs/DEVELOPMENT.md +29 -8
  86. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +21 -4
  87. package/docs/PROJECT_DESCRIPTION.md +188 -162
  88. package/docs/PROJECT_OVERVIEW.md +26 -11
  89. package/docs/QUICKSTART.md +7 -0
  90. package/docs/ROADMAP.md +28 -4
  91. package/docs/SECURITY.md +72 -11
  92. package/docs/WORKFLOWS.md +81 -16
  93. package/docs/plans/v0.8-implementation-plan.md +655 -0
  94. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -0
  95. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -0
  96. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -0
  97. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -0
  98. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -0
  99. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -0
  100. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -0
  101. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -0
  102. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -0
  103. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -0
  104. package/package.json +12 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## 0.8.0 - 2026-09-10
6
+
7
+ Interactive Local Observation Viewer.
8
+
9
+ - New `view` command: starts a loopback-only (`127.0.0.1`) Node server
10
+ serving a React + TypeScript + Vite viewer application, usable in a normal
11
+ browser or as an installed Progressive Web App, over an existing evidence
12
+ root (`--root`). Metadata-first evidence discovery and on-demand
13
+ artifact/media loading; never mutates target source or any Observer
14
+ evidence artifact.
15
+ - Observation inspection: screenshot plus SVG target overlays, geometry,
16
+ semantics, visibility/overflow/scroll evidence, and on-demand layout
17
+ relationships.
18
+ - Comparison/contract inspection: before/after side-by-side views and
19
+ contract/change-scope evaluation results, including the required
20
+ protected/preserved-failure safety case (a locally successful requested
21
+ change alongside a genuine protected/preserved regression, shown as
22
+ overall `FAIL`).
23
+ - External reference/candidate inspection: reference image and region
24
+ overlays, explicit (never auto-selected) candidate selection,
25
+ reference/candidate compatibility and applicability.
26
+ - Explicit reference-region/runtime-target binding cross-selection
27
+ (`--bindings-file`), independent bounded zoom/pan, conditional view lock,
28
+ and on-demand reference-fidelity evaluation shown independently alongside
29
+ any selected contract evaluation.
30
+ - Bounded agent context inspection (`--context-file`): session-only display
31
+ of context identity, adequacy, omissions/truncations, and runtime/static
32
+ correlation, plus safe raw-evidence navigation. The viewer never rebuilds
33
+ a bounded context or its correlation and never runs `@dailephd/my-dev-kit`.
34
+ - PWA hardening: real service-worker registration, an application-shell
35
+ precache that excludes `/api/` routes, and a proven server-down behavior
36
+ that never presents stale evidence as current.
37
+ - No second evidence engine: every canonical result the viewer displays is
38
+ produced by the same single engine the CLI uses, called from at most one
39
+ designated server-side call site.
40
+ - Security hardening found during pre-release readiness: the viewer's media
41
+ route now rejects any evidence filename that is itself a symlink/junction
42
+ pointing outside the evidence root, instead of following it.
43
+ - Cross-platform packed-candidate validation: the pre-version-bump
44
+ implementation candidate tarball `my-frontend-observer-0.7.0.tgz`
45
+ (SHA-256 `b80729bc64b3b01378effd2b8aa3b7743ccedc46b3148ba0b1ff1ae5a4b68c55`)
46
+ was hash-verified and proven on Windows, Linux, and macOS, including an
47
+ installed-package smoke of the new `view` command (loopback binding,
48
+ read-only API, path containment, SVG overlay/media, service-worker
49
+ registration, the PWA no-authoritative-cache boundary, and the
50
+ server-down stale-evidence hard gate) alongside every pre-existing
51
+ v0.1-v0.7 packed behavior, before this release's version bump - see
52
+ `docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md`.
53
+
3
54
  ## 0.7.0 - 2026-09-06
4
55
 
5
56
  End-to-End Coding-Agent Frontend Change Review.
package/README.md CHANGED
@@ -6,10 +6,11 @@ in [docs/PROJECT_DESCRIPTION.md](docs/PROJECT_DESCRIPTION.md).
6
6
 
7
7
  ## Current status
8
8
 
9
- `v0.7.0`, End-to-End Coding-Agent Frontend Change Review, is the current
10
- published release. It builds on `v0.6.0`, Bounded Agent Context and Native
11
- my-dev-kit Ecosystem Integration, and `v0.5.0`, Executable Frontend
12
- Contracts and Explicit Change Scope: `my-frontend-observer observe` launches a real,
9
+ `v0.8.0`, Interactive Local Observation Viewer, is the current published
10
+ release. It builds on `v0.7.0`, End-to-End Coding-Agent Frontend Change
11
+ Review, `v0.6.0`, Bounded Agent Context and Native my-dev-kit Ecosystem
12
+ Integration, and `v0.5.0`, Executable Frontend Contracts and Explicit Change
13
+ Scope: `my-frontend-observer observe` launches a real,
13
14
  sandboxed Chromium browser, enforces a loopback-only safety policy, captures
14
15
  a viewport screenshot plus bounded page/target evidence, and persists it as
15
16
  one portable `manifest.json` + `screenshot.png` artifact (observation schema
@@ -170,6 +171,36 @@ this package) makes the actual source change between review attempts. See
170
171
  and [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for the full
171
172
  implementation record.
172
173
 
174
+ ### Interactive local viewer (v0.8.0)
175
+
176
+ `my-frontend-observer view --root <evidence-root> [--bindings-file <json-file>] [--context-file <json-file>] [--port <n>] [--no-open]`
177
+ starts a loopback-only (`127.0.0.1`) Node server that serves a React +
178
+ TypeScript + Vite viewer application - usable in a normal browser or as an
179
+ installed Progressive Web App - over the same evidence root used by every
180
+ other command above. It never edits target source, never mutates any
181
+ evidence artifact, and never runs `@dailephd/my-dev-kit`:
182
+
183
+ ```powershell
184
+ my-frontend-observer view --root observations --port 4319 --no-open
185
+ ```
186
+
187
+ (From a source checkout, use `node dist/cli.js view ...` instead.)
188
+
189
+ The viewer shows observation screenshots and SVG target overlays,
190
+ before/after comparisons and contract/change-scope results, approved
191
+ external references beside candidate observations with explicit binding
192
+ cross-selection and on-demand fidelity evaluation, and - when
193
+ `--context-file` supplies one - a read-only inspection of a bounded agent
194
+ context's adequacy, omissions/truncations, and runtime/static correlation.
195
+ Both `--bindings-file` and `--context-file` are explicit, session-only
196
+ input: read once at startup, held only in server memory, never persisted,
197
+ and never exposed as a filesystem path to the browser. See
198
+ [docs/COMMANDS.md](docs/COMMANDS.md#view) for the full flag reference and
199
+ [docs/WORKFLOWS.md](docs/WORKFLOWS.md) for the viewer workflow.
200
+
201
+ See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for the exact current
202
+ implementation and release state.
203
+
173
204
  Validation:
174
205
 
175
206
  ```powershell
@@ -188,8 +219,8 @@ Planning authorities:
188
219
  intent and responsibility boundaries.
189
220
  - [Project Milestones](docs/PROJECT_MILESTONES.md): complete ordered capability
190
221
  design and cross-milestone rules.
191
- - [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.7 are
192
- released; v0.8+ remain future.
222
+ - [ROADMAP](docs/ROADMAP.md): version-level requirements; v0.1-v0.8 are
223
+ released; v0.9+ remain future.
193
224
  - [Current State](docs/CURRENT_STATE.md): retained scaffold and release state.
194
225
 
195
226
  No sibling ecosystem repository is a runtime dependency of the retained
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync, readFileSync } from 'node:fs';
2
+ import { realpathSync, readFileSync, statSync } from 'node:fs';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { normalizeRequest } from './request/request.js';
5
5
  import { getProducerInfo } from './domain/schema.js';
@@ -9,6 +9,10 @@ import { approveAndPersistBaseline, persistPerChangeContract } from './applicati
9
9
  import { evaluateAndPersistFromArtifactRoots } from './application/frontendContractEvaluationService.js';
10
10
  import { importExternalReference, approveExternalReference } from './application/externalReferencePersistenceService.js';
11
11
  import { evaluateReferenceCandidateFidelityFromArtifactRoots } from './application/referenceFidelityEvaluationService.js';
12
+ import { startViewer } from './viewerServer/viewerService.js';
13
+ import { openInDefaultBrowser } from './viewerServer/openBrowser.js';
14
+ import { DEFAULT_VIEWER_PORT } from './viewerServer/port.js';
15
+ import { classifyContextFileContent, MAX_CONTEXT_FILE_BYTES } from './viewerServer/context.js';
12
16
  const defaultIO = {
13
17
  stdout: (text) => {
14
18
  process.stdout.write(text);
@@ -48,6 +52,9 @@ Commands:
48
52
  reference/candidate compatibility, and explicit
49
53
  region-to-target bindings. Prints a structured
50
54
  result; persists nothing.
55
+ view Start the local, loopback-only viewer server and
56
+ print its URL for a normal browser or an installed
57
+ Progressive Web App.
51
58
 
52
59
  Options:
53
60
  --help Show this help.
@@ -55,6 +62,80 @@ Options:
55
62
 
56
63
  Run "my-frontend-observer <command> --help" for command-specific options.
57
64
  `;
65
+ const VIEW_HELP = `Usage:
66
+ my-frontend-observer view --root <evidence-root> [--bindings-file <json-file>] [--context-file <json-file>] [options]
67
+
68
+ Required:
69
+ --root <path> Local evidence-root directory the viewer session
70
+ represents. Validated operationally (must exist and be a
71
+ directory) - this batch does not read or interpret any
72
+ Observer artifacts under it.
73
+
74
+ Options:
75
+ --port <n> TCP port to bind, in [0, 65535]. Defaults to ${DEFAULT_VIEWER_PORT}.
76
+ An explicit alternate port is a different web origin than
77
+ the default - an installed PWA is not portable across
78
+ origins. If the requested port is already in use, this
79
+ command fails with an actionable error; it never silently
80
+ falls back to a different port.
81
+ --bindings-file <json-file> Local JSON file of the form
82
+ { "bindings": [ { "referenceRegion": "...", "runtimeTarget": "..." } ] }
83
+ (the exact same operational wrapper format as
84
+ \`evaluate-reference-fidelity --bindings-file\`, sharing its
85
+ parser). Explicit, session-only viewer input: read once at
86
+ startup, never persisted, never written into any Observer
87
+ artifact, and never exposed as a path to the browser. Its
88
+ declarations become available for on-demand binding/
89
+ fidelity evaluation once a reference and candidate are
90
+ explicitly selected in the viewer. Reference-specific
91
+ validity (region existence, etc.) is checked when a
92
+ reference is actually selected, not at startup - only the
93
+ file's own readability/JSON/wrapper shape is validated at
94
+ startup. Omit to run with no binding declarations (the
95
+ viewer remains fully usable; cross-selection stays
96
+ disabled).
97
+ --context-file <json-file> Local JSON file containing exactly one
98
+ BoundedAgentContextArtifact value directly (no wrapper
99
+ object) - e.g. { "artifactKind":
100
+ "my-frontend-observer/bounded-agent-context",
101
+ "schemaVersion": "1.0.0", ... }. Explicit, session-only
102
+ viewer input: read once at startup, validated through the
103
+ existing canonical isValidBoundedAgentContextArtifact,
104
+ held only in server memory, never persisted, never written
105
+ into any Observer artifact, and never exposed as a path to
106
+ the browser. Bounded agent context remains programmatic-
107
+ only as an Observer-produced contract - this command does
108
+ not add a way to generate, save, or write one; the viewer
109
+ never rebuilds it (no projectBoundedAgentContext call) and
110
+ never derives runtime/static correlation (no
111
+ deriveRuntimeStaticCorrelations/attachRuntimeStaticCorrelations
112
+ call) - it only displays the exact context it was given.
113
+ A recognized artifactKind with a schemaVersion other than
114
+ the currently supported one starts the viewer showing an
115
+ honest "unsupported version" context state rather than
116
+ failing. An unreadable file, invalid JSON, wrong
117
+ artifactKind, or a structurally invalid current-schema
118
+ artifact fails startup clearly. Omit to run with no
119
+ bounded context supplied (the viewer remains fully usable;
120
+ the context panel says none was supplied). May be combined
121
+ with --bindings-file.
122
+ --no-open Do not attempt to open the system default browser after
123
+ the server starts. Browser auto-open is a best-effort
124
+ convenience only: its failure is never fatal and never
125
+ affects server startup success.
126
+ --help Show this help.
127
+
128
+ Starts one Node HTTP server bound only to 127.0.0.1, serving the built
129
+ React + TypeScript + Vite viewer application (and its PWA manifest/service
130
+ worker) plus one minimal read-only status endpoint. The server never writes
131
+ to the supplied evidence root, never exposes it as a generic static
132
+ directory, and never launches a browser observation. The process keeps
133
+ running (serving the viewer) until interrupted. On success, prints the
134
+ viewer URL and exits only when the server stops. On invalid syntax, a
135
+ missing/non-directory --root, an invalid --port, or a port already in use,
136
+ prints structured diagnostics to stderr and exits nonzero without starting
137
+ a server.
138
+ `;
58
139
  const OBSERVE_HELP = `Usage:
59
140
  my-frontend-observer observe --url <loopback-url> [options]
60
141
 
@@ -715,6 +796,51 @@ function loadBindingsFile(filePath) {
715
796
  }
716
797
  return { ok: true, bindings: record.bindings };
717
798
  }
799
+ /**
800
+ * v0.8 Batch 7 CLI/input-boundary-only responsibility, mirroring
801
+ * `loadBindingsFile`'s exact shape: read one local JSON file (size-bounded
802
+ * via `MAX_CONTEXT_FILE_BYTES`, checked via `statSync` before ever reading
803
+ * the file's bytes), parse it, and hand the parsed value to
804
+ * `classifyContextFileContent` - every artifactKind/schemaVersion/structural
805
+ * rule stays owned there (which itself defers all current-schema structural
806
+ * validation to the existing canonical `isValidBoundedAgentContextArtifact`,
807
+ * never a second validator). Unlike `--bindings-file`, the context file's
808
+ * root IS the artifact value directly (task §12) - no wrapper object. The
809
+ * file path itself is never returned beyond this function.
810
+ */
811
+ function loadContextFile(filePath) {
812
+ let size;
813
+ try {
814
+ size = statSync(filePath).size;
815
+ }
816
+ catch (err) {
817
+ const message = err instanceof Error ? err.message : String(err);
818
+ return { ok: false, error: `--context-file could not be read: ${message}` };
819
+ }
820
+ if (size > MAX_CONTEXT_FILE_BYTES) {
821
+ return { ok: false, error: `--context-file exceeds the bounded size limit (${MAX_CONTEXT_FILE_BYTES} bytes)` };
822
+ }
823
+ let rawText;
824
+ try {
825
+ rawText = readFileSync(filePath, 'utf8');
826
+ }
827
+ catch (err) {
828
+ const message = err instanceof Error ? err.message : String(err);
829
+ return { ok: false, error: `--context-file could not be read: ${message}` };
830
+ }
831
+ let parsed;
832
+ try {
833
+ parsed = JSON.parse(rawText);
834
+ }
835
+ catch (err) {
836
+ const message = err instanceof Error ? err.message : String(err);
837
+ return { ok: false, error: `--context-file is not valid JSON: ${message}` };
838
+ }
839
+ const classified = classifyContextFileContent(parsed);
840
+ if (!classified.ok)
841
+ return { ok: false, error: classified.error };
842
+ return { ok: true, state: classified.state };
843
+ }
718
844
  /**
719
845
  * CLI/input-boundary-only responsibility: read one local JSON file, validate
720
846
  * only the root wrapper this file format owns (object root, exactly the
@@ -1858,6 +1984,173 @@ async function runEvaluateReferenceFidelityCommand(argv, io) {
1858
1984
  return 1;
1859
1985
  return 0;
1860
1986
  }
1987
+ /** CLI-syntax-only parsing, mirroring `parseApproveBaselineArgs`. `--port` shape/range checking happens here; root existence/directory-ness is the application layer's job (see `startViewer`). */
1988
+ function parseViewArgs(argv) {
1989
+ const errors = [];
1990
+ let root;
1991
+ let rootFlagCount = 0;
1992
+ let port;
1993
+ let portFlagCount = 0;
1994
+ let noOpen = false;
1995
+ let bindingsFilePath;
1996
+ let bindingsFileFlagCount = 0;
1997
+ let contextFilePath;
1998
+ let contextFileFlagCount = 0;
1999
+ for (let i = 0; i < argv.length; i += 1) {
2000
+ const arg = argv[i];
2001
+ switch (arg) {
2002
+ case '--root': {
2003
+ const value = argv[(i += 1)];
2004
+ rootFlagCount += 1;
2005
+ if (value === undefined)
2006
+ errors.push('--root requires a path argument');
2007
+ else if (rootFlagCount > 1)
2008
+ errors.push('--root may only be specified once');
2009
+ else
2010
+ root = value;
2011
+ break;
2012
+ }
2013
+ case '--port': {
2014
+ const value = argv[(i += 1)];
2015
+ portFlagCount += 1;
2016
+ if (value === undefined) {
2017
+ errors.push('--port requires a numeric argument');
2018
+ }
2019
+ else if (portFlagCount > 1) {
2020
+ errors.push('--port may only be specified once');
2021
+ }
2022
+ else {
2023
+ const parsed = Number(value);
2024
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
2025
+ errors.push(`--port must be an integer between 0 and 65535; got ${JSON.stringify(value)}`);
2026
+ }
2027
+ else {
2028
+ port = parsed;
2029
+ }
2030
+ }
2031
+ break;
2032
+ }
2033
+ case '--no-open':
2034
+ noOpen = true;
2035
+ break;
2036
+ case '--bindings-file': {
2037
+ const value = argv[(i += 1)];
2038
+ bindingsFileFlagCount += 1;
2039
+ if (value === undefined)
2040
+ errors.push('--bindings-file requires a file path argument');
2041
+ else if (bindingsFileFlagCount > 1)
2042
+ errors.push('--bindings-file may only be specified once');
2043
+ else
2044
+ bindingsFilePath = value;
2045
+ break;
2046
+ }
2047
+ case '--context-file': {
2048
+ const value = argv[(i += 1)];
2049
+ contextFileFlagCount += 1;
2050
+ if (value === undefined)
2051
+ errors.push('--context-file requires a file path argument');
2052
+ else if (contextFileFlagCount > 1)
2053
+ errors.push('--context-file may only be specified once');
2054
+ else
2055
+ contextFilePath = value;
2056
+ break;
2057
+ }
2058
+ default:
2059
+ errors.push(`unrecognized argument: ${arg}`);
2060
+ }
2061
+ }
2062
+ if (root === undefined)
2063
+ errors.push('--root is required');
2064
+ if (errors.length > 0)
2065
+ return { ok: false, errors };
2066
+ return {
2067
+ ok: true,
2068
+ root: root,
2069
+ ...(port === undefined ? {} : { port }),
2070
+ noOpen,
2071
+ ...(bindingsFilePath === undefined ? {} : { bindingsFilePath }),
2072
+ ...(contextFilePath === undefined ? {} : { contextFilePath }),
2073
+ };
2074
+ }
2075
+ /**
2076
+ * Thin orchestration only: parse args, delegate to the existing
2077
+ * `startViewer` application function exactly once, print status, and
2078
+ * optionally attempt a best-effort browser open. Never parses Observer
2079
+ * artifacts, never derives evidence, never mutates anything. Returns as soon
2080
+ * as the server is confirmed listening (or has failed to start) - the
2081
+ * process itself keeps running afterward only because the server's open
2082
+ * listening socket keeps the Node event loop alive, not because this
2083
+ * function blocks.
2084
+ */
2085
+ async function runViewCommand(argv, io) {
2086
+ if (argv.includes('--help')) {
2087
+ io.stdout(VIEW_HELP);
2088
+ return 0;
2089
+ }
2090
+ const parsedArgs = parseViewArgs(argv);
2091
+ if (!parsedArgs.ok) {
2092
+ for (const error of parsedArgs.errors)
2093
+ io.stderr(`error: ${error}\n`);
2094
+ io.stderr(VIEW_HELP);
2095
+ return 1;
2096
+ }
2097
+ // Reuses the exact same operational binding-file wrapper parser as `evaluate-reference-fidelity --bindings-file`
2098
+ // (see loadBindingsFile above) - one shared parser, never a second divergent one. Reference-specific declaration
2099
+ // validity (region existence, shape) is deferred to the moment a reference is actually selected in the viewer,
2100
+ // via the existing canonical isValidReferenceRuntimeBindingDeclarations - never checked here without a reference.
2101
+ let bindingDeclarations = [];
2102
+ if (parsedArgs.bindingsFilePath !== undefined) {
2103
+ const loaded = loadBindingsFile(parsedArgs.bindingsFilePath);
2104
+ if (!loaded.ok) {
2105
+ io.stderr(`error: ${loaded.error}\n`);
2106
+ io.stderr(VIEW_HELP);
2107
+ return 1;
2108
+ }
2109
+ if (!Array.isArray(loaded.bindings)) {
2110
+ io.stderr('error: --bindings-file "bindings" property must be an array\n');
2111
+ io.stderr(VIEW_HELP);
2112
+ return 1;
2113
+ }
2114
+ bindingDeclarations = loaded.bindings;
2115
+ }
2116
+ // Reuses the exact same canonical validator (isValidBoundedAgentContextArtifact, via
2117
+ // classifyContextFileContent) that owns current-schema structural validity - never a second validator.
2118
+ // A recognized-kind, non-current-schema file is not a startup failure (task §16); every other problem is.
2119
+ let contextState = { status: 'none' };
2120
+ if (parsedArgs.contextFilePath !== undefined) {
2121
+ const loaded = loadContextFile(parsedArgs.contextFilePath);
2122
+ if (!loaded.ok) {
2123
+ io.stderr(`error: ${loaded.error}\n`);
2124
+ io.stderr(VIEW_HELP);
2125
+ return 1;
2126
+ }
2127
+ contextState = loaded.state;
2128
+ }
2129
+ const result = await startViewer({
2130
+ root: parsedArgs.root,
2131
+ ...(parsedArgs.port === undefined ? {} : { port: parsedArgs.port }),
2132
+ bindingDeclarations,
2133
+ context: contextState,
2134
+ });
2135
+ if (!result.ok) {
2136
+ for (const diagnostic of result.diagnostics)
2137
+ io.stderr(`${formatDiagnostic(diagnostic)}\n`);
2138
+ return 1;
2139
+ }
2140
+ io.stdout(`Viewer: ${result.url}\n`);
2141
+ io.stdout(`Root: ${result.root}\n`);
2142
+ io.stdout(`Press Ctrl+C to stop.\n`);
2143
+ if (!parsedArgs.noOpen) {
2144
+ try {
2145
+ await openInDefaultBrowser(result.url);
2146
+ }
2147
+ catch (err) {
2148
+ const message = err instanceof Error ? err.message : String(err);
2149
+ io.stderr(`note: could not open the default browser automatically: ${message}\n`);
2150
+ }
2151
+ }
2152
+ return 0;
2153
+ }
1861
2154
  /** Testable CLI entry point: pure function of argv (+ injectable IO), no direct process.exit. */
1862
2155
  export async function runCli(argv, io = defaultIO) {
1863
2156
  const [command, ...rest] = argv;
@@ -1897,6 +2190,9 @@ export async function runCli(argv, io = defaultIO) {
1897
2190
  if (command === 'evaluate-reference-fidelity') {
1898
2191
  return runEvaluateReferenceFidelityCommand(rest, io);
1899
2192
  }
2193
+ if (command === 'view') {
2194
+ return runViewCommand(rest, io);
2195
+ }
1900
2196
  io.stderr(`error: unrecognized command "${command}"\n`);
1901
2197
  io.stderr(TOP_LEVEL_HELP);
1902
2198
  return 1;