@clue-ai/cli 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -26
- package/bin/clue-cli.mjs +93 -54
- package/package.json +1 -1
- package/src/cli-invocation.mjs +34 -0
- package/src/command-spec.mjs +3 -0
- package/src/init-tool.mjs +2 -1
- package/src/lifecycle-guard.mjs +168 -103
- package/src/lifecycle-init.mjs +593 -187
- package/src/semantic-agent-runner.mjs +3 -1
- package/src/setup-check.mjs +641 -47
- package/src/setup-help.mjs +69 -0
- package/src/setup-prepare.mjs +75 -15
- package/src/setup-tool.mjs +421 -388
package/src/setup-check.mjs
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
|
-
import { join, relative, resolve } from "node:path";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { validateSemanticCiRequest } from "./contracts.mjs";
|
|
4
4
|
import {
|
|
5
|
+
SEMANTIC_GEN_WORKFLOW_COMMAND,
|
|
6
|
+
workflowHasCompatibleSemanticGenCommand,
|
|
7
|
+
} from "./cli-invocation.mjs";
|
|
8
|
+
import {
|
|
9
|
+
extractExecutableModuleStatements,
|
|
5
10
|
findLifecycleCallApiNames,
|
|
6
11
|
findLifecycleGuardViolations,
|
|
12
|
+
stripSourceNoise,
|
|
7
13
|
} from "./lifecycle-guard.mjs";
|
|
8
14
|
import { listAllowedSourceFiles } from "./path-policy.mjs";
|
|
9
15
|
import { runSemanticInventory } from "./semantic-ci.mjs";
|
|
@@ -21,6 +27,45 @@ const SETUP_SKILLS = [
|
|
|
21
27
|
"clue-local-verification",
|
|
22
28
|
"clue-setup-report",
|
|
23
29
|
];
|
|
30
|
+
const SETUP_SKILL_CONTENT_VERSION = "2026-05-10.lifecycle-placement-only.v1";
|
|
31
|
+
const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
32
|
+
"clue-sdk-instrumentation": [
|
|
33
|
+
"Do not create no-op wrappers",
|
|
34
|
+
"Lifecycle calls must resolve to real Clue SDK imports",
|
|
35
|
+
"add the real `@clue-ai/browser-sdk` dependency",
|
|
36
|
+
"Do not invent `clue-js-sdk`",
|
|
37
|
+
"The implementation scope is only ClueInit, ClueIdentify, ClueSetAccount, and ClueLogout placement",
|
|
38
|
+
"For Django code, use `clue-django-sdk` only after package-manager or registry verification confirms it is installable",
|
|
39
|
+
],
|
|
40
|
+
"clue-setup-audit": [
|
|
41
|
+
"Reject wrong SDK package names",
|
|
42
|
+
"Reject Django SDK setup when `clue-django-sdk` installability has not been verified",
|
|
43
|
+
"Reject ClueTrack instrumentation unless the user explicitly requested product event tracking",
|
|
44
|
+
"Reject unrelated refactors, renames, file moves",
|
|
45
|
+
"Execution agents must not approve, certify, or mark their own work complete",
|
|
46
|
+
],
|
|
47
|
+
"clue-local-verification": [
|
|
48
|
+
"`setup-check --require-sdk-lifecycle` is a static source check only",
|
|
49
|
+
"Verify frontend SDK installability/import",
|
|
50
|
+
"Verify backend SDK installability/import",
|
|
51
|
+
"Do not run `npx -y @clue-ai/cli setup-watch --local` automatically",
|
|
52
|
+
"user_verification_pending",
|
|
53
|
+
`Confirm \`.github/workflows/clue-semantic-snapshot.yml\` calls \`${SEMANTIC_GEN_WORKFLOW_COMMAND}\``,
|
|
54
|
+
],
|
|
55
|
+
"clue-setup-orchestrator": [
|
|
56
|
+
"Clue CLI public npm package: `@clue-ai/cli`",
|
|
57
|
+
"help --json",
|
|
58
|
+
"Required implementation agent: SDK Lifecycle Placement Agent only",
|
|
59
|
+
"Treat semantic snapshot readiness and semantic CI as generated/static verification surfaces",
|
|
60
|
+
"Do not search for a global `clue-ai` binary",
|
|
61
|
+
"Read `.clue/setup-manifest.json` `cli_invocation`",
|
|
62
|
+
],
|
|
63
|
+
"clue-setup-report": [
|
|
64
|
+
"Never claim `setup completed` from `setup-check --require-sdk-lifecycle` alone",
|
|
65
|
+
"Completion requires all applicable evidence",
|
|
66
|
+
"For every completion claim, include the evidence source",
|
|
67
|
+
],
|
|
68
|
+
};
|
|
24
69
|
const TARGET_SKILL_ROOTS = {
|
|
25
70
|
codex: [".agents", "skills"],
|
|
26
71
|
claude_code: [".claude", "skills"],
|
|
@@ -50,14 +95,25 @@ const semanticRequestFromWorkflow = (workflow) => {
|
|
|
50
95
|
return null;
|
|
51
96
|
}
|
|
52
97
|
};
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
98
|
+
const FRONTEND_SDK_PACKAGE = "@clue-ai/browser-sdk";
|
|
99
|
+
const WRONG_FRONTEND_SDK_PACKAGES = ["clue-js-sdk", "@clue/browser-sdk"];
|
|
100
|
+
const BACKEND_SDK_BY_FRAMEWORK = {
|
|
101
|
+
fastapi: {
|
|
102
|
+
packages: ["clue-fastapi-sdk"],
|
|
103
|
+
imports: ["clue_fastapi_sdk"],
|
|
104
|
+
initPattern: /clue_init_fastapi|CluePythonBootstrapConfig/,
|
|
105
|
+
installabilityStatus: "published",
|
|
106
|
+
},
|
|
107
|
+
django: {
|
|
108
|
+
packages: ["clue-django-sdk"],
|
|
109
|
+
imports: ["clue_django_sdk"],
|
|
110
|
+
initPattern:
|
|
111
|
+
/clue_init_django|configure_settings|CluePythonBootstrapConfig/,
|
|
112
|
+
installabilityStatus: "unverified",
|
|
113
|
+
blocker:
|
|
114
|
+
"clue-django-sdk installability has not been verified; Django setup must remain blocked until the package is published and import-checked",
|
|
115
|
+
},
|
|
116
|
+
};
|
|
61
117
|
const DEPENDENCY_FILE_CANDIDATES = [
|
|
62
118
|
"package.json",
|
|
63
119
|
"pnpm-lock.yaml",
|
|
@@ -78,6 +134,124 @@ const exists = async (path) => {
|
|
|
78
134
|
}
|
|
79
135
|
};
|
|
80
136
|
|
|
137
|
+
const readSetupManifest = async (repoRoot) => {
|
|
138
|
+
const manifestPath = join(repoRoot, ".clue", "setup-manifest.json");
|
|
139
|
+
if (!(await exists(manifestPath))) return undefined;
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(await readFile(manifestPath, "utf8"));
|
|
142
|
+
} catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const validateSetupManifestContract = (manifest) => {
|
|
148
|
+
if (!manifest || typeof manifest !== "object") {
|
|
149
|
+
return {
|
|
150
|
+
checked: false,
|
|
151
|
+
findings: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const findings = [];
|
|
155
|
+
if (
|
|
156
|
+
manifest.cli_invocation?.ai_help_command !==
|
|
157
|
+
"npx -y @clue-ai/cli help --json"
|
|
158
|
+
) {
|
|
159
|
+
findings.push("cli_invocation.ai_help_command is missing or stale");
|
|
160
|
+
}
|
|
161
|
+
if (manifest.lifecycle_verification?.owner !== "user") {
|
|
162
|
+
findings.push("lifecycle_verification.owner must be user");
|
|
163
|
+
}
|
|
164
|
+
if (
|
|
165
|
+
manifest.lifecycle_verification?.ai_agent_must_run_setup_watch !== false
|
|
166
|
+
) {
|
|
167
|
+
findings.push(
|
|
168
|
+
"lifecycle_verification.ai_agent_must_run_setup_watch must be false",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
!String(manifest.lifecycle_verification?.rule ?? "").includes(
|
|
173
|
+
"AI implementation agents must not run setup-watch automatically",
|
|
174
|
+
)
|
|
175
|
+
) {
|
|
176
|
+
findings.push("lifecycle_verification.rule must prohibit AI setup-watch");
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
!Array.isArray(manifest.ai_owned_workstreams) ||
|
|
180
|
+
manifest.ai_owned_workstreams.length !== 1 ||
|
|
181
|
+
manifest.ai_owned_workstreams[0] !== "sdk_lifecycle_placement"
|
|
182
|
+
) {
|
|
183
|
+
findings.push("ai_owned_workstreams must be sdk_lifecycle_placement only");
|
|
184
|
+
}
|
|
185
|
+
const lifecycleApis = manifest.ai_implementation_scope?.lifecycle_apis;
|
|
186
|
+
if (
|
|
187
|
+
!Array.isArray(lifecycleApis) ||
|
|
188
|
+
lifecycleApis.length !== REQUIRED_LIFECYCLE_APIS.length ||
|
|
189
|
+
!REQUIRED_LIFECYCLE_APIS.every((apiName) => lifecycleApis.includes(apiName))
|
|
190
|
+
) {
|
|
191
|
+
findings.push(
|
|
192
|
+
"ai_implementation_scope.lifecycle_apis must contain only ClueInit, ClueIdentify, ClueSetAccount, and ClueLogout",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (
|
|
196
|
+
!Array.isArray(manifest.ai_implementation_scope?.out_of_scope_by_default) ||
|
|
197
|
+
!manifest.ai_implementation_scope.out_of_scope_by_default.includes(
|
|
198
|
+
"ClueTrack",
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
findings.push("ai_implementation_scope must mark ClueTrack out of scope");
|
|
202
|
+
}
|
|
203
|
+
const localEventDelivery = Array.isArray(manifest.required_final_verification)
|
|
204
|
+
? manifest.required_final_verification.find(
|
|
205
|
+
(entry) => entry?.id === "local_event_delivery",
|
|
206
|
+
)
|
|
207
|
+
: null;
|
|
208
|
+
if (
|
|
209
|
+
localEventDelivery?.command !==
|
|
210
|
+
"user runs npx -y @clue-ai/cli setup-watch --local"
|
|
211
|
+
) {
|
|
212
|
+
findings.push(
|
|
213
|
+
"required_final_verification.local_event_delivery must be user-operated",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
!String(localEventDelivery?.completion_meaning ?? "").includes(
|
|
218
|
+
"user_verification_pending",
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
findings.push(
|
|
222
|
+
"required_final_verification.local_event_delivery must report user_verification_pending without user evidence",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
checked: true,
|
|
227
|
+
findings,
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
232
|
+
|
|
233
|
+
const validateSetupSkillContent = async ({ skillRoot, rootParts }) => {
|
|
234
|
+
const missingRequiredPhrases = [];
|
|
235
|
+
for (const skillName of SETUP_SKILLS) {
|
|
236
|
+
const skillPath = join(skillRoot, skillName, "SKILL.md");
|
|
237
|
+
if (!(await exists(skillPath))) continue;
|
|
238
|
+
const text = await readFile(skillPath, "utf8");
|
|
239
|
+
const requiredPhrases = [
|
|
240
|
+
`setup_skill_version: ${SETUP_SKILL_CONTENT_VERSION}`,
|
|
241
|
+
...(REQUIRED_SETUP_SKILL_PHRASES[skillName] ?? []),
|
|
242
|
+
];
|
|
243
|
+
for (const phrase of requiredPhrases) {
|
|
244
|
+
if (!text.includes(phrase)) {
|
|
245
|
+
missingRequiredPhrases.push({
|
|
246
|
+
file_path: join(...rootParts, skillName, "SKILL.md"),
|
|
247
|
+
phrase,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return missingRequiredPhrases;
|
|
253
|
+
};
|
|
254
|
+
|
|
81
255
|
const normalizeTarget = (target) => {
|
|
82
256
|
if (typeof target !== "string" || target.trim() === "") return undefined;
|
|
83
257
|
const normalized = target
|
|
@@ -116,9 +290,12 @@ const readAllowedSourceText = async ({
|
|
|
116
290
|
};
|
|
117
291
|
|
|
118
292
|
const readDependencyText = async ({ repoRoot, roots }) => {
|
|
293
|
+
const expandedRoots = roots.flatMap((root) =>
|
|
294
|
+
root.endsWith("/src") ? [root, dirname(root)] : [root],
|
|
295
|
+
);
|
|
119
296
|
const candidatePaths = [
|
|
120
297
|
...DEPENDENCY_FILE_CANDIDATES,
|
|
121
|
-
...
|
|
298
|
+
...expandedRoots.flatMap((root) =>
|
|
122
299
|
DEPENDENCY_FILE_CANDIDATES.map((file) => join(root, file)),
|
|
123
300
|
),
|
|
124
301
|
];
|
|
@@ -174,26 +351,342 @@ const findSecretLeaks = (sources) =>
|
|
|
174
351
|
const startsWithRoot = (filePath, root) =>
|
|
175
352
|
filePath === root || filePath.startsWith(`${root.replace(/\/+$/, "")}/`);
|
|
176
353
|
|
|
177
|
-
const
|
|
178
|
-
|
|
354
|
+
const packageJsonDependencyNames = (text) => {
|
|
355
|
+
try {
|
|
356
|
+
const parsed = JSON.parse(text);
|
|
357
|
+
return [
|
|
358
|
+
"dependencies",
|
|
359
|
+
"devDependencies",
|
|
360
|
+
"optionalDependencies",
|
|
361
|
+
"peerDependencies",
|
|
362
|
+
].flatMap((field) =>
|
|
363
|
+
parsed && typeof parsed[field] === "object" && parsed[field] !== null
|
|
364
|
+
? Object.keys(parsed[field])
|
|
365
|
+
: [],
|
|
366
|
+
);
|
|
367
|
+
} catch {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const dependencySourceHasPackage = (source, packageName) => {
|
|
373
|
+
if (source.file_path.endsWith("package.json")) {
|
|
374
|
+
return packageJsonDependencyNames(source.text).includes(packageName);
|
|
375
|
+
}
|
|
376
|
+
const packagePattern = new RegExp(
|
|
377
|
+
`(^|[\\s"'=,{\\[]+)${escapeRegex(packageName)}($|[\\s"'=<>~!,}\\]]+)`,
|
|
378
|
+
"i",
|
|
379
|
+
);
|
|
380
|
+
return source.text
|
|
381
|
+
.split(/\r?\n/)
|
|
382
|
+
.map((line) => line.trim())
|
|
383
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
384
|
+
.some((line) => packagePattern.test(line));
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const dependencyHasAnyPackage = (dependencySources, packageNames) =>
|
|
388
|
+
dependencySources.some((source) =>
|
|
389
|
+
packageNames.some((packageName) =>
|
|
390
|
+
dependencySourceHasPackage(source, packageName),
|
|
391
|
+
),
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const sourceHasPythonImport = (source, importName) => {
|
|
395
|
+
const importPattern = new RegExp(
|
|
396
|
+
`(^|\\n)\\s*(?:from\\s+${escapeRegex(importName)}\\b|import\\s+${escapeRegex(importName)}\\b)`,
|
|
397
|
+
);
|
|
398
|
+
return importPattern.test(
|
|
399
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const sourcesHavePythonImport = (sources, importNames) =>
|
|
404
|
+
sources.some((source) =>
|
|
405
|
+
importNames.some((importName) => sourceHasPythonImport(source, importName)),
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const sourcesHaveFrontendSdkImport = (sources) =>
|
|
409
|
+
sources.some((source) => sourceImportsFrontendSdk(source));
|
|
410
|
+
|
|
411
|
+
const sourceImportsFrontendSdk = (source) =>
|
|
412
|
+
sourceTextImportsFrontendSdk(source.text);
|
|
413
|
+
|
|
414
|
+
const sourceTextImportsFrontendSdk = (text) =>
|
|
415
|
+
extractExecutableModuleStatements(text).some((statement) =>
|
|
416
|
+
new RegExp(
|
|
417
|
+
`(?:from\\s*["']${escapeRegex(FRONTEND_SDK_PACKAGE)}["']|import\\s*["']${escapeRegex(FRONTEND_SDK_PACKAGE)}["'])`,
|
|
418
|
+
).test(statement),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const parseNamedSpecifiers = (specifiers) =>
|
|
422
|
+
specifiers
|
|
423
|
+
.split(",")
|
|
424
|
+
.map((specifier) => specifier.trim())
|
|
425
|
+
.filter(Boolean)
|
|
426
|
+
.map((specifier) => {
|
|
427
|
+
const match = /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/.exec(
|
|
428
|
+
specifier,
|
|
429
|
+
);
|
|
430
|
+
if (!match) return null;
|
|
431
|
+
return {
|
|
432
|
+
imported: match[1],
|
|
433
|
+
local: match[2] ?? match[1],
|
|
434
|
+
exported: match[2] ?? match[1],
|
|
435
|
+
};
|
|
436
|
+
})
|
|
437
|
+
.filter(Boolean);
|
|
438
|
+
|
|
439
|
+
const sourceDirectlyProvidesApiFromFrontendSdk = (text, apiName) => {
|
|
440
|
+
const statements = extractExecutableModuleStatements(text);
|
|
441
|
+
for (const match of statements
|
|
442
|
+
.join("\n")
|
|
443
|
+
.matchAll(/import\s*{([^}]+)}\s*from\s*["']([^"']+)["']/g)) {
|
|
444
|
+
if (match[2] !== FRONTEND_SDK_PACKAGE) continue;
|
|
445
|
+
if (
|
|
446
|
+
parseNamedSpecifiers(match[1]).some(
|
|
447
|
+
(specifier) =>
|
|
448
|
+
specifier.imported === apiName && specifier.local === apiName,
|
|
449
|
+
)
|
|
450
|
+
) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const match of statements
|
|
455
|
+
.join("\n")
|
|
456
|
+
.matchAll(/export\s*{([^}]+)}\s*from\s*["']([^"']+)["']/g)) {
|
|
457
|
+
if (match[2] !== FRONTEND_SDK_PACKAGE) continue;
|
|
458
|
+
if (
|
|
459
|
+
parseNamedSpecifiers(match[1]).some(
|
|
460
|
+
(specifier) =>
|
|
461
|
+
specifier.imported === apiName && specifier.exported === apiName,
|
|
462
|
+
)
|
|
463
|
+
) {
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return false;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const frontendSdkImportedLocalsForApi = (text, apiName) => {
|
|
471
|
+
const locals = [];
|
|
472
|
+
for (const match of extractExecutableModuleStatements(text)
|
|
473
|
+
.join("\n")
|
|
474
|
+
.matchAll(/import\s*{([^}]+)}\s*from\s*["']([^"']+)["']/g)) {
|
|
475
|
+
if (match[2] !== FRONTEND_SDK_PACKAGE) continue;
|
|
476
|
+
for (const specifier of parseNamedSpecifiers(match[1])) {
|
|
477
|
+
if (specifier.imported === apiName) {
|
|
478
|
+
locals.push(specifier.local);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return locals;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const frontendSdkNamespaces = (text) =>
|
|
486
|
+
[
|
|
487
|
+
...extractExecutableModuleStatements(text)
|
|
488
|
+
.join("\n")
|
|
489
|
+
.matchAll(
|
|
490
|
+
/import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*from\s*["']([^"']+)["']/g,
|
|
491
|
+
),
|
|
492
|
+
]
|
|
493
|
+
.filter((match) => match[2] === FRONTEND_SDK_PACKAGE)
|
|
494
|
+
.map((match) => match[1]);
|
|
495
|
+
|
|
496
|
+
const sourceExportsLocalAsApi = (text, localName, apiName) => {
|
|
497
|
+
const statements = extractExecutableModuleStatements(text).join("\n");
|
|
498
|
+
for (const match of statements.matchAll(
|
|
499
|
+
/export\s*{([^}]+)}(?:\s*from\s*["'][^"']+["'])?/g,
|
|
500
|
+
)) {
|
|
501
|
+
if (match[0].includes(" from ")) continue;
|
|
502
|
+
if (
|
|
503
|
+
parseNamedSpecifiers(match[1]).some(
|
|
504
|
+
(specifier) =>
|
|
505
|
+
specifier.imported === localName && specifier.exported === apiName,
|
|
506
|
+
)
|
|
507
|
+
) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return new RegExp(
|
|
512
|
+
`export\\s+const\\s+${escapeRegex(apiName)}\\s*=\\s*${escapeRegex(localName)}\\b`,
|
|
513
|
+
).test(statements);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const sourceForwardsFrontendSdkApi = (source, apiName) => {
|
|
517
|
+
const text = source.text;
|
|
518
|
+
if (sourceDirectlyProvidesApiFromFrontendSdk(text, apiName)) return true;
|
|
519
|
+
const importedLocals = frontendSdkImportedLocalsForApi(text, apiName);
|
|
520
|
+
if (
|
|
521
|
+
importedLocals.some((localName) =>
|
|
522
|
+
sourceExportsLocalAsApi(text, localName, apiName),
|
|
523
|
+
)
|
|
524
|
+
) {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
return frontendSdkNamespaces(text).some((namespaceName) =>
|
|
528
|
+
new RegExp(
|
|
529
|
+
`export\\s+const\\s+${escapeRegex(apiName)}\\s*=\\s*${escapeRegex(namespaceName)}\\.${escapeRegex(apiName)}\\b`,
|
|
530
|
+
).test(text),
|
|
531
|
+
);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const localImportSpecifiersForApi = (text, apiName) =>
|
|
535
|
+
[
|
|
536
|
+
...extractExecutableModuleStatements(text)
|
|
537
|
+
.join("\n")
|
|
538
|
+
.matchAll(/import\s*{([^}]+)}\s*from\s*["']([^"']+)["']/g),
|
|
539
|
+
]
|
|
540
|
+
.filter((match) => match[2].startsWith(".") || match[2].startsWith("@/"))
|
|
541
|
+
.flatMap((match) =>
|
|
542
|
+
parseNamedSpecifiers(match[1])
|
|
543
|
+
.filter(
|
|
544
|
+
(specifier) =>
|
|
545
|
+
specifier.imported === apiName && specifier.local === apiName,
|
|
546
|
+
)
|
|
547
|
+
.map(() => match[2]),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const importSpecifiers = (text) =>
|
|
551
|
+
[
|
|
552
|
+
...extractExecutableModuleStatements(text)
|
|
553
|
+
.join("\n")
|
|
554
|
+
.matchAll(/import\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']/g),
|
|
555
|
+
]
|
|
556
|
+
.map((match) => match[1] ?? match[2])
|
|
557
|
+
.filter(Boolean);
|
|
558
|
+
|
|
559
|
+
const candidateSourcePaths = (basePath) => [
|
|
560
|
+
basePath,
|
|
561
|
+
...SOURCE_EXTENSIONS.map((extension) => `${basePath}${extension}`),
|
|
562
|
+
...SOURCE_EXTENSIONS.map((extension) => join(basePath, `index${extension}`)),
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
const resolveLocalSource = ({ importerPath, sourceByPath, specifier }) => {
|
|
566
|
+
const candidates = [];
|
|
567
|
+
if (specifier.startsWith(".")) {
|
|
568
|
+
candidates.push(
|
|
569
|
+
...candidateSourcePaths(join(dirname(importerPath), specifier)),
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
if (specifier.startsWith("@/")) {
|
|
573
|
+
const srcIndex = importerPath.lastIndexOf("/src/");
|
|
574
|
+
if (srcIndex >= 0) {
|
|
575
|
+
candidates.push(
|
|
576
|
+
...candidateSourcePaths(
|
|
577
|
+
join(
|
|
578
|
+
importerPath.slice(0, srcIndex + "/src".length),
|
|
579
|
+
specifier.slice(2),
|
|
580
|
+
),
|
|
581
|
+
),
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
for (const root of [
|
|
585
|
+
"src",
|
|
586
|
+
"frontend/src",
|
|
587
|
+
"apps/web/src",
|
|
588
|
+
"apps/admin/src",
|
|
589
|
+
"apps/visitor/src",
|
|
590
|
+
]) {
|
|
591
|
+
candidates.push(...candidateSourcePaths(join(root, specifier.slice(2))));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return candidates
|
|
595
|
+
.map((candidate) => sourceByPath.get(candidate))
|
|
596
|
+
.find(Boolean);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const sourceHasVerifiedFrontendSdkAccess = ({
|
|
600
|
+
apiNames,
|
|
601
|
+
source,
|
|
602
|
+
sourceByPath,
|
|
603
|
+
}) =>
|
|
604
|
+
apiNames.every((apiName) => {
|
|
605
|
+
if (sourceDirectlyProvidesApiFromFrontendSdk(source.text, apiName)) {
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
return localImportSpecifiersForApi(source.text, apiName).some(
|
|
609
|
+
(specifier) => {
|
|
610
|
+
const importedSource = resolveLocalSource({
|
|
611
|
+
importerPath: source.file_path,
|
|
612
|
+
sourceByPath,
|
|
613
|
+
specifier,
|
|
614
|
+
});
|
|
615
|
+
return importedSource
|
|
616
|
+
? sourceForwardsFrontendSdkApi(importedSource, apiName)
|
|
617
|
+
: false;
|
|
618
|
+
},
|
|
619
|
+
);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
const findWrongFrontendSdkPackages = ({ sources, dependencySources }) => {
|
|
623
|
+
const combined = [...sources, ...dependencySources]
|
|
624
|
+
.map((source) => stripSourceNoise(source.text))
|
|
625
|
+
.join("\n");
|
|
626
|
+
return WRONG_FRONTEND_SDK_PACKAGES.filter((packageName) =>
|
|
627
|
+
combined.includes(packageName),
|
|
628
|
+
);
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const backendSdkSpec = (framework) =>
|
|
632
|
+
BACKEND_SDK_BY_FRAMEWORK[String(framework ?? "").toLowerCase()] ?? {
|
|
633
|
+
packages: Object.values(BACKEND_SDK_BY_FRAMEWORK).flatMap(
|
|
634
|
+
(spec) => spec.packages,
|
|
635
|
+
),
|
|
636
|
+
imports: Object.values(BACKEND_SDK_BY_FRAMEWORK).flatMap(
|
|
637
|
+
(spec) => spec.imports,
|
|
638
|
+
),
|
|
639
|
+
initPattern:
|
|
640
|
+
/clue_init_fastapi|clue_init_django|configure_settings|CluePythonBootstrapConfig/,
|
|
641
|
+
installabilityStatus: "framework_unverified",
|
|
642
|
+
};
|
|
179
643
|
|
|
180
644
|
const checkSdkLifecycle = ({
|
|
181
645
|
backendRootPaths = [],
|
|
182
646
|
dependencySources = [],
|
|
647
|
+
framework,
|
|
183
648
|
sources,
|
|
184
649
|
}) => {
|
|
185
|
-
const combined = sources
|
|
650
|
+
const combined = sources
|
|
651
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
652
|
+
.join("\n");
|
|
186
653
|
const backendSources = sources.filter((source) =>
|
|
187
654
|
backendRootPaths.some((root) => startsWithRoot(source.file_path, root)),
|
|
188
655
|
);
|
|
656
|
+
const frontendSources = sources.filter(
|
|
657
|
+
(source) =>
|
|
658
|
+
!backendRootPaths.some((root) => startsWithRoot(source.file_path, root)),
|
|
659
|
+
);
|
|
189
660
|
const backendCombined = backendSources
|
|
190
|
-
.map((source) => source.text)
|
|
661
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
191
662
|
.join("\n");
|
|
192
|
-
const
|
|
193
|
-
.map((source) => source.text)
|
|
663
|
+
const frontendCombined = frontendSources
|
|
664
|
+
.map((source) => stripSourceNoise(source.text, { stripStrings: true }))
|
|
194
665
|
.join("\n");
|
|
195
666
|
const foundApiNames = findLifecycleCallApiNames(combined);
|
|
196
667
|
const backendFoundApiNames = findLifecycleCallApiNames(backendCombined);
|
|
668
|
+
const frontendFoundApiNames = findLifecycleCallApiNames(frontendCombined);
|
|
669
|
+
const sourceByPath = new Map(
|
|
670
|
+
sources.map((source) => [source.file_path, source]),
|
|
671
|
+
);
|
|
672
|
+
const frontendLifecycleFilesWithoutVerifiedSdk = frontendSources
|
|
673
|
+
.filter(
|
|
674
|
+
(source) =>
|
|
675
|
+
findLifecycleCallApiNames(
|
|
676
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
677
|
+
).length > 0,
|
|
678
|
+
)
|
|
679
|
+
.filter((source) => {
|
|
680
|
+
const apiNames = findLifecycleCallApiNames(
|
|
681
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
682
|
+
);
|
|
683
|
+
return !sourceHasVerifiedFrontendSdkAccess({
|
|
684
|
+
apiNames,
|
|
685
|
+
source,
|
|
686
|
+
sourceByPath,
|
|
687
|
+
});
|
|
688
|
+
})
|
|
689
|
+
.map((source) => source.file_path);
|
|
197
690
|
const foundApis = REQUIRED_LIFECYCLE_APIS.filter((api) =>
|
|
198
691
|
foundApiNames.includes(api),
|
|
199
692
|
);
|
|
@@ -204,30 +697,49 @@ const checkSdkLifecycle = ({
|
|
|
204
697
|
/window\.Clue(?:Init|Identify|SetAccount|Logout)|(?:function|const|let|var)\s+Clue(?:Init|Identify|SetAccount|Logout)\b/;
|
|
205
698
|
const componentLifecycleInitFiles = sources
|
|
206
699
|
.filter((source) =>
|
|
207
|
-
/useEffect\s*\([\s\S]{0,1200}ClueInit/.test(
|
|
700
|
+
/useEffect\s*\([\s\S]{0,1200}ClueInit/.test(
|
|
701
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
702
|
+
),
|
|
208
703
|
)
|
|
209
704
|
.map((source) => source.file_path);
|
|
210
|
-
const
|
|
211
|
-
findLifecycleGuardViolations(
|
|
705
|
+
const blockingLifecycleCalls = sources.flatMap((source) =>
|
|
706
|
+
findLifecycleGuardViolations(
|
|
707
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
708
|
+
).map((violation) => ({
|
|
212
709
|
file_path: source.file_path,
|
|
213
710
|
...violation,
|
|
214
711
|
})),
|
|
215
712
|
);
|
|
216
|
-
const
|
|
217
|
-
...new Set(
|
|
713
|
+
const blockingLifecycleFiles = [
|
|
714
|
+
...new Set(blockingLifecycleCalls.map((violation) => violation.file_path)),
|
|
218
715
|
];
|
|
219
716
|
const backendPresent = backendSources.length > 0;
|
|
220
|
-
const
|
|
717
|
+
const backendSpec = backendSdkSpec(framework);
|
|
718
|
+
const backendSdkDependencyPresent =
|
|
221
719
|
!backendPresent ||
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
BACKEND_SDK_MARKERS,
|
|
225
|
-
);
|
|
226
|
-
const backendInitPresent =
|
|
720
|
+
dependencyHasAnyPackage(dependencySources, backendSpec.packages);
|
|
721
|
+
const backendSdkImportPresent =
|
|
227
722
|
!backendPresent ||
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
);
|
|
723
|
+
sourcesHavePythonImport(backendSources, backendSpec.imports);
|
|
724
|
+
const backendSdkPresent =
|
|
725
|
+
!backendPresent || (backendSdkDependencyPresent && backendSdkImportPresent);
|
|
726
|
+
const backendSdkInstallabilityVerified =
|
|
727
|
+
!backendPresent || backendSpec.installabilityStatus !== "unverified";
|
|
728
|
+
const backendInitPresent =
|
|
729
|
+
!backendPresent || backendSpec.initPattern.test(backendCombined);
|
|
730
|
+
const frontendLifecyclePresent = frontendFoundApiNames.length > 0;
|
|
731
|
+
const frontendSdkDependencyPresent =
|
|
732
|
+
!frontendLifecyclePresent ||
|
|
733
|
+
dependencyHasAnyPackage(dependencySources, [FRONTEND_SDK_PACKAGE]);
|
|
734
|
+
const frontendSdkImportPresent =
|
|
735
|
+
!frontendLifecyclePresent || sourcesHaveFrontendSdkImport(frontendSources);
|
|
736
|
+
const frontendSdkPresent =
|
|
737
|
+
!frontendLifecyclePresent ||
|
|
738
|
+
(frontendSdkDependencyPresent && frontendSdkImportPresent);
|
|
739
|
+
const wrongFrontendSdkPackages = findWrongFrontendSdkPackages({
|
|
740
|
+
sources: frontendSources,
|
|
741
|
+
dependencySources,
|
|
742
|
+
});
|
|
231
743
|
const backendIdentityRequired =
|
|
232
744
|
backendPresent &&
|
|
233
745
|
/\b(login|signin|sign_in|auth|token|session)\b/i.test(backendCombined);
|
|
@@ -256,6 +768,15 @@ const checkSdkLifecycle = ({
|
|
|
256
768
|
backend_present: backendPresent,
|
|
257
769
|
backend_root_paths: backendRootPaths,
|
|
258
770
|
dependency_files: dependencySources.map((source) => source.file_path),
|
|
771
|
+
expected_sdk_packages: backendSpec.packages,
|
|
772
|
+
expected_sdk_imports: backendSpec.imports,
|
|
773
|
+
sdk_installability_status: backendSpec.installabilityStatus,
|
|
774
|
+
sdk_installability_verified: backendSdkInstallabilityVerified,
|
|
775
|
+
sdk_blocker: backendSdkInstallabilityVerified
|
|
776
|
+
? null
|
|
777
|
+
: backendSpec.blocker,
|
|
778
|
+
sdk_dependency_present: backendSdkDependencyPresent,
|
|
779
|
+
sdk_import_present: backendSdkImportPresent,
|
|
259
780
|
sdk_dependency_or_import_present: backendSdkPresent,
|
|
260
781
|
sdk_init_present: backendInitPresent,
|
|
261
782
|
required_apis: [
|
|
@@ -265,18 +786,35 @@ const checkSdkLifecycle = ({
|
|
|
265
786
|
],
|
|
266
787
|
missing_apis: backendMissingApis,
|
|
267
788
|
},
|
|
789
|
+
frontend_lifecycle: {
|
|
790
|
+
lifecycle_present: frontendLifecyclePresent,
|
|
791
|
+
found_apis: frontendFoundApiNames,
|
|
792
|
+
expected_sdk_package: FRONTEND_SDK_PACKAGE,
|
|
793
|
+
sdk_dependency_present: frontendSdkDependencyPresent,
|
|
794
|
+
sdk_import_present: frontendSdkImportPresent,
|
|
795
|
+
sdk_dependency_or_import_present: frontendSdkPresent,
|
|
796
|
+
lifecycle_files_without_verified_sdk:
|
|
797
|
+
frontendLifecycleFilesWithoutVerifiedSdk,
|
|
798
|
+
wrong_sdk_packages: wrongFrontendSdkPackages,
|
|
799
|
+
},
|
|
268
800
|
has_noop_wrapper: noOpPattern.test(combined),
|
|
269
801
|
component_lifecycle_init_files: componentLifecycleInitFiles,
|
|
270
|
-
|
|
271
|
-
|
|
802
|
+
blocking_lifecycle_files: blockingLifecycleFiles,
|
|
803
|
+
blocking_lifecycle_calls: blockingLifecycleCalls,
|
|
804
|
+
unguarded_lifecycle_files: blockingLifecycleFiles,
|
|
805
|
+
unguarded_lifecycle_calls: blockingLifecycleCalls,
|
|
272
806
|
passed:
|
|
273
807
|
missingApis.length === 0 &&
|
|
274
808
|
backendSdkPresent &&
|
|
809
|
+
backendSdkInstallabilityVerified &&
|
|
275
810
|
backendInitPresent &&
|
|
276
811
|
backendMissingApis.length === 0 &&
|
|
812
|
+
frontendSdkPresent &&
|
|
813
|
+
frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
|
|
814
|
+
wrongFrontendSdkPackages.length === 0 &&
|
|
277
815
|
!noOpPattern.test(combined) &&
|
|
278
816
|
componentLifecycleInitFiles.length === 0 &&
|
|
279
|
-
|
|
817
|
+
blockingLifecycleFiles.length === 0,
|
|
280
818
|
};
|
|
281
819
|
};
|
|
282
820
|
|
|
@@ -288,22 +826,39 @@ export const runSetupCheck = async ({
|
|
|
288
826
|
}) => {
|
|
289
827
|
const resolvedRepoRoot = resolve(repoRoot ?? ".");
|
|
290
828
|
const checks = [];
|
|
291
|
-
const
|
|
829
|
+
const setupManifest = await readSetupManifest(resolvedRepoRoot);
|
|
830
|
+
const manifestContract = validateSetupManifestContract(setupManifest);
|
|
831
|
+
addCheck(
|
|
832
|
+
checks,
|
|
833
|
+
"setup_manifest_contract",
|
|
834
|
+
!manifestContract.checked || manifestContract.findings.length === 0,
|
|
835
|
+
manifestContract.checked
|
|
836
|
+
? manifestContract.findings.length === 0
|
|
837
|
+
? "setup manifest contains current AI setup responsibility boundaries"
|
|
838
|
+
: "setup manifest is missing current AI setup responsibility boundaries"
|
|
839
|
+
: "setup manifest contract was not checked because .clue/setup-manifest.json is absent or unreadable",
|
|
840
|
+
{
|
|
841
|
+
checked: manifestContract.checked,
|
|
842
|
+
findings: manifestContract.findings,
|
|
843
|
+
},
|
|
844
|
+
);
|
|
845
|
+
const normalizedTarget =
|
|
846
|
+
normalizeTarget(target) ?? normalizeTarget(setupManifest?.target);
|
|
292
847
|
|
|
293
848
|
if (normalizedTarget) {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
...TARGET_SKILL_ROOTS[normalizedTarget],
|
|
297
|
-
);
|
|
849
|
+
const rootParts = TARGET_SKILL_ROOTS[normalizedTarget];
|
|
850
|
+
const skillRoot = join(resolvedRepoRoot, ...rootParts);
|
|
298
851
|
const missingSkills = [];
|
|
299
852
|
for (const skillName of SETUP_SKILLS) {
|
|
300
853
|
const skillPath = join(skillRoot, skillName, "SKILL.md");
|
|
301
854
|
if (!(await exists(skillPath))) {
|
|
302
|
-
missingSkills.push(
|
|
303
|
-
join(...TARGET_SKILL_ROOTS[normalizedTarget], skillName, "SKILL.md"),
|
|
304
|
-
);
|
|
855
|
+
missingSkills.push(join(...rootParts, skillName, "SKILL.md"));
|
|
305
856
|
}
|
|
306
857
|
}
|
|
858
|
+
const missingRequiredPhrases = await validateSetupSkillContent({
|
|
859
|
+
skillRoot,
|
|
860
|
+
rootParts,
|
|
861
|
+
});
|
|
307
862
|
addCheck(
|
|
308
863
|
checks,
|
|
309
864
|
"setup_skills",
|
|
@@ -313,6 +868,36 @@ export const runSetupCheck = async ({
|
|
|
313
868
|
: "setup skills are missing",
|
|
314
869
|
{ missing_files: missingSkills },
|
|
315
870
|
);
|
|
871
|
+
addCheck(
|
|
872
|
+
checks,
|
|
873
|
+
"setup_skill_content",
|
|
874
|
+
missingSkills.length === 0 && missingRequiredPhrases.length === 0,
|
|
875
|
+
missingSkills.length === 0 && missingRequiredPhrases.length === 0
|
|
876
|
+
? "setup skills contain current safety rules"
|
|
877
|
+
: "setup skills are stale or missing required safety rules",
|
|
878
|
+
{
|
|
879
|
+
expected_version: SETUP_SKILL_CONTENT_VERSION,
|
|
880
|
+
missing_required_phrases: missingRequiredPhrases,
|
|
881
|
+
},
|
|
882
|
+
);
|
|
883
|
+
} else {
|
|
884
|
+
addCheck(
|
|
885
|
+
checks,
|
|
886
|
+
"setup_skills",
|
|
887
|
+
false,
|
|
888
|
+
"setup target is required or must be inferable from .clue/setup-manifest.json",
|
|
889
|
+
{ missing_target: true },
|
|
890
|
+
);
|
|
891
|
+
addCheck(
|
|
892
|
+
checks,
|
|
893
|
+
"setup_skill_content",
|
|
894
|
+
false,
|
|
895
|
+
"setup skills were not checked because setup target is missing",
|
|
896
|
+
{
|
|
897
|
+
expected_version: SETUP_SKILL_CONTENT_VERSION,
|
|
898
|
+
missing_required_phrases: [],
|
|
899
|
+
},
|
|
900
|
+
);
|
|
316
901
|
}
|
|
317
902
|
|
|
318
903
|
const workflowPath = request?.ci_workflow_path ?? DEFAULT_WORKFLOW_PATH;
|
|
@@ -323,9 +908,7 @@ export const runSetupCheck = async ({
|
|
|
323
908
|
addCheck(
|
|
324
909
|
checks,
|
|
325
910
|
"semantic_workflow",
|
|
326
|
-
workflow
|
|
327
|
-
"npx @clue-ai/cli semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .",
|
|
328
|
-
) &&
|
|
911
|
+
workflowHasCompatibleSemanticGenCommand(workflow) &&
|
|
329
912
|
semanticRequest !== null &&
|
|
330
913
|
workflow.includes("CLUE_SEMANTIC_REQUEST_JSON: |") &&
|
|
331
914
|
workflow.includes("CLUE_API_KEY: ${{ secrets.CLUE_API_KEY }}") &&
|
|
@@ -341,7 +924,7 @@ export const runSetupCheck = async ({
|
|
|
341
924
|
workflow.includes("permissions:\n contents: read") &&
|
|
342
925
|
workflow.includes("persist-credentials: false") &&
|
|
343
926
|
!disallowedWorkflowMetadataPattern.test(workflow),
|
|
344
|
-
"semantic workflow uses
|
|
927
|
+
"semantic workflow uses a compatible env runtime request, least-privilege checkout, and privacy-minimized GitHub metadata",
|
|
345
928
|
{ workflow_path: workflowPath },
|
|
346
929
|
);
|
|
347
930
|
} else {
|
|
@@ -406,7 +989,7 @@ export const runSetupCheck = async ({
|
|
|
406
989
|
});
|
|
407
990
|
const dependencySources = await readDependencyText({
|
|
408
991
|
repoRoot: resolvedRepoRoot,
|
|
409
|
-
roots:
|
|
992
|
+
roots: sourcePaths,
|
|
410
993
|
});
|
|
411
994
|
const secretLeaks = findSecretLeaks([
|
|
412
995
|
...sources,
|
|
@@ -434,22 +1017,26 @@ export const runSetupCheck = async ({
|
|
|
434
1017
|
const sdkLifecycle = checkSdkLifecycle({
|
|
435
1018
|
backendRootPaths: request?.allowed_source_paths ?? [],
|
|
436
1019
|
dependencySources,
|
|
1020
|
+
framework: request?.framework,
|
|
437
1021
|
sources,
|
|
438
1022
|
});
|
|
439
1023
|
addCheck(
|
|
440
1024
|
checks,
|
|
441
1025
|
"sdk_lifecycle",
|
|
442
1026
|
sdkLifecycle.passed,
|
|
443
|
-
"static SDK lifecycle references are present and
|
|
1027
|
+
"static SDK lifecycle references are present and non-blocking; dependency install, import, app startup, and event delivery are not verified by this check",
|
|
444
1028
|
{
|
|
445
1029
|
found_apis: sdkLifecycle.foundApis,
|
|
446
1030
|
missing_apis: sdkLifecycle.missingApis,
|
|
447
1031
|
verification_scope:
|
|
448
1032
|
"static_source_only_not_dependency_install_or_runtime_event_delivery",
|
|
449
1033
|
backend_lifecycle: sdkLifecycle.backend_lifecycle,
|
|
1034
|
+
frontend_lifecycle: sdkLifecycle.frontend_lifecycle,
|
|
450
1035
|
has_noop_wrapper: sdkLifecycle.has_noop_wrapper,
|
|
451
1036
|
component_lifecycle_init_files:
|
|
452
1037
|
sdkLifecycle.component_lifecycle_init_files,
|
|
1038
|
+
blocking_lifecycle_files: sdkLifecycle.blocking_lifecycle_files,
|
|
1039
|
+
blocking_lifecycle_calls: sdkLifecycle.blocking_lifecycle_calls,
|
|
453
1040
|
unguarded_lifecycle_files: sdkLifecycle.unguarded_lifecycle_files,
|
|
454
1041
|
unguarded_lifecycle_calls: sdkLifecycle.unguarded_lifecycle_calls,
|
|
455
1042
|
},
|
|
@@ -459,6 +1046,13 @@ export const runSetupCheck = async ({
|
|
|
459
1046
|
const passed = checks.every((check) => check.passed);
|
|
460
1047
|
return {
|
|
461
1048
|
passed,
|
|
1049
|
+
static_passed: passed,
|
|
1050
|
+
completion_status:
|
|
1051
|
+
passed && requireSdkLifecycle
|
|
1052
|
+
? "static_passed_runtime_verification_required"
|
|
1053
|
+
: passed
|
|
1054
|
+
? "passed"
|
|
1055
|
+
: "failed",
|
|
462
1056
|
checks,
|
|
463
1057
|
};
|
|
464
1058
|
};
|