@genn-inc/cluebase-cli 0.0.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 (38) hide show
  1. package/README.md +101 -0
  2. package/bin/cluebase-cli.mjs +11 -0
  3. package/package.json +17 -0
  4. package/src/cli-command.mjs +515 -0
  5. package/src/cli-invocation.mjs +17 -0
  6. package/src/code-evidence-analyzer.mjs +2041 -0
  7. package/src/contracts.mjs +36 -0
  8. package/src/generated-code-evidence-contract.mjs +22 -0
  9. package/src/generated-sdk-version-contract.mjs +5 -0
  10. package/src/generated-source-path-policy.mjs +20 -0
  11. package/src/lifecycle-guard.mjs +202 -0
  12. package/src/path-policy.mjs +81 -0
  13. package/src/setup-ai-contract.mjs +221 -0
  14. package/src/setup-check-constants.mjs +110 -0
  15. package/src/setup-check-scan-a.mjs +849 -0
  16. package/src/setup-check-scan-b.mjs +994 -0
  17. package/src/setup-check.mjs +575 -0
  18. package/src/setup-discover-check.mjs +755 -0
  19. package/src/setup-doctor-deadline.mjs +221 -0
  20. package/src/setup-doctor-env.mjs +331 -0
  21. package/src/setup-doctor-file-boundary.mjs +426 -0
  22. package/src/setup-doctor-probe.mjs +719 -0
  23. package/src/setup-doctor-quality-checks-a.mjs +593 -0
  24. package/src/setup-doctor-quality-checks-b.mjs +638 -0
  25. package/src/setup-doctor-quality-shared.mjs +382 -0
  26. package/src/setup-doctor-quality.mjs +209 -0
  27. package/src/setup-doctor-route-scan.mjs +160 -0
  28. package/src/setup-doctor-sdk-probe.mjs +340 -0
  29. package/src/setup-doctor.mjs +545 -0
  30. package/src/setup-documents.mjs +112 -0
  31. package/src/setup-help.mjs +130 -0
  32. package/src/setup-prepare.mjs +360 -0
  33. package/src/setup-repository-discovery.mjs +764 -0
  34. package/src/setup-step-builders-discover.mjs +701 -0
  35. package/src/setup-step-builders-events.mjs +229 -0
  36. package/src/setup-step-builders-implement.mjs +710 -0
  37. package/src/setup-step-commands.mjs +427 -0
  38. package/src/setup-tool.mjs +27 -0
