@clue-ai/cli 0.0.17 → 0.0.19
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 +15 -0
- package/package.json +1 -1
- package/src/ai-provider.mjs +92 -2
- package/src/fastapi-analyzer.mjs +36 -2
- package/src/lifecycle-init.mjs +234 -16
- package/src/public-schema.cjs +1 -0
- package/src/semantic-ci.mjs +235 -20
- package/src/setup-agent.mjs +448 -0
- package/src/setup-ai-contract.mjs +25 -1
- package/src/setup-check.mjs +199 -11
- package/src/setup-doctor.mjs +11 -4
- package/src/setup-help.mjs +32 -2
- package/src/setup-prepare.mjs +19 -0
- package/src/setup-tool.mjs +20 -8
package/src/setup-check.mjs
CHANGED
|
@@ -39,11 +39,18 @@ 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
|
-
"
|
|
42
|
+
"Install Clue SDK dependencies through the latest channel",
|
|
43
|
+
"`@clue-ai/browser-sdk` must use `latest`",
|
|
44
|
+
"Python backend SDK dependencies must not be pinned",
|
|
43
45
|
"Whitespace-only changes are allowed only on lines directly changed for Clue SDK wiring",
|
|
44
46
|
"The local backend token endpoint is part of the customer app, not the Clue API",
|
|
45
|
-
"
|
|
47
|
+
"send the same service key used by `ClueInit`",
|
|
48
|
+
"NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT",
|
|
46
49
|
"Do not forward `origin`, `projectKey`, or `environment` from JSON/body payload fields under server `CLUE_API_KEY`",
|
|
50
|
+
"Frontend SDK adapter code is contract-owned Clue setup wiring",
|
|
51
|
+
"Do not derive it from `NEXT_PUBLIC_API_URL`",
|
|
52
|
+
"Do not call `ClueInit` with empty-string fallbacks",
|
|
53
|
+
"do not set `initialized = true` before `ClueInit`",
|
|
47
54
|
"For Django code, use `clue-django-sdk` only after package-manager or registry verification confirms it is installable",
|
|
48
55
|
],
|
|
49
56
|
"clue-setup-audit": [
|
|
@@ -52,6 +59,11 @@ const REQUIRED_SETUP_SKILL_PHRASES = {
|
|
|
52
59
|
"Reject ClueTrack instrumentation unless the user explicitly requested product event tracking",
|
|
53
60
|
"Reject Next.js browser/client code that reads non-public `process.env.CLUE_*` variables",
|
|
54
61
|
"Reject browser token proxy code that forwards origin, projectKey, or environment from request JSON/body under server `CLUE_API_KEY`",
|
|
62
|
+
"Reject frontend browser token providers that derive the Clue proxy URL from `NEXT_PUBLIC_API_URL`",
|
|
63
|
+
"Reject frontend adapters that mix stale browser-token paths",
|
|
64
|
+
"Reject frontend adapters that set `initialized = true` before calling `ClueInit`",
|
|
65
|
+
"Reject Clue SDK dependency entries that pin stale fixed versions",
|
|
66
|
+
"Audit the setup diff against the Clue setup contract even when the code was written by another agent",
|
|
55
67
|
"Reject whitespace-only edits, import sorting, formatter churn",
|
|
56
68
|
"Reject unrelated refactors, renames, file moves",
|
|
57
69
|
"Execution agents must not approve, certify, or mark their own work complete",
|
|
@@ -138,6 +150,12 @@ const DEPENDENCY_FILE_CANDIDATES = [
|
|
|
138
150
|
"poetry.lock",
|
|
139
151
|
"Pipfile",
|
|
140
152
|
];
|
|
153
|
+
const PYTHON_BACKEND_DEPENDENCY_DECLARATION_FILES = [
|
|
154
|
+
"requirements.txt",
|
|
155
|
+
"requirements-dev.txt",
|
|
156
|
+
"pyproject.toml",
|
|
157
|
+
"Pipfile",
|
|
158
|
+
];
|
|
141
159
|
const exists = async (path) => {
|
|
142
160
|
try {
|
|
143
161
|
await access(path);
|
|
@@ -385,6 +403,20 @@ const setupSourcePaths = async ({ repoRoot, request, includeFrontend }) => {
|
|
|
385
403
|
return existing.length ? existing : requested;
|
|
386
404
|
};
|
|
387
405
|
|
|
406
|
+
const setupBackendRootPaths = (request) => {
|
|
407
|
+
const configuredRoots = [
|
|
408
|
+
...(Array.isArray(request?.backend_root_paths)
|
|
409
|
+
? request.backend_root_paths
|
|
410
|
+
: []),
|
|
411
|
+
request?.backend_root_path,
|
|
412
|
+
]
|
|
413
|
+
.filter((root) => typeof root === "string" && root.trim())
|
|
414
|
+
.map((root) => root.trim());
|
|
415
|
+
return configuredRoots.length
|
|
416
|
+
? [...new Set(configuredRoots)]
|
|
417
|
+
: (request?.allowed_source_paths ?? []);
|
|
418
|
+
};
|
|
419
|
+
|
|
388
420
|
const secretLeakPatterns = [
|
|
389
421
|
/pk_(live|test)_[A-Za-z0-9_-]+/,
|
|
390
422
|
/sk_(live|test)_[A-Za-z0-9_-]+/,
|
|
@@ -736,7 +768,13 @@ const NEXT_PUBLIC_CLUE_NAMES = [
|
|
|
736
768
|
"CLUE_ENVIRONMENT",
|
|
737
769
|
"CLUE_SERVICE_KEY",
|
|
738
770
|
"CLUE_INGEST_ENDPOINT",
|
|
771
|
+
"CLUE_BROWSER_TOKEN_ENDPOINT",
|
|
739
772
|
];
|
|
773
|
+
const CANONICAL_BROWSER_TOKEN_PROXY_PATH = "/api/v1/clue/browser-tokens";
|
|
774
|
+
const BROWSER_TOKEN_PATH_PATTERN =
|
|
775
|
+
/\/[^"'`\s]*(?:browser[-_]?tokens?|browser[-_]?token)\b/i;
|
|
776
|
+
const GENERIC_PUBLIC_API_ENV_PATTERN =
|
|
777
|
+
/\bprocess\.env\.(?:(?:NEXT_PUBLIC|VITE|REACT_APP)_(?!CLUE_)[A-Z0-9_]*(?:API|BACKEND|BASE|URL)[A-Z0-9_]*)\b/;
|
|
740
778
|
|
|
741
779
|
const findNextLifecycleNonPublicEnvFiles = ({
|
|
742
780
|
dependencySources,
|
|
@@ -761,17 +799,71 @@ const findNextLifecycleNonPublicEnvFiles = ({
|
|
|
761
799
|
.map((source) => source.file_path);
|
|
762
800
|
};
|
|
763
801
|
|
|
764
|
-
const
|
|
802
|
+
const findNonLatestFrontendSdkDependencyFiles = (dependencySources) =>
|
|
765
803
|
packageJsonSourcesWithDependency(dependencySources, FRONTEND_SDK_PACKAGE)
|
|
766
804
|
.filter((source) => {
|
|
767
805
|
const version = packageJsonDependencyVersion(
|
|
768
806
|
source.text,
|
|
769
807
|
FRONTEND_SDK_PACKAGE,
|
|
770
808
|
);
|
|
771
|
-
return version
|
|
809
|
+
return version !== "latest";
|
|
772
810
|
})
|
|
773
811
|
.map((source) => source.file_path);
|
|
774
812
|
|
|
813
|
+
const sourceIsPythonBackendDependencyDeclaration = (source) =>
|
|
814
|
+
PYTHON_BACKEND_DEPENDENCY_DECLARATION_FILES.some((fileName) =>
|
|
815
|
+
source.file_path.endsWith(fileName),
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
const backendSdkDependencyLineHasPinnedVersion = (line, packageName) => {
|
|
819
|
+
const stripped = line.trim();
|
|
820
|
+
if (!stripped || stripped.startsWith("#")) return false;
|
|
821
|
+
const withoutInlineComment = stripped.replace(/\s+#.*$/, "");
|
|
822
|
+
const packagePattern = new RegExp(
|
|
823
|
+
`(^|[\\s"'=,{\\[]+)${escapeRegex(packageName)}($|[\\s"'=<>~!@,;)}\\]]+)`,
|
|
824
|
+
"i",
|
|
825
|
+
);
|
|
826
|
+
if (!packagePattern.test(withoutInlineComment)) return false;
|
|
827
|
+
|
|
828
|
+
const requirementPattern = new RegExp(
|
|
829
|
+
`${escapeRegex(packageName)}(?:\\[[^\\]]+\\])?([^\\s"',;}\\]]*)`,
|
|
830
|
+
"i",
|
|
831
|
+
);
|
|
832
|
+
const requirementMatch = requirementPattern.exec(withoutInlineComment);
|
|
833
|
+
const suffix = requirementMatch?.[1] ?? "";
|
|
834
|
+
if (suffix && suffix !== ";") {
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const keyPattern = new RegExp(
|
|
839
|
+
`^\\s*["']?${escapeRegex(packageName)}["']?\\s*=\\s*(.+)$`,
|
|
840
|
+
"i",
|
|
841
|
+
);
|
|
842
|
+
const keyMatch = keyPattern.exec(withoutInlineComment);
|
|
843
|
+
if (!keyMatch) return false;
|
|
844
|
+
const value = keyMatch[1].trim();
|
|
845
|
+
if (/^["']\*["']$/.test(value)) return false;
|
|
846
|
+
if (/version\s*=\s*["']\*["']/.test(value)) return false;
|
|
847
|
+
return true;
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const findPinnedBackendSdkDependencyFiles = (
|
|
851
|
+
dependencySources,
|
|
852
|
+
packageNames,
|
|
853
|
+
) =>
|
|
854
|
+
dependencySources
|
|
855
|
+
.filter(sourceIsPythonBackendDependencyDeclaration)
|
|
856
|
+
.filter((source) =>
|
|
857
|
+
source.text
|
|
858
|
+
.split(/\r?\n/)
|
|
859
|
+
.some((line) =>
|
|
860
|
+
packageNames.some((packageName) =>
|
|
861
|
+
backendSdkDependencyLineHasPinnedVersion(line, packageName),
|
|
862
|
+
),
|
|
863
|
+
),
|
|
864
|
+
)
|
|
865
|
+
.map((source) => source.file_path);
|
|
866
|
+
|
|
775
867
|
const sourceLooksLikeBrowserTokenProvider = (source) => {
|
|
776
868
|
const text = stripSourceNoise(source.text);
|
|
777
869
|
return (
|
|
@@ -810,14 +902,83 @@ const findNextBrowserTokenProviderMissingPublicServiceKeyFiles = ({
|
|
|
810
902
|
.map((source) => source.file_path);
|
|
811
903
|
};
|
|
812
904
|
|
|
905
|
+
const findNextBrowserTokenProviderMissingPublicEndpointFiles = ({
|
|
906
|
+
dependencySources,
|
|
907
|
+
frontendSources,
|
|
908
|
+
}) => {
|
|
909
|
+
const roots = nextPackageRoots(dependencySources);
|
|
910
|
+
if (roots.length === 0) return [];
|
|
911
|
+
return frontendSources
|
|
912
|
+
.filter((source) => sourceIsUnderAnyRoot(source, roots))
|
|
913
|
+
.filter(sourceLooksLikeBrowserTokenProvider)
|
|
914
|
+
.filter(
|
|
915
|
+
(source) =>
|
|
916
|
+
!/\bprocess\.env\.NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT\b/.test(
|
|
917
|
+
source.text,
|
|
918
|
+
),
|
|
919
|
+
)
|
|
920
|
+
.map((source) => source.file_path);
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
const browserTokenPathLiterals = (text) =>
|
|
924
|
+
[
|
|
925
|
+
...stripSourceNoise(text).matchAll(
|
|
926
|
+
/(["'`])([^"'`]*(?:browser[-_]?tokens?|browser[-_]?token)[^"'`]*)\1/gi,
|
|
927
|
+
),
|
|
928
|
+
].map((match) => match[2]);
|
|
929
|
+
|
|
813
930
|
const findBrowserTokenProviderWrongProxyPathFiles = (frontendSources) =>
|
|
814
931
|
frontendSources
|
|
815
932
|
.filter(sourceLooksLikeBrowserTokenProvider)
|
|
816
933
|
.filter((source) => {
|
|
817
934
|
const text = stripSourceNoise(source.text);
|
|
818
935
|
if (!/\bfetch\s*\(/.test(text)) return false;
|
|
819
|
-
|
|
820
|
-
|
|
936
|
+
const tokenPathLiterals = browserTokenPathLiterals(text).filter(
|
|
937
|
+
(literal) => BROWSER_TOKEN_PATH_PATTERN.test(literal),
|
|
938
|
+
);
|
|
939
|
+
if (tokenPathLiterals.length === 0) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
return tokenPathLiterals.some(
|
|
943
|
+
(literal) => !literal.includes(CANONICAL_BROWSER_TOKEN_PROXY_PATH),
|
|
944
|
+
);
|
|
945
|
+
})
|
|
946
|
+
.map((source) => source.file_path);
|
|
947
|
+
|
|
948
|
+
const findBrowserTokenProviderNonClueEndpointEnvFiles = (frontendSources) =>
|
|
949
|
+
frontendSources
|
|
950
|
+
.filter(sourceLooksLikeBrowserTokenProvider)
|
|
951
|
+
.filter((source) =>
|
|
952
|
+
GENERIC_PUBLIC_API_ENV_PATTERN.test(stripSourceNoise(source.text)),
|
|
953
|
+
)
|
|
954
|
+
.map((source) => source.file_path);
|
|
955
|
+
|
|
956
|
+
const findClueInitEmptyEnvFallbackFiles = (frontendSources) =>
|
|
957
|
+
frontendSources
|
|
958
|
+
.filter(
|
|
959
|
+
(source) =>
|
|
960
|
+
sourceImportsFrontendSdk(source) ||
|
|
961
|
+
sourceLooksLikeBrowserTokenProvider(source),
|
|
962
|
+
)
|
|
963
|
+
.filter((source) =>
|
|
964
|
+
/process\.env\.NEXT_PUBLIC_CLUE_[A-Z0-9_]+\s*(?:\?\?|\|\|)\s*(["'])\1/.test(
|
|
965
|
+
stripSourceNoise(source.text),
|
|
966
|
+
),
|
|
967
|
+
)
|
|
968
|
+
.map((source) => source.file_path);
|
|
969
|
+
|
|
970
|
+
const findFrontendInitBeforeClueInitFiles = (frontendSources) =>
|
|
971
|
+
frontendSources
|
|
972
|
+
.filter((source) => sourceImportsFrontendSdk(source))
|
|
973
|
+
.filter((source) => {
|
|
974
|
+
const text = stripSourceNoise(source.text, { stripStrings: true });
|
|
975
|
+
const initializedIndex = text.search(/\binitialized\s*=\s*true\b/);
|
|
976
|
+
const clueInitIndex = text.search(/\bClueInit\s*\(/);
|
|
977
|
+
return (
|
|
978
|
+
initializedIndex >= 0 &&
|
|
979
|
+
clueInitIndex >= 0 &&
|
|
980
|
+
initializedIndex < clueInitIndex
|
|
981
|
+
);
|
|
821
982
|
})
|
|
822
983
|
.map((source) => source.file_path);
|
|
823
984
|
|
|
@@ -969,6 +1130,10 @@ const checkSdkLifecycle = ({
|
|
|
969
1130
|
const backendSdkDependencyPresent =
|
|
970
1131
|
!backendPresent ||
|
|
971
1132
|
dependencyHasAnyPackage(dependencySources, backendSpec.packages);
|
|
1133
|
+
const pinnedBackendSdkDependencyFiles = findPinnedBackendSdkDependencyFiles(
|
|
1134
|
+
dependencySources,
|
|
1135
|
+
backendSpec.packages,
|
|
1136
|
+
);
|
|
972
1137
|
const backendSdkImportPresent =
|
|
973
1138
|
!backendPresent ||
|
|
974
1139
|
sourcesHavePythonImport(backendSources, backendSpec.imports);
|
|
@@ -995,17 +1160,28 @@ const checkSdkLifecycle = ({
|
|
|
995
1160
|
dependencySources,
|
|
996
1161
|
frontendSources,
|
|
997
1162
|
});
|
|
998
|
-
const
|
|
999
|
-
|
|
1163
|
+
const nonLatestFrontendSdkDependencyFiles =
|
|
1164
|
+
findNonLatestFrontendSdkDependencyFiles(dependencySources);
|
|
1000
1165
|
const browserTokenProviderMissingServiceKeyFiles =
|
|
1001
1166
|
findBrowserTokenProviderMissingServiceKeyFiles(frontendSources);
|
|
1002
1167
|
const browserTokenProviderWrongProxyPathFiles =
|
|
1003
1168
|
findBrowserTokenProviderWrongProxyPathFiles(frontendSources);
|
|
1169
|
+
const browserTokenProviderNonClueEndpointEnvFiles =
|
|
1170
|
+
findBrowserTokenProviderNonClueEndpointEnvFiles(frontendSources);
|
|
1171
|
+
const clueInitEmptyEnvFallbackFiles =
|
|
1172
|
+
findClueInitEmptyEnvFallbackFiles(frontendSources);
|
|
1173
|
+
const frontendInitBeforeClueInitFiles =
|
|
1174
|
+
findFrontendInitBeforeClueInitFiles(frontendSources);
|
|
1004
1175
|
const nextBrowserTokenProviderMissingPublicServiceKeyFiles =
|
|
1005
1176
|
findNextBrowserTokenProviderMissingPublicServiceKeyFiles({
|
|
1006
1177
|
dependencySources,
|
|
1007
1178
|
frontendSources,
|
|
1008
1179
|
});
|
|
1180
|
+
const nextBrowserTokenProviderMissingPublicEndpointFiles =
|
|
1181
|
+
findNextBrowserTokenProviderMissingPublicEndpointFiles({
|
|
1182
|
+
dependencySources,
|
|
1183
|
+
frontendSources,
|
|
1184
|
+
});
|
|
1009
1185
|
const browserTokenProxyUsingBackendServiceKeyFiles =
|
|
1010
1186
|
findBrowserTokenProxyUsingBackendServiceKeyFiles(backendSources);
|
|
1011
1187
|
const browserTokenProxyTrustingBodyOriginFiles =
|
|
@@ -1048,6 +1224,7 @@ const checkSdkLifecycle = ({
|
|
|
1048
1224
|
? null
|
|
1049
1225
|
: backendSpec.blocker,
|
|
1050
1226
|
sdk_dependency_present: backendSdkDependencyPresent,
|
|
1227
|
+
pinned_sdk_dependency_files: pinnedBackendSdkDependencyFiles,
|
|
1051
1228
|
sdk_import_present: backendSdkImportPresent,
|
|
1052
1229
|
sdk_dependency_or_import_present: backendSdkPresent,
|
|
1053
1230
|
sdk_init_present: backendInitPresent,
|
|
@@ -1069,13 +1246,19 @@ const checkSdkLifecycle = ({
|
|
|
1069
1246
|
frontendLifecycleFilesWithoutVerifiedSdk,
|
|
1070
1247
|
wrong_sdk_packages: wrongFrontendSdkPackages,
|
|
1071
1248
|
next_lifecycle_non_public_env_files: nextLifecycleNonPublicEnvFiles,
|
|
1072
|
-
|
|
1249
|
+
non_latest_sdk_dependency_files: nonLatestFrontendSdkDependencyFiles,
|
|
1073
1250
|
browser_token_provider_missing_service_key_files:
|
|
1074
1251
|
browserTokenProviderMissingServiceKeyFiles,
|
|
1075
1252
|
browser_token_provider_wrong_proxy_path_files:
|
|
1076
1253
|
browserTokenProviderWrongProxyPathFiles,
|
|
1254
|
+
browser_token_provider_non_clue_endpoint_env_files:
|
|
1255
|
+
browserTokenProviderNonClueEndpointEnvFiles,
|
|
1256
|
+
clue_init_empty_env_fallback_files: clueInitEmptyEnvFallbackFiles,
|
|
1257
|
+
frontend_init_before_clue_init_files: frontendInitBeforeClueInitFiles,
|
|
1077
1258
|
next_browser_token_provider_missing_public_service_key_files:
|
|
1078
1259
|
nextBrowserTokenProviderMissingPublicServiceKeyFiles,
|
|
1260
|
+
next_browser_token_provider_missing_public_endpoint_files:
|
|
1261
|
+
nextBrowserTokenProviderMissingPublicEndpointFiles,
|
|
1079
1262
|
browser_token_proxy_uses_backend_service_key_files:
|
|
1080
1263
|
browserTokenProxyUsingBackendServiceKeyFiles,
|
|
1081
1264
|
browser_token_proxy_trusts_body_origin_files:
|
|
@@ -1093,16 +1276,21 @@ const checkSdkLifecycle = ({
|
|
|
1093
1276
|
missingApis.length === 0 &&
|
|
1094
1277
|
backendSdkPresent &&
|
|
1095
1278
|
backendSdkInstallabilityVerified &&
|
|
1279
|
+
pinnedBackendSdkDependencyFiles.length === 0 &&
|
|
1096
1280
|
backendInitPresent &&
|
|
1097
1281
|
backendMissingApis.length === 0 &&
|
|
1098
1282
|
frontendSdkPresent &&
|
|
1099
1283
|
frontendLifecycleFilesWithoutVerifiedSdk.length === 0 &&
|
|
1100
1284
|
wrongFrontendSdkPackages.length === 0 &&
|
|
1101
1285
|
nextLifecycleNonPublicEnvFiles.length === 0 &&
|
|
1102
|
-
|
|
1286
|
+
nonLatestFrontendSdkDependencyFiles.length === 0 &&
|
|
1103
1287
|
browserTokenProviderMissingServiceKeyFiles.length === 0 &&
|
|
1104
1288
|
browserTokenProviderWrongProxyPathFiles.length === 0 &&
|
|
1289
|
+
browserTokenProviderNonClueEndpointEnvFiles.length === 0 &&
|
|
1290
|
+
clueInitEmptyEnvFallbackFiles.length === 0 &&
|
|
1291
|
+
frontendInitBeforeClueInitFiles.length === 0 &&
|
|
1105
1292
|
nextBrowserTokenProviderMissingPublicServiceKeyFiles.length === 0 &&
|
|
1293
|
+
nextBrowserTokenProviderMissingPublicEndpointFiles.length === 0 &&
|
|
1106
1294
|
browserTokenProxyUsingBackendServiceKeyFiles.length === 0 &&
|
|
1107
1295
|
browserTokenProxyTrustingBodyOriginFiles.length === 0 &&
|
|
1108
1296
|
browserTokenProxyTrustingBodyProjectEnvironmentFiles.length === 0 &&
|
|
@@ -1309,7 +1497,7 @@ export const runSetupCheck = async ({
|
|
|
1309
1497
|
|
|
1310
1498
|
if (requireSdkLifecycle) {
|
|
1311
1499
|
const sdkLifecycle = checkSdkLifecycle({
|
|
1312
|
-
backendRootPaths: request
|
|
1500
|
+
backendRootPaths: setupBackendRootPaths(request),
|
|
1313
1501
|
dependencySources,
|
|
1314
1502
|
framework: request?.framework,
|
|
1315
1503
|
sources,
|
package/src/setup-doctor.mjs
CHANGED
|
@@ -247,9 +247,11 @@ export const runSetupDoctor = async ({
|
|
|
247
247
|
optionalString(flags.get("origin")) ??
|
|
248
248
|
clientFrontendUrl ??
|
|
249
249
|
"http://localhost";
|
|
250
|
-
const browserTokenProxyUrl =
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
const browserTokenProxyUrl =
|
|
251
|
+
optionalString(flags.get("browser-token-proxy-url")) ??
|
|
252
|
+
optionalString(env.NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT) ??
|
|
253
|
+
optionalString(env.CLUE_BROWSER_TOKEN_ENDPOINT) ??
|
|
254
|
+
(clientBackendUrl ? joinUrl(clientBackendUrl, BROWSER_TOKEN_PROXY_PATH) : null);
|
|
253
255
|
const clueBrowserTokenUrl = clueApiBaseUrl
|
|
254
256
|
? joinUrl(clueApiBaseUrl, CLUE_BROWSER_TOKEN_PATH)
|
|
255
257
|
: null;
|
|
@@ -272,7 +274,11 @@ export const runSetupDoctor = async ({
|
|
|
272
274
|
requiredInputCheck({
|
|
273
275
|
id: "client_backend_browser_token_proxy",
|
|
274
276
|
missing: [
|
|
275
|
-
...(!browserTokenProxyUrl
|
|
277
|
+
...(!browserTokenProxyUrl
|
|
278
|
+
? [
|
|
279
|
+
"NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT or CLUE_BROWSER_TOKEN_ENDPOINT or client-backend-url",
|
|
280
|
+
]
|
|
281
|
+
: []),
|
|
276
282
|
...(!serviceKey ? ["service-key"] : []),
|
|
277
283
|
],
|
|
278
284
|
url: browserTokenProxyUrl,
|
|
@@ -424,6 +430,7 @@ export const runSetupDoctor = async ({
|
|
|
424
430
|
manifest_loaded: Boolean(manifest),
|
|
425
431
|
client_backend_url_configured: Boolean(clientBackendUrl),
|
|
426
432
|
client_frontend_url_configured: Boolean(clientFrontendUrl),
|
|
433
|
+
browser_token_proxy_url_configured: Boolean(browserTokenProxyUrl),
|
|
427
434
|
clue_api_base_url_configured: Boolean(clueApiBaseUrl),
|
|
428
435
|
project_key_configured: Boolean(projectKey),
|
|
429
436
|
environment_configured: Boolean(environment),
|
package/src/setup-help.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AI_SETUP_CONTRACT_VERSION,
|
|
7
7
|
API_CONNECTIVITY_CONTRACT,
|
|
8
8
|
DETERMINISTIC_CONTROL_MODEL,
|
|
9
|
+
FRONTEND_ADAPTER_CONTRACT,
|
|
9
10
|
SETUP_DOCTRINE,
|
|
10
11
|
} from "./setup-ai-contract.mjs";
|
|
11
12
|
import { buildSetupDocumentationContract } from "./setup-documents.mjs";
|
|
@@ -22,6 +23,7 @@ export const buildAiSetupHelp = () => ({
|
|
|
22
23
|
setup_doctrine: SETUP_DOCTRINE,
|
|
23
24
|
deterministic_control_model: DETERMINISTIC_CONTROL_MODEL,
|
|
24
25
|
api_connectivity_contract: API_CONNECTIVITY_CONTRACT,
|
|
26
|
+
frontend_adapter_contract: FRONTEND_ADAPTER_CONTRACT,
|
|
25
27
|
agent_primary_task:
|
|
26
28
|
"Decide where to place ClueInit, ClueIdentify, ClueSetAccount, and ClueLogout in existing repository lifecycle boundaries, then apply only those minimal Clue SDK wiring changes.",
|
|
27
29
|
implementation_workstreams: ["sdk_lifecycle_placement"],
|
|
@@ -53,6 +55,21 @@ export const buildAiSetupHelp = () => ({
|
|
|
53
55
|
"whitespace-only edits, import sorting, formatter churn, or comment/style cleanup outside the exact Clue SDK wiring lines",
|
|
54
56
|
],
|
|
55
57
|
},
|
|
58
|
+
sdk_dependency_contract: {
|
|
59
|
+
frontend: {
|
|
60
|
+
package: "@clue-ai/browser-sdk",
|
|
61
|
+
install: "package-manager add @clue-ai/browser-sdk@latest",
|
|
62
|
+
rule:
|
|
63
|
+
"Frontend package.json must declare @clue-ai/browser-sdk as latest. Fixed versions and * are rejected by setup-check because stale SDKs break setup contracts.",
|
|
64
|
+
},
|
|
65
|
+
backend: {
|
|
66
|
+
fastapi_package: "clue-fastapi-sdk",
|
|
67
|
+
django_package: "clue-django-sdk",
|
|
68
|
+
install: "pip install --upgrade <backend-sdk-package>",
|
|
69
|
+
rule:
|
|
70
|
+
"Python backend SDK dependency declarations must be unpinned so pip resolves the latest release. Fixed ==, ~=, <=, >=, <, >, local path, and exact-version declarations are rejected by setup-check.",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
56
73
|
environment_contract: {
|
|
57
74
|
nextjs_frontend_client_env: {
|
|
58
75
|
variables: [
|
|
@@ -60,8 +77,9 @@ export const buildAiSetupHelp = () => ({
|
|
|
60
77
|
"NEXT_PUBLIC_CLUE_ENVIRONMENT",
|
|
61
78
|
"NEXT_PUBLIC_CLUE_SERVICE_KEY",
|
|
62
79
|
"NEXT_PUBLIC_CLUE_INGEST_ENDPOINT",
|
|
80
|
+
"NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT",
|
|
63
81
|
],
|
|
64
|
-
rule: "When the frontend framework is Next.js, browser/client code must read only these NEXT_PUBLIC_* Clue variables. Do not add process.env.CLUE_* fallbacks in client-bundled code.",
|
|
82
|
+
rule: "When the frontend framework is Next.js, browser/client code must read only these NEXT_PUBLIC_* Clue variables. Do not add process.env.CLUE_* fallbacks in client-bundled code. browserTokenProvider must call NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT, not a relative frontend-origin path or generic NEXT_PUBLIC_API_URL.",
|
|
65
83
|
},
|
|
66
84
|
backend_browser_token_proxy_env: {
|
|
67
85
|
variables: ["CLUE_API_KEY", "CLUE_API_BASE_URL"],
|
|
@@ -83,9 +101,21 @@ export const buildAiSetupHelp = () => ({
|
|
|
83
101
|
owner: "ai_or_user",
|
|
84
102
|
ai_agent_may_run: true,
|
|
85
103
|
command: clueCliCommand("setup-doctor --local"),
|
|
86
|
-
rule: "Run setup-doctor after local frontend/backend services and required env are available. It checks API connectivity only and does not replace user-operated setup-watch.",
|
|
104
|
+
rule: "Run setup-doctor after local frontend/backend services and required env are available. setup-agent blocks when required setup-doctor inputs are missing or connectivity fails, unless setup-doctor is explicitly skipped. It checks API connectivity only and does not replace user-operated setup-watch.",
|
|
87
105
|
checked_hops: Object.keys(API_CONNECTIVITY_CONTRACT.hops),
|
|
88
106
|
},
|
|
107
|
+
setup_agent: {
|
|
108
|
+
owner: "clue_cli",
|
|
109
|
+
command: clueCliCommand("setup-agent --repo ."),
|
|
110
|
+
providers: ["openai", "anthropic"],
|
|
111
|
+
env:
|
|
112
|
+
"Set exactly CLUE_AI_PROVIDER, CLUE_AI_PROVIDER_API_KEY, and CLUE_AI_MODEL. setup-agent uses the same AI provider configuration as the rest of the Clue CLI. Optional CLI overrides are --ai-provider, --ai-provider-api-key, and --ai-model; do not use ambiguous --api_key flags.",
|
|
113
|
+
model_role:
|
|
114
|
+
"Return a strict structured lifecycle plan only; the CLI owns scanning, edit application, checks, doctor preflight, retry, and final status.",
|
|
115
|
+
tool_calling:
|
|
116
|
+
"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
|
+
setup_watch_auto_run: false,
|
|
118
|
+
},
|
|
89
119
|
completion_boundary: {
|
|
90
120
|
ai_may_claim: [
|
|
91
121
|
"Clue setup code changes were applied",
|
package/src/setup-prepare.mjs
CHANGED
|
@@ -16,11 +16,14 @@ const DEFAULT_SETUP_MANIFEST_PATH = ".clue/setup-manifest.json";
|
|
|
16
16
|
const DEFAULT_ENV_GUIDE_PATH = ".env.clue";
|
|
17
17
|
const BROWSER_INGEST_PATH = "/api/v1/ingest/browser";
|
|
18
18
|
const BACKEND_INGEST_PATH = "/api/v1/ingest/backend";
|
|
19
|
+
const BROWSER_TOKEN_PROXY_PATH =
|
|
20
|
+
API_CONNECTIVITY_CONTRACT.hops.client_backend_browser_token_proxy.path;
|
|
19
21
|
const FRONTEND_PUBLIC_ENV_NAMES = [
|
|
20
22
|
"CLUE_INGEST_ENDPOINT",
|
|
21
23
|
"CLUE_PROJECT_KEY",
|
|
22
24
|
"CLUE_ENVIRONMENT",
|
|
23
25
|
"CLUE_SERVICE_KEY",
|
|
26
|
+
"CLUE_BROWSER_TOKEN_ENDPOINT",
|
|
24
27
|
];
|
|
25
28
|
const BACKEND_RUNTIME_ENV_NAMES = [
|
|
26
29
|
"CLUE_SERVICE_KEY",
|
|
@@ -187,6 +190,7 @@ const frontendEnvName = ({ target, name }) =>
|
|
|
187
190
|
: name;
|
|
188
191
|
|
|
189
192
|
const buildServiceEnvBlock = ({
|
|
193
|
+
browserTokenEndpoint,
|
|
190
194
|
target,
|
|
191
195
|
setupContext,
|
|
192
196
|
includeBrowserTokenProxyConfig = false,
|
|
@@ -211,6 +215,12 @@ const buildServiceEnvBlock = ({
|
|
|
211
215
|
value: target.service_key,
|
|
212
216
|
},
|
|
213
217
|
];
|
|
218
|
+
if (target.kind === "frontend") {
|
|
219
|
+
variables.push({
|
|
220
|
+
name: frontendEnvName({ target, name: "CLUE_BROWSER_TOKEN_ENDPOINT" }),
|
|
221
|
+
value: browserTokenEndpoint,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
214
224
|
if (target.kind === "backend") {
|
|
215
225
|
variables.push({ name: "CLUE_API_KEY", value: setupContext.clue_api_key });
|
|
216
226
|
if (includeBrowserTokenProxyConfig) {
|
|
@@ -266,6 +276,14 @@ const buildEnvironmentInstructions = ({ manifest, setupContext }) => {
|
|
|
266
276
|
const includeBrowserTokenProxyConfig = watchTargets.some(
|
|
267
277
|
(target) => target.kind === "frontend",
|
|
268
278
|
);
|
|
279
|
+
const backendTarget = watchTargets.find((target) => target.kind === "backend");
|
|
280
|
+
const backendUrlCandidate =
|
|
281
|
+
optionalString(backendTarget?.local_url_candidates?.[0]) ??
|
|
282
|
+
"http://<client-backend-url>";
|
|
283
|
+
const browserTokenEndpoint = buildEndpoint(
|
|
284
|
+
backendUrlCandidate,
|
|
285
|
+
BROWSER_TOKEN_PROXY_PATH,
|
|
286
|
+
);
|
|
269
287
|
return {
|
|
270
288
|
status: "ready",
|
|
271
289
|
env_file_path: DEFAULT_ENV_GUIDE_PATH,
|
|
@@ -277,6 +295,7 @@ const buildEnvironmentInstructions = ({ manifest, setupContext }) => {
|
|
|
277
295
|
service_key: target.service_key,
|
|
278
296
|
env_file_candidates: envFileCandidates(target),
|
|
279
297
|
env_block: buildServiceEnvBlock({
|
|
298
|
+
browserTokenEndpoint,
|
|
280
299
|
target,
|
|
281
300
|
setupContext,
|
|
282
301
|
includeBrowserTokenProxyConfig,
|
package/src/setup-tool.mjs
CHANGED
|
@@ -316,27 +316,34 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
316
316
|
"Do not add per-call try/catch, try/except, `.catch`, or custom safe wrappers solely around official Clue SDK public lifecycle calls.",
|
|
317
317
|
"Never await a Clue lifecycle call in a way that can block login, logout, account selection, request handling, page rendering, or API responses.",
|
|
318
318
|
"Add or report the required SDK dependency instead of fabricating lifecycle APIs.",
|
|
319
|
-
"For frontend code, add the real `@clue-ai/browser-sdk` dependency when missing. Do not invent `clue-js-sdk`, `@clue/browser-sdk`, local placeholder modules, or dynamic imports that hide a missing SDK.",
|
|
319
|
+
"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
320
|
`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
321
|
"If npm/npx cannot fetch the Clue CLI package, report a blocker with the exact command and error instead of manually applying replacement plans.",
|
|
322
322
|
"Delete the temporary lifecycle plan file after applying it unless the user explicitly asks to keep it for review.",
|
|
323
323
|
"Use environment variable names for Clue configuration values; do not paste project keys or API keys into code.",
|
|
324
324
|
`For local env files, use the service-specific env blocks written to \`.env.clue\` by \`${clueCliCommand("setup")}\`; do not ask the user to guess \`CLUE_SERVICE_KEY\`.`,
|
|
325
|
-
"For Next.js browser/client code, use only `NEXT_PUBLIC_CLUE_PROJECT_KEY`, `NEXT_PUBLIC_CLUE_ENVIRONMENT`, `NEXT_PUBLIC_CLUE_SERVICE_KEY`, and `
|
|
325
|
+
"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
326
|
"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
|
+
"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.",
|
|
328
|
+
"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
|
+
"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
|
+
"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.",
|
|
331
|
+
"If a singleton guard is used, do not set `initialized = true` before `ClueInit` has actually been called with required config present.",
|
|
327
332
|
"For non-Next.js browser code, use the exact frontend env names written in `.env.clue` for that service instead of inventing a framework-specific prefix.",
|
|
328
333
|
"Never put `CLUE_API_KEY` in frontend code, frontend env files, browser bundles, or client-readable config.",
|
|
329
334
|
"When browser SDK ingest is configured, implement a backend-owned browser token endpoint that reads server-side `CLUE_API_KEY` and requests `POST /api/v1/ingest/browser-tokens` from Clue.",
|
|
330
335
|
"Configure frontend `ClueInit` with `browserTokenProvider` that calls the local backend token endpoint and returns the token string.",
|
|
331
336
|
"The local backend token endpoint is part of the customer app, not the Clue API. Place it under a Clue-reserved local route such as `/api/v1/clue/browser-tokens`; do not use a generic path such as `/browser-tokens` that could be confused with product behavior. It must call Clue server-side at `/api/v1/ingest/browser-tokens`.",
|
|
332
|
-
"The frontend `browserTokenProvider` must send the same service key used by `ClueInit` to the customer backend token endpoint. For Next.js this value comes from `NEXT_PUBLIC_CLUE_SERVICE_KEY`.",
|
|
337
|
+
"The frontend `browserTokenProvider` must call `NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT` and send the same service key used by `ClueInit` to the customer backend token endpoint. For Next.js this value comes from `NEXT_PUBLIC_CLUE_SERVICE_KEY`.",
|
|
333
338
|
"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.",
|
|
334
339
|
"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`.",
|
|
335
340
|
"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`.",
|
|
336
341
|
"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.",
|
|
337
|
-
"For FastAPI code, add `clue-fastapi-sdk` to the backend dependency file when missing, 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.",
|
|
342
|
+
"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.",
|
|
338
343
|
"`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.",
|
|
339
|
-
"
|
|
344
|
+
"Python backend SDK dependencies must not be pinned.",
|
|
345
|
+
"`@clue-ai/browser-sdk` must use `latest`.",
|
|
346
|
+
"Install Clue SDK dependencies through the latest channel. Frontend package managers must use `@clue-ai/browser-sdk@latest`; Python backend dependency declarations must not pin `clue-fastapi-sdk` or `clue-django-sdk` to a fixed version.",
|
|
340
347
|
"Use `CLUE_SERVICE_KEY` as the canonical local service identifier. Do not ask the user to manage a separate producer id; SDKs should send producer id as the service key for setup verification compatibility.",
|
|
341
348
|
"For frontend code, pass `serviceKey` from the frontend service env name written by `.env.clue` to `ClueInit`; in Next.js browser/client code that name is `NEXT_PUBLIC_CLUE_SERVICE_KEY`.",
|
|
342
349
|
"For Django code, use `clue-django-sdk` only after package-manager or registry verification confirms it is installable; if it is not published or cannot be verified, report a blocker instead of adding a guessed dependency or import.",
|
|
@@ -367,12 +374,17 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
367
374
|
"Reject backend setup when backend routes exist but no backend Clue SDK dependency/import/init was added.",
|
|
368
375
|
"Reject awaited lifecycle calls that can block host service behavior.",
|
|
369
376
|
"Reject browser token proxy code that forwards origin, projectKey, or environment from request JSON/body under server `CLUE_API_KEY`.",
|
|
377
|
+
"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
|
+
"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
|
+
"Reject frontend adapters that set `initialized = true` before calling `ClueInit`, or pass empty-string fallbacks for required `NEXT_PUBLIC_CLUE_*` values into `ClueInit`.",
|
|
380
|
+
"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.",
|
|
370
381
|
"Reject setup that covers only one login path when multiple login success paths are clearly present.",
|
|
371
382
|
"Reject ClueInit inside React component lifecycle hooks, page components, sidebars, login/register success callbacks, or any repeated user interaction path.",
|
|
372
383
|
"Reject broad ClueTrack instrumentation and DOM clue tags.",
|
|
373
384
|
"Reject ClueTrack instrumentation unless the user explicitly requested product event tracking.",
|
|
374
385
|
"Reject Next.js browser/client code that reads non-public `process.env.CLUE_*` variables.",
|
|
375
|
-
"Reject Clue SDK dependency entries that use
|
|
386
|
+
"Reject Clue SDK dependency entries that pin stale fixed versions. Frontend package.json must use `@clue-ai/browser-sdk: latest`; Python backend SDK dependency declarations must be unpinned or package-manager wildcard-latest.",
|
|
387
|
+
"Reject Next.js browser token providers that do not read `NEXT_PUBLIC_CLUE_BROWSER_TOKEN_ENDPOINT`.",
|
|
376
388
|
"Confirm no project key, API key, secret, or env value appears in diff or report.",
|
|
377
389
|
"Confirm lifecycle insertions are minimal and reviewable.",
|
|
378
390
|
"Reject whitespace-only edits, import sorting, formatter churn, or comment/style cleanup outside the exact Clue SDK wiring lines.",
|
|
@@ -392,7 +404,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
392
404
|
"Confirm the semantic workflow does not send GitHub actor, triggering_actor, sender, repository owner, repository name, or default branch to Clue.",
|
|
393
405
|
"Confirm `.clue/semantic-request.runtime.json` is not created, committed, or staged.",
|
|
394
406
|
`Run \`${clueCliCommand("setup-check --framework <framework> --backend-root-path <path> --repo . --target <codex|claude_code> --require-sdk-lifecycle")}\` when possible.`,
|
|
395
|
-
`Run \`${clueCliCommand("setup-doctor --local")}\` when local frontend/backend services are running
|
|
407
|
+
`Run \`${clueCliCommand("setup-doctor --local")}\` when local frontend/backend services are running. Missing setup-doctor inputs or failed API hops are blockers unless the user explicitly skipped setup-doctor.`,
|
|
396
408
|
`Do not run \`${clueCliCommand("setup-watch --local")}\` automatically. setup-watch requires the user to operate real local frontend/backend services and login/logout/account flows.`,
|
|
397
409
|
"If the user has not provided setup-watch or setup-screen evidence, report event delivery verification as `user_verification_pending` and do not state `setup completed`.",
|
|
398
410
|
"Local static verification passed does not mean setup complete unless dependency install, SDK imports, app startup, and user-provided setup-watch or setup-screen event delivery were all verified.",
|
|
@@ -465,7 +477,7 @@ const skillBody = (name, { documentsUrl } = {}) => {
|
|
|
465
477
|
`- Setup API connectivity has four distinct hops: ${Object.entries(API_CONNECTIVITY_CONTRACT.hops)
|
|
466
478
|
.map(([name, hop]) => `${name}=${hop.method} ${hop.path}`)
|
|
467
479
|
.join(", ")}.`,
|
|
468
|
-
`- Run \`${clueCliCommand("setup-doctor --local")}\` when local services
|
|
480
|
+
`- Run \`${clueCliCommand("setup-doctor --local")}\` when local services are running. Missing setup-doctor inputs or failed API hops are blockers unless the user explicitly skipped setup-doctor. This checks API connectivity only; it does not replace user-operated setup-watch.`,
|
|
469
481
|
"- Do not implement or refresh semantic snapshot CI during lifecycle placement; report a blocker if generated semantic artifacts are missing or stale.",
|
|
470
482
|
`- Do not run \`${clueCliCommand("setup-watch --local")}\` automatically. setup-watch and the Clue setup screen are user-operated verification steps, not implementation-agent responsibility.`,
|
|
471
483
|
"- The full setup must start with `clue-setup-orchestrator`.",
|