@clue-ai/cli 0.0.22 → 0.0.24
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/bin/clue-cli.mjs +16 -1
- package/package.json +1 -1
- package/src/ai-provider.mjs +62 -4
- package/src/lifecycle-init.mjs +208 -14
- package/src/setup-agent.mjs +235 -4
- package/src/setup-ai-contract.mjs +89 -4
- package/src/setup-check.mjs +132 -9
- package/src/setup-detect.mjs +23 -1
- package/src/setup-documents.mjs +1 -1
- package/src/setup-help.mjs +2 -0
- package/src/setup-tool.mjs +17 -1
package/src/setup-check.mjs
CHANGED
|
@@ -39,6 +39,8 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
39
39
|
"For Next.js browser/client code, use only `NEXT_PUBLIC_CLUE_PROJECT_KEY`",
|
|
40
40
|
"Do not read `process.env.CLUE_PROJECT_KEY`",
|
|
41
41
|
"CLUE_API_BASE_URL` is not part of backend SDK initialization",
|
|
42
|
+
"FastAPI `clue_init_fastapi` must pass `service_key`",
|
|
43
|
+
"Do not use `os.environ[\"CLUE_*\"]`",
|
|
42
44
|
"Install Clue SDK dependencies through the latest channel",
|
|
43
45
|
"`@clue-ai/browser-sdk` must use `latest`",
|
|
44
46
|
"Python backend SDK dependencies must not be pinned",
|
|
@@ -49,6 +51,7 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
49
51
|
"Do not forward `origin`, `projectKey`, or `environment` from JSON/body payload fields under server `CLUE_API_KEY`",
|
|
50
52
|
"Frontend SDK adapter code is contract-owned Clue setup wiring",
|
|
51
53
|
"Do not derive it from `NEXT_PUBLIC_API_URL`",
|
|
54
|
+
"Next.js browser SDK adapter files must start with `\"use client\"`",
|
|
52
55
|
"Do not call `ClueInit` with empty-string fallbacks",
|
|
53
56
|
"do not set `initialized = true` before `ClueInit`",
|
|
54
57
|
"For Django code, use `clue-django-sdk` only after package-manager or registry verification confirms it is installable",
|
|
@@ -58,9 +61,13 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
58
61
|
"Reject Django SDK setup when `clue-django-sdk` installability has not been verified",
|
|
59
62
|
"Reject ClueTrack instrumentation unless the user explicitly requested product event tracking",
|
|
60
63
|
"Reject Next.js browser/client code that reads non-public `process.env.CLUE_*` variables",
|
|
64
|
+
"Reject backend Clue setup code that uses `os.environ[\"CLUE_*\"]` required indexing instead of non-crashing env reads",
|
|
61
65
|
"Reject browser token proxy code that forwards origin, projectKey, or environment from request JSON/body under server `CLUE_API_KEY`",
|
|
66
|
+
"Reject browser token proxy code that sends `CLUE_API_KEY` as `Authorization: Bearer`",
|
|
67
|
+
"Reject browser token proxy code that imports undeclared HTTP client dependencies",
|
|
62
68
|
"Reject frontend browser token providers that derive the Clue proxy URL from `NEXT_PUBLIC_API_URL`",
|
|
63
69
|
"Reject frontend adapters that mix stale browser-token paths",
|
|
70
|
+
"Reject Next.js browser SDK adapter files that omit `\"use client\"`",
|
|
64
71
|
"Reject frontend adapters that set `initialized = true` before calling `ClueInit`",
|
|
65
72
|
"Reject Clue SDK dependency entries that pin stale fixed versions",
|
|
66
73
|
"Audit the setup diff against the Clue setup contract even when the code was written by another agent",
|
|
@@ -71,6 +78,7 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
71
78
|
"clue-local-verification": [
|
|
72
79
|
"`setup-check --require-sdk-lifecycle` is a static source check only",
|
|
73
80
|
"`setup-doctor --local` API connectivity preflight",
|
|
81
|
+
"setup-agent quality gates must be reported honestly",
|
|
74
82
|
"Verify frontend SDK installability/import",
|
|
75
83
|
"Verify backend SDK installability/import",
|
|
76
84
|
"Do not run `npx -y @clue-ai/cli setup-watch --local` automatically",
|
|
@@ -88,6 +96,7 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
88
96
|
"clue-setup-report": [
|
|
89
97
|
"Never claim `setup completed` from `setup-check --require-sdk-lifecycle` alone",
|
|
90
98
|
"Completion requires all applicable evidence",
|
|
99
|
+
"setup-agent quality gates must be reported honestly",
|
|
91
100
|
"For every completion claim, include the evidence source",
|
|
92
101
|
],
|
|
93
102
|
};
|
|
@@ -504,6 +513,9 @@ const sourceIsUnderAnyRoot = (source, roots) =>
|
|
|
504
513
|
);
|
|
505
514
|
});
|
|
506
515
|
|
|
516
|
+
const hasUseClientDirective = (text) =>
|
|
517
|
+
/^\s*(?:"use client"|'use client')\s*;?/.test(text);
|
|
518
|
+
|
|
507
519
|
const dependencySourceHasPackage = (source, packageName) => {
|
|
508
520
|
if (source.file_path.endsWith("package.json")) {
|
|
509
521
|
return packageJsonDependencyNames(source.text).includes(packageName);
|
|
@@ -920,6 +932,25 @@ const findNextBrowserTokenProviderMissingPublicEndpointFiles = ({
|
|
|
920
932
|
.map((source) => source.file_path);
|
|
921
933
|
};
|
|
922
934
|
|
|
935
|
+
const findNextLifecycleMissingUseClientFiles = ({
|
|
936
|
+
dependencySources,
|
|
937
|
+
frontendSources,
|
|
938
|
+
}) => {
|
|
939
|
+
const roots = nextPackageRoots(dependencySources);
|
|
940
|
+
if (roots.length === 0) return [];
|
|
941
|
+
return frontendSources
|
|
942
|
+
.filter((source) => sourceIsUnderAnyRoot(source, roots))
|
|
943
|
+
.filter(
|
|
944
|
+
(source) =>
|
|
945
|
+
sourceImportsFrontendSdk(source) ||
|
|
946
|
+
/\bClueInit\s*\(/.test(
|
|
947
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
948
|
+
),
|
|
949
|
+
)
|
|
950
|
+
.filter((source) => !hasUseClientDirective(source.text))
|
|
951
|
+
.map((source) => source.file_path);
|
|
952
|
+
};
|
|
953
|
+
|
|
923
954
|
const browserTokenPathLiterals = (text) =>
|
|
924
955
|
[
|
|
925
956
|
...stripSourceNoise(text).matchAll(
|
|
@@ -990,6 +1021,35 @@ const sourceLooksLikeBrowserTokenProxy = (source) => {
|
|
|
990
1021
|
);
|
|
991
1022
|
};
|
|
992
1023
|
|
|
1024
|
+
const findBackendSdkInitMissingServiceKeyFiles = ({
|
|
1025
|
+
backendSources,
|
|
1026
|
+
framework,
|
|
1027
|
+
}) => {
|
|
1028
|
+
if (framework !== "fastapi") return [];
|
|
1029
|
+
return backendSources
|
|
1030
|
+
.filter((source) =>
|
|
1031
|
+
/\bclue_init_fastapi\s*\(/.test(
|
|
1032
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
1033
|
+
),
|
|
1034
|
+
)
|
|
1035
|
+
.filter(
|
|
1036
|
+
(source) =>
|
|
1037
|
+
!/\bclue_init_fastapi\s*\([\s\S]{0,1600}\bservice_key\s*=/.test(
|
|
1038
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
1039
|
+
),
|
|
1040
|
+
)
|
|
1041
|
+
.map((source) => source.file_path);
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
const findBackendClueEnvRequiredIndexFiles = (backendSources) =>
|
|
1045
|
+
backendSources
|
|
1046
|
+
.filter((source) =>
|
|
1047
|
+
/os\.environ\s*\[\s*["']CLUE_[A-Z0-9_]+["']\s*\]/.test(
|
|
1048
|
+
stripSourceNoise(source.text),
|
|
1049
|
+
),
|
|
1050
|
+
)
|
|
1051
|
+
.map((source) => source.file_path);
|
|
1052
|
+
|
|
993
1053
|
const findBrowserTokenProxyUsingBackendServiceKeyFiles = (backendSources) =>
|
|
994
1054
|
backendSources
|
|
995
1055
|
.filter(sourceLooksLikeBrowserTokenProxy)
|
|
@@ -1004,18 +1064,18 @@ const findBrowserTokenProxyUsingBackendServiceKeyFiles = (backendSources) =>
|
|
|
1004
1064
|
.map((source) => source.file_path);
|
|
1005
1065
|
|
|
1006
1066
|
const bodyOriginPatterns = [
|
|
1007
|
-
/\b(?:
|
|
1008
|
-
/\b(?:
|
|
1009
|
-
/\b(?:
|
|
1010
|
-
/["']origin["']\s*:\s*(?:
|
|
1011
|
-
/\borigin\s*=\s*(?:
|
|
1067
|
+
/\b(?:body|requestBody|request_body|data|input)\.origin\b/i,
|
|
1068
|
+
/\b(?:body|requestBody|request_body|data|input)\s*\[\s*["']origin["']\s*\]/i,
|
|
1069
|
+
/\b(?:body|requestBody|request_body|data|input)\.get\s*\(\s*["']origin["']/i,
|
|
1070
|
+
/["']origin["']\s*:\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1071
|
+
/\borigin\s*=\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1012
1072
|
];
|
|
1013
1073
|
|
|
1014
1074
|
const bodyProjectEnvironmentPatterns = [
|
|
1015
|
-
/\b(?:projectKey|project_key)\s*:\s*(?:
|
|
1016
|
-
/["'](?:projectKey|project_key)["']\s*:\s*(?:
|
|
1017
|
-
/\benvironment\s*:\s*(?:
|
|
1018
|
-
/["']environment["']\s*:\s*(?:
|
|
1075
|
+
/\b(?:projectKey|project_key)\s*:\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1076
|
+
/["'](?:projectKey|project_key)["']\s*:\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1077
|
+
/\benvironment\s*:\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1078
|
+
/["']environment["']\s*:\s*(?:body|requestBody|request_body|data|input)\b/i,
|
|
1019
1079
|
];
|
|
1020
1080
|
|
|
1021
1081
|
const findBrowserTokenProxyTrustingBodyOriginFiles = (backendSources) =>
|
|
@@ -1040,6 +1100,37 @@ const findBrowserTokenProxyTrustingBodyProjectEnvironmentFiles = (
|
|
|
1040
1100
|
})
|
|
1041
1101
|
.map((source) => source.file_path);
|
|
1042
1102
|
|
|
1103
|
+
const findBrowserTokenProxyMissingApiKeyHeaderFiles = (backendSources) =>
|
|
1104
|
+
backendSources
|
|
1105
|
+
.filter(sourceLooksLikeBrowserTokenProxy)
|
|
1106
|
+
.filter(
|
|
1107
|
+
(source) =>
|
|
1108
|
+
!/["']x-clue-api-key["']\s*:/.test(stripSourceNoise(source.text)),
|
|
1109
|
+
)
|
|
1110
|
+
.map((source) => source.file_path);
|
|
1111
|
+
|
|
1112
|
+
const findBrowserTokenProxyUndeclaredHttpClientFiles = ({
|
|
1113
|
+
backendSources,
|
|
1114
|
+
dependencySources,
|
|
1115
|
+
}) => {
|
|
1116
|
+
const declaredHttpClients = ["httpx", "requests"].filter((packageName) =>
|
|
1117
|
+
dependencyHasAnyPackage(dependencySources, [packageName]),
|
|
1118
|
+
);
|
|
1119
|
+
return backendSources
|
|
1120
|
+
.filter(sourceLooksLikeBrowserTokenProxy)
|
|
1121
|
+
.filter((source) => {
|
|
1122
|
+
const text = stripSourceNoise(source.text, { stripStrings: true });
|
|
1123
|
+
const importsHttpx = /(?:^|\n)\s*import\s+httpx\b/.test(text);
|
|
1124
|
+
const importsRequests =
|
|
1125
|
+
/(?:^|\n)\s*(?:import\s+requests\b|from\s+requests\b)/.test(text);
|
|
1126
|
+
return (
|
|
1127
|
+
(importsHttpx && !declaredHttpClients.includes("httpx")) ||
|
|
1128
|
+
(importsRequests && !declaredHttpClients.includes("requests"))
|
|
1129
|
+
);
|
|
1130
|
+
})
|
|
1131
|
+
.map((source) => source.file_path);
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1043
1134
|
const backendSdkSpec = (framework) =>
|
|
1044
1135
|
BACKEND_SDK_BY_FRAMEWORK[String(framework ?? "").toLowerCase()] ?? {
|
|
1045
1136
|
packages: Object.values(BACKEND_SDK_BY_FRAMEWORK).flatMap(
|
|
@@ -1143,6 +1234,13 @@ const checkSdkLifecycle = ({
|
|
|
1143
1234
|
!backendPresent || backendSpec.installabilityStatus !== "unverified";
|
|
1144
1235
|
const backendInitPresent =
|
|
1145
1236
|
!backendPresent || backendSpec.initPattern.test(backendCombined);
|
|
1237
|
+
const backendSdkInitMissingServiceKeyFiles =
|
|
1238
|
+
findBackendSdkInitMissingServiceKeyFiles({
|
|
1239
|
+
backendSources,
|
|
1240
|
+
framework,
|
|
1241
|
+
});
|
|
1242
|
+
const backendClueEnvRequiredIndexFiles =
|
|
1243
|
+
findBackendClueEnvRequiredIndexFiles(backendSources);
|
|
1146
1244
|
const frontendLifecyclePresent = frontendFoundApiNames.length > 0;
|
|
1147
1245
|
const frontendSdkDependencyPresent =
|
|
1148
1246
|
!frontendLifecyclePresent ||
|
|
@@ -1182,12 +1280,24 @@ const checkSdkLifecycle = ({
|
|
|
1182
1280
|
dependencySources,
|
|
1183
1281
|
frontendSources,
|
|
1184
1282
|
});
|
|
1283
|
+
const nextLifecycleMissingUseClientFiles =
|
|
1284
|
+
findNextLifecycleMissingUseClientFiles({
|
|
1285
|
+
dependencySources,
|
|
1286
|
+
frontendSources,
|
|
1287
|
+
});
|
|
1185
1288
|
const browserTokenProxyUsingBackendServiceKeyFiles =
|
|
1186
1289
|
findBrowserTokenProxyUsingBackendServiceKeyFiles(backendSources);
|
|
1187
1290
|
const browserTokenProxyTrustingBodyOriginFiles =
|
|
1188
1291
|
findBrowserTokenProxyTrustingBodyOriginFiles(backendSources);
|
|
1189
1292
|
const browserTokenProxyTrustingBodyProjectEnvironmentFiles =
|
|
1190
1293
|
findBrowserTokenProxyTrustingBodyProjectEnvironmentFiles(backendSources);
|
|
1294
|
+
const browserTokenProxyMissingApiKeyHeaderFiles =
|
|
1295
|
+
findBrowserTokenProxyMissingApiKeyHeaderFiles(backendSources);
|
|
1296
|
+
const browserTokenProxyUndeclaredHttpClientFiles =
|
|
1297
|
+
findBrowserTokenProxyUndeclaredHttpClientFiles({
|
|
1298
|
+
backendSources,
|
|
1299
|
+
dependencySources,
|
|
1300
|
+
});
|
|
1191
1301
|
const backendIdentityRequired =
|
|
1192
1302
|
backendPresent &&
|
|
1193
1303
|
/\b(login|signin|sign_in|auth|token|session)\b/i.test(backendCombined);
|
|
@@ -1228,6 +1338,8 @@ const checkSdkLifecycle = ({
|
|
|
1228
1338
|
sdk_import_present: backendSdkImportPresent,
|
|
1229
1339
|
sdk_dependency_or_import_present: backendSdkPresent,
|
|
1230
1340
|
sdk_init_present: backendInitPresent,
|
|
1341
|
+
sdk_init_missing_service_key_files: backendSdkInitMissingServiceKeyFiles,
|
|
1342
|
+
clue_env_required_index_files: backendClueEnvRequiredIndexFiles,
|
|
1231
1343
|
required_apis: [
|
|
1232
1344
|
...(backendIdentityRequired ? ["ClueIdentify"] : []),
|
|
1233
1345
|
...(backendAccountRequired ? ["ClueSetAccount"] : []),
|
|
@@ -1259,12 +1371,18 @@ const checkSdkLifecycle = ({
|
|
|
1259
1371
|
nextBrowserTokenProviderMissingPublicServiceKeyFiles,
|
|
1260
1372
|
next_browser_token_provider_missing_public_endpoint_files:
|
|
1261
1373
|
nextBrowserTokenProviderMissingPublicEndpointFiles,
|
|
1374
|
+
next_lifecycle_missing_use_client_files:
|
|
1375
|
+
nextLifecycleMissingUseClientFiles,
|
|
1262
1376
|
browser_token_proxy_uses_backend_service_key_files:
|
|
1263
1377
|
browserTokenProxyUsingBackendServiceKeyFiles,
|
|
1264
1378
|
browser_token_proxy_trusts_body_origin_files:
|
|
1265
1379
|
browserTokenProxyTrustingBodyOriginFiles,
|
|
1266
1380
|
browser_token_proxy_trusts_body_project_environment_files:
|
|
1267
1381
|
browserTokenProxyTrustingBodyProjectEnvironmentFiles,
|
|
1382
|
+
browser_token_proxy_missing_api_key_header_files:
|
|
1383
|
+
browserTokenProxyMissingApiKeyHeaderFiles,
|
|
1384
|
+
browser_token_proxy_undeclared_http_client_files:
|
|
1385
|
+
browserTokenProxyUndeclaredHttpClientFiles,
|
|
1268
1386
|
},
|
|
1269
1387
|
has_noop_wrapper: noOpPattern.test(combined),
|
|
1270
1388
|
component_lifecycle_init_files: componentLifecycleInitFiles,
|
|
@@ -1278,6 +1396,8 @@ const checkSdkLifecycle = ({
|
|
|
1278
1396
|
backendSdkInstallabilityVerified &&
|
|
1279
1397
|
pinnedBackendSdkDependencyFiles.length === 0 &&
|
|
1280
1398
|
backendInitPresent &&
|
|
1399
|
+
backendSdkInitMissingServiceKeyFiles.length === 0 &&
|
|
1400
|
+
backendClueEnvRequiredIndexFiles.length === 0 &&
|
|
1281
1401
|
backendMissingApis.length === 0 &&
|
|
1282
1402
|
frontendSdkPresent &&
|
|
1283
1403
|
frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
|
|
@@ -1291,9 +1411,12 @@ const checkSdkLifecycle = ({
|
|
|
1291
1411
|
frontendInitBeforeClueInitFiles.length === 0 &&
|
|
1292
1412
|
nextBrowserTokenProviderMissingPublicServiceKeyFiles.length === 0 &&
|
|
1293
1413
|
nextBrowserTokenProviderMissingPublicEndpointFiles.length === 0 &&
|
|
1414
|
+
nextLifecycleMissingUseClientFiles.length === 0 &&
|
|
1294
1415
|
browserTokenProxyUsingBackendServiceKeyFiles.length === 0 &&
|
|
1295
1416
|
browserTokenProxyTrustingBodyOriginFiles.length === 0 &&
|
|
1296
1417
|
browserTokenProxyTrustingBodyProjectEnvironmentFiles.length === 0 &&
|
|
1418
|
+
browserTokenProxyMissingApiKeyHeaderFiles.length === 0 &&
|
|
1419
|
+
browserTokenProxyUndeclaredHttpClientFiles.length === 0 &&
|
|
1297
1420
|
!noOpPattern.test(combined) &&
|
|
1298
1421
|
componentLifecycleInitFiles.length === 0 &&
|
|
1299
1422
|
blockingLifecycleFiles.length === 0,
|
package/src/setup-detect.mjs
CHANGED
|
@@ -34,6 +34,23 @@ const commonDirectory = (paths) => {
|
|
|
34
34
|
return common.length ? common.join("/") : ".";
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
const isPathInside = (childPath, parentPath) =>
|
|
38
|
+
childPath === parentPath || childPath.startsWith(`${parentPath}/`);
|
|
39
|
+
|
|
40
|
+
const findFastApiAppRoot = async ({ repoRoot, files, routeFiles }) => {
|
|
41
|
+
const appRootCandidates = [];
|
|
42
|
+
for (const absolutePath of files) {
|
|
43
|
+
const relativePath = relative(repoRoot, absolutePath);
|
|
44
|
+
const source = await readFile(absolutePath, "utf8");
|
|
45
|
+
if (!/\bFastAPI\s*\(/.test(source)) continue;
|
|
46
|
+
const candidate = dirname(relativePath);
|
|
47
|
+
if (routeFiles.every((routeFile) => isPathInside(routeFile, candidate))) {
|
|
48
|
+
appRootCandidates.push(candidate);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return appRootCandidates.sort((left, right) => right.length - left.length)[0] ?? null;
|
|
52
|
+
};
|
|
53
|
+
|
|
37
54
|
const ignoredPackageDirs = new Set([
|
|
38
55
|
".git",
|
|
39
56
|
".next",
|
|
@@ -162,7 +179,12 @@ export const runSetupDetect = async ({ repoRoot, excludedSourcePaths = [] }) =>
|
|
|
162
179
|
fastApiRoutes.map((route) => route.ai_context.relative_path).filter(Boolean),
|
|
163
180
|
),
|
|
164
181
|
].sort();
|
|
165
|
-
const backendRootPath =
|
|
182
|
+
const backendRootPath =
|
|
183
|
+
(await findFastApiAppRoot({
|
|
184
|
+
repoRoot: resolvedRepoRoot,
|
|
185
|
+
files,
|
|
186
|
+
routeFiles,
|
|
187
|
+
})) ?? commonDirectory(routeFiles);
|
|
166
188
|
const backendServiceKey = deriveServiceKeyFromPath(backendRootPath);
|
|
167
189
|
return {
|
|
168
190
|
detected: true,
|
package/src/setup-documents.mjs
CHANGED
|
@@ -8,6 +8,7 @@ export const CORE_SETUP_DOCUMENT_IDS = [
|
|
|
8
8
|
"clue-boundary",
|
|
9
9
|
"environment-and-secrets",
|
|
10
10
|
"find-integration-points",
|
|
11
|
+
"official-sdk-contract",
|
|
11
12
|
"browser-token-endpoint",
|
|
12
13
|
"clue-init",
|
|
13
14
|
"clue-identify",
|
|
@@ -96,4 +97,3 @@ export const buildSetupDocumentationContract = ({
|
|
|
96
97
|
"Read the relevant Clue setup documents before editing and list consulted_document_ids in the final report.",
|
|
97
98
|
};
|
|
98
99
|
};
|
|
99
|
-
|
package/src/setup-help.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DETERMINISTIC_CONTROL_MODEL,
|
|
9
9
|
FRONTEND_ADAPTER_CONTRACT,
|
|
10
10
|
SETUP_DOCTRINE,
|
|
11
|
+
SETUP_AGENT_QUALITY_GATES,
|
|
11
12
|
} from "./setup-ai-contract.mjs";
|
|
12
13
|
import { buildSetupDocumentationContract } from "./setup-documents.mjs";
|
|
13
14
|
|
|
@@ -116,6 +117,7 @@ export const buildAiSetupHelp = () => ({
|
|
|
116
117
|
"OpenAI uses Responses API forced function tools with strict schema and parallel_tool_calls false. Anthropic uses Messages API tool_choice with the same input schema.",
|
|
117
118
|
setup_watch_auto_run: false,
|
|
118
119
|
},
|
|
120
|
+
setup_agent_quality_gates: SETUP_AGENT_QUALITY_GATES,
|
|
119
121
|
completion_boundary: {
|
|
120
122
|
ai_may_claim: [
|
|
121
123
|
"Clue setup code changes were applied",
|
package/src/setup-tool.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
AI_SETUP_CONTRACT_VERSION,
|
|
12
12
|
API_CONNECTIVITY_CONTRACT,
|
|
13
|
+
SETUP_AGENT_QUALITY_GATES,
|
|
13
14
|
setupDoctrineSkillLines,
|
|
14
15
|
} from "./setup-ai-contract.mjs";
|
|
15
16
|
import { buildSetupDocumentationContract } from "./setup-documents.mjs";
|
|
@@ -54,6 +55,9 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
54
55
|
)
|
|
55
56
|
.map(([framework, docId]) => `${framework} -> ${docId}`)
|
|
56
57
|
.join(", ");
|
|
58
|
+
const setupAgentQualityGateIds = SETUP_AGENT_QUALITY_GATES.map(
|
|
59
|
+
(gate) => gate.id,
|
|
60
|
+
).join(", ");
|
|
57
61
|
const descriptions = {
|
|
58
62
|
"clue-setup-orchestrator":
|
|
59
63
|
"Use first when running Clue setup so lifecycle placement remains the only implementation workstream and read-only checks stay separate.",
|
|
@@ -260,7 +264,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
260
264
|
"Open the docs page when the AI tool has browser or HTTP access. If it cannot be opened, continue only from the generated skill text and manifest doc ids, then report `documentation_access_blocked`.",
|
|
261
265
|
"Read all ids in `documentation.required_doc_ids` that apply to the detected repository.",
|
|
262
266
|
"For framework-specific implementation, select ids from `documentation.selected_framework_doc_ids` first. If empty, use `documentation.framework_doc_ids_by_framework` for the detected framework.",
|
|
263
|
-
"Minimum lifecycle docs before implementation: ai-setup-order, environment-and-secrets, browser-token-endpoint, clue-init, clue-identify, clue-set-account, clue-logout, setup-verification, forbidden-patterns.",
|
|
267
|
+
"Minimum lifecycle docs before implementation: ai-setup-order, environment-and-secrets, official-sdk-contract, browser-token-endpoint, clue-init, clue-identify, clue-set-account, clue-logout, setup-verification, forbidden-patterns.",
|
|
264
268
|
"For Next.js, read framework-nextjs. For React, Vite, Vue, or Angular browser apps, read framework-react-spa. For FastAPI, read framework-fastapi. For Django, read framework-django.",
|
|
265
269
|
"Return `consulted_document_ids` to the orchestrator and final report.",
|
|
266
270
|
"If a document contradicts `.clue/setup-manifest.json` or generated skills, stop and report a blocker instead of choosing behavior silently.",
|
|
@@ -316,6 +320,8 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
316
320
|
"Do not add per-call try/catch, try/except, `.catch`, or custom safe wrappers solely around official Clue SDK public lifecycle calls.",
|
|
317
321
|
"Never await a Clue lifecycle call in a way that can block login, logout, account selection, request handling, page rendering, or API responses.",
|
|
318
322
|
"Add or report the required SDK dependency instead of fabricating lifecycle APIs.",
|
|
323
|
+
"A clean customer repository is expected to have no existing Clue SDK imports, dependencies, browserTokenProvider wiring, or Clue adapter. Do not treat that absence as a blocker; add the minimal official Clue setup wiring.",
|
|
324
|
+
"When no existing Clue adapter or browser-token proxy module exists, creating a minimal Clue-owned setup file under the existing source root is allowed. Do not use new files for unrelated host app abstractions.",
|
|
319
325
|
"For frontend code, add the real `@clue-ai/browser-sdk` dependency when missing. Use the latest channel (`@clue-ai/browser-sdk@latest`) so setup receives current SDK fixes. Do not invent `clue-js-sdk`, `@clue/browser-sdk`, local placeholder modules, or dynamic imports that hide a missing SDK.",
|
|
320
326
|
`When lifecycle edits are clear, write an exact replacement plan to a temporary local JSON file and apply it with \`${clueCliCommand("lifecycle-apply --plan <plan-file> --repo .")}\`.`,
|
|
321
327
|
"If npm/npx cannot fetch the Clue CLI package, report a blocker with the exact command and error instead of manually applying replacement plans.",
|
|
@@ -325,6 +331,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
325
331
|
"For Next.js browser/client code, use only `NEXT_PUBLIC_CLUE_PROJECT_KEY`, `NEXT_PUBLIC_CLUE_ENVIRONMENT`, `NEXT_PUBLIC_CLUE_SERVICE_KEY`, `NEXT_PUBLIC_CLUE_INGEST_ENDPOINT`, and `NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT` from the frontend `.env.local` block.",
|
|
326
332
|
"Do not read `process.env.CLUE_PROJECT_KEY`, `process.env.CLUE_ENVIRONMENT`, `process.env.CLUE_SERVICE_KEY`, or `process.env.CLUE_INGEST_ENDPOINT` in Next.js browser/client code, and do not add non-public `CLUE_*` fallbacks there.",
|
|
327
333
|
"Frontend SDK adapter code is contract-owned Clue setup wiring. The AI may choose the existing import/mount point, but must not invent token URL, env, or initialization semantics.",
|
|
334
|
+
"Next.js browser SDK adapter files must start with `\"use client\"` so Clue browser SDK code is not imported through a server component module.",
|
|
328
335
|
"For Next.js frontend adapters, read the full customer-backend browser-token proxy URL from `NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT`. Do not derive it from `NEXT_PUBLIC_API_URL`, generic app API env names, detected backend ports, or relative frontend-origin paths.",
|
|
329
336
|
"Do not mix stale browser-token paths such as `/api/clue/browser-token`, `/clue/browser-tokens`, or `/browser-tokens` with the canonical `/api/v1/clue/browser-tokens` path.",
|
|
330
337
|
"Do not call `ClueInit` with empty-string fallbacks for required `NEXT_PUBLIC_CLUE_*` values. If required Clue env is absent, skip initialization and report the missing env names.",
|
|
@@ -338,8 +345,12 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
338
345
|
"The browser token request must include the frontend service key used by `ClueInit`. Project key and environment may be included only as public consistency hints; the backend must use server configuration or validate them against server configuration before calling Clue.",
|
|
339
346
|
"The backend browser token proxy must derive origin from trusted request headers or server request metadata. Do not forward `origin`, `projectKey`, or `environment` from JSON/body payload fields under server `CLUE_API_KEY`.",
|
|
340
347
|
"For browser token proxy code, the service key sent to Clue must be the frontend `ClueInit` serviceKey from the browser request, not the backend service's `CLUE_SERVICE_KEY`.",
|
|
348
|
+
"When the backend browser token proxy calls Clue, send the server `CLUE_API_KEY` as the `x-clue-api-key` header. Do not use `Authorization: Bearer`, query parameters, or JSON body fields for the Clue API key.",
|
|
349
|
+
"Do not introduce non-Clue HTTP client dependencies for browser-token proxy code unless they already exist in dependency files. For Python, prefer an existing HTTP client or standard library instead of importing undeclared `httpx` or `requests`.",
|
|
341
350
|
"If a backend-owned browser token endpoint is implemented, read `CLUE_API_BASE_URL` from the backend env block and normalize it so values with or without a trailing `/api/v1` do not produce duplicate paths.",
|
|
342
351
|
"For FastAPI code, add unpinned `clue-fastapi-sdk` to the backend dependency file when missing so pip resolves the latest release, import `clue_init_fastapi` plus `ClueIdentify`, `ClueSetAccount`, and `ClueLogout` where needed, and use `CLUE_PROJECT_KEY`, `CLUE_ENVIRONMENT`, `CLUE_API_KEY`, and `CLUE_INGEST_ENDPOINT` from the backend env block.",
|
|
352
|
+
"Do not use `os.environ[\"CLUE_*\"]` required indexing for Clue env values; Clue setup must not crash the host service when env is missing.",
|
|
353
|
+
"FastAPI `clue_init_fastapi` must pass `service_key` from the setup manifest/`CLUE_SERVICE_KEY`; do not rely on `service_name` alone because setup-watch targets are keyed by service key.",
|
|
343
354
|
"`CLUE_API_BASE_URL` is not part of backend SDK initialization. Use it only when the application backend owns a browser-token proxy for a frontend service.",
|
|
344
355
|
"Python backend SDK dependencies must not be pinned.",
|
|
345
356
|
"`@clue-ai/browser-sdk` must use `latest`.",
|
|
@@ -372,11 +383,15 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
372
383
|
"Reject wrong SDK package names. Frontend must use `@clue-ai/browser-sdk`; FastAPI must use `clue-fastapi-sdk`; Django must use `clue-django-sdk`.",
|
|
373
384
|
"Reject Django SDK setup when `clue-django-sdk` installability has not been verified.",
|
|
374
385
|
"Reject backend setup when backend routes exist but no backend Clue SDK dependency/import/init was added.",
|
|
386
|
+
"Reject backend Clue setup code that uses `os.environ[\"CLUE_*\"]` required indexing instead of non-crashing env reads.",
|
|
375
387
|
"Reject awaited lifecycle calls that can block host service behavior.",
|
|
376
388
|
"Reject browser token proxy code that forwards origin, projectKey, or environment from request JSON/body under server `CLUE_API_KEY`.",
|
|
389
|
+
"Reject browser token proxy code that sends `CLUE_API_KEY` as `Authorization: Bearer`, query parameters, or JSON body fields instead of the `x-clue-api-key` header.",
|
|
390
|
+
"Reject browser token proxy code that imports undeclared HTTP client dependencies such as `httpx` or `requests`.",
|
|
377
391
|
"Reject frontend browser token providers that derive the Clue proxy URL from `NEXT_PUBLIC_API_URL`, generic app API env names, detected backend ports, or non-Clue routing assumptions.",
|
|
378
392
|
"Reject frontend adapters that mix stale browser-token paths such as `/api/clue/browser-token`, `/clue/browser-tokens`, or `/browser-tokens` with the canonical `/api/v1/clue/browser-tokens` path.",
|
|
379
393
|
"Reject frontend adapters that set `initialized = true` before calling `ClueInit`, or pass empty-string fallbacks for required `NEXT_PUBLIC_CLUE_*` values into `ClueInit`.",
|
|
394
|
+
"Reject Next.js browser SDK adapter files that omit `\"use client\"`.",
|
|
380
395
|
"Audit the setup diff against the Clue setup contract even when the code was written by another agent or an earlier pass. Ownership of authorship is irrelevant to approval.",
|
|
381
396
|
"Reject setup that covers only one login path when multiple login success paths are clearly present.",
|
|
382
397
|
"Reject ClueInit inside React component lifecycle hooks, page components, sidebars, login/register success callbacks, or any repeated user interaction path.",
|
|
@@ -483,6 +498,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
483
498
|
"- The full setup must start with `clue-setup-orchestrator`.",
|
|
484
499
|
"- The implementation agent owns only lifecycle placement; monitoring agents review one surface at a time and report P0/P1 issues.",
|
|
485
500
|
"- Do not continue past a P0/P1 monitoring finding until it is fixed or explicitly reported as blocked.",
|
|
501
|
+
`- setup-agent quality gates must be reported honestly when available: ${setupAgentQualityGateIds}. A skipped setup-doctor gate is not API connectivity verification.`,
|
|
486
502
|
"- If subagents are unavailable, run the same structure as separate named review passes and say so in the final report.",
|
|
487
503
|
"- Do not expose project keys, API keys, secrets, tokens, or environment variable values.",
|
|
488
504
|
"- Do not ask the user to paste secret values.",
|