@@ -0,0 +1,575 @@
1
+ // setup-check: mechanical verification of installed setup steps, secret
2
+ // leaks, and SDK lifecycle presence. Constants live in setup-check-constants,
3
+ // scan/detector helpers in setup-check-scan-a / -scan-b.
4
+
5
+ import { readFile } from "node:fs/promises";
6
+ import { join, resolve } from "node:path";
7
+ import {
8
+ findLifecycleCallApiNames,
9
+ findLifecycleGuardViolations,
10
+ stripSourceNoise,
11
+ } from "./lifecycle-guard.mjs";
12
+ import {
13
+ backendSdkSpecForFramework,
14
+ FRONTEND_SDK_PACKAGE,
15
+ REQUIRED_LIFECYCLE_APIS,
16
+ sourceMatchesBackendInit,
17
+ } from "./setup-check-constants.mjs";
18
+ import {
19
+ addCheck,
20
+ dependencyHasAnyPackage,
21
+ findFrontendLifecycleNonPublicEnvFiles,
22
+ findSecretLeaks,
23
+ frontendPackageRoots,
24
+ packageJsonSourcesWithDependency,
25
+ readAllowedSourceText,
26
+ readDependencyText,
27
+ readFrontendConfigText,
28
+ readSetupManifest,
29
+ setupBackendRootPaths,
30
+ setupSourcePaths,
31
+ sourceHasAuthCallbackScopedCluebaseInitCall,
32
+ sourceHasComponentScopedCluebaseInitCall,
33
+ sourceHasVerifiedFrontendSdkAccess,
34
+ sourceIsUnderFrontendRoot,
35
+ sourcesHaveBackendSdkImport,
36
+ sourcesHaveFrontendSdkImport,
37
+ startsWithRoot,
38
+ validateSetupManifestContract,
39
+ validateSetupStepFiles,
40
+ } from "./setup-check-scan-a.mjs";
41
+ import {
42
+ companyBoundaryPattern,
43
+ findBackendCluebaseEnvRequiredIndexFiles,
44
+ findBackendLifecycleReadEndpointFiles,
45
+ findBackendSdkInitMissingServiceKeyFiles,
46
+ findCluebaseInitCallEmptyEnvFallbackFiles,
47
+ findFastapiMissingMiddlewareFiles,
48
+ findFrontendBackendTracePropagationMissingFiles,
49
+ findFrontendInitBeforeCluebaseInitCallFiles,
50
+ findFrontendLifecycleReadSideEffectFiles,
51
+ findHardcodedLifecycleIdentityFiles,
52
+ findNextLifecycleMissingUseClientFiles,
53
+ findRepeatedIdentityHelperFiles,
54
+ frontendIdentityBoundaryPattern,
55
+ identityBoundaryPattern,
56
+ logoutBoundaryPattern,
57
+ } from "./setup-check-scan-b.mjs";
58
+
59
+ // FIX-118: the discovery artifact records where the setup AI decided the
60
+ // lifecycle boundaries actually are. setup-check uses that placement as the
61
+ // source of truth for whether the backend needs identify/group, instead of
62
+ // re-pattern-matching the raw backend source (which cannot distinguish a real
63
+ // one-time auth boundary from a reused token-hydration path).
64
+ const readDiscoveries = async (repoRoot) => {
65
+ try {
66
+ const raw = await readFile(
67
+ join(repoRoot, ".cluebase", "discoveries.json"),
68
+ "utf8",
69
+ );
70
+ const parsed = JSON.parse(raw);
71
+ return parsed && typeof parsed === "object" ? parsed : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ };
76
+
77
+ const siteIsUnderBackend = (site, backendRootPaths) =>
78
+ typeof site?.file === "string" &&
79
+ backendRootPaths.some((root) => startsWithRoot(site.file, root));
80
+
81
+ // Returns { present, hasBackendIdentify, hasBackendGroup } from discoveries.
82
+ // `present: false` means no discovery artifact — callers fall back to the
83
+ // pattern-based requirement.
84
+ export const backendBoundariesFromDiscovery = (
85
+ discoveries,
86
+ backendRootPaths = [],
87
+ ) => {
88
+ if (!discoveries || typeof discoveries !== "object") {
89
+ return { present: false };
90
+ }
91
+ const identifySites = Array.isArray(discoveries.identify_sites)
92
+ ? discoveries.identify_sites
93
+ : [];
94
+ const groupSites = Array.isArray(discoveries.group_sites)
95
+ ? discoveries.group_sites
96
+ : [];
97
+ return {
98
+ present: true,
99
+ hasBackendIdentify: identifySites.some((site) =>
100
+ siteIsUnderBackend(site, backendRootPaths),
101
+ ),
102
+ hasBackendGroup: groupSites.some((site) =>
103
+ siteIsUnderBackend(site, backendRootPaths),
104
+ ),
105
+ };
106
+ };
107
+
108
+ const checkSdkLifecycle = ({
109
+ backendRootPaths = [],
110
+ dependencySources = [],
111
+ discoveryBackendBoundaries = null,
112
+ framework,
113
+ sources,
114
+ }) => {
115
+ const combined = sources
116
+ .map((source) => stripSourceNoise(source.text, { stripStrings: true }))
117
+ .join("\n");
118
+ const backendSources = sources.filter((source) =>
119
+ backendRootPaths.some((root) => startsWithRoot(source.file_path, root)),
120
+ );
121
+ const frontendRootPaths = frontendPackageRoots(dependencySources);
122
+ const frontendSources = sources.filter(
123
+ (source) =>
124
+ !backendRootPaths.some((root) =>
125
+ startsWithRoot(source.file_path, root),
126
+ ) && sourceIsUnderFrontendRoot(source, frontendRootPaths),
127
+ );
128
+ const backendCombined = backendSources
129
+ .map((source) => stripSourceNoise(source.text, { stripStrings: true }))
130
+ .join("\n");
131
+ const frontendCombined = frontendSources
132
+ .map((source) => stripSourceNoise(source.text, { stripStrings: true }))
133
+ .join("\n");
134
+ const foundApiNames = findLifecycleCallApiNames(combined);
135
+ const backendFoundApiNames = findLifecycleCallApiNames(backendCombined);
136
+ const frontendFoundApiNames = findLifecycleCallApiNames(frontendCombined);
137
+ const sourceByPath = new Map(
138
+ sources.map((source) => [source.file_path, source]),
139
+ );
140
+ const frontendLifecycleFilesWithoutVerifiedSdk = frontendSources
141
+ .filter(
142
+ (source) =>
143
+ findLifecycleCallApiNames(
144
+ stripSourceNoise(source.text, { stripStrings: true }),
145
+ ).length > 0,
146
+ )
147
+ .filter((source) => {
148
+ const apiNames = findLifecycleCallApiNames(
149
+ stripSourceNoise(source.text, { stripStrings: true }),
150
+ );
151
+ return !sourceHasVerifiedFrontendSdkAccess({
152
+ apiNames,
153
+ source,
154
+ sourceByPath,
155
+ });
156
+ })
157
+ .map((source) => source.file_path);
158
+ let missingApis = [];
159
+ // `CluebaseInit` is the prescribed App Router bootstrap Client Component
160
+ // (see STEP 5: `export function CluebaseInit() { return null; }`), so a
161
+ // declaration of that name is the intended pattern, not a no-op shim. A
162
+ // `window.CluebaseInit` global assignment is still a shim signal, and
163
+ // `function/const CluebaseIdentify|CluebaseSetOrganization|CluebaseLogout` are never
164
+ // prescribed component names, so those remain flagged.
165
+ const noOpPattern =
166
+ /window\.Cluebase(?:Init|Identify|SetOrganization|Logout)|(?:function|const|let|var)\s+Cluebase(?:Identify|SetOrganization|Logout)\b/;
167
+ const componentLifecycleInitFiles = sources
168
+ .filter((source) => sourceHasComponentScopedCluebaseInitCall(source.text))
169
+ .map((source) => source.file_path);
170
+ const authCallbackLifecycleInitFiles = frontendSources
171
+ .filter((source) => sourceHasAuthCallbackScopedCluebaseInitCall(source.text))
172
+ .map((source) => source.file_path);
173
+ const blockingLifecycleCalls = sources.flatMap((source) =>
174
+ findLifecycleGuardViolations(
175
+ stripSourceNoise(source.text, { stripStrings: true }),
176
+ ).map((violation) => ({
177
+ file_path: source.file_path,
178
+ ...violation,
179
+ })),
180
+ );
181
+ const blockingLifecycleFiles = [
182
+ ...new Set(blockingLifecycleCalls.map((violation) => violation.file_path)),
183
+ ];
184
+ const backendPresent = backendSources.length > 0;
185
+ const backendSpec = backendSdkSpecForFramework(framework);
186
+ const backendSdkDependencyPresent =
187
+ !backendPresent ||
188
+ dependencyHasAnyPackage(dependencySources, backendSpec.packages);
189
+ const backendSdkImportPresent =
190
+ !backendPresent ||
191
+ sourcesHaveBackendSdkImport(backendSources, backendSpec);
192
+ const backendSdkPresent =
193
+ !backendPresent || (backendSdkDependencyPresent && backendSdkImportPresent);
194
+ const backendSdkInstallabilityVerified =
195
+ !backendPresent || backendSpec.installabilityStatus !== "unverified";
196
+ const backendInitPresent =
197
+ !backendPresent || sourceMatchesBackendInit(framework, backendCombined);
198
+ const backendInitObserved = backendPresent && backendInitPresent;
199
+ const foundApis = REQUIRED_LIFECYCLE_APIS.filter(
200
+ (api) =>
201
+ foundApiNames.includes(api) ||
202
+ (api === "cluebase.init" && backendInitObserved),
203
+ );
204
+ const backendSdkInitMissingServiceKeyFiles =
205
+ findBackendSdkInitMissingServiceKeyFiles({
206
+ backendSources,
207
+ framework,
208
+ });
209
+ const fastapiMissingMiddlewareFiles = findFastapiMissingMiddlewareFiles({
210
+ backendSources,
211
+ framework,
212
+ });
213
+ const backendCluebaseEnvRequiredIndexFiles =
214
+ findBackendCluebaseEnvRequiredIndexFiles(backendSources);
215
+ const hardcodedLifecycleIdentityFiles =
216
+ findHardcodedLifecycleIdentityFiles(sources);
217
+ const backendLifecycleReadEndpointFiles =
218
+ findBackendLifecycleReadEndpointFiles(backendSources);
219
+ const frontendLifecyclePresent = frontendFoundApiNames.length > 0;
220
+ const frontendSdkDependencyPresent =
221
+ !frontendLifecyclePresent ||
222
+ packageJsonSourcesWithDependency(dependencySources, FRONTEND_SDK_PACKAGE)
223
+ .length > 0;
224
+ const frontendSdkImportPresent =
225
+ !frontendLifecyclePresent || sourcesHaveFrontendSdkImport(frontendSources);
226
+ const frontendSdkPresent =
227
+ !frontendLifecyclePresent ||
228
+ (frontendSdkDependencyPresent && frontendSdkImportPresent);
229
+ const frontendLifecycleNonPublicEnvFiles =
230
+ findFrontendLifecycleNonPublicEnvFiles({
231
+ frontendSources,
232
+ });
233
+ const frontendLifecycleReadSideEffectFiles =
234
+ findFrontendLifecycleReadSideEffectFiles(frontendSources);
235
+ const repeatedIdentityHelperFiles = findRepeatedIdentityHelperFiles(sources);
236
+ const cluebaseInitEmptyEnvFallbackFiles =
237
+ findCluebaseInitCallEmptyEnvFallbackFiles(frontendSources);
238
+ const frontendInitBeforeCluebaseInitCallFiles =
239
+ findFrontendInitBeforeCluebaseInitCallFiles(frontendSources);
240
+ const frontendBackendTracePropagationMissingFiles =
241
+ findFrontendBackendTracePropagationMissingFiles({
242
+ backendPresent,
243
+ frontendSources,
244
+ });
245
+ const nextLifecycleMissingUseClientFiles =
246
+ findNextLifecycleMissingUseClientFiles({
247
+ dependencySources,
248
+ frontendSources,
249
+ });
250
+ // FIX-118: pattern-matching the backend source only tells us the backend
251
+ // *has auth/organization-shaped routes*, not whether any of them is a real
252
+ // one-time boundary. When the discovery artifact is available, use its
253
+ // actual placement decision as the source of truth; fall back to the
254
+ // pattern only when discovery is absent (e.g. setup-check run standalone).
255
+ const backendPatternIdentity =
256
+ backendPresent && identityBoundaryPattern.test(backendCombined);
257
+ const backendPatternOrganization =
258
+ backendPresent && companyBoundaryPattern.test(backendCombined);
259
+ const backendIdentityRequired = discoveryBackendBoundaries?.present
260
+ ? Boolean(discoveryBackendBoundaries.hasBackendIdentify)
261
+ : backendPatternIdentity;
262
+ const backendOrganizationRequired = discoveryBackendBoundaries?.present
263
+ ? Boolean(discoveryBackendBoundaries.hasBackendGroup)
264
+ : backendPatternOrganization;
265
+ // No silent skip: when the source pattern matched but discovery deferred the
266
+ // boundary to the client (browser auth), record why the requirement lifted.
267
+ const backendIdentityDeferredToClient =
268
+ Boolean(discoveryBackendBoundaries?.present) &&
269
+ backendPatternIdentity &&
270
+ !backendIdentityRequired;
271
+ const backendOrganizationDeferredToClient =
272
+ Boolean(discoveryBackendBoundaries?.present) &&
273
+ backendPatternOrganization &&
274
+ !backendOrganizationRequired;
275
+ const backendLogoutRequired =
276
+ backendPresent && logoutBoundaryPattern.test(backendCombined);
277
+ const backendMissingApis = [
278
+ ...(backendIdentityRequired &&
279
+ !backendFoundApiNames.includes("cluebase.identify")
280
+ ? ["cluebase.identify"]
281
+ : []),
282
+ ...(backendLogoutRequired && !backendFoundApiNames.includes("cluebase.reset")
283
+ ? ["cluebase.reset"]
284
+ : []),
285
+ ...(backendOrganizationRequired &&
286
+ !backendFoundApiNames.includes("cluebase.group")
287
+ ? ["cluebase.group"]
288
+ : []),
289
+ ];
290
+ const frontendIdentityRequired =
291
+ frontendSources.length > 0 &&
292
+ frontendIdentityBoundaryPattern.test(frontendCombined);
293
+ const frontendLogoutRequired =
294
+ frontendSources.length > 0 && logoutBoundaryPattern.test(frontendCombined);
295
+ const frontendOrganizationRequired =
296
+ frontendSources.length > 0 && companyBoundaryPattern.test(frontendCombined);
297
+ const frontendLifecycleRequired =
298
+ frontendLifecyclePresent ||
299
+ frontendIdentityRequired ||
300
+ frontendOrganizationRequired ||
301
+ frontendLogoutRequired;
302
+ const frontendMissingApis = [
303
+ ...(frontendLifecycleRequired &&
304
+ !frontendFoundApiNames.includes("cluebase.init")
305
+ ? ["cluebase.init"]
306
+ : []),
307
+ ...(frontendIdentityRequired &&
308
+ !frontendFoundApiNames.includes("cluebase.identify")
309
+ ? ["cluebase.identify"]
310
+ : []),
311
+ ...(frontendOrganizationRequired &&
312
+ !frontendFoundApiNames.includes("cluebase.group")
313
+ ? ["cluebase.group"]
314
+ : []),
315
+ ...(frontendLogoutRequired && !frontendFoundApiNames.includes("cluebase.reset")
316
+ ? ["cluebase.reset"]
317
+ : []),
318
+ ];
319
+ const requiredApis = [
320
+ ...(backendPresent || frontendSources.length > 0 ? ["cluebase.init"] : []),
321
+ ...(backendIdentityRequired || frontendIdentityRequired
322
+ ? ["cluebase.identify"]
323
+ : []),
324
+ ...(backendOrganizationRequired || frontendOrganizationRequired
325
+ ? ["cluebase.group"]
326
+ : []),
327
+ ...(backendLogoutRequired || frontendLogoutRequired ? ["cluebase.reset"] : []),
328
+ ];
329
+ missingApis = [...new Set(requiredApis)].filter(
330
+ (api) => !foundApis.includes(api),
331
+ );
332
+ return {
333
+ foundApis,
334
+ missingApis,
335
+ required_apis: [...new Set(requiredApis)],
336
+ backend_lifecycle: {
337
+ backend_present: backendPresent,
338
+ backend_root_paths: backendRootPaths,
339
+ dependency_files: dependencySources.map((source) => source.file_path),
340
+ expected_sdk_packages: backendSpec.packages,
341
+ expected_sdk_imports: backendSpec.imports,
342
+ sdk_installability_status: backendSpec.installabilityStatus,
343
+ sdk_installability_verified: backendSdkInstallabilityVerified,
344
+ sdk_blocker: backendSdkInstallabilityVerified
345
+ ? null
346
+ : backendSpec.blocker,
347
+ sdk_dependency_present: backendSdkDependencyPresent,
348
+ sdk_import_present: backendSdkImportPresent,
349
+ sdk_dependency_or_import_present: backendSdkPresent,
350
+ sdk_init_present: backendInitPresent,
351
+ sdk_init_missing_service_key_files: backendSdkInitMissingServiceKeyFiles,
352
+ fastapi_missing_middleware_files: fastapiMissingMiddlewareFiles,
353
+ cluebase_env_required_index_files: backendCluebaseEnvRequiredIndexFiles,
354
+ hardcoded_lifecycle_identity_files:
355
+ hardcodedLifecycleIdentityFiles.filter((filePath) =>
356
+ backendRootPaths.some((root) => startsWithRoot(filePath, root)),
357
+ ),
358
+ repeated_identity_helper_files:
359
+ repeatedIdentityHelperFiles.filter((filePath) =>
360
+ backendRootPaths.some((root) => startsWithRoot(filePath, root)),
361
+ ),
362
+ lifecycle_read_endpoint_files: backendLifecycleReadEndpointFiles,
363
+ required_apis: [
364
+ ...(backendIdentityRequired ? ["cluebase.identify"] : []),
365
+ ...(backendOrganizationRequired ? ["cluebase.group"] : []),
366
+ ...(backendLogoutRequired ? ["cluebase.reset"] : []),
367
+ ],
368
+ missing_apis: backendMissingApis,
369
+ identity_deferred_to_client: backendIdentityDeferredToClient,
370
+ organization_deferred_to_client: backendOrganizationDeferredToClient,
371
+ lifecycle_deferral_reason:
372
+ backendIdentityDeferredToClient || backendOrganizationDeferredToClient
373
+ ? "discovery determined the user's identity / active organization is established client-side (browser auth); the backend has no one-time auth boundary, so backend cluebase.identify / cluebase.group are not required here. Frontend identify / group coverage is still verified."
374
+ : null,
375
+ },
376
+ frontend_lifecycle: {
377
+ lifecycle_present: frontendLifecyclePresent,
378
+ found_apis: frontendFoundApiNames,
379
+ expected_sdk_package: FRONTEND_SDK_PACKAGE,
380
+ sdk_dependency_present: frontendSdkDependencyPresent,
381
+ sdk_import_present: frontendSdkImportPresent,
382
+ sdk_dependency_or_import_present: frontendSdkPresent,
383
+ lifecycle_files_without_verified_sdk:
384
+ frontendLifecycleFilesWithoutVerifiedSdk,
385
+ wrong_sdk_packages: [],
386
+ frontend_lifecycle_non_public_env_files:
387
+ frontendLifecycleNonPublicEnvFiles,
388
+ cluebase_init_empty_env_fallback_files: cluebaseInitEmptyEnvFallbackFiles,
389
+ frontend_init_before_cluebase_init_files: frontendInitBeforeCluebaseInitCallFiles,
390
+ backend_trace_propagation_missing_files:
391
+ frontendBackendTracePropagationMissingFiles,
392
+ next_lifecycle_missing_use_client_files:
393
+ nextLifecycleMissingUseClientFiles,
394
+ lifecycle_read_side_effect_files: frontendLifecycleReadSideEffectFiles,
395
+ hardcoded_lifecycle_identity_files:
396
+ hardcodedLifecycleIdentityFiles.filter(
397
+ (filePath) =>
398
+ !backendRootPaths.some((root) => startsWithRoot(filePath, root)),
399
+ ),
400
+ repeated_identity_helper_files:
401
+ repeatedIdentityHelperFiles.filter(
402
+ (filePath) =>
403
+ !backendRootPaths.some((root) => startsWithRoot(filePath, root)),
404
+ ),
405
+ required_apis: [
406
+ ...(frontendLifecycleRequired ? ["cluebase.init"] : []),
407
+ ...(frontendIdentityRequired ? ["cluebase.identify"] : []),
408
+ ...(frontendOrganizationRequired ? ["cluebase.group"] : []),
409
+ ...(frontendLogoutRequired ? ["cluebase.reset"] : []),
410
+ ],
411
+ missing_apis: frontendMissingApis,
412
+ },
413
+ has_noop_wrapper: noOpPattern.test(combined),
414
+ component_lifecycle_init_files: componentLifecycleInitFiles,
415
+ auth_callback_lifecycle_init_files: authCallbackLifecycleInitFiles,
416
+ blocking_lifecycle_files: blockingLifecycleFiles,
417
+ blocking_lifecycle_calls: blockingLifecycleCalls,
418
+ unguarded_lifecycle_files: blockingLifecycleFiles,
419
+ unguarded_lifecycle_calls: blockingLifecycleCalls,
420
+ passed:
421
+ missingApis.length === 0 &&
422
+ backendSdkPresent &&
423
+ backendSdkInstallabilityVerified &&
424
+ backendInitPresent &&
425
+ backendSdkInitMissingServiceKeyFiles.length === 0 &&
426
+ fastapiMissingMiddlewareFiles.length === 0 &&
427
+ backendCluebaseEnvRequiredIndexFiles.length === 0 &&
428
+ hardcodedLifecycleIdentityFiles.length === 0 &&
429
+ repeatedIdentityHelperFiles.length === 0 &&
430
+ backendLifecycleReadEndpointFiles.length === 0 &&
431
+ backendMissingApis.length === 0 &&
432
+ frontendMissingApis.length === 0 &&
433
+ frontendSdkPresent &&
434
+ frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
435
+ frontendLifecycleNonPublicEnvFiles.length === 0 &&
436
+ cluebaseInitEmptyEnvFallbackFiles.length === 0 &&
437
+ frontendInitBeforeCluebaseInitCallFiles.length === 0 &&
438
+ frontendBackendTracePropagationMissingFiles.length === 0 &&
439
+ nextLifecycleMissingUseClientFiles.length === 0 &&
440
+ authCallbackLifecycleInitFiles.length === 0 &&
441
+ frontendLifecycleReadSideEffectFiles.length === 0 &&
442
+ !noOpPattern.test(combined) &&
443
+ componentLifecycleInitFiles.length === 0 &&
444
+ blockingLifecycleFiles.length === 0,
445
+ };
446
+ };
447
+
448
+ export const runSetupCheck = async ({
449
+ repoRoot,
450
+ request,
451
+ requireSdkLifecycle = false,
452
+ }) => {
453
+ const resolvedRepoRoot = resolve(repoRoot ?? ".");
454
+ const checks = [];
455
+ const setupManifest = await readSetupManifest(resolvedRepoRoot);
456
+ const manifestContract = validateSetupManifestContract(setupManifest);
457
+ addCheck(
458
+ checks,
459
+ "setup_manifest_contract",
460
+ !(requireSdkLifecycle || setupManifest) ||
461
+ (manifestContract.checked && manifestContract.findings.length === 0),
462
+ manifestContract.checked
463
+ ? manifestContract.findings.length === 0
464
+ ? "setup manifest contains current AI setup responsibility boundaries"
465
+ : "setup manifest is missing current AI setup responsibility boundaries"
466
+ : "setup manifest contract was not checked because .cluebase/setup-manifest.json is absent or unreadable",
467
+ {
468
+ checked: manifestContract.checked,
469
+ findings: manifestContract.findings,
470
+ },
471
+ );
472
+
473
+ const missingSetupSteps = await validateSetupStepFiles({
474
+ repoRoot: resolvedRepoRoot,
475
+ });
476
+ addCheck(
477
+ checks,
478
+ "setup_step_files",
479
+ missingSetupSteps.length === 0,
480
+ missingSetupSteps.length === 0
481
+ ? "nine setup step files are installed"
482
+ : "setup step files are missing",
483
+ { missing_files: missingSetupSteps },
484
+ );
485
+
486
+ const sourcePaths = await setupSourcePaths({
487
+ repoRoot: resolvedRepoRoot,
488
+ request,
489
+ includeFrontend: requireSdkLifecycle,
490
+ setupManifest,
491
+ });
492
+ const excludedPaths = request?.excluded_source_paths ?? [];
493
+ const sources = await readAllowedSourceText({
494
+ repoRoot: resolvedRepoRoot,
495
+ allowedSourcePaths: sourcePaths,
496
+ excludedSourcePaths: excludedPaths,
497
+ });
498
+ const dependencySources = await readDependencyText({
499
+ repoRoot: resolvedRepoRoot,
500
+ roots: sourcePaths,
501
+ });
502
+ const frontendConfigSources = requireSdkLifecycle
503
+ ? await readFrontendConfigText({
504
+ repoRoot: resolvedRepoRoot,
505
+ roots: sourcePaths,
506
+ })
507
+ : [];
508
+ const secretLeaks = findSecretLeaks([
509
+ ...sources,
510
+ ...dependencySources,
511
+ ...frontendConfigSources,
512
+ ]);
513
+ addCheck(
514
+ checks,
515
+ "no_secret_values",
516
+ secretLeaks.length === 0,
517
+ "no obvious secret values found",
518
+ {
519
+ files: secretLeaks,
520
+ },
521
+ );
522
+
523
+ if (requireSdkLifecycle) {
524
+ const backendRootPaths = setupBackendRootPaths(request);
525
+ const discoveries = await readDiscoveries(resolvedRepoRoot);
526
+ const discoveryBackendBoundaries = backendBoundariesFromDiscovery(
527
+ discoveries,
528
+ backendRootPaths,
529
+ );
530
+ const sdkLifecycle = checkSdkLifecycle({
531
+ backendRootPaths,
532
+ dependencySources,
533
+ discoveryBackendBoundaries,
534
+ framework: request?.framework,
535
+ sources,
536
+ });
537
+ addCheck(
538
+ checks,
539
+ "sdk_lifecycle",
540
+ sdkLifecycle.passed,
541
+ "static SDK lifecycle references are present and non-blocking; dependency install, import, app startup, and event delivery are not verified by this check",
542
+ {
543
+ found_apis: sdkLifecycle.foundApis,
544
+ missing_apis: sdkLifecycle.missingApis,
545
+ required_apis: sdkLifecycle.required_apis,
546
+ verification_scope:
547
+ "static_source_only_not_dependency_install_or_runtime_event_delivery",
548
+ backend_lifecycle: sdkLifecycle.backend_lifecycle,
549
+ frontend_lifecycle: sdkLifecycle.frontend_lifecycle,
550
+ has_noop_wrapper: sdkLifecycle.has_noop_wrapper,
551
+ component_lifecycle_init_files:
552
+ sdkLifecycle.component_lifecycle_init_files,
553
+ auth_callback_lifecycle_init_files:
554
+ sdkLifecycle.auth_callback_lifecycle_init_files,
555
+ blocking_lifecycle_files: sdkLifecycle.blocking_lifecycle_files,
556
+ blocking_lifecycle_calls: sdkLifecycle.blocking_lifecycle_calls,
557
+ unguarded_lifecycle_files: sdkLifecycle.unguarded_lifecycle_files,
558
+ unguarded_lifecycle_calls: sdkLifecycle.unguarded_lifecycle_calls,
559
+ },
560
+ );
561
+ }
562
+
563
+ const passed = checks.every((check) => check.passed);
564
+ return {
565
+ passed,
566
+ static_passed: passed,
567
+ completion_status:
568
+ passed && requireSdkLifecycle
569
+ ? "static_passed_runtime_verification_required"
570
+ : passed
571
+ ? "passed"
572
+ : "failed",
573
+ checks,
574
+ };
575
+ };