@grifhinz/logics-manager 2.2.0 → 2.3.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 (43) hide show
  1. package/README.md +95 -1
  2. package/VERSION +1 -1
  3. package/clients/README.md +9 -0
  4. package/clients/shared-web/media/css/board.css +658 -0
  5. package/clients/shared-web/media/css/details.css +457 -0
  6. package/clients/shared-web/media/css/layout.css +123 -0
  7. package/clients/shared-web/media/css/toolbar.css +576 -0
  8. package/clients/shared-web/media/harnessApi.js +324 -0
  9. package/clients/shared-web/media/hostApi.js +213 -0
  10. package/clients/shared-web/media/hostApiContract.js +55 -0
  11. package/clients/shared-web/media/icon.png +0 -0
  12. package/clients/shared-web/media/layoutController.js +246 -0
  13. package/clients/shared-web/media/logics.svg +7 -0
  14. package/clients/shared-web/media/logicsModel.js +910 -0
  15. package/clients/shared-web/media/main.css +112 -0
  16. package/clients/shared-web/media/main.js +3 -0
  17. package/clients/shared-web/media/mainApp.js +1005 -0
  18. package/clients/shared-web/media/mainCore.js +604 -0
  19. package/clients/shared-web/media/mainInteractionHandlers.js +324 -0
  20. package/clients/shared-web/media/mainInteractions.js +378 -0
  21. package/clients/shared-web/media/renderBoard.js +3 -0
  22. package/clients/shared-web/media/renderBoardApp.js +1339 -0
  23. package/clients/shared-web/media/renderDetails.js +685 -0
  24. package/clients/shared-web/media/renderMarkdown.js +449 -0
  25. package/clients/shared-web/media/toolsPanelLayout.js +172 -0
  26. package/clients/shared-web/media/uiStatus.js +54 -0
  27. package/clients/shared-web/media/webviewChrome.js +405 -0
  28. package/clients/shared-web/media/webviewPersistence.js +116 -0
  29. package/clients/shared-web/media/webviewSelectors.js +491 -0
  30. package/clients/viewer/README.md +5 -0
  31. package/clients/viewer/browser-host.js +847 -0
  32. package/clients/viewer/index.html +237 -0
  33. package/clients/viewer/viewer.css +433 -0
  34. package/logics_manager/assist.py +9 -142
  35. package/logics_manager/assist_handoff.py +132 -0
  36. package/logics_manager/assist_surface.py +38 -0
  37. package/logics_manager/cli.py +78 -5
  38. package/logics_manager/flow.py +126 -24
  39. package/logics_manager/flow_evidence.py +63 -0
  40. package/logics_manager/update_check.py +138 -0
  41. package/logics_manager/viewer.py +533 -0
  42. package/package.json +12 -6
  43. package/pyproject.toml +1 -1
