codehere 0.8.0 → 1.0.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 (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,983 +0,0 @@
1
- // Copyright 2026
2
- /**
3
- * /verify · verify-at-the-surface dashboard (the Claude-team way).
4
- *
5
- * Modeled on Anthropic's how-we-claude-code/phase-3-verify harness, adapted to
6
- * codehere's reality: the product isn't React components, it's a server-rendered
7
- * page. So the "unit × fixture" matrix becomes "honesty-invariant × seeded
8
- * session", and the surface we observe is the REAL product page (`/`) loaded in
9
- * a same-origin iframe — never a clone. We drive it via the real showD(), read
10
- * the data-verify-* DOM contract back, and compute PASS / FAIL / BLOCKED.
11
- *
12
- * - window.__verify.manifest() — discover every invariant × fixture
13
- * - window.__verify.runAll() — drive the whole matrix, return structured results
14
- * - window.__verify.current() — the last single result (what replay is showing)
15
- * Agent, human (Run all), and CI (scripts/verify.mjs) all read this one handle.
16
- *
17
- * The probe fixture (demo-probe-lie-v1) is DESIGNED to fail H5: its outcome
18
- * headline claims 3 files but renders 1. A green there means the harness is
19
- * blind, so CI treats a non-failing probe as a hard failure.
20
- *
21
- * Replay mode (?mode=replay) drives the real iframe in slow motion with a
22
- * highlight ring + caption (the visibleAct pattern) and can record one .webm
23
- * per fixture in-browser, or be driven headless by scripts/record-replay.mjs.
24
- */
25
- export const VERIFY_HTML = `<!doctype html>
26
- <html lang="en">
27
- <head>
28
- <meta charset="utf-8" />
29
- <meta name="viewport" content="width=device-width, initial-scale=1" />
30
- <title>codehere · /verify</title>
31
- <style>
32
- :root{
33
- --bg:hsl(0 0% 3.9%); --bg-1:hsl(0 0% 6%); --bg-2:hsl(0 0% 9%);
34
- --fg:hsl(0 0% 98%); --fg-70:rgba(255,255,255,.70); --fg-50:rgba(255,255,255,.50);
35
- --fg-40:rgba(255,255,255,.40); --fg-30:rgba(255,255,255,.30);
36
- --border:rgba(255,255,255,.10); --border-soft:rgba(255,255,255,.06);
37
- --accent:#ff9a76; --accent-wash:rgba(255,154,118,.10);
38
- --allow:#28c840; --block:#ff5f57; --warn:#febc2e; --info:#76b7ff;
39
- --radius:8px;
40
- --sans:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
41
- --mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
42
- }
43
- *{box-sizing:border-box}
44
- body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:13px;line-height:1.55;-webkit-font-smoothing:antialiased;
45
- background-image:radial-gradient(circle,rgba(74,144,226,.045) 1px,transparent 1px);background-size:24px 24px;}
46
- a{color:inherit}
47
- header.top{display:flex;align-items:center;gap:14px;padding:10px 20px;border-bottom:1px solid var(--border-soft);background:rgba(0,0,0,.55);backdrop-filter:blur(8px);position:sticky;top:0;z-index:5;flex-wrap:wrap}
48
- .logo{font-size:15px;font-weight:700;display:inline-flex;align-items:center;gap:7px;letter-spacing:-.01em}
49
- .logo .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent);animation:pulse 2s infinite}
50
- @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
51
- .logo .name{background:linear-gradient(135deg,#ff9a76 0%,#4a90e2 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
52
- .route{color:var(--fg-40);font-family:var(--mono);font-size:12px}
53
- .pill{font-size:10.5px;padding:3px 8px;border:1px solid var(--border);border-radius:999px;color:var(--fg-50)}
54
- .pill.local{border-color:rgba(40,200,64,.3);color:var(--allow)}
55
- .spacer{flex:1}
56
- .hint{color:var(--fg-40);font-size:11.5px}
57
- .hint code{font-family:var(--mono);color:var(--info)}
58
- .wrap{max-width:1180px;margin:0 auto;padding:22px 20px 80px}
59
- .actions{display:flex;align-items:center;gap:12px;margin:18px 0 8px;flex-wrap:wrap}
60
- button.run{background:linear-gradient(135deg,#ff9a76,#ff7a4d);color:#1a0f08;border:0;font-weight:700;font-size:13px;padding:9px 18px;border-radius:var(--radius);cursor:pointer;box-shadow:0 0 0 1px rgba(255,154,118,.3),0 8px 24px -8px rgba(255,154,118,.5)}
61
- button.run:disabled{opacity:.6;cursor:default}
62
- .linkbtn{color:var(--fg-70);text-decoration:none;border:1px solid var(--border);padding:8px 14px;border-radius:var(--radius);font-size:12.5px;cursor:pointer;background:transparent}
63
- .linkbtn:hover{border-color:var(--accent);color:var(--fg)}
64
- .tally{display:inline-flex;gap:14px;align-items:center;font-family:var(--mono);font-size:13px;margin-left:6px}
65
- .tally .p{color:var(--allow)} .tally .f{color:var(--block)} .tally .b{color:var(--fg-40)} .tally .t{color:var(--fg-50)}
66
- .verdict-line{font-size:12px;margin:4px 0 0}
67
- .verdict-line.green{color:var(--allow)} .verdict-line.red{color:var(--block)}
68
- .legend{display:flex;gap:18px;margin:8px 0 0;font-size:11.5px;color:var(--fg-50);flex-wrap:wrap}
69
- .legend b{font-family:var(--mono)}
70
- h2.section{font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--fg-40);margin:26px 0 10px;font-weight:600}
71
- .verifiers{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px}
72
- .vchip{border:1px solid var(--border);border-radius:999px;padding:4px 11px;font-size:11.5px;color:var(--fg-70)}
73
- .vchip code{font-family:var(--mono);color:var(--accent)}
74
- table.matrix{width:100%;border-collapse:collapse;font-size:12.5px;border:1px solid var(--border-soft);border-radius:var(--radius);overflow:hidden}
75
- table.matrix th,table.matrix td{padding:9px 11px;text-align:left;border-bottom:1px solid var(--border-soft)}
76
- table.matrix thead th{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-40);background:var(--bg-1);font-weight:600;vertical-align:bottom}
77
- table.matrix tbody tr:hover{background:rgba(255,255,255,.02)}
78
- .unit-id{font-family:var(--mono);color:var(--fg)}
79
- .unit-desc{color:var(--fg-50);font-size:11.5px}
80
- .needs{font-family:var(--mono);color:var(--fg-30);font-size:11px}
81
- .cell{display:inline-flex;align-items:center;justify-content:center;width:26px;height:22px;border-radius:5px;font-size:12px;cursor:pointer;border:0;background:transparent;color:var(--fg-30);font-family:var(--mono)}
82
- .cell.pass{background:rgba(40,200,64,.12);color:var(--allow)}
83
- .cell.fail{background:rgba(255,95,87,.16);color:var(--block);box-shadow:0 0 0 1px rgba(255,95,87,.5)}
84
- .cell.fail.expected{animation:flag 1.6s infinite}
85
- .cell.blocked{background:rgba(255,255,255,.05);color:var(--fg-40)}
86
- @keyframes flag{0%,100%{box-shadow:0 0 0 1px rgba(255,95,87,.5)}50%{box-shadow:0 0 0 1px rgba(255,95,87,1),0 0 12px rgba(255,95,87,.5)}}
87
- .fixhdr{font-family:var(--mono);font-size:10.5px;color:var(--fg-50);writing-mode:vertical-rl;transform:rotate(180deg);white-space:nowrap;height:96px}
88
- .fixhdr.probe{color:var(--block)}
89
- .probe-row td{background:rgba(255,95,87,.04)}
90
- .probe-tag{font-size:9.5px;color:var(--block);border:1px solid rgba(255,95,87,.4);border-radius:4px;padding:1px 5px;margin-left:8px;font-family:var(--mono)}
91
- .note{color:var(--fg-40);font-size:11.5px;margin-top:8px}
92
- .note code{font-family:var(--mono);color:var(--info)}
93
- /* replay */
94
- .grid2{display:grid;grid-template-columns:1.4fr 1fr;gap:18px;margin-top:6px}
95
- .stage-wrap{border:1px solid var(--border);border-radius:10px;overflow:hidden;background:var(--bg-1);position:relative}
96
- .stage-wrap.v-pass{box-shadow:0 0 0 1px rgba(40,200,64,.4)}
97
- .stage-wrap.v-fail{box-shadow:0 0 0 1px rgba(255,95,87,.55)}
98
- .stage-wrap.v-blocked{box-shadow:0 0 0 1px rgba(255,255,255,.12)}
99
- .titlebar{display:flex;align-items:center;gap:7px;padding:8px 12px;background:var(--bg-2);border-bottom:1px solid var(--border-soft)}
100
- .tl-dot{width:11px;height:11px;border-radius:50%}.tl-dot.r{background:#ff5f57}.tl-dot.y{background:#febc2e}.tl-dot.g{background:#28c840}
101
- .tl-title{margin-left:8px;font-size:11.5px;color:var(--fg-70)}.tl-title .dim{color:var(--fg-30);font-family:var(--mono)}
102
- iframe#surface{width:100%;height:560px;border:0;display:block;background:var(--bg)}
103
- .checklist{position:absolute;top:46px;right:14px;width:226px;background:rgba(0,0,0,.82);border:1px solid var(--border);border-radius:10px;padding:10px 12px;z-index:52;font-size:11.5px;backdrop-filter:blur(6px);opacity:0;transition:opacity .2s}
104
- .checklist.on{opacity:1}
105
- .checklist .cl-h{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-40);margin-bottom:7px}
106
- .checklist .cl-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;color:var(--fg-50);transition:color .2s}
107
- .checklist .cl-row.run{color:var(--fg)}
108
- .checklist .cl-row .cl-id{font-family:var(--mono);font-weight:700;width:22px}
109
- .checklist .cl-row .cl-st{width:14px;text-align:center}
110
- .checklist .cl-row.pass .cl-id{color:var(--allow)} .checklist .cl-row.fail .cl-id{color:var(--block)} .checklist .cl-row.blocked .cl-id{color:var(--fg-40)}
111
- .ring{position:fixed;border:2px solid var(--accent);border-radius:8px;box-shadow:0 0 0 4px rgba(255,154,118,.25),0 0 18px rgba(255,154,118,.6);pointer-events:none;z-index:50;animation:ringpulse 1s infinite;transition:all .25s ease}
112
- @keyframes ringpulse{0%,100%{opacity:1}50%{opacity:.55}}
113
- .caption{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.88);border:1px solid var(--border);border-radius:999px;padding:7px 16px;font-size:12.5px;color:var(--fg);font-family:var(--mono);white-space:nowrap;z-index:51;opacity:0;transition:opacity .2s}
114
- .caption.on{opacity:1}
115
- .framestatus{padding:10px 14px;border-top:1px solid var(--border-soft);font-size:12px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
116
- .framestatus .badge{font-family:var(--mono);font-weight:700;padding:2px 9px;border-radius:5px}
117
- .badge.pass{color:var(--allow);background:rgba(40,200,64,.12)} .badge.fail{color:var(--block);background:rgba(255,95,87,.14)} .badge.blocked{color:var(--fg-40);background:rgba(255,255,255,.05)}
118
- .framestatus .issues{color:var(--block);font-size:11.5px}
119
- .framestatus .issues.blk{color:var(--fg-40)}
120
- .replay-side h3{font-size:12px;color:var(--fg-70);margin:0 0 10px}
121
- .ctrlrow{display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
122
- .progress{height:5px;background:var(--bg-2);border-radius:3px;overflow:hidden;margin:8px 0 14px}
123
- .progress>div{height:100%;width:0;background:linear-gradient(90deg,#ff9a76,#4a90e2);transition:width .3s}
124
- .steplist{font-size:12px}
125
- .steprow{display:flex;align-items:center;gap:8px;padding:5px 7px;border-radius:6px;cursor:pointer}
126
- .steprow.active{background:var(--accent-wash)}
127
- .steprow .st{width:16px;text-align:center}
128
- .steprow .nm{font-family:var(--mono);font-size:11.5px;color:var(--fg-70)}
129
- .steprow.fail .nm{color:var(--block)}
130
- .clipnote{margin-top:14px;font-size:11.5px;color:var(--fg-40)}
131
- .clipnote code{font-family:var(--mono);color:var(--info)}
132
- details.manifest{margin-top:26px;border:1px solid var(--border-soft);border-radius:var(--radius);padding:10px 14px}
133
- details.manifest summary{cursor:pointer;color:var(--fg-50);font-size:12px}
134
- details.manifest pre{font-family:var(--mono);font-size:11px;color:var(--fg-50);overflow:auto;margin:10px 0 0;max-height:340px}
135
- .hidden{display:none !important}
136
- </style>
137
- </head>
138
- <body data-verify-page="dashboard">
139
- <header class="top">
140
- <span class="logo"><span class="dot"></span><span class="name">codehere</span></span>
141
- <span class="route">/verify</span>
142
- <span class="pill local">&#9679; local &middot; zero telemetry</span>
143
- <span class="spacer"></span>
144
- <span class="hint">agent handle: <code>window.__verify</code> &mdash; <code>__verify.manifest()</code> &middot; <code>await __verify.runAll()</code></span>
145
- </header>
146
-
147
- <div class="wrap">
148
- <div class="actions">
149
- <button class="run" id="run-all">&#9654; Run all</button>
150
- <button class="linkbtn" id="toggle-replay">&#9654; Replay all (slow-mo)</button>
151
- <span class="tally" id="tally"><span class="t">not run yet &mdash; click Run all</span></span>
152
- </div>
153
- <div class="verdict-line" id="verdict-line"></div>
154
- <div class="legend">
155
- <span><b style="color:var(--allow)">PASS</b> observed &amp; honest</span>
156
- <span><b style="color:var(--block)">FAIL</b> observed &amp; lying</span>
157
- <span><b style="color:var(--fg-40)">BLOCKED</b> 0 units to observe &mdash; never a silent green</span>
158
- <span><b>&#128269; probe</b> designed-to-fail self-test</span>
159
- </div>
160
-
161
- <h2 class="section">Pluggable verifiers</h2>
162
- <div class="verifiers">
163
- <span class="vchip"><code>contract</code> &mdash; reads data-verify-* off the rendered surface</span>
164
- <span class="vchip"><code>invariants</code> &mdash; runs each honesty predicate against the DOM</span>
165
- <span class="vchip"><code>counts</code> &mdash; claimed numbers must equal rendered elements</span>
166
- </div>
167
-
168
- <h2 class="section">Matrix &middot; honesty invariant &times; seeded session</h2>
169
- <table class="matrix" id="matrix"><thead></thead><tbody></tbody></table>
170
- <p class="note" id="probe-note"></p>
171
-
172
- <h2 class="section">Feature coverage &middot; v0 &rarr; v1 MVP &mdash; does each PRODUCT_STATE claim actually render?</h2>
173
- <div class="actions">
174
- <button class="run" id="run-cov">&#9654; Run coverage</button>
175
- <span class="tally" id="cov-tally"><span class="t">not run yet</span></span>
176
- </div>
177
- <div class="verdict-line" id="cov-verdict"></div>
178
- <div class="legend">
179
- <span><b style="color:var(--allow)">OBSERVED</b> claimed shipped &amp; rendered</span>
180
- <span><b style="color:var(--block)">MISSING</b> claimed shipped, NOT rendered (doc lies)</span>
181
- <span><b style="color:var(--warn)">PARTIAL-GAP</b> 🟡 not yet visible</span>
182
- <span><b style="color:var(--fg-40)">SKIP</b> roadmap / future (correctly absent)</span>
183
- </div>
184
- <table class="matrix" id="cov-matrix"><thead><tr><th style="width:52%">Feature (PRODUCT_STATE id)</th><th class="needs" style="width:12%">tier</th><th class="needs" style="width:12%">surface</th><th>state</th></tr></thead><tbody></tbody></table>
185
-
186
- <div id="replay" class="hidden">
187
- <h2 class="section">Replay &middot; drive the real UI in slow motion</h2>
188
- <div class="grid2">
189
- <div class="stage-wrap" id="stage-wrap">
190
- <div class="titlebar">
191
- <span class="tl-dot r"></span><span class="tl-dot y"></span><span class="tl-dot g"></span>
192
- <span class="tl-title" id="stage-title">feature &middot; <strong>session thread</strong> <span class="dim">/ &mdash;</span></span>
193
- </div>
194
- <iframe id="surface" title="codehere product surface" src="/"></iframe>
195
- <div class="checklist" id="checklist"></div>
196
- <div class="caption" id="caption"></div>
197
- <div class="framestatus" id="framestatus"><span class="sub" style="color:var(--fg-40)">press Play</span></div>
198
- </div>
199
- <div class="replay-side">
200
- <h3>Replay controls</h3>
201
- <div class="ctrlrow">
202
- <button class="linkbtn" id="play">&#9654; Play</button>
203
- <button class="linkbtn" id="skip">&#9197; Skip</button>
204
- <button class="linkbtn" id="record">&#9210; Record clips</button>
205
- <span class="hint" id="dwell-label">dwell 1500ms</span>
206
- </div>
207
- <div class="progress"><div id="prog"></div></div>
208
- <div class="steplist" id="steplist"></div>
209
- <div class="clipnote">Each fixture &rarr; its own <code>.webm</code> (in-browser, pick this tab). Headless: <code>node scripts/record-replay.mjs</code> hands you one webm, no setup.</div>
210
- </div>
211
- </div>
212
- </div>
213
-
214
- <details class="manifest">
215
- <summary>Raw manifest (what an agent / CI sees &mdash; same truth as this page)</summary>
216
- <pre id="manifest-json"></pre>
217
- </details>
218
- </div>
219
-
220
- <script>
221
- (function(){
222
- "use strict";
223
-
224
- /* -------- fixtures: the seeded sessions (the product's real surfaces) -------- */
225
- var FIXTURES = [
226
- { id:"demo-rate-limit-shipped-v0.9", label:"rate-limit-shipped" },
227
- { id:"demo-recovery-test-failed-v0.9", label:"recovery-failed" },
228
- { id:"demo-crash-orphaned-mid-write-v0.9",label:"crash-orphaned" },
229
- { id:"demo-hook-block-bulk-rm-v0.9", label:"hook-block-rm" },
230
- { id:"demo-stalled-opencode-hung-v1", label:"stalled-hung" },
231
- { id:"demo-non-git-honest-v1", label:"non-git-honest" },
232
- { id:"demo-running-streaming-v0.9", label:"running-stream" },
233
- { id:"demo-probe-lie-v1", label:"probe-lie", probe:true, expectFail:"H5" }
234
- ];
235
-
236
- /* -------- invariants: the honesty contracts (the "units") --------
237
- needs(s) -> can this invariant observe anything here? (else BLOCKED)
238
- check(s) -> [] when honest, or violation strings when lying (FAIL) */
239
- var INVARIANTS = [
240
- { id:"H1", title:"no-verified-without-git", needs:"verifier",
241
- desc:"verdict is not 'verified' when git is untracked",
242
- can:function(s){ return s.verifiers.length>0; },
243
- check:function(s){ return s.verifiers.filter(function(v){ return v.gittracked==="false" && v.verdict==="verified"; }).map(function(){ return "verifier verdict=verified while git untracked (overclaim)"; }); } },
244
- { id:"H2", title:"say-never-bare", needs:"say",
245
- desc:"every reasoning bubble has real words, not the bare label",
246
- can:function(s){ return s.says.length>0; },
247
- check:function(s){ return s.says.filter(function(x){ return x.len===0 || x.text.toLowerCase()==="say"; }).map(function(){ return "bare 'say' bubble with no agent words"; }); } },
248
- { id:"H3", title:"no-empty-outcome", needs:"terminal outcome",
249
- desc:"a finished outcome panel renders observable content, not an empty box",
250
- can:function(s){ return s.hasOutcome && s.terminal; },
251
- check:function(s){ return s.outcomeItems>0 ? [] : ["terminal Outcome panel rendered 0 items (empty container)"]; } },
252
- { id:"H5", title:"counts-consistent", needs:"outcome+files",
253
- desc:"the 'N files changed' headline equals the rendered file rows",
254
- can:function(s){ return s.hasOutcome && s.filesClaimed>0 && s.hasFiletree; },
255
- check:function(s){ return s.filesClaimed===s.filetreeRendered ? [] : ["claims "+s.filesClaimed+" files changed but renders "+s.filetreeRendered+" file row(s)"]; } },
256
- { id:"H6", title:"fail-has-honest-reason", needs:"terminal-fail",
257
- desc:"a failed / killed / crashed run shows a plain reason, never a bare dot",
258
- can:function(s){ return s.terminalFail; },
259
- check:function(s){ return s.hasExplanation ? [] : ["failed session shows no recovery / crash / stall explanation"]; } }
260
- ];
261
-
262
- var PROTO_VERSION = "codehere-verify/1";
263
-
264
- /* -------- feature coverage: every granular PRODUCT_STATE claim, v0->v1 --------
265
- Turns the ~150-row inventory's claims into watchable checks against the REAL
266
- surface. status drives the verdict:
267
- shipped (✅/🟢) -> element MUST render -> OBSERVED, else MISSING (the claim is a lie)
268
- partial (🟡) -> render if present -> OBSERVED, else PARTIAL-GAP (honest, not a lie)
269
- roadmap (⛔) -> SKIP-ROADMAP (planned, not built — correctly absent)
270
- future (🟦/cut) -> SKIP-FUTURE (deferred by design / cut)
271
- surface: 'list' = dashboard · 'detail' = opens demo-rate-limit-shipped-v0.9.
272
- sel = real CSS selector confirmed against the live DOM (not invented). */
273
- var COV_FIXTURE = "demo-rate-limit-shipped-v0.9";
274
- var FEATURES = [
275
- { id:"F-AUDIT-FOOTER", name:"Audit chain footer", surface:"list", sel:".audit-chain-panel", status:"shipped", tier:"v0.7" },
276
- { id:"F-BUDGET-CHIP", name:"Budget pill", surface:"list", sel:".budget", status:"shipped", tier:"v0.7" },
277
- { id:"F-BUDGET-WARN-BANNER", name:"Budget warning banner", surface:"list", sel:".budget-banner", status:"partial", tier:"v0.6" },
278
- { id:"F-CARD-AGENT-BORDER", name:"Per-vendor left-border color on task-card", surface:"list", sel:".task-card.agent-claude-code", status:"shipped", tier:"v0.9" },
279
- { id:"F-CARD-CLICK-DETAIL", name:"Click card → detail view", surface:"list", sel:".task-card", status:"shipped", tier:"v0.6" },
280
- { id:"F-CARD-OUTCOME-CHIP", name:"Outcome chip right-aligned", surface:"list", sel:".card-outcome.ok", status:"shipped", tier:"v0.7" },
281
- { id:"F-CUSTOM-VIEW-BANNER", name:"Custom view banner", surface:"list", sel:".custom-view-banner", status:"shipped", tier:"v0.8" },
282
- { id:"F-DAEMON-HEARTBEAT", name:"Local pill auto-update", surface:"list", sel:".local-pill", status:"shipped", tier:"v0.6" },
283
- { id:"F-FAILOVER-BANNER", name:"Failover banner", surface:"list", sel:".failover-banner", status:"partial", tier:"v0.7" },
284
- { id:"F-FILTER-PILLS", name:"All/Running/Done/Failed segmented filters", surface:"list", sel:".filter-tab", status:"shipped", tier:"v0.7" },
285
- { id:"F-INDEXING-BANNER", name:"First-run indexing banner", surface:"list", sel:".indexing-banner", status:"partial", tier:"v0.7" },
286
- { id:"F-LOCAL-PILL", name:"Local-first / zero-telemetry pill", surface:"list", sel:".local-pill", status:"shipped", tier:"v0.7" },
287
- { id:"F-LOGO", name:"Brand wordmark", surface:"list", sel:".logo .name", status:"shipped", tier:"v0.7" },
288
- { id:"F-NEW-TASK-BTN", name:"+ New Task primary button", surface:"list", sel:".new-btn", status:"shipped", tier:"v0.7" },
289
- { id:"F-OBSERVED-THREAD", name:"Observed session as thread", surface:"list", sel:".obs-row", status:"shipped", tier:"v0.7" },
290
- { id:"F-OSS-FOOTER", name:"OSS footer", surface:"list", sel:".oss-footer", status:"shipped", tier:"v0.7" },
291
- { id:"F-PERSONA-SELECTOR", name:"Header role selector", surface:"list", sel:".persona-select", status:"shipped", tier:"v0.7" },
292
- { id:"F-PIN-DEMO", name:"Demo-shape sessions pinned to FEATURED", surface:"list", sel:".task-card.tc-pinned", status:"shipped", tier:"v0.9" },
293
- { id:"F-PORT-FAILOVER-NOTICE", name:"Port-collision UI surface", surface:"list", sel:".failover-banner", status:"shipped", tier:"v0.7" },
294
- { id:"F-RESHAPE-BTN", name:"Reshape view button", surface:"list", sel:".reshape-btn", status:"shipped", tier:"v0.8" },
295
- { id:"F-SETTINGS-COG", name:"Settings drawer trigger", surface:"list", sel:".settings-btn", status:"shipped", tier:"v0.8" },
296
- { id:"F-SHARE-BTN", name:"Share thread button", surface:"list", sel:".share-btn-hdr", status:"shipped", tier:"v0.8" },
297
- { id:"F-SQUAD-ROW", name:"Agent picker chips + presence dots", surface:"list", sel:".squad-row", status:"shipped", tier:"v0.7" },
298
- { id:"F-TASK-CARD", name:"Task card", surface:"list", sel:".task-card", status:"shipped", tier:"v0.7" },
299
- { id:"F-TIME-GAP-PILL", name:"Brand pill + relative-time sub", surface:"list", sel:".time-gap", status:"shipped", tier:"v0.7" },
300
- { id:"F-WORKTREE-ALERT", name:"Worktree alert", surface:"list", sel:".worktree-alert", status:"partial", tier:"v0.7" },
301
- /* goal-first home + goal room (v1 spec · shared-room hand-off). Goal groups
302
- render from REAL non-demo tasks (same data dependency as F-TIME-GAP-PILL);
303
- room features are observed by driving the REAL openRoom() on the first
304
- goal-head's data-goal-cwd, then closeRoom() — same pattern as showD(). */
305
- { id:"F-GOAL-GROUP", name:"Goal-first home · tasks grouped by goal", surface:"list", sel:".goal-group", status:"shipped", tier:"v1" },
306
- { id:"F-GOAL-HEAD-OPEN", name:"Goal head opens its room (carries cwd)", surface:"list", sel:".goal-head[data-goal-cwd]", status:"shipped", tier:"v1" },
307
- { id:"F-AGENT-PICK", name:"Composer agent picker (Auto + 5 agents)", surface:"list", sel:"select#agent-pick", status:"shipped", tier:"v1" },
308
- /* room selectors are scoped to #task-list: the hidden detail view's DOM
309
- persists (display:none still matches querySelectorAll) and also has an
310
- .audit-line — unscoped, a dead room would false-OBSERVE off it. */
311
- { id:"F-ROOM-HEAD", name:"Goal room header (title + outcome sub)", surface:"room", sel:"#task-list .room-head .room-title", status:"shipped", tier:"v1" },
312
- { id:"F-ROOM-BACK", name:"Room back-to-all-goals affordance", surface:"room", sel:"#task-list .room-back", status:"shipped", tier:"v1" },
313
- { id:"F-ROOM-TURNS", name:"Turns in order (how agents worked the goal)", surface:"room", sel:"#task-list .turns-label", status:"partial", tier:"v1" },
314
- { id:"F-ROOM-AUDIT", name:"Room audit line (steps in local chain)", surface:"room", sel:"#task-list .audit-line", status:"partial", tier:"v1" },
315
- /* partial by nature: renders ONLY when the daemon reports project memory
316
- OFF for the opened room (initialized projects correctly show nothing). */
317
- { id:"F-ROOM-MEM-NUDGE", name:"Project-memory init nudge (turn on memory)", surface:"room", sel:"#task-list .mem-nudge", status:"partial", tier:"v1" },
318
- { id:"M-OVERLAY-LINEAR", name:"Overlay-renderer Linear", surface:"list", sel:".or-overlay", status:"shipped", tier:"v0.9" },
319
- { id:"M-PRIMITIVES-NOTE", name:"Primitives note", surface:"list", sel:".primitives-note .prims .p", status:"shipped", tier:"v0.8" },
320
- { id:"M-RESHAPE-AGENT-COMPOSE", name:"Agent-compose animation", surface:"list", sel:".agent-compose .line", status:"shipped", tier:"v0.8" },
321
- { id:"M-SETTINGS-DRAWER", name:"Settings drawer", surface:"list", sel:".drawer", status:"shipped", tier:"v0.8" },
322
- { id:"M-SHARE-TOAST", name:"Share toast notification", surface:"list", sel:".share-toast", status:"shipped", tier:"v0.8" },
323
- { id:"M-WPASS-APPROVAL", name:"W-PASS approval modal", surface:"list", sel:".wp-modal", status:"shipped", tier:"v0.9" },
324
- { id:"P-AB-BADGE", name:"Agent identity badge", surface:"list", sel:".ab", status:"shipped", tier:"v0.6" },
325
- { id:"P-STATUS-PILL", name:"Per-status pill", surface:"list", sel:".status-icon.running", status:"shipped", tier:"v0.7" },
326
- { id:"F-DETAIL-HEADER", name:"Detail view header (back button + title + agent badge + status line)", surface:"detail", sel:".back-btn", status:"shipped", tier:"v0.7" },
327
- { id:"F-DETAIL-KICKER", name:"Detail kicker + failover context", surface:"detail", sel:".detail-kicker", status:"shipped", tier:"v0.7" },
328
- { id:"F-OUTCOME-PANEL", name:"Outcome section (files/steps/Final Report)", surface:"detail", sel:"[data-verify-unit='outcome']", status:"shipped", tier:"v0.7" },
329
- { id:"F-SHIMMER-TITLE", name:"Animated gradient title", surface:"detail", sel:".detail-title", status:"shipped", tier:"v0.7" },
330
- { id:"P-CODE-CARD", name:"Inline syntax-tinted code preview", surface:"detail", sel:".code-card", status:"shipped", tier:"v0.7" },
331
- { id:"P-CTX-URL-CHIP", name:"Clickable context-source URL preview chip", surface:"detail", sel:".ctx-url-chip", status:"shipped", tier:"v0.8" },
332
- { id:"P-VERIFIED-BLOCK", name:"Centered verification celebration card", surface:"detail", sel:".verified-block", status:"shipped", tier:"v0.9" },
333
- { id:"P-WRITE-PASS", name:"Write-pass inline card (blue-accented)", surface:"detail", sel:".write-pass .head + h4", status:"shipped", tier:"v0.9" },
334
- { id:"F-RECOVERY-CARD", name:"failed-task recovery card", surface:"detail", sel:".recovery-card", status:"shipped", tier:"v0.7", fixture:"demo-recovery-test-failed-v0.9" },
335
- { id:"F-CRASH-RECOVERY", name:"orphaned-session banner", surface:"detail", sel:".crash-recovery", status:"shipped", tier:"v0.7", fixture:"demo-crash-orphaned-mid-write-v0.9" },
336
- { id:"F-HOOK-BLOCK-CARD", name:"hook-block card + override", surface:"detail", sel:".hook-block-card", status:"shipped", tier:"v0.7", fixture:"demo-hook-block-bulk-rm-v0.9" },
337
- { id:"F-OVERRIDE-FLOW", name:"override + log reason", surface:"detail", sel:".hook-block-actions", status:"shipped", tier:"v0.7", fixture:"demo-hook-block-bulk-rm-v0.9" },
338
- { id:"F-GIT-NOTE", name:"non-git honest note", surface:"detail", sel:".git-note,.verifier-panel.partial", status:"shipped", tier:"v0.7", fixture:"demo-non-git-honest-v1" },
339
- { id:"F-CONVERSATION", name:"multi-speaker conversation", surface:"detail", sel:".conv-turn,.turn,.speaker", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
340
- { id:"P-ARTIFACTS-STRUCTURED", name:"structured artifacts", surface:"detail", sel:".art-cmp,.artifact,.art-card,.struct-art", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
341
- { id:"P-PANEL-TITLE", name:"uppercase panel title", surface:"detail", sel:".panel-title", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
342
- { id:"F-THREAD-PHASED", name:"phased narrative timeline", surface:"detail", sel:".tl-div,.chapter-card,.phase-pos", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
343
- { id:"F-FILES-TESTS", name:"files & tests panel", surface:"detail", sel:".file-tree", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
344
- { id:"P-TOOL-CARD", name:"tool-call activity card", surface:"detail", sel:".tl-acts", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
345
- { id:"F-THINKING-EMITTER", name:"live thinking dots (needs SSE token stream)", surface:"detail", sel:".thinking", status:"partial", tier:"v0.7", fixture:"demo-running-streaming-v0.9" },
346
- { id:"P-REPL-PRIMITIVES", name:"streaming repl-beat (needs SSE token stream)", surface:"detail", sel:".repl-beat", status:"partial", tier:"v0.7", fixture:"demo-running-streaming-v0.9" },
347
- { id:"P-AUDIT-LINE", name:"inline audit beat (host-thread / live audit)", surface:"detail", sel:".audit-line", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
348
- { id:"F-TOKEN-TABLE", name:"per-agent token table (needs artifacts.usage)", surface:"detail", sel:".token-table", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
349
- { id:"M-OVERLAY-FIGMA", name:"overlay-renderer · Figma", surface:"detail", sel:".overlay-figma", status:"future", tier:"v1.0" },
350
- { id:"M-OVERLAY-DRAWIO", name:"overlay-renderer · draw.io", surface:"detail", sel:".overlay-drawio", status:"future", tier:"v1.0" },
351
- { id:"V1-MULTITASK", name:"multi-task projects · parent/child", surface:"list", sel:".project-rollup", status:"roadmap", tier:"v1.0" },
352
- { id:"V1-FANOUT-ALL", name:"@all fanout to ready adapters", surface:"list", sel:".fanout-all", status:"roadmap", tier:"v1.0" },
353
- { id:"F-AGENT-SWITCHER", name:"'reply as' agent picker", surface:"detail", sel:".agent-switcher", status:"future", tier:"cut" }
354
- ];
355
-
356
- /* -------- read the data-verify-* contract off the real surface -------- */
357
- function readSurface(doc){
358
- var dv = doc.getElementById("task-detail-view");
359
- var visible = dv && dv.style.display !== "none";
360
- var meta = doc.querySelector('[data-verify-unit="detail"]');
361
- var outcome = doc.querySelector('[data-verify-unit="outcome"]');
362
- var filetree = doc.querySelector('[data-verify-unit="filetree"]');
363
- var verifiers = Array.prototype.slice.call(doc.querySelectorAll('[data-verify-unit="verifier"]')).map(function(el){
364
- return { verdict: el.getAttribute("data-verify-verdict"), gittracked: el.getAttribute("data-verify-gittracked") };
365
- });
366
- var says = Array.prototype.slice.call(doc.querySelectorAll('.tl-say')).map(function(el){
367
- var t = (el.querySelector(".tl-say-text") ? el.querySelector(".tl-say-text").textContent : "") || "";
368
- t = t.replace(/^\\s+|\\s+$/g, "");
369
- return { len:t.length, text:t };
370
- });
371
- var found = !!(meta && visible);
372
- return {
373
- found: found,
374
- status: meta ? meta.getAttribute("data-verify-status") : null,
375
- terminalFail: meta ? meta.getAttribute("data-verify-terminal-fail")==="true" : false,
376
- hasExplanation: meta ? meta.getAttribute("data-verify-has-explanation")==="true" : false,
377
- hasOutcome: !!outcome,
378
- outcomeItems: outcome ? outcome.querySelectorAll(".outcome-item").length : 0,
379
- filesClaimed: outcome ? Number(outcome.getAttribute("data-verify-files-claimed")||0) : 0,
380
- terminal: outcome ? outcome.getAttribute("data-verify-terminal")==="true" : false,
381
- hasReport: outcome ? outcome.getAttribute("data-verify-has-report")==="true" : false,
382
- steps: outcome ? Number(outcome.getAttribute("data-verify-steps")||0) : 0,
383
- hasFiletree: !!filetree,
384
- filetreeRendered: filetree ? Number(filetree.getAttribute("data-verify-rendered")||0) : 0,
385
- verifiers: verifiers,
386
- says: says
387
- };
388
- }
389
-
390
- /* -------- iframe surface plumbing -------- */
391
- var iframe = document.getElementById("surface");
392
- function frameWin(){ return iframe.contentWindow; }
393
- function frameDoc(){ return iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); }
394
-
395
- function sleep(ms){ return new Promise(function(r){ setTimeout(r, ms); }); }
396
-
397
- // Wait until the product page inside the iframe is ready to drive.
398
- function surfaceReady(){
399
- return new Promise(function(resolve, reject){
400
- var tries = 0;
401
- (function poll(){
402
- tries++;
403
- try {
404
- var w = frameWin(), d = frameDoc();
405
- if (w && typeof w.showD === "function" && d && d.querySelectorAll(".task-card, .obs-row").length > 0) return resolve();
406
- } catch(e){ /* not loaded yet */ }
407
- if (tries > 80) return reject(new Error("surface never became ready (no showD / no task cards) — is the daemon serving seeded sessions?"));
408
- setTimeout(poll, 150);
409
- })();
410
- });
411
- }
412
-
413
- // Navigate the REAL surface to a session and read its contract. Honest about
414
- // the not-seeded case: returns found=false rather than inventing a result.
415
- function observe(sid){
416
- var w = frameWin(), d = frameDoc();
417
- try { if (typeof w.hideD === "function") w.hideD(); } catch(e){}
418
- try { w.showD(sid); } catch(e){ return Promise.resolve({ found:false, error:String(e), verifiers:[], says:[] }); }
419
- return sleep(140).then(function(){ return readSurface(frameDoc()); });
420
- }
421
-
422
- /* -------- run one (invariant × fixture) into a structured result -------- */
423
- function runOne(inv, fx, s){
424
- var base = { unitId: inv.id, fixtureId: fx.id, fixtureLabel: fx.label, probe: !!fx.probe, timestamp: new Date().toISOString(), domSnapshot: snapshot(s) };
425
- if (!s.found){
426
- return Object.assign(base, { verdict:"BLOCKED", checks:[], blockedReason: s.error ? ("surface error: "+s.error) : "session not rendered (not seeded?)" });
427
- }
428
- if (!inv.can(s)){
429
- return Object.assign(base, { verdict:"BLOCKED", checks:[], blockedReason: "0 units to observe (no "+inv.needs+")" });
430
- }
431
- var violations = inv.check(s);
432
- if (violations.length){
433
- return Object.assign(base, { verdict:"FAIL", checks: violations.map(function(v){ return { status:"fail", label:v }; }) });
434
- }
435
- return Object.assign(base, { verdict:"PASS", checks: [{ status:(fx.probe?"probe":"ok"), label: inv.desc }] });
436
- }
437
-
438
- function snapshot(s){
439
- if (!s.found) return {};
440
- return { status:s.status, terminalFail:s.terminalFail, hasExplanation:s.hasExplanation, filesClaimed:s.filesClaimed, filetreeRendered:s.filetreeRendered, outcomeItems:s.outcomeItems, steps:s.steps, verifiers:s.verifiers.length, says:s.says.length };
441
- }
442
-
443
- /* -------- the matrix UI -------- */
444
- var resultMap = {}; // key "H1::sid" -> result
445
- function key(u,f){ return u+"::"+f; }
446
-
447
- function buildMatrix(){
448
- var thead = document.querySelector("#matrix thead");
449
- var tbody = document.querySelector("#matrix tbody");
450
- var hrow = "<tr><th style=\\"width:40%\\">Invariant (unit)</th><th class=\\"needs\\" style=\\"width:13%\\">needs</th>";
451
- FIXTURES.forEach(function(fx){
452
- hrow += "<th><div class=\\"fixhdr"+(fx.probe?" probe":"")+"\\">"+(fx.probe?"&#128269; ":"")+fx.label+"</div></th>";
453
- });
454
- hrow += "</tr>";
455
- thead.innerHTML = hrow;
456
- tbody.innerHTML = INVARIANTS.map(function(inv){
457
- var cells = FIXTURES.map(function(fx){
458
- return "<td><button class=\\"cell\\" id=\\"c-"+inv.id+"-"+cssId(fx.id)+"\\" data-u=\\""+inv.id+"\\" data-f=\\""+fx.id+"\\">&middot;</button></td>";
459
- }).join("");
460
- var probeTag = inv.id==="H5" ? "<span class=\\"probe-tag\\">self-test</span>" : "";
461
- return "<tr"+(inv.id==="H5"?" class=\\"probe-row\\"":"")+"><td><span class=\\"unit-id\\">"+inv.id+" &middot; "+inv.title+"</span>"+probeTag+"<div class=\\"unit-desc\\">"+inv.desc+"</div></td><td class=\\"needs\\">"+inv.needs+"</td>"+cells+"</tr>";
462
- }).join("");
463
- // clicking a cell jumps replay to that fixture
464
- Array.prototype.slice.call(document.querySelectorAll(".cell")).forEach(function(btn){
465
- btn.addEventListener("click", function(){
466
- var fid = btn.getAttribute("data-f");
467
- var i = FIXTURES.map(function(f){return f.id;}).indexOf(fid);
468
- if (i>=0){ openReplay(); jumpTo(i); }
469
- });
470
- });
471
- }
472
-
473
- function cssId(s){ return s.replace(/[^a-zA-Z0-9_-]/g,"_"); }
474
-
475
- function paintCell(r){
476
- var el = document.getElementById("c-"+r.unitId+"-"+cssId(r.fixtureId));
477
- if (!el) return;
478
- el.className = "cell " + r.verdict.toLowerCase();
479
- if (r.verdict==="FAIL" && r.probe) el.className += " expected";
480
- el.textContent = r.verdict==="PASS" ? "\\u2713" : r.verdict==="FAIL" ? "\\u2715" : r.verdict==="BLOCKED" ? "\\u00B7" : "\\u2013";
481
- el.title = r.blockedReason || (r.checks[0] && r.checks[0].label) || r.verdict;
482
- }
483
-
484
- /* -------- expected-vs-actual: the honesty self-check on the harness -------- */
485
- function classify(results){
486
- var pass=0, fail=0, blocked=0, unexpectedFail=[], probeMissed=[];
487
- results.forEach(function(r){
488
- if (r.verdict==="PASS") pass++;
489
- else if (r.verdict==="BLOCKED") blocked++;
490
- else if (r.verdict==="FAIL"){
491
- fail++;
492
- if (!r.probe) unexpectedFail.push(r.unitId+"\\u00d7"+r.fixtureLabel);
493
- }
494
- });
495
- // every fixture that declared expectFail MUST have produced that FAIL
496
- FIXTURES.forEach(function(fx){
497
- if (!fx.expectFail) return;
498
- var r = resultMap[key(fx.expectFail, fx.id)];
499
- if (!r || r.verdict!=="FAIL") probeMissed.push(fx.expectFail+"\\u00d7"+fx.label);
500
- });
501
- var ok = unexpectedFail.length===0 && probeMissed.length===0;
502
- return { pass:pass, fail:fail, blocked:blocked, total:results.length, ok:ok, unexpectedFail:unexpectedFail, probeMissed:probeMissed };
503
- }
504
-
505
- function paintTally(c){
506
- document.getElementById("tally").innerHTML =
507
- "<span class=\\"p\\">\\u2705 "+c.pass+"</span><span class=\\"f\\">\\u274c "+c.fail+"</span><span class=\\"b\\">\\u26d4 "+c.blocked+"</span><span class=\\"t\\">/ "+c.total+"</span>";
508
- var line = document.getElementById("verdict-line");
509
- if (c.ok){
510
- line.className = "verdict-line green";
511
- line.textContent = "\\u2713 honest: every PASS was observed, and the probe FAILED exactly as designed (the harness can see a lie).";
512
- } else {
513
- line.className = "verdict-line red";
514
- var parts = [];
515
- if (c.unexpectedFail.length) parts.push("unexpected FAIL: "+c.unexpectedFail.join(", "));
516
- if (c.probeMissed.length) parts.push("PROBE DID NOT FAIL ("+c.probeMissed.join(", ")+") \\u2014 the harness is blind, treat the whole board as void");
517
- line.textContent = "\\u2717 " + parts.join(" \\u00b7 ");
518
- }
519
- }
520
-
521
- /* -------- the agent / CI handle -------- */
522
- var lastSingle = null;
523
- function buildManifest(){
524
- return INVARIANTS.map(function(inv){
525
- return { unit: inv.id+"-"+inv.title, needs: inv.needs, description: inv.desc,
526
- fixtures: FIXTURES.map(function(fx){ return { id:fx.id, label:fx.label, probe:!!fx.probe, expectFail:fx.expectFail||null }; }) };
527
- });
528
- }
529
-
530
- function runAll(){
531
- var btn = document.getElementById("run-all");
532
- btn.disabled = true; btn.textContent = "Running\\u2026";
533
- var results = [];
534
- return surfaceReady().then(function(){
535
- // iterate fixtures, observe each once, run all invariants against it
536
- return FIXTURES.reduce(function(p, fx){
537
- return p.then(function(){
538
- return observe(fx.id).then(function(s){
539
- INVARIANTS.forEach(function(inv){
540
- var r = runOne(inv, fx, s);
541
- resultMap[key(inv.id, fx.id)] = r;
542
- results.push(r);
543
- paintCell(r);
544
- });
545
- });
546
- });
547
- }, Promise.resolve());
548
- }).then(function(){
549
- var c = classify(results);
550
- paintTally(c);
551
- lastSingle = results[results.length-1] || null;
552
- window.__verify_lastRun = results;
553
- window.__verify_summary = c;
554
- btn.disabled = false; btn.textContent = "\\u25b6 Run all";
555
- return results;
556
- }).catch(function(err){
557
- btn.disabled = false; btn.textContent = "\\u25b6 Run all";
558
- document.getElementById("verdict-line").className = "verdict-line red";
559
- document.getElementById("verdict-line").textContent = "\\u26d4 BLOCKED: "+String(err && err.message || err);
560
- throw err;
561
- });
562
- }
563
-
564
- /* ===================== feature coverage ===================== */
565
- function covVerdict(feat, present){
566
- if (feat.status==="shipped") return present ? "OBSERVED" : "MISSING";
567
- if (feat.status==="partial") return present ? "OBSERVED" : "PARTIAL-GAP";
568
- if (feat.status==="roadmap") return "SKIP-ROADMAP";
569
- return "SKIP-FUTURE"; // future / cut
570
- }
571
- function covClass(v){
572
- return v==="OBSERVED"?"pass":v==="MISSING"?"fail":v==="PARTIAL-GAP"?"warn":"blocked";
573
- }
574
- function covGlyph(v){
575
- return v==="OBSERVED"?"\\u2713":v==="MISSING"?"\\u2715":v==="PARTIAL-GAP"?"\\u25d0":"\\u00b7";
576
- }
577
-
578
- var covResults = [];
579
- function paintCov(r){
580
- var el = document.getElementById("cov-"+cssId(r.id));
581
- if (!el) return;
582
- el.className = "cell " + covClass(r.verdict);
583
- el.textContent = covGlyph(r.verdict);
584
- el.title = r.verdict + (r.present?" · rendered":" · not in DOM");
585
- }
586
-
587
- function buildCovMatrix(){
588
- var tb = document.querySelector("#cov-matrix tbody");
589
- if (!tb) return;
590
- tb.innerHTML = FEATURES.map(function(f){
591
- var statusTag = f.status==="shipped"?"":f.status==="partial"?"<span class=\\"probe-tag\\" style=\\"border-color:rgba(254,188,46,.5);color:var(--warn)\\">partial</span>":f.status==="roadmap"?"<span class=\\"probe-tag\\" style=\\"border-color:var(--border);color:var(--fg-40)\\">roadmap</span>":"<span class=\\"probe-tag\\" style=\\"border-color:var(--border);color:var(--fg-40)\\">"+(f.tier==="cut"?"cut":"future")+"</span>";
592
- return "<tr><td><span class=\\"unit-id\\">"+f.id+"</span> <span class=\\"unit-desc\\">"+f.name+"</span>"+statusTag+"</td>"+
593
- "<td class=\\"needs\\">"+f.tier+"</td>"+
594
- "<td class=\\"needs\\">"+f.surface+"</td>"+
595
- "<td><button class=\\"cell\\" id=\\"cov-"+cssId(f.id)+"\\">&middot;</button></td></tr>";
596
- }).join("");
597
- }
598
-
599
- function classifyCov(results){
600
- var observed=0, missing=0, partialGap=0, roadmap=0, future=0;
601
- var missingIds=[];
602
- results.forEach(function(r){
603
- if (r.verdict==="OBSERVED") observed++;
604
- else if (r.verdict==="MISSING"){ missing++; missingIds.push(r.id); }
605
- else if (r.verdict==="PARTIAL-GAP") partialGap++;
606
- else if (r.verdict==="SKIP-ROADMAP") roadmap++;
607
- else future++;
608
- });
609
- var shipped = observed + missing;
610
- return { observed:observed, missing:missing, partialGap:partialGap, roadmap:roadmap, future:future,
611
- shipped:shipped, total:results.length, ok: missing===0, missingIds:missingIds };
612
- }
613
-
614
- function paintCovTally(c){
615
- var t = document.getElementById("cov-tally");
616
- if (t) t.innerHTML =
617
- "<span class=\\"p\\">\\u2713 "+c.observed+" observed</span>"+
618
- "<span class=\\"f\\">\\u2715 "+c.missing+" missing</span>"+
619
- "<span style=\\"color:var(--warn)\\">\\u25d0 "+c.partialGap+" partial-gap</span>"+
620
- "<span class=\\"b\\">\\u00b7 "+(c.roadmap+c.future)+" roadmap/future</span>"+
621
- "<span class=\\"t\\">/ "+c.total+"</span>";
622
- var line = document.getElementById("cov-verdict");
623
- if (!line) return;
624
- if (c.ok){
625
- line.className = "verdict-line green";
626
- line.textContent = "\\u2713 every feature claimed shipped was OBSERVED on the real surface · "+c.shipped+" shipped checked · "+(c.roadmap+c.future)+" correctly absent (roadmap/future).";
627
- } else {
628
- line.className = "verdict-line red";
629
- line.textContent = "\\u2717 "+c.missing+" feature(s) claimed shipped but NOT rendered: "+c.missingIds.join(", ")+" — PRODUCT_STATE overclaims, fix the doc or the code.";
630
- }
631
- }
632
-
633
- function runCoverage(){
634
- var btn = document.getElementById("run-cov");
635
- if (btn){ btn.disabled = true; btn.textContent = "Running\\u2026"; }
636
- covResults = [];
637
- return surfaceReady().then(function(){
638
- var w = frameWin();
639
- try { if (typeof w.hideD==="function") w.hideD(); } catch(e){}
640
- return sleep(140);
641
- }).then(function(){
642
- // list-surface features (dashboard visible)
643
- var d = frameDoc();
644
- FEATURES.filter(function(f){ return f.surface==="list" && f.status!=="roadmap" && f.status!=="future"; }).forEach(function(f){
645
- var present = d.querySelectorAll(f.sel).length > 0;
646
- var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
647
- covResults.push(r); paintCov(r);
648
- });
649
- return sleep(40);
650
- }).then(function(){
651
- // detail-surface features, grouped by the fixture that TRIGGERS them.
652
- // Conditional surfaces (recovery/crash/hook-block/...) only render on the
653
- // right seeded session — open each fixture once, check its group.
654
- var detail = FEATURES.filter(function(f){ return f.surface==="detail" && f.status!=="roadmap" && f.status!=="future"; });
655
- var byFix = {};
656
- detail.forEach(function(f){ var fx = f.fixture || COV_FIXTURE; (byFix[fx] = byFix[fx] || []).push(f); });
657
- var fixtures = Object.keys(byFix);
658
- return fixtures.reduce(function(p, fx){
659
- return p.then(function(){
660
- try { var w = frameWin(); if (w.hideD) w.hideD(); w.showD(fx); } catch(e){}
661
- return sleep(200).then(function(){
662
- var d = frameDoc();
663
- byFix[fx].forEach(function(f){
664
- var present = d.querySelectorAll(f.sel).length > 0;
665
- var r = { id:f.id, present:present, verdict:covVerdict(f, present), fixture:fx };
666
- covResults.push(r); paintCov(r);
667
- });
668
- });
669
- });
670
- }, Promise.resolve());
671
- }).then(function(){
672
- // room-surface features · drive the REAL openRoom() (same pattern as
673
- // showD above) on the first goal-head's data-goal-cwd, observe, close.
674
- // No goal-head on the home = no real tasks; the room genuinely cannot
675
- // be opened, so room features go MISSING/PARTIAL-GAP per their status —
676
- // honest, and F-GOAL-GROUP fails alongside so the cause is visible.
677
- var room = FEATURES.filter(function(f){ return f.surface==="room" && f.status!=="roadmap" && f.status!=="future"; });
678
- if (!room.length) return;
679
- var w0 = frameWin();
680
- var gh = frameDoc().querySelector(".goal-head[data-goal-cwd]");
681
- var check = function(){
682
- var d = frameDoc();
683
- room.forEach(function(f){
684
- var present = d.querySelectorAll(f.sel).length > 0;
685
- var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
686
- covResults.push(r); paintCov(r);
687
- });
688
- };
689
- if (!gh){ check(); return; }
690
- try { w0.openRoom(gh.getAttribute("data-goal-cwd")); } catch(e){}
691
- return sleep(200).then(function(){
692
- check();
693
- try { if (w0.closeRoom) w0.closeRoom(); } catch(e){}
694
- return sleep(120);
695
- });
696
- }).then(function(){
697
- // roadmap / future: SKIP (correctly absent), no observation
698
- FEATURES.filter(function(f){ return f.status==="roadmap" || f.status==="future"; }).forEach(function(f){
699
- var r = { id:f.id, present:false, verdict:covVerdict(f, false) };
700
- covResults.push(r); paintCov(r);
701
- });
702
- var c = classifyCov(covResults);
703
- paintCovTally(c);
704
- window.__verify_coverage = { results:covResults, summary:c };
705
- if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
706
- return covResults;
707
- }).catch(function(err){
708
- if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
709
- throw err;
710
- });
711
- }
712
-
713
- window.__verify = {
714
- version: PROTO_VERSION,
715
- manifest: buildManifest,
716
- current: function(){ return lastSingle; },
717
- runAll: runAll,
718
- coverage: runCoverage,
719
- features: function(){ return FEATURES.map(function(f){ return { id:f.id, name:f.name, surface:f.surface, status:f.status, tier:f.tier }; }); }
720
- };
721
-
722
- document.getElementById("manifest-json").textContent =
723
- JSON.stringify(buildManifest(), null, 2) +
724
- "\\n\\nawait window.__verify.runAll() -> one result per invariant\\u00d7fixture {verdict, checks, blockedReason, domSnapshot}\\n" +
725
- "window.__verify_summary -> {pass, fail, blocked, ok, unexpectedFail, probeMissed}\\n" +
726
- "ok===false (unexpected FAIL, or the probe failed to fail) -> scripts/verify.mjs exits 1.";
727
-
728
- /* ===================== replay (visible, slow-motion) ===================== */
729
- var replayOpen = false, playing = false, idx = 0, replayResults = [];
730
- var DWELL = clampInt(qs("dwell"), 1500, 200, 30000);
731
- var KEY_HOLD = clampInt(qs("hold"), 700, 100, 5000);
732
- document.getElementById("dwell-label").textContent = "dwell "+DWELL+"ms";
733
-
734
- function qs(k){ var m = new RegExp("[?&]"+k+"=([^&]+)").exec(location.search); return m ? decodeURIComponent(m[1]) : null; }
735
- function clampInt(raw, fb, min, max){ var n = raw?parseInt(raw,10):NaN; if(isNaN(n))return fb; return Math.min(max,Math.max(min,n)); }
736
-
737
- function openReplay(){ replayOpen = true; document.getElementById("replay").classList.remove("hidden"); buildStepList(); }
738
- function buildStepList(){
739
- document.getElementById("steplist").innerHTML = FIXTURES.map(function(fx,i){
740
- return "<div class=\\"steprow"+(fx.probe?" fail":"")+"\\" data-i=\\""+i+"\\"><span class=\\"st\\">\\u25cb</span><span class=\\"nm\\">"+(fx.probe?"&#128269; ":"")+fx.label+"</span></div>";
741
- }).join("");
742
- Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(row){
743
- row.addEventListener("click", function(){ jumpTo(Number(row.getAttribute("data-i"))); });
744
- });
745
- }
746
- function markStep(i, verdict){
747
- var row = document.querySelector('.steprow[data-i="'+i+'"]');
748
- if (!row) return;
749
- Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(r){ r.classList.remove("active"); });
750
- if (verdict==null){ row.classList.add("active"); row.querySelector(".st").textContent="\\u25b8"; return; }
751
- row.querySelector(".st").textContent = verdict==="PASS"?"\\u2705":verdict==="FAIL"?"\\u274c":"\\u26d4";
752
- }
753
-
754
- var caption = document.getElementById("caption");
755
- var ring = null;
756
- function showCaption(t){ if(t){ caption.textContent=t; caption.classList.add("on"); } else { caption.classList.remove("on"); } }
757
- function drawRing(el){
758
- removeRing();
759
- if (!el) return;
760
- var ir = iframe.getBoundingClientRect();
761
- var er = el.getBoundingClientRect();
762
- ring = document.createElement("div");
763
- ring.className = "ring";
764
- ring.style.left = (ir.left + er.left - 3) + "px";
765
- ring.style.top = (ir.top + er.top - 3) + "px";
766
- ring.style.width = (er.width + 6) + "px";
767
- ring.style.height = (Math.min(er.height, 120) + 6) + "px";
768
- document.body.appendChild(ring);
769
- }
770
- function removeRing(){ if(ring){ ring.remove(); ring=null; } }
771
-
772
- function setStageVerdict(v){
773
- var w = document.getElementById("stage-wrap");
774
- w.className = "stage-wrap" + (v ? " v-"+v.toLowerCase() : "");
775
- }
776
- function setFrameStatus(results, sid){
777
- var fs = document.getElementById("framestatus");
778
- var fails = results.filter(function(r){ return r.verdict==="FAIL"; });
779
- var blocked = results.filter(function(r){ return r.verdict==="BLOCKED"; });
780
- var pass = results.filter(function(r){ return r.verdict==="PASS"; });
781
- var verdict = fails.length ? "FAIL" : (pass.length ? "PASS" : "BLOCKED");
782
- var html = "<span class=\\"badge "+verdict.toLowerCase()+"\\">"+verdict+"</span>";
783
- html += "<span class=\\"sub\\" style=\\"color:var(--fg-50)\\">\\u2705 "+pass.length+" \\u00b7 \\u274c "+fails.length+" \\u00b7 \\u26d4 "+blocked.length+"</span>";
784
- if (fails.length) html += "<span class=\\"issues\\">"+fails.map(function(f){ return f.unitId+": "+(f.checks[0]?f.checks[0].label:""); }).join(" \\u00b7 ")+"</span>";
785
- else if (!pass.length && blocked.length) html += "<span class=\\"issues blk\\">"+blocked[0].blockedReason+"</span>";
786
- fs.innerHTML = html;
787
- }
788
-
789
- // Hide the app chrome inside the iframe so the stage shows ONLY the session
790
- // detail being verified — an isolated mount, not the whole app in a tab.
791
- var staged = false;
792
- function stageClean(){
793
- var d = frameDoc(); if (!d || staged) return;
794
- var st = d.createElement("style");
795
- st.textContent =
796
- ".header,.custom-view-banner,.failover-banner,.indexing-banner,.budget-banner,.squad-row,.bar,#task-detail-view .input-area{display:none!important}" +
797
- "body{overflow:auto!important;height:auto!important}" +
798
- ".content{padding:0!important}#task-detail-view{padding:18px 22px!important}";
799
- d.head.appendChild(st);
800
- staged = true;
801
- }
802
-
803
- // The per-case checklist that fills in live (the granular part the human watches).
804
- function clReset(){
805
- var box = document.getElementById("checklist");
806
- box.classList.add("on");
807
- box.innerHTML = "<div class=\\"cl-h\\">honesty checks</div>" + INVARIANTS.map(function(inv){
808
- return "<div class=\\"cl-row\\" id=\\"cl-"+inv.id+"\\"><span class=\\"cl-id\\">"+inv.id+"</span><span class=\\"cl-st\\">\\u25cb</span><span class=\\"cl-tx\\">"+inv.title+"</span></div>";
809
- }).join("");
810
- }
811
- function clSet(invId, verdict, text){
812
- var row = document.getElementById("cl-"+invId);
813
- if (!row) return;
814
- row.className = "cl-row run " + verdict.toLowerCase();
815
- row.querySelector(".cl-st").textContent = verdict==="PASS"?"\\u2705":verdict==="FAIL"?"\\u274c":"\\u26d4";
816
- if (text) row.querySelector(".cl-tx").textContent = text;
817
- }
818
-
819
- // The element each invariant reads from — so the ring lands on what's observed.
820
- function targetFor(invId, d){
821
- if (invId==="H1") return d.querySelector('[data-verify-unit="verifier"]');
822
- if (invId==="H2") return d.querySelector('.tl-say');
823
- if (invId==="H3") return d.querySelector('[data-verify-unit="outcome"]');
824
- if (invId==="H5") return d.querySelector('[data-verify-unit="filetree"]') || d.querySelector('[data-verify-unit="outcome"]');
825
- if (invId==="H6") return d.querySelector('.recovery-card,.crash-recovery,.hook-block-card') || d.querySelector('[data-verify-unit="detail"]');
826
- return d.querySelector('[data-verify-unit="detail"]');
827
- }
828
- // A granular, human-readable line for what THIS check observed.
829
- function narrate(inv, s, r){
830
- if (r.verdict==="BLOCKED") return inv.id+" \\u00b7 n/a here (no "+inv.needs+")";
831
- if (inv.id==="H1") return "H1 \\u00b7 verifier verdict='"+(s.verifiers[0]||{}).verdict+"' git="+(s.verifiers[0]||{}).gittracked;
832
- if (inv.id==="H2") return "H2 \\u00b7 "+s.says.length+" reasoning bubble(s), all have words";
833
- if (inv.id==="H3") return "H3 \\u00b7 outcome renders "+s.outcomeItems+" item(s)";
834
- if (inv.id==="H5") return "H5 \\u00b7 claims "+s.filesClaimed+" files, renders "+s.filetreeRendered+" row(s)";
835
- if (inv.id==="H6") return "H6 \\u00b7 status='"+s.status+"', reason shown="+s.hasExplanation;
836
- return inv.desc;
837
- }
838
-
839
- // Drive ONE invariant visibly: scroll its target into view, ring it, show a
840
- // caption with the observed numbers, land the verdict on the checklist + matrix.
841
- function playCheck(inv, fx, s){
842
- var r = runOne(inv, fx, s);
843
- resultMap[key(inv.id, fx.id)] = r;
844
- paintCell(r);
845
- var d = frameDoc();
846
- if (r.verdict==="BLOCKED"){
847
- // brief, dim — honest "nothing to observe", no theatrics
848
- clSet(inv.id, "BLOCKED", inv.title+" \\u2014 n/a");
849
- showCaption("\\u26d4 " + narrate(inv, s, r));
850
- return sleep(Math.round(KEY_HOLD*0.45)).then(function(){ showCaption(null); return r; });
851
- }
852
- var el = targetFor(inv.id, d);
853
- if (el && el.scrollIntoView) try { el.scrollIntoView({block:"center"}); } catch(e){}
854
- return sleep(120).then(function(){
855
- drawRing(el);
856
- showCaption("\\ud83d\\udd0d " + narrate(inv, s, r));
857
- return sleep(KEY_HOLD);
858
- }).then(function(){
859
- removeRing();
860
- var verdictWord = r.verdict==="FAIL" ? " \\u2192 \\u274c FAIL" : " \\u2192 \\u2705 PASS";
861
- showCaption((r.verdict==="FAIL"?"\\u26a0 ":"") + narrate(inv, s, r) + verdictWord);
862
- clSet(inv.id, r.verdict, inv.title);
863
- return sleep(Math.round(KEY_HOLD*0.7));
864
- }).then(function(){ showCaption(null); return r; });
865
- }
866
-
867
- // Drive one fixture: open it on a clean stage, then walk each honesty check
868
- // one at a time so the human SEES each case tested granularly.
869
- function playFixture(i){
870
- var fx = FIXTURES[i];
871
- idx = i;
872
- markStep(i, null);
873
- clReset();
874
- document.getElementById("stage-title").innerHTML = "case " + (i+1) + "/" + FIXTURES.length + " &middot; <strong>" + (fx.probe?"&#128269; ":"") + fx.label + "</strong> <span class=\\"dim\\">/ "+fx.id+"</span>";
875
- document.getElementById("framestatus").innerHTML = "<span class=\\"sub\\" style=\\"color:var(--fg-40)\\">opening case\\u2026</span>";
876
- setStageVerdict(null);
877
- document.getElementById("prog").style.width = ((i)/FIXTURES.length*100)+"%";
878
- return surfaceReady().then(function(){
879
- var w = frameWin();
880
- try { if (typeof w.hideD==="function") w.hideD(); } catch(e){}
881
- return sleep(120);
882
- }).then(function(){
883
- stageClean();
884
- showCaption("\\ud83d\\udc46 open case \\u2192 showD('"+fx.label+"')");
885
- try { frameWin().showD(fx.id); } catch(e){}
886
- return sleep(Math.round(KEY_HOLD*0.7));
887
- }).then(function(){
888
- showCaption(null);
889
- var s = readSurface(frameDoc());
890
- // walk the invariants sequentially, visibly
891
- var results = [];
892
- return INVARIANTS.reduce(function(p, inv){
893
- return p.then(function(){ return playCheck(inv, fx, s).then(function(r){ results.push(r); }); });
894
- }, Promise.resolve()).then(function(){ return results; });
895
- }).then(function(results){
896
- replayResults = replayResults.concat(results);
897
- var verdict = results.some(function(r){return r.verdict==="FAIL";}) ? "FAIL" : (results.some(function(r){return r.verdict==="PASS";}) ? "PASS" : "BLOCKED");
898
- setStageVerdict(verdict);
899
- setFrameStatus(results, fx.id);
900
- markStep(i, verdict);
901
- lastSingle = results[results.length-1];
902
- document.getElementById("prog").style.width = ((i+1)/FIXTURES.length*100)+"%";
903
- });
904
- }
905
-
906
- function jumpTo(i){ playing=false; openReplay(); playFixture(i); }
907
- function advance(){
908
- if (idx+1 >= FIXTURES.length){ playing=false; document.getElementById("play").innerHTML="&#8635; Replay again"; finishReplay(); return; }
909
- playFixture(idx+1).then(function(){ if(playing) setTimeout(advance, DWELL); });
910
- }
911
- function startReplay(){
912
- replayResults = []; idx = 0; playing = true;
913
- document.getElementById("play").innerHTML = "&#10074;&#10074; Pause";
914
- playFixture(0).then(function(){ if(playing) setTimeout(advance, DWELL); });
915
- }
916
- function finishReplay(){
917
- var c = classify(replayResults);
918
- window.__verify_replay = { done:true, results:replayResults, summary:c };
919
- }
920
-
921
- document.getElementById("toggle-replay").addEventListener("click", function(){ openReplay(); document.getElementById("replay").scrollIntoView({behavior:"smooth"}); });
922
- document.getElementById("play").addEventListener("click", function(){
923
- if (playing){ playing=false; this.innerHTML="&#9654; Play"; }
924
- else { startReplay(); }
925
- });
926
- document.getElementById("skip").addEventListener("click", function(){ advance(); });
927
-
928
- /* in-browser clip recorder (getDisplayMedia, one webm per fixture) */
929
- var recStream=null, recorder=null, recChunks=[], recClips=[];
930
- document.getElementById("record").addEventListener("click", function(){
931
- var btn = this;
932
- if (recStream){ stopRecording(); btn.innerHTML="&#9210; Record clips"; return; }
933
- navigator.mediaDevices.getDisplayMedia({ video:{frameRate:30}, audio:false, preferCurrentTab:true })
934
- .then(function(stream){
935
- recStream = stream; btn.innerHTML="&#9210; Recording \\u2014 stop";
936
- stream.getVideoTracks()[0].addEventListener("ended", function(){ stopRecording(); btn.innerHTML="&#9210; Record clips"; });
937
- // record one continuous clip across the whole replay (simple + reliable);
938
- // download offered on stop.
939
- recChunks = [];
940
- recorder = new MediaRecorder(stream, pickMime());
941
- recorder.ondataavailable = function(e){ if(e.data.size>0) recChunks.push(e.data); };
942
- recorder.onstop = function(){
943
- var blob = new Blob(recChunks, {type:"video/webm"});
944
- var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "codehere-verify-replay.webm";
945
- document.body.appendChild(a); a.click(); a.remove();
946
- };
947
- recorder.start(200);
948
- startReplay();
949
- })
950
- .catch(function(e){ btn.innerHTML="&#9210; Record clips"; alert("recording cancelled: "+e); });
951
- });
952
- function stopRecording(){
953
- try { if(recorder && recorder.state!=="inactive") recorder.stop(); } catch(e){}
954
- if (recStream){ recStream.getTracks().forEach(function(t){t.stop();}); recStream=null; }
955
- }
956
- function pickMime(){
957
- var cs = ["video/webm;codecs=vp9","video/webm;codecs=vp8","video/webm"];
958
- for (var i=0;i<cs.length;i++){ if (window.MediaRecorder && MediaRecorder.isTypeSupported(cs[i])) return {mimeType:cs[i]}; }
959
- return undefined;
960
- }
961
-
962
- /* -------- boot -------- */
963
- buildMatrix();
964
- buildCovMatrix();
965
- // Run all = honesty matrix THEN feature coverage (one click, both truths).
966
- document.getElementById("run-all").addEventListener("click", function(){ runAll().then(runCoverage); });
967
- document.getElementById("run-cov").addEventListener("click", function(){ runCoverage(); });
968
-
969
- // Auto modes for headless drivers (scripts/verify.mjs, scripts/record-replay.mjs).
970
- // Replay mode is selected by the ROUTE (/verify/replay) or ?mode=replay.
971
- var bootDone = false;
972
- iframe.addEventListener("load", function(){
973
- if (bootDone) return; bootDone = true; // the iframe reloads as we drive it; boot once
974
- var isReplay = /\\/verify\\/replay/.test(location.pathname) || qs("mode")==="replay";
975
- var auto = qs("auto");
976
- if (isReplay){ openReplay(); if (auto!=="0") surfaceReady().then(function(){ setTimeout(startReplay, 400); }); }
977
- else if (auto==="1"){ surfaceReady().then(function(){ setTimeout(function(){ runAll().then(runCoverage); }, 300); }); }
978
- });
979
- })();
980
- </script>
981
- </body>
982
- </html>`;
983
- //# sourceMappingURL=verify-page.js.map