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.
- package/CHANGELOG.md +51 -0
- package/README.md +37 -6
- package/dist/cli.js +297 -1
- package/dist/cli.js.map +1 -1
- package/dist/domain/diagnostics.d.ts +1 -1
- package/dist/domain/diagnostics.js +4 -0
- package/dist/domain/diagnostics.js.map +1 -1
- package/dist/domain/externalReferenceFidelity.d.ts +30 -0
- package/dist/domain/externalReferenceFidelity.js +7 -1
- package/dist/domain/externalReferenceFidelity.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/viewer/assets/index-CN_yb9Uf.css +1 -0
- package/dist/viewer/assets/index-D0pn5vP_.js +9 -0
- package/dist/viewer/icons/icon-192.png +0 -0
- package/dist/viewer/icons/icon-512.png +0 -0
- package/dist/viewer/index.html +15 -0
- package/dist/viewer/manifest.webmanifest +1 -0
- package/dist/viewer/registerSW.js +1 -0
- package/dist/viewer/sw.js +1 -0
- package/dist/viewer/workbox-9c191d2f.js +1 -0
- package/dist/viewerServer/context.d.ts +48 -0
- package/dist/viewerServer/context.js +60 -0
- package/dist/viewerServer/context.js.map +1 -0
- package/dist/viewerServer/evidence/classify.d.ts +59 -0
- package/dist/viewerServer/evidence/classify.js +124 -0
- package/dist/viewerServer/evidence/classify.js.map +1 -0
- package/dist/viewerServer/evidence/comparisonView.d.ts +28 -0
- package/dist/viewerServer/evidence/comparisonView.js +43 -0
- package/dist/viewerServer/evidence/comparisonView.js.map +1 -0
- package/dist/viewerServer/evidence/contextSourceView.d.ts +25 -0
- package/dist/viewerServer/evidence/contextSourceView.js +20 -0
- package/dist/viewerServer/evidence/contextSourceView.js.map +1 -0
- package/dist/viewerServer/evidence/discovery.d.ts +31 -0
- package/dist/viewerServer/evidence/discovery.js +78 -0
- package/dist/viewerServer/evidence/discovery.js.map +1 -0
- package/dist/viewerServer/evidence/evaluationView.d.ts +32 -0
- package/dist/viewerServer/evidence/evaluationView.js +50 -0
- package/dist/viewerServer/evidence/evaluationView.js.map +1 -0
- package/dist/viewerServer/evidence/handles.d.ts +21 -0
- package/dist/viewerServer/evidence/handles.js +43 -0
- package/dist/viewerServer/evidence/handles.js.map +1 -0
- package/dist/viewerServer/evidence/index.d.ts +41 -0
- package/dist/viewerServer/evidence/index.js +80 -0
- package/dist/viewerServer/evidence/index.js.map +1 -0
- package/dist/viewerServer/evidence/limits.d.ts +27 -0
- package/dist/viewerServer/evidence/limits.js +28 -0
- package/dist/viewerServer/evidence/limits.js.map +1 -0
- package/dist/viewerServer/evidence/linkedEvidence.d.ts +43 -0
- package/dist/viewerServer/evidence/linkedEvidence.js +151 -0
- package/dist/viewerServer/evidence/linkedEvidence.js.map +1 -0
- package/dist/viewerServer/evidence/mediaResolver.d.ts +16 -0
- package/dist/viewerServer/evidence/mediaResolver.js +85 -0
- package/dist/viewerServer/evidence/mediaResolver.js.map +1 -0
- package/dist/viewerServer/evidence/observationView.d.ts +29 -0
- package/dist/viewerServer/evidence/observationView.js +46 -0
- package/dist/viewerServer/evidence/observationView.js.map +1 -0
- package/dist/viewerServer/evidence/pathSafety.d.ts +11 -0
- package/dist/viewerServer/evidence/pathSafety.js +31 -0
- package/dist/viewerServer/evidence/pathSafety.js.map +1 -0
- package/dist/viewerServer/evidence/projection.d.ts +54 -0
- package/dist/viewerServer/evidence/projection.js +152 -0
- package/dist/viewerServer/evidence/projection.js.map +1 -0
- package/dist/viewerServer/evidence/referenceView.d.ts +133 -0
- package/dist/viewerServer/evidence/referenceView.js +169 -0
- package/dist/viewerServer/evidence/referenceView.js.map +1 -0
- package/dist/viewerServer/httpServer.d.ts +25 -0
- package/dist/viewerServer/httpServer.js +381 -0
- package/dist/viewerServer/httpServer.js.map +1 -0
- package/dist/viewerServer/openBrowser.d.ts +7 -0
- package/dist/viewerServer/openBrowser.js +32 -0
- package/dist/viewerServer/openBrowser.js.map +1 -0
- package/dist/viewerServer/port.d.ts +16 -0
- package/dist/viewerServer/port.js +19 -0
- package/dist/viewerServer/port.js.map +1 -0
- package/dist/viewerServer/viewerService.d.ts +57 -0
- package/dist/viewerServer/viewerService.js +83 -0
- package/dist/viewerServer/viewerService.js.map +1 -0
- package/docs/ARCHITECTURE.md +683 -55
- package/docs/CI_CD.md +32 -0
- package/docs/COMMANDS.md +182 -2
- package/docs/CONTRACTS.md +13 -7
- package/docs/CURRENT_STATE.md +158 -18
- package/docs/DEVELOPMENT.md +29 -8
- package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +21 -4
- package/docs/PROJECT_DESCRIPTION.md +188 -162
- package/docs/PROJECT_OVERVIEW.md +26 -11
- package/docs/QUICKSTART.md +7 -0
- package/docs/ROADMAP.md +28 -4
- package/docs/SECURITY.md +72 -11
- package/docs/WORKFLOWS.md +81 -16
- package/docs/plans/v0.8-implementation-plan.md +655 -0
- package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -0
- package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -0
- package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -0
- package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -0
- package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -0
- package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -0
- package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -0
- package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -0
- package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -0
- package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -0
- 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.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
192
|
-
released; v0.
|
|
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;
|