@@ -0,0 +1,324 @@
1
+ (() => {
2
+ window.createCdxLogicsHarnessApi = function createCdxLogicsHarnessApi(options) {
3
+ const {
4
+ isHarnessMode,
5
+ harnessBridge,
6
+ markdownApi,
7
+ escapeHtml,
8
+ showStatus,
9
+ projectGithubUrl
10
+ } = options;
11
+
12
+ let currentRoot = null;
13
+ let harnessRootHandle = null;
14
+ let canResetProjectRoot = false;
15
+
16
+ function encodePathForUrl(relativePath) {
17
+ return relativePath
18
+ .split("/")
19
+ .filter((part) => part.length > 0)
20
+ .map((part) => encodeURIComponent(part))
21
+ .join("/");
22
+ }
23
+
24
+ function getItemRelativePath(item) {
25
+ if (!item) {
26
+ return "";
27
+ }
28
+ if (typeof item.relPath === "string" && item.relPath.trim()) {
29
+ return item.relPath.trim().replace(/\\/g, "/").replace(/^\.?\//, "");
30
+ }
31
+ if (typeof item.path === "string" && item.path.trim()) {
32
+ const normalizedPath = item.path.replace(/\\/g, "/");
33
+ if (currentRoot && normalizedPath.startsWith(currentRoot)) {
34
+ const relative = normalizedPath.slice(currentRoot.length).replace(/^\/+/, "");
35
+ if (relative) {
36
+ return relative;
37
+ }
38
+ }
39
+ if (!normalizedPath.startsWith("/") && !normalizedPath.includes(":")) {
40
+ return normalizedPath.replace(/^\.?\//, "");
41
+ }
42
+ }
43
+ return "";
44
+ }
45
+
46
+ function buildHarnessDocUrl(item) {
47
+ const relativePath = getItemRelativePath(item);
48
+ if (!relativePath) {
49
+ return "";
50
+ }
51
+ return `/${encodePathForUrl(relativePath)}`;
52
+ }
53
+
54
+ async function readHarnessFileFromHandle(relativePath) {
55
+ if (!harnessRootHandle || typeof harnessRootHandle.getDirectoryHandle !== "function") {
56
+ return null;
57
+ }
58
+ const segments = relativePath
59
+ .split("/")
60
+ .map((part) => part.trim())
61
+ .filter((part) => part.length > 0 && part !== ".");
62
+ if (!segments.length || segments.includes("..")) {
63
+ return null;
64
+ }
65
+ const filename = segments[segments.length - 1];
66
+ if (!filename) {
67
+ return null;
68
+ }
69
+
70
+ let directoryHandle = harnessRootHandle;
71
+ for (const segment of segments.slice(0, -1)) {
72
+ directoryHandle = await directoryHandle.getDirectoryHandle(segment, { create: false });
73
+ }
74
+ const fileHandle = await directoryHandle.getFileHandle(filename, { create: false });
75
+ const file = await fileHandle.getFile();
76
+ return file.text();
77
+ }
78
+
79
+ function openHarnessContentTab(item, mode, sourceLabel, content) {
80
+ const popup = window.open("", "_blank", "noopener,noreferrer");
81
+ if (!popup) {
82
+ showStatus("Popup blocked by the browser. Enable popups for harness preview.", "warn");
83
+ return false;
84
+ }
85
+ const editMode = mode === "edit";
86
+ const body = editMode
87
+ ? `<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>${escapeHtml(
88
+ item && item.title ? item.title : "Logics item"
89
+ )}</title><style>body{font-family:system-ui,sans-serif;margin:20px;line-height:1.45;}code{background:#f4f4f4;padding:2px 4px;border-radius:4px;}</style></head><body><h1>${escapeHtml(
90
+ item && item.title ? item.title : "Logics item"
91
+ )}</h1><p>Source: <code>${escapeHtml(sourceLabel || "unknown")}</code></p><textarea style="width:100%;height:70vh;font:13px ui-monospace,SFMono-Regular,Menlo,monospace;">${escapeHtml(
92
+ content || ""
93
+ )}</textarea><p style="font-size:12px;opacity:0.8;">Harness edit mode is preview-only (saving is disabled).</p></body></html>`
94
+ : markdownApi.buildReadPreviewDocument(item, sourceLabel, content);
95
+ popup.document.open();
96
+ popup.document.write(body);
97
+ popup.document.close();
98
+ return true;
99
+ }
100
+
101
+ async function resolveHarnessItemContent(item) {
102
+ const relativePath = getItemRelativePath(item);
103
+ if (!relativePath) {
104
+ return { relativePath: "", content: "", source: "", error: "missing-path" };
105
+ }
106
+
107
+ if (harnessRootHandle) {
108
+ try {
109
+ const content = await readHarnessFileFromHandle(relativePath);
110
+ if (typeof content === "string") {
111
+ return { relativePath, content, source: `filesystem:${currentRoot || "selected-root"}`, error: "" };
112
+ }
113
+ } catch (error) {
114
+ const reason = error instanceof Error ? error.message : String(error);
115
+ showStatus(`Could not read from selected root (${reason}). Falling back to server path.`, "warn");
116
+ }
117
+ }
118
+
119
+ const target = `/${encodePathForUrl(relativePath)}`;
120
+ try {
121
+ const response = await fetch(target);
122
+ if (!response.ok) {
123
+ throw new Error(`HTTP ${response.status}`);
124
+ }
125
+ const content = await response.text();
126
+ return { relativePath, content, source: target, error: "" };
127
+ } catch (error) {
128
+ const reason = error instanceof Error ? error.message : String(error);
129
+ return { relativePath, content: "", source: target, error: reason };
130
+ }
131
+ }
132
+
133
+ function openHarnessInfoTab(item, heading, message) {
134
+ const popup = window.open("", "_blank", "noopener,noreferrer");
135
+ if (!popup) {
136
+ showStatus("Popup blocked by the browser. Enable popups for harness preview.", "warn");
137
+ return;
138
+ }
139
+ const safeTitle = escapeHtml(item && item.title ? item.title : "Logics item");
140
+ const safeHeading = escapeHtml(heading);
141
+ const safeMessage = escapeHtml(message);
142
+ popup.document.open();
143
+ popup.document.write(`<!doctype html><html lang="en"><head><meta charset="UTF-8" /><title>${safeTitle}</title><style>body{font-family:system-ui,sans-serif;margin:24px;line-height:1.5;}code{background:#f4f4f4;padding:2px 4px;border-radius:4px;}</style></head><body><h1>${safeHeading}</h1><p>${safeMessage}</p></body></html>`);
144
+ popup.document.close();
145
+ }
146
+
147
+ async function openHarnessEditTab(item) {
148
+ const target = buildHarnessDocUrl(item);
149
+ if (!target) {
150
+ openHarnessInfoTab(item, "Edit view unavailable", "No file path is available for this mocked item in harness mode.");
151
+ showStatus("No file path available for this item in harness mode.", "warn");
152
+ return;
153
+ }
154
+
155
+ const resolved = await resolveHarnessItemContent(item);
156
+ if (!resolved.error) {
157
+ const opened = openHarnessContentTab(item, "edit", resolved.source, resolved.content);
158
+ if (opened) {
159
+ showStatus(`Opened edit tab for ${resolved.relativePath} (${resolved.source}).`, "info");
160
+ }
161
+ return;
162
+ }
163
+
164
+ const opened = window.open(target, "_blank", "noopener,noreferrer");
165
+ if (!opened) {
166
+ showStatus("Popup blocked by the browser. Enable popups to open files from harness mode.", "warn");
167
+ return;
168
+ }
169
+ showStatus(`Opened ${target} in a new tab.`, "info");
170
+ }
171
+
172
+ async function openHarnessReadTab(item) {
173
+ const target = buildHarnessDocUrl(item);
174
+ if (!target) {
175
+ openHarnessInfoTab(item, "Read view unavailable", "No file path is available for this mocked item in harness mode.");
176
+ showStatus("No file path available for this item in harness mode.", "warn");
177
+ return;
178
+ }
179
+
180
+ const resolved = await resolveHarnessItemContent(item);
181
+ if (!resolved.error) {
182
+ const opened = openHarnessContentTab(item, "read", resolved.relativePath || resolved.source, resolved.content);
183
+ if (opened) {
184
+ showStatus(`Opened read preview for ${resolved.relativePath} (${resolved.source}).`, "info");
185
+ }
186
+ return;
187
+ }
188
+
189
+ const opened = window.open(target, "_blank", "noopener,noreferrer");
190
+ if (!opened) {
191
+ showStatus("Popup blocked by the browser. Enable popups for harness preview.", "warn");
192
+ return;
193
+ }
194
+ const reason = resolved.error || "preview unavailable";
195
+ showStatus(`Preview unavailable (${reason}). Opened raw file instead.`, "warn");
196
+ }
197
+
198
+ function openHarnessItem(item, mode) {
199
+ if (mode === "read") {
200
+ void openHarnessReadTab(item);
201
+ } else {
202
+ void openHarnessEditTab(item);
203
+ }
204
+ }
205
+
206
+ function pickDirectoryFromInput() {
207
+ return new Promise((resolve) => {
208
+ const input = document.createElement("input");
209
+ let settled = false;
210
+ const finalize = (value) => {
211
+ if (settled) {
212
+ return;
213
+ }
214
+ settled = true;
215
+ window.removeEventListener("focus", onWindowFocus, true);
216
+ input.remove();
217
+ resolve(value);
218
+ };
219
+ const onWindowFocus = () => {
220
+ window.setTimeout(() => {
221
+ if (settled) {
222
+ return;
223
+ }
224
+ const files = input.files;
225
+ if (!files || files.length === 0) {
226
+ finalize(null);
227
+ }
228
+ }, 0);
229
+ };
230
+
231
+ input.type = "file";
232
+ input.multiple = true;
233
+ input.webkitdirectory = true;
234
+ input.style.display = "none";
235
+ input.addEventListener("change", () => {
236
+ const files = input.files;
237
+ if (!files || files.length === 0) {
238
+ finalize(null);
239
+ return;
240
+ }
241
+ const first = files[0];
242
+ const relative = typeof first.webkitRelativePath === "string" ? first.webkitRelativePath : "";
243
+ const rootName = relative.split("/")[0] || first.name;
244
+ finalize(rootName || null);
245
+ });
246
+ document.body.appendChild(input);
247
+ window.addEventListener("focus", onWindowFocus, true);
248
+ input.click();
249
+ });
250
+ }
251
+
252
+ function applyHarnessRoot(rootLabel, nextHandle = null) {
253
+ currentRoot = rootLabel || null;
254
+ harnessRootHandle = nextHandle || null;
255
+ canResetProjectRoot = Boolean(rootLabel);
256
+ if (harnessBridge && typeof harnessBridge.setProjectRootLabel === "function") {
257
+ harnessBridge.setProjectRootLabel(rootLabel || null);
258
+ }
259
+ }
260
+
261
+ async function handleHarnessChangeProjectRoot() {
262
+ try {
263
+ if (typeof window.showDirectoryPicker === "function") {
264
+ const directoryHandle = await window.showDirectoryPicker({ mode: "read" });
265
+ const label = directoryHandle && directoryHandle.name ? directoryHandle.name : "selected-folder";
266
+ applyHarnessRoot(label, directoryHandle);
267
+ showStatus(`Harness project root set to "${label}".`, "info");
268
+ return;
269
+ }
270
+ } catch (error) {
271
+ if (!(error && error.name === "AbortError")) {
272
+ const reason = error instanceof Error ? error.message : String(error);
273
+ showStatus(`Directory picker unavailable (${reason}). Trying fallback.`, "warn");
274
+ } else {
275
+ showStatus("Project root selection canceled.", "warn");
276
+ return;
277
+ }
278
+ }
279
+
280
+ const fallbackRoot = await pickDirectoryFromInput();
281
+ if (fallbackRoot) {
282
+ applyHarnessRoot(fallbackRoot, null);
283
+ showStatus(`Harness project root set to "${fallbackRoot}" (directory selection fallback).`, "info");
284
+ return;
285
+ }
286
+
287
+ const manual = window.prompt(
288
+ "Directory picker unavailable. Enter a root hint/path for harness mode:",
289
+ currentRoot || ""
290
+ );
291
+ if (manual && manual.trim()) {
292
+ applyHarnessRoot(manual.trim(), null);
293
+ showStatus(`Harness project root set to "${manual.trim()}".`, "info");
294
+ return;
295
+ }
296
+ showStatus("Project root selection canceled.", "warn");
297
+ }
298
+
299
+ return {
300
+ isHarnessMode,
301
+ getCurrentRoot() {
302
+ return currentRoot;
303
+ },
304
+ setCurrentRoot(value) {
305
+ currentRoot = value || null;
306
+ },
307
+ canResetProjectRoot() {
308
+ return canResetProjectRoot;
309
+ },
310
+ setCanResetProjectRoot(value) {
311
+ canResetProjectRoot = Boolean(value);
312
+ },
313
+ applyHarnessRoot,
314
+ handleHarnessChangeProjectRoot,
315
+ openHarnessItem,
316
+ about() {
317
+ const opened = window.open(projectGithubUrl, "_blank", "noopener,noreferrer");
318
+ if (!opened) {
319
+ showStatus("Popup blocked by the browser. Enable popups to open project page.", "warn");
320
+ }
321
+ }
322
+ };
323
+ };
324
+ })();
@@ -0,0 +1,213 @@
1
+ (() => {
2
+ window.createCdxLogicsHostApi = function createCdxLogicsHostApi(options) {
3
+ const {
4
+ vscode,
5
+ debugLog,
6
+ showStatus,
7
+ isHarnessMode,
8
+ handleHarnessChangeProjectRoot,
9
+ applyHarnessRoot,
10
+ openHarnessItem,
11
+ harnessBridge,
12
+ setCanResetProjectRoot,
13
+ projectGithubUrl
14
+ } = options;
15
+
16
+ function post(message) {
17
+ debugLog("host:post", message);
18
+ vscode.postMessage(message);
19
+ }
20
+
21
+ function invokeHostOnly(type, payload, label) {
22
+ if (isHarnessMode) {
23
+ showStatus(`${label} requires the VS Code extension host. Action forwarded to harness mock.`, "warn");
24
+ }
25
+ post({ type, ...payload });
26
+ }
27
+
28
+ function confirmLifecycleAction(item, action) {
29
+ if (!item) {
30
+ return false;
31
+ }
32
+ const itemLabel = item.title ? `${item.id} (${item.title})` : item.id;
33
+ if (action === "obsolete") {
34
+ return window.confirm(
35
+ `Mark ${itemLabel} as obsolete?\n\nThis is a more cautionary lifecycle change and should only be used when the item should no longer be pursued.`
36
+ );
37
+ }
38
+ return window.confirm(`Mark ${itemLabel} as done?`);
39
+ }
40
+
41
+ const api = {
42
+ post,
43
+ ready() {
44
+ post({ type: "ready" });
45
+ },
46
+ refresh() {
47
+ post({ type: "refresh" });
48
+ },
49
+ createItem(kind) {
50
+ invokeHostOnly("create-item", { kind }, "Create item");
51
+ },
52
+ newRequest() {
53
+ invokeHostOnly("new-request", {}, "New Request");
54
+ },
55
+ createCompanionDoc(id, preferredKind) {
56
+ invokeHostOnly("create-companion-doc", { id, preferredKind }, "Create companion doc");
57
+ },
58
+ renameEntry(id) {
59
+ invokeHostOnly("rename-entry", { id }, "Rename entry");
60
+ },
61
+ addReference(id) {
62
+ invokeHostOnly("add-reference", { id }, "Add reference");
63
+ },
64
+ addUsedBy(id) {
65
+ invokeHostOnly("add-used-by", { id }, "Add used-by link");
66
+ },
67
+ fixDocs() {
68
+ invokeHostOnly("fix-docs", {}, "Fix Logics");
69
+ },
70
+ newGuidedRequest() {
71
+ invokeHostOnly("new-request-guided", {}, "New Request");
72
+ },
73
+ launchCodexOverlay() {
74
+ invokeHostOnly("launch-codex-overlay", {}, "Launch Codex");
75
+ },
76
+ launchClaude() {
77
+ invokeHostOnly("launch-claude", {}, "Launch Claude");
78
+ },
79
+ bootstrapLogics() {
80
+ invokeHostOnly("bootstrap-logics", {}, "Bootstrap Logics");
81
+ },
82
+ updateLogicsKit() {
83
+ invokeHostOnly("update-logics-kit", {}, "Update Logics Kit");
84
+ },
85
+ syncCodexOverlay() {
86
+ invokeHostOnly("sync-codex-overlay", {}, "Publish Global Codex Kit");
87
+ },
88
+ repairLogicsKit() {
89
+ invokeHostOnly("repair-logics-kit", {}, "Repair Logics Kit");
90
+ },
91
+ checkEnvironment() {
92
+ invokeHostOnly("check-environment", {}, "Check Environment");
93
+ },
94
+ checkHybridRuntime() {
95
+ invokeHostOnly("check-hybrid-runtime", {}, "Check Hybrid Runtime");
96
+ },
97
+ openHybridInsights() {
98
+ invokeHostOnly("open-hybrid-insights", {}, "Hybrid Insights");
99
+ },
100
+ openLogicsInsights() {
101
+ invokeHostOnly("open-logics-insights", {}, "Logics Insights");
102
+ },
103
+ assistCommitAll() {
104
+ invokeHostOnly("assist-commit-all", {}, "Commit All Changes");
105
+ },
106
+ assistNextStep() {
107
+ invokeHostOnly("assist-next-step", {}, "Suggest Next Step");
108
+ },
109
+ assistTriage(id) {
110
+ invokeHostOnly("assist-triage", id ? { id } : {}, "Triage Item");
111
+ },
112
+ assistDiffRisk() {
113
+ invokeHostOnly("assist-diff-risk", {}, "Assess Diff Risk");
114
+ },
115
+ assistSummarizeValidation() {
116
+ invokeHostOnly("assist-summarize-validation", {}, "Summarize Validation");
117
+ },
118
+ assistSummarizeChangelog() {
119
+ invokeHostOnly("assist-summarize-changelog", {}, "Generate Changelog Summary");
120
+ },
121
+ assistPrepareRelease() {
122
+ invokeHostOnly("assist-prepare-release", {}, "Prepare Release");
123
+ },
124
+ assistPublishRelease() {
125
+ invokeHostOnly("assist-publish-release", {}, "Publish Release");
126
+ },
127
+ assistValidationChecklist() {
128
+ invokeHostOnly("assist-validation-checklist", {}, "Validation Checklist");
129
+ },
130
+ assistDocConsistency() {
131
+ invokeHostOnly("assist-doc-consistency", {}, "Doc Consistency");
132
+ },
133
+ openOnboarding() {
134
+ invokeHostOnly("open-onboarding", {}, "Getting Started");
135
+ },
136
+ selectAgent() {
137
+ invokeHostOnly("select-agent", {}, "Select Agent");
138
+ },
139
+ injectPrompt(prompt, options) {
140
+ if (!prompt) {
141
+ return;
142
+ }
143
+ invokeHostOnly("inject-prompt", { prompt, options }, "Copy for Assistant");
144
+ },
145
+ promote(id) {
146
+ invokeHostOnly("promote", { id }, "Promote");
147
+ },
148
+ markDone(item) {
149
+ if (!confirmLifecycleAction(item, "done")) {
150
+ return;
151
+ }
152
+ invokeHostOnly("mark-done", { id: item.id }, "Mark as done");
153
+ },
154
+ markObsolete(item) {
155
+ if (!confirmLifecycleAction(item, "obsolete")) {
156
+ return;
157
+ }
158
+ invokeHostOnly("mark-obsolete", { id: item.id }, "Mark as obsolete");
159
+ },
160
+ changeStatus(item) {
161
+ if (!item) {
162
+ return;
163
+ }
164
+ invokeHostOnly("change-status", { id: item.id }, "Change status");
165
+ },
166
+ async changeProjectRoot() {
167
+ if (isHarnessMode) {
168
+ await handleHarnessChangeProjectRoot();
169
+ return;
170
+ }
171
+ post({ type: "change-project-root" });
172
+ },
173
+ resetProjectRoot() {
174
+ if (isHarnessMode) {
175
+ applyHarnessRoot(null);
176
+ if (harnessBridge && typeof harnessBridge.resetProjectRoot === "function") {
177
+ harnessBridge.resetProjectRoot();
178
+ }
179
+ setCanResetProjectRoot(false);
180
+ showStatus("Harness project root reset to default mock workspace.", "info");
181
+ return;
182
+ }
183
+ post({ type: "reset-project-root" });
184
+ },
185
+ about() {
186
+ if (isHarnessMode) {
187
+ const opened = window.open(projectGithubUrl, "_blank", "noopener,noreferrer");
188
+ if (!opened) {
189
+ showStatus("Popup blocked by the browser. Enable popups to open project page.", "warn");
190
+ }
191
+ return;
192
+ }
193
+ post({ type: "about" });
194
+ },
195
+ openItem(item, mode) {
196
+ if (!item) {
197
+ return;
198
+ }
199
+ if (isHarnessMode) {
200
+ if (typeof openHarnessItem === "function") {
201
+ openHarnessItem(item, mode);
202
+ }
203
+ return;
204
+ }
205
+ post({ type: mode, id: item.id });
206
+ }
207
+ };
208
+ if (typeof window.assertCdxLogicsHostApiContract === "function") {
209
+ window.assertCdxLogicsHostApiContract(api);
210
+ }
211
+ return api;
212
+ };
213
+ })();
@@ -0,0 +1,55 @@
1
+ (() => {
2
+ const HOST_API_METHODS = [
3
+ "post",
4
+ "ready",
5
+ "refresh",
6
+ "createItem",
7
+ "newRequest",
8
+ "createCompanionDoc",
9
+ "renameEntry",
10
+ "addReference",
11
+ "addUsedBy",
12
+ "fixDocs",
13
+ "newGuidedRequest",
14
+ "launchCodexOverlay",
15
+ "launchClaude",
16
+ "bootstrapLogics",
17
+ "updateLogicsKit",
18
+ "syncCodexOverlay",
19
+ "repairLogicsKit",
20
+ "checkEnvironment",
21
+ "checkHybridRuntime",
22
+ "openHybridInsights",
23
+ "openLogicsInsights",
24
+ "assistCommitAll",
25
+ "assistNextStep",
26
+ "assistTriage",
27
+ "assistDiffRisk",
28
+ "assistSummarizeValidation",
29
+ "assistSummarizeChangelog",
30
+ "assistPrepareRelease",
31
+ "assistPublishRelease",
32
+ "assistValidationChecklist",
33
+ "assistDocConsistency",
34
+ "openOnboarding",
35
+ "selectAgent",
36
+ "injectPrompt",
37
+ "promote",
38
+ "markDone",
39
+ "markObsolete",
40
+ "changeStatus",
41
+ "changeProjectRoot",
42
+ "resetProjectRoot",
43
+ "about",
44
+ "openItem"
45
+ ];
46
+
47
+ window.CDX_LOGICS_HOST_API_METHODS = HOST_API_METHODS;
48
+ window.assertCdxLogicsHostApiContract = function assertCdxLogicsHostApiContract(api) {
49
+ const missing = HOST_API_METHODS.filter((method) => typeof api?.[method] !== "function");
50
+ if (missing.length > 0) {
51
+ throw new Error(`Logics host API is missing required method(s): ${missing.join(", ")}`);
52
+ }
53
+ return api;
54
+ };
55
+ })();