thumbgate 1.34.3 → 1.37.1

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 (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. package/src/security-questionnaire.js +195 -0
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * OpenUI catalog-compose honesty doctor (FORMAT steal, not a product clone).
6
+ *
7
+ * Source: https://www.openui.com/
8
+ *
9
+ * Transfers (process only):
10
+ * 1. Catalog-compose-only — model may compose from an allowlisted component
11
+ * catalog; never invent arbitrary components or run generated code.
12
+ * 2. Root-first streaming — structure assigns `root` before children fill in.
13
+ * 3. Repair-before-claim — validate line-by-line, drop/repair invalid lines,
14
+ * and refuse "UI ready / compose done" claims without a clean repair pass.
15
+ *
16
+ * Does NOT install @openuidev/cli, OpenUI Lang, Thesys Gateway, or Observability.
17
+ * Does NOT ship a generative-UI SKU. ECI: no net-new agent-governance product.
18
+ */
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ const SOURCE_URL = 'https://www.openui.com/';
24
+
25
+ const ARBITRARY_CODE_RE =
26
+ /\b(eval\s*\(|new\s+Function\s*\(|dangerouslySetInnerHTML|innerHTML\s*=|document\.write\s*\(|<script\b|Function\s*\(\s*['"`])/i;
27
+
28
+ const OPENUI_SKU_CLONE_RE =
29
+ /@openuidev\/cli|openui\s+gateway|thesys\.dev|pnpx\s+@openuidev|clone\s+openui|install\s+openui\s+lang/i;
30
+
31
+ const COMPOSE_LINE_RE =
32
+ /^\s*([A-Za-z_][\w.-]*)\s*=\s*([A-Za-z_][\w.-]*)\s*(?:\((.*)\))?\s*$/;
33
+
34
+ function normalizeBoolean(value) {
35
+ if (value === true) return true;
36
+ if (value === false || value === undefined || value === null) return false;
37
+ return /^(1|true|yes|on)$/i.test(String(value).trim());
38
+ }
39
+
40
+ function readText(filePath) {
41
+ if (!filePath) return null;
42
+ if (!fs.existsSync(filePath)) {
43
+ const err = new Error(`file not found: ${filePath}`);
44
+ err.code = 'ENOENT';
45
+ throw err;
46
+ }
47
+ return fs.readFileSync(filePath, 'utf8');
48
+ }
49
+
50
+ /**
51
+ * Accepts:
52
+ * { "components": ["Stack","Card"] }
53
+ * { "components": [{ "id": "Stack" }, { "id": "Card", "props": ["title"] }] }
54
+ * ["Stack","Card"]
55
+ */
56
+ function parseCatalog(raw) {
57
+ if (raw == null || raw === '') {
58
+ return { ok: false, error: 'empty_catalog', ids: new Set(), entries: [] };
59
+ }
60
+ let data;
61
+ try {
62
+ data = typeof raw === 'string' ? JSON.parse(raw) : raw;
63
+ } catch {
64
+ return { ok: false, error: 'catalog_parse_error', ids: new Set(), entries: [] };
65
+ }
66
+
67
+ let list = [];
68
+ if (Array.isArray(data)) list = data;
69
+ else if (data && Array.isArray(data.components)) list = data.components;
70
+ else {
71
+ return { ok: false, error: 'catalog_shape_error', ids: new Set(), entries: [] };
72
+ }
73
+
74
+ const entries = [];
75
+ const ids = new Set();
76
+ for (const item of list) {
77
+ if (typeof item === 'string' && item.trim()) {
78
+ const id = item.trim();
79
+ ids.add(id);
80
+ entries.push({ id, props: null });
81
+ continue;
82
+ }
83
+ if (item && typeof item === 'object' && item.id) {
84
+ const id = String(item.id).trim();
85
+ if (!id) continue;
86
+ ids.add(id);
87
+ entries.push({
88
+ id,
89
+ props: Array.isArray(item.props) ? item.props.map(String) : null,
90
+ });
91
+ }
92
+ }
93
+ if (ids.size === 0) {
94
+ return { ok: false, error: 'empty_catalog_ids', ids, entries };
95
+ }
96
+ return { ok: true, error: null, ids, entries };
97
+ }
98
+
99
+ /**
100
+ * Line-oriented compose stream inspired by OpenUI Lang's streaming shape,
101
+ * not a clone of OpenUI Lang syntax. Form:
102
+ * root = Stack([header, body])
103
+ * header = Card()
104
+ * body = Table([row])
105
+ */
106
+ function parseComposeStream(raw) {
107
+ const text = String(raw == null ? '' : raw);
108
+ const lines = text.split(/\r?\n/);
109
+ const parsed = [];
110
+ for (let i = 0; i < lines.length; i += 1) {
111
+ const original = lines[i];
112
+ const trimmed = original.trim();
113
+ if (!trimmed || trimmed.startsWith('#')) {
114
+ parsed.push({
115
+ line: i + 1,
116
+ original,
117
+ kind: 'skip',
118
+ binding: null,
119
+ component: null,
120
+ refs: [],
121
+ validSyntax: true,
122
+ });
123
+ continue;
124
+ }
125
+ if (ARBITRARY_CODE_RE.test(trimmed) || OPENUI_SKU_CLONE_RE.test(trimmed)) {
126
+ parsed.push({
127
+ line: i + 1,
128
+ original,
129
+ kind: 'dangerous',
130
+ binding: null,
131
+ component: null,
132
+ refs: [],
133
+ validSyntax: false,
134
+ danger: ARBITRARY_CODE_RE.test(trimmed) ? 'arbitrary_code' : 'openui_sku_clone',
135
+ });
136
+ continue;
137
+ }
138
+ const m = COMPOSE_LINE_RE.exec(trimmed);
139
+ if (!m) {
140
+ parsed.push({
141
+ line: i + 1,
142
+ original,
143
+ kind: 'invalid',
144
+ binding: null,
145
+ component: null,
146
+ refs: [],
147
+ validSyntax: false,
148
+ });
149
+ continue;
150
+ }
151
+ const binding = m[1];
152
+ const component = m[2];
153
+ const args = m[3] == null ? '' : m[3];
154
+ const refs = [];
155
+ const refRe = /\b([A-Za-z_][\w.-]*)\b/g;
156
+ let rm;
157
+ while ((rm = refRe.exec(args)) !== null) {
158
+ const token = rm[1];
159
+ // Skip obvious literals / keywords
160
+ if (/^(true|false|null|undefined)$/i.test(token)) continue;
161
+ if (/^\d/.test(token)) continue;
162
+ refs.push(token);
163
+ }
164
+ parsed.push({
165
+ line: i + 1,
166
+ original,
167
+ kind: binding === 'root' ? 'root' : 'node',
168
+ binding,
169
+ component,
170
+ refs,
171
+ validSyntax: true,
172
+ });
173
+ }
174
+ return parsed;
175
+ }
176
+
177
+ function collectComponentRefs(parsedLines) {
178
+ const refs = [];
179
+ for (const row of parsedLines) {
180
+ if (!row.validSyntax || !row.component) continue;
181
+ refs.push({ line: row.line, binding: row.binding, component: row.component });
182
+ }
183
+ return refs;
184
+ }
185
+
186
+ function repairComposeStream(parsedLines, catalogIds) {
187
+ const kept = [];
188
+ const dropped = [];
189
+ for (const row of parsedLines) {
190
+ if (row.kind === 'skip') {
191
+ kept.push(row);
192
+ continue;
193
+ }
194
+ if (!row.validSyntax || row.kind === 'dangerous' || row.kind === 'invalid') {
195
+ dropped.push({ line: row.line, reason: row.danger || row.kind, text: row.original });
196
+ continue;
197
+ }
198
+ if (!catalogIds.has(row.component)) {
199
+ dropped.push({
200
+ line: row.line,
201
+ reason: 'unknown_component',
202
+ component: row.component,
203
+ text: row.original,
204
+ });
205
+ continue;
206
+ }
207
+ kept.push(row);
208
+ }
209
+ const repairedText = kept
210
+ .filter((r) => r.kind !== 'skip' || String(r.original).trim() === '')
211
+ .map((r) => r.original)
212
+ .join('\n')
213
+ .replace(/\n+$/, '');
214
+ const hasRoot = kept.some((r) => r.kind === 'root');
215
+ return {
216
+ kept,
217
+ dropped,
218
+ repairedText: repairedText.length ? `${repairedText}\n` : '',
219
+ hasRoot,
220
+ clean: dropped.length === 0 && hasRoot,
221
+ };
222
+ }
223
+
224
+ function buildFindings({
225
+ catalog,
226
+ parsed,
227
+ repair,
228
+ claimReady,
229
+ cloneSignal,
230
+ }) {
231
+ const findings = [];
232
+
233
+ if (!catalog.ok) {
234
+ findings.push({
235
+ id: catalog.error || 'missing_catalog',
236
+ severity: 'fail',
237
+ gateId: 'require-catalog-compose-only',
238
+ message: 'Catalog missing or unparseable. Provide --catalog=path with a non-empty components list.',
239
+ });
240
+ } else if (catalog.ids.size === 0) {
241
+ findings.push({
242
+ id: 'empty_catalog_ids',
243
+ severity: 'fail',
244
+ gateId: 'require-catalog-compose-only',
245
+ message: 'Catalog has zero component ids.',
246
+ });
247
+ }
248
+
249
+ const structural = parsed.filter((p) => p.kind !== 'skip');
250
+ if (structural.length === 0) {
251
+ findings.push({
252
+ id: 'empty_compose_stream',
253
+ severity: 'fail',
254
+ gateId: 'require-catalog-compose-only',
255
+ message: 'Compose stream is empty. Provide --stream=path with root-first catalog lines.',
256
+ });
257
+ }
258
+
259
+ for (const row of parsed) {
260
+ if (row.kind === 'dangerous' && row.danger === 'arbitrary_code') {
261
+ findings.push({
262
+ id: 'arbitrary_code_in_compose',
263
+ severity: 'fail',
264
+ gateId: 'require-catalog-compose-only',
265
+ line: row.line,
266
+ message: `Line ${row.line}: arbitrary code marker — catalog compose must never eval/run generated code.`,
267
+ });
268
+ }
269
+ if (row.kind === 'dangerous' && row.danger === 'openui_sku_clone') {
270
+ findings.push({
271
+ id: 'openui_sku_clone',
272
+ severity: 'fail',
273
+ gateId: 'require-catalog-compose-only',
274
+ line: row.line,
275
+ message: `Line ${row.line}: OpenUI/Thesys SKU clone signal — steal FORMAT only; do not install @openuidev or Gateway.`,
276
+ });
277
+ }
278
+ if (row.kind === 'invalid') {
279
+ findings.push({
280
+ id: 'invalid_compose_syntax',
281
+ severity: 'fail',
282
+ gateId: 'require-repair-before-compose-claim',
283
+ line: row.line,
284
+ message: `Line ${row.line}: invalid compose syntax (expected binding = Component(...)).`,
285
+ });
286
+ }
287
+ }
288
+
289
+ if (catalog.ok) {
290
+ for (const ref of collectComponentRefs(parsed)) {
291
+ if (!catalog.ids.has(ref.component)) {
292
+ findings.push({
293
+ id: 'unknown_component',
294
+ severity: 'fail',
295
+ gateId: 'require-catalog-compose-only',
296
+ line: ref.line,
297
+ component: ref.component,
298
+ message: `Line ${ref.line}: component "${ref.component}" is not in the allowlisted catalog.`,
299
+ });
300
+ }
301
+ }
302
+ }
303
+
304
+ const rootLines = parsed.filter((p) => p.kind === 'root');
305
+ if (structural.length > 0 && rootLines.length === 0) {
306
+ findings.push({
307
+ id: 'missing_root',
308
+ severity: 'fail',
309
+ gateId: 'require-catalog-compose-only',
310
+ message: 'Compose stream has no root = ... assignment (root-first streaming honesty).',
311
+ });
312
+ }
313
+
314
+ const firstStructural = structural[0];
315
+ if (firstStructural && firstStructural.kind !== 'root' && rootLines.length > 0) {
316
+ findings.push({
317
+ id: 'root_not_first',
318
+ severity: 'warn',
319
+ gateId: 'require-catalog-compose-only',
320
+ message: `Root appears after other nodes (first structural line ${firstStructural.line}). Prefer root-first streaming.`,
321
+ });
322
+ }
323
+
324
+ if (cloneSignal) {
325
+ findings.push({
326
+ id: 'openui_sku_clone',
327
+ severity: 'fail',
328
+ gateId: 'require-catalog-compose-only',
329
+ message: 'Input requests cloning OpenUI/Thesys product surface. Refuse SKU clone; keep FORMAT steal only.',
330
+ });
331
+ }
332
+
333
+ if (claimReady) {
334
+ if (!repair || !repair.clean) {
335
+ findings.push({
336
+ id: 'claim_without_repair',
337
+ severity: 'fail',
338
+ gateId: 'require-repair-before-compose-claim',
339
+ message:
340
+ 'Claimed compose/UI ready without a clean validate+repair pass. Run with --repair and zero drops + root present before claiming.',
341
+ });
342
+ }
343
+ }
344
+
345
+ return findings;
346
+ }
347
+
348
+ function normalizeOptions(raw = {}) {
349
+ const rootDir = path.resolve(
350
+ String(raw.root || raw.rootDir || process.cwd())
351
+ );
352
+ const catalogPath = raw.catalog
353
+ ? path.resolve(rootDir, String(raw.catalog))
354
+ : raw.catalogPath
355
+ ? path.resolve(rootDir, String(raw.catalogPath))
356
+ : null;
357
+ const streamPath = raw.stream || raw.compose
358
+ ? path.resolve(rootDir, String(raw.stream || raw.compose))
359
+ : raw.streamPath
360
+ ? path.resolve(rootDir, String(raw.streamPath))
361
+ : null;
362
+
363
+ return {
364
+ rootDir,
365
+ catalogPath,
366
+ streamPath,
367
+ catalogText: raw.catalogText != null ? String(raw.catalogText) : null,
368
+ streamText: raw.streamText != null ? String(raw.streamText) : null,
369
+ repair: normalizeBoolean(raw.repair),
370
+ claimReady: normalizeBoolean(raw['claim-ready'] || raw.claimReady),
371
+ strict: normalizeBoolean(raw.strict),
372
+ json: normalizeBoolean(raw.json),
373
+ };
374
+ }
375
+
376
+ function buildOpenuiCatalogComposeHonestyReport(rawOptions = {}) {
377
+ const options = normalizeOptions(rawOptions);
378
+ let catalogRaw = options.catalogText;
379
+ let streamRaw = options.streamText;
380
+ const ioErrors = [];
381
+
382
+ if (catalogRaw == null && options.catalogPath) {
383
+ try {
384
+ catalogRaw = readText(options.catalogPath);
385
+ } catch (err) {
386
+ ioErrors.push({ id: 'catalog_read_error', message: err.message });
387
+ catalogRaw = '';
388
+ }
389
+ }
390
+ if (streamRaw == null && options.streamPath) {
391
+ try {
392
+ streamRaw = readText(options.streamPath);
393
+ } catch (err) {
394
+ ioErrors.push({ id: 'stream_read_error', message: err.message });
395
+ streamRaw = '';
396
+ }
397
+ }
398
+
399
+ // Fixture mode when nothing provided: demonstrate fail-closed empty state.
400
+ if (catalogRaw == null && streamRaw == null) {
401
+ catalogRaw = '';
402
+ streamRaw = '';
403
+ }
404
+
405
+ const catalog = parseCatalog(catalogRaw || '');
406
+ const parsed = parseComposeStream(streamRaw || '');
407
+ const cloneSignal = OPENUI_SKU_CLONE_RE.test(String(catalogRaw || ''))
408
+ || OPENUI_SKU_CLONE_RE.test(String(streamRaw || ''));
409
+
410
+ const repair = repairComposeStream(parsed, catalog.ids);
411
+ const findings = [
412
+ ...ioErrors.map((e) => ({
413
+ id: e.id,
414
+ severity: 'fail',
415
+ gateId: 'require-catalog-compose-only',
416
+ message: e.message,
417
+ })),
418
+ ...buildFindings({
419
+ catalog,
420
+ parsed,
421
+ repair,
422
+ claimReady: options.claimReady,
423
+ cloneSignal,
424
+ }),
425
+ ];
426
+
427
+ // Deduplicate finding ids+line
428
+ const seen = new Set();
429
+ const deduped = [];
430
+ for (const f of findings) {
431
+ const key = `${f.id}:${f.line || ''}:${f.component || ''}`;
432
+ if (seen.has(key)) continue;
433
+ seen.add(key);
434
+ deduped.push(f);
435
+ }
436
+
437
+ const failCount = deduped.filter((f) => f.severity === 'fail').length;
438
+ const warnCount = deduped.filter((f) => f.severity === 'warn').length;
439
+ let status = 'ready';
440
+ if (failCount > 0) status = 'fail';
441
+ else if (warnCount > 0) status = 'actionable';
442
+
443
+ return {
444
+ name: 'thumbgate-openui-catalog-compose-honesty',
445
+ status,
446
+ source: SOURCE_URL,
447
+ disclaimer:
448
+ 'FORMAT steal from OpenUI (catalog-compose-only, root-first streaming, repair-before-claim). Not affiliated with OpenUI/Thesys. Does not install @openuidev or ship a generative-UI SKU.',
449
+ rootDir: options.rootDir,
450
+ metrics: {
451
+ catalogPath: options.catalogPath,
452
+ streamPath: options.streamPath,
453
+ catalogOk: catalog.ok,
454
+ catalogSize: catalog.ids.size,
455
+ streamLines: parsed.length,
456
+ structuralLines: parsed.filter((p) => p.kind !== 'skip').length,
457
+ rootPresent: parsed.some((p) => p.kind === 'root'),
458
+ repairRequested: options.repair,
459
+ claimReady: options.claimReady,
460
+ droppedLineCount: repair.dropped.length,
461
+ keptLineCount: repair.kept.filter((k) => k.kind !== 'skip').length,
462
+ repairClean: repair.clean,
463
+ },
464
+ repair: options.repair
465
+ ? {
466
+ clean: repair.clean,
467
+ dropped: repair.dropped,
468
+ repairedText: repair.repairedText,
469
+ }
470
+ : {
471
+ clean: repair.clean,
472
+ droppedCount: repair.dropped.length,
473
+ note: 'Pass --repair to include dropped lines and repaired stream text.',
474
+ },
475
+ findings: deduped,
476
+ summary: {
477
+ failCount,
478
+ warnCount,
479
+ findingCount: deduped.length,
480
+ recommendedGateCount: [...new Set(deduped.map((f) => f.gateId).filter(Boolean))].length,
481
+ },
482
+ recommendedGates: [...new Set(deduped.map((f) => f.gateId).filter(Boolean))],
483
+ nextActions: [
484
+ 'Compose only from an allowlisted component catalog — never invent components or eval generated code.',
485
+ 'Emit root = ... first, then fill child bindings (root-first streaming).',
486
+ 'Validate + repair invalid lines before claiming UI/compose ready.',
487
+ 'Do not install @openuidev/cli, OpenUI Gateway, or Thesys Observability as a ThumbGate SKU.',
488
+ 'Pair with gates require-catalog-compose-only and require-repair-before-compose-claim.',
489
+ ],
490
+ exampleCommand:
491
+ 'npx thumbgate openui-catalog-compose-honesty --catalog=catalog.json --stream=compose.txt --repair --json',
492
+ };
493
+ }
494
+
495
+ function formatOpenuiCatalogComposeHonestyReport(report) {
496
+ const lines = [
497
+ '',
498
+ 'ThumbGate OpenUI Catalog-Compose Honesty Doctor',
499
+ '-'.repeat(48),
500
+ `Status : ${report.status}`,
501
+ `Root : ${report.rootDir}`,
502
+ `Catalog : ${report.metrics.catalogPath || '(inline/empty)'} (${report.metrics.catalogSize} ids, ok=${report.metrics.catalogOk})`,
503
+ `Stream : ${report.metrics.streamPath || '(inline/empty)'} (${report.metrics.structuralLines} structural)`,
504
+ `Root line: ${report.metrics.rootPresent}`,
505
+ `Repair : clean=${report.metrics.repairClean} dropped=${report.metrics.droppedLineCount}`,
506
+ `Findings : ${report.summary.findingCount} (fail=${report.summary.failCount}, warn=${report.summary.warnCount})`,
507
+ `Source : ${report.source}`,
508
+ ];
509
+ if (report.findings.length) {
510
+ lines.push('', 'Findings:');
511
+ for (const f of report.findings) {
512
+ const gate = f.gateId ? ` [${f.gateId}]` : '';
513
+ const loc = f.line ? ` L${f.line}` : '';
514
+ lines.push(` - [${f.severity}] ${f.id}${loc}${gate}`);
515
+ lines.push(` ${f.message}`);
516
+ }
517
+ }
518
+ if (report.repair && report.repair.dropped && report.repair.dropped.length) {
519
+ lines.push('', 'Repaired drops:');
520
+ for (const d of report.repair.dropped) {
521
+ lines.push(` - L${d.line} ${d.reason}${d.component ? ` (${d.component})` : ''}`);
522
+ }
523
+ }
524
+ lines.push('', 'Next actions:');
525
+ for (const a of report.nextActions) lines.push(` - ${a}`);
526
+ lines.push('', `Example: ${report.exampleCommand}`);
527
+ lines.push(`Note: ${report.disclaimer}`, '');
528
+ return `${lines.join('\n')}\n`;
529
+ }
530
+
531
+ function parseCliArgs(argv) {
532
+ const options = {};
533
+ for (const arg of argv) {
534
+ if (arg === '--json') { options.json = true; continue; }
535
+ if (arg === '--strict') { options.strict = true; continue; }
536
+ if (arg === '--repair') { options.repair = true; continue; }
537
+ if (arg === '--claim-ready') { options['claim-ready'] = true; continue; }
538
+ if (arg === '--help' || arg === '-h') { options.help = true; continue; }
539
+ const m = /^--([^=]+)(?:=(.*))?$/.exec(arg);
540
+ if (!m) continue;
541
+ options[m[1]] = m[2] === undefined ? true : m[2];
542
+ }
543
+ return options;
544
+ }
545
+
546
+ function printHelp() {
547
+ process.stdout.write(`Usage: node scripts/openui-catalog-compose-honesty.js [flags]
548
+
549
+ Flags:
550
+ --catalog=PATH JSON catalog ({"components":["Stack","Card"]})
551
+ --stream=PATH Line-oriented compose stream (root = Stack([...]))
552
+ --compose=PATH Alias for --stream
553
+ --repair Include dropped lines + repaired stream text
554
+ --claim-ready Fail unless repair is clean (root + zero drops)
555
+ --root=DIR Repo root for relative paths (default: cwd)
556
+ --strict Exit 1 on fail/actionable
557
+ --json
558
+
559
+ Source: ${SOURCE_URL}
560
+ `);
561
+ }
562
+
563
+ function runCli(argv = process.argv.slice(2)) {
564
+ const args = parseCliArgs(argv);
565
+ if (args.help) {
566
+ printHelp();
567
+ return 0;
568
+ }
569
+ const report = buildOpenuiCatalogComposeHonestyReport(args);
570
+ if (args.json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
571
+ else process.stdout.write(formatOpenuiCatalogComposeHonestyReport(report));
572
+ if (args.strict && report.status !== 'ready') return 1;
573
+ if (report.status === 'fail') return 1;
574
+ return 0;
575
+ }
576
+
577
+ module.exports = {
578
+ SOURCE_URL,
579
+ ARBITRARY_CODE_RE,
580
+ OPENUI_SKU_CLONE_RE,
581
+ parseCatalog,
582
+ parseComposeStream,
583
+ repairComposeStream,
584
+ collectComponentRefs,
585
+ buildOpenuiCatalogComposeHonestyReport,
586
+ formatOpenuiCatalogComposeHonestyReport,
587
+ runCli,
588
+ };
589
+
590
+ if (require.main === module
591
+ || (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename))) {
592
+ process.exitCode = runCli(process.argv.slice(2));
593
+ }
@@ -393,8 +393,26 @@ function gitMergeBaseIsAncestor(repoPath, commit, ref) {
393
393
  }
394
394
 
395
395
  function resolveRepoRoot(repoPath = process.cwd()) {
396
+ let target = process.cwd();
397
+ if (typeof repoPath === 'string') {
398
+ target = repoPath;
399
+ } else if (repoPath && typeof repoPath === 'object') {
400
+ const cmd = String(repoPath.command || '');
401
+ const gitCMatch = cmd.match(/(?:^|\s)git(?:\s+-[^\s]+)*\s+-C(?:\s+|=)(?:'([^']+)'|"([^"]+)"|([^\s'"]+))/i);
402
+ const gitCDir = gitCMatch ? (gitCMatch[1] || gitCMatch[2] || gitCMatch[3] || '').trim() : '';
403
+ if (gitCDir) {
404
+ let cPath = gitCDir;
405
+ if (cPath.startsWith('~/')) {
406
+ const homedir = process.env.HOME || '/tmp';
407
+ cPath = path.join(homedir, cPath.slice(2));
408
+ }
409
+ target = path.resolve(repoPath.cwd || process.cwd(), cPath);
410
+ } else {
411
+ target = repoPath.repoPath || repoPath.cwd || process.cwd();
412
+ }
413
+ }
396
414
  try {
397
- return gitShowTopLevel(repoPath);
415
+ return gitShowTopLevel(target);
398
416
  } catch {
399
417
  return null;
400
418
  }