@genn-inc/cluebase-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/bin/cluebase-cli.mjs +11 -0
- package/package.json +17 -0
- package/src/cli-command.mjs +515 -0
- package/src/cli-invocation.mjs +17 -0
- package/src/code-evidence-analyzer.mjs +2041 -0
- package/src/contracts.mjs +36 -0
- package/src/generated-code-evidence-contract.mjs +22 -0
- package/src/generated-sdk-version-contract.mjs +5 -0
- package/src/generated-source-path-policy.mjs +20 -0
- package/src/lifecycle-guard.mjs +202 -0
- package/src/path-policy.mjs +81 -0
- package/src/setup-ai-contract.mjs +221 -0
- package/src/setup-check-constants.mjs +110 -0
- package/src/setup-check-scan-a.mjs +849 -0
- package/src/setup-check-scan-b.mjs +994 -0
- package/src/setup-check.mjs +575 -0
- package/src/setup-discover-check.mjs +755 -0
- package/src/setup-doctor-deadline.mjs +221 -0
- package/src/setup-doctor-env.mjs +331 -0
- package/src/setup-doctor-file-boundary.mjs +426 -0
- package/src/setup-doctor-probe.mjs +719 -0
- package/src/setup-doctor-quality-checks-a.mjs +593 -0
- package/src/setup-doctor-quality-checks-b.mjs +638 -0
- package/src/setup-doctor-quality-shared.mjs +382 -0
- package/src/setup-doctor-quality.mjs +209 -0
- package/src/setup-doctor-route-scan.mjs +160 -0
- package/src/setup-doctor-sdk-probe.mjs +340 -0
- package/src/setup-doctor.mjs +545 -0
- package/src/setup-documents.mjs +112 -0
- package/src/setup-help.mjs +130 -0
- package/src/setup-prepare.mjs +360 -0
- package/src/setup-repository-discovery.mjs +764 -0
- package/src/setup-step-builders-discover.mjs +701 -0
- package/src/setup-step-builders-events.mjs +229 -0
- package/src/setup-step-builders-implement.mjs +710 -0
- package/src/setup-step-commands.mjs +427 -0
- package/src/setup-tool.mjs +27 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLUEBASE_CLI_INVOCATION_CONTRACT,
|
|
3
|
+
cluebaseCliCommand,
|
|
4
|
+
} from "./cli-invocation.mjs";
|
|
5
|
+
import {
|
|
6
|
+
AI_SETUP_CONTRACT_VERSION,
|
|
7
|
+
API_CONNECTIVITY_CONTRACT,
|
|
8
|
+
DETERMINISTIC_CONTROL_MODEL,
|
|
9
|
+
FRONTEND_ADAPTER_CONTRACT,
|
|
10
|
+
GROUP_BOUNDARY_CONTRACT,
|
|
11
|
+
IDENTITY_BOUNDARY_CONTRACT,
|
|
12
|
+
OTEL_FIRST_SETUP_CONTRACT,
|
|
13
|
+
SETUP_DOCTRINE,
|
|
14
|
+
} from "./setup-ai-contract.mjs";
|
|
15
|
+
import {
|
|
16
|
+
buildSetupDocumentationContract,
|
|
17
|
+
EXTERNAL_DATA_CONNECTION_PATH,
|
|
18
|
+
EXTERNAL_DATA_CONNECTION_SCREEN_NAME,
|
|
19
|
+
} from "./setup-documents.mjs";
|
|
20
|
+
export const AI_SETUP_HELP_VERSION = AI_SETUP_CONTRACT_VERSION;
|
|
21
|
+
|
|
22
|
+
export const buildAiSetupHelp = () => ({
|
|
23
|
+
name: "@genn-inc/cluebase-cli AI setup help",
|
|
24
|
+
version: AI_SETUP_HELP_VERSION,
|
|
25
|
+
purpose:
|
|
26
|
+
"Machine-readable Cluebase setup contract for AI coding agents. Use this before editing a customer repository for Cluebase setup.",
|
|
27
|
+
cli_invocation: CLUEBASE_CLI_INVOCATION_CONTRACT,
|
|
28
|
+
setup_execution_contract: {
|
|
29
|
+
setup_doctrine: SETUP_DOCTRINE,
|
|
30
|
+
deterministic_control_model: DETERMINISTIC_CONTROL_MODEL,
|
|
31
|
+
api_connectivity_contract: API_CONNECTIVITY_CONTRACT,
|
|
32
|
+
frontend_adapter_contract: FRONTEND_ADAPTER_CONTRACT,
|
|
33
|
+
identity_boundary_contract: IDENTITY_BOUNDARY_CONTRACT,
|
|
34
|
+
group_boundary_contract: GROUP_BOUNDARY_CONTRACT,
|
|
35
|
+
otel_first_setup_contract: OTEL_FIRST_SETUP_CONTRACT,
|
|
36
|
+
agent_primary_task:
|
|
37
|
+
"Decide where to place cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset in existing repository lifecycle boundaries, then apply only those minimal Cluebase SDK wiring changes.",
|
|
38
|
+
implementation_workstreams: ["sdk_lifecycle_placement"],
|
|
39
|
+
lifecycle_apis_in_scope: [
|
|
40
|
+
"cluebase.init",
|
|
41
|
+
"cluebase.identify",
|
|
42
|
+
"cluebase.group",
|
|
43
|
+
"cluebase.reset",
|
|
44
|
+
],
|
|
45
|
+
lifecycle_apis_out_of_scope_by_default: ["cluebase.track"],
|
|
46
|
+
allowed_change_scope: {
|
|
47
|
+
rule: "Only exact changes required to place cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset are allowed.",
|
|
48
|
+
allowed: [
|
|
49
|
+
"Cluebase SDK dependency declarations and lockfile changes needed to install those SDKs",
|
|
50
|
+
"Cluebase SDK imports, bootstrap adapters, and single initialization points",
|
|
51
|
+
"Cluebase lifecycle calls at existing clear login, logout, company, organization, or tenant boundaries",
|
|
52
|
+
"tests or verification scripts directly proving the four lifecycle API placement decisions",
|
|
53
|
+
],
|
|
54
|
+
forbidden: [
|
|
55
|
+
"cluebase.track instrumentation unless the user explicitly requested product event tracking",
|
|
56
|
+
"unrelated refactors, renames, file moves, formatting churn, or broad cleanup",
|
|
57
|
+
"business logic changes that are not required for Cluebase setup",
|
|
58
|
+
"auth/session flow rewrites beyond the minimal Cluebase hook insertion point",
|
|
59
|
+
"default cluebase.identify insertion in token refresh, current-user sync, session polling, request exchange, read hook, or repeated helper paths unless repository evidence proves that path is the one-time auth success boundary",
|
|
60
|
+
"UI layout, styling, copy, or navigation changes unrelated to Cluebase setup",
|
|
61
|
+
"dependency upgrades unrelated to Cluebase SDK installability",
|
|
62
|
+
"whitespace-only edits, import sorting, formatter churn, or comment/style cleanup outside the exact Cluebase SDK wiring lines",
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
instrumentation_contract: {
|
|
66
|
+
rule:
|
|
67
|
+
"OpenTelemetry/OpenLLMetry/GenAI semantic instrumentation is primary wherever available; Cluebase helpers are enrichment/fallback only.",
|
|
68
|
+
details: OTEL_FIRST_SETUP_CONTRACT.rules,
|
|
69
|
+
},
|
|
70
|
+
sdk_dependency_contract: {
|
|
71
|
+
frontend: {
|
|
72
|
+
package: "@genn-inc/cluebase-frontend-sdk",
|
|
73
|
+
install: "package-manager add @genn-inc/cluebase-frontend-sdk@latest",
|
|
74
|
+
rule:
|
|
75
|
+
"Frontend package.json must declare @genn-inc/cluebase-frontend-sdk. The dependency version and lockfile resolution are not part of lifecycle verification.",
|
|
76
|
+
},
|
|
77
|
+
backend: {
|
|
78
|
+
fastapi_package: "cluebase-backend-sdk",
|
|
79
|
+
django_package: "cluebase-backend-sdk",
|
|
80
|
+
install: "pip install cluebase-backend-sdk",
|
|
81
|
+
rule:
|
|
82
|
+
"Python backend SDK dependency declarations must include cluebase-backend-sdk. The dependency version is not part of lifecycle verification.",
|
|
83
|
+
node_package: "@genn-inc/cluebase-backend-sdk",
|
|
84
|
+
node_install: "package-manager add @genn-inc/cluebase-backend-sdk",
|
|
85
|
+
node_rule:
|
|
86
|
+
"Node backend SDK dependency declarations must include @genn-inc/cluebase-backend-sdk. The dependency version is not part of lifecycle verification.",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
environment_contract: {
|
|
90
|
+
nextjs_frontend_client_env: {
|
|
91
|
+
variables: [
|
|
92
|
+
"NEXT_PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
93
|
+
"NEXT_PUBLIC_CLUEBASE_API_BASE_URL",
|
|
94
|
+
],
|
|
95
|
+
rule: "NEXT_PUBLIC_* is the Next.js browser/client env contract only. For non-Next.js frontend frameworks, use the framework-specific public env names generated by Cluebase setup. Browser token issuance uses the Cluebase API base URL, projectKey, and request Origin.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
documentation_contract: buildSetupDocumentationContract(),
|
|
99
|
+
external_data_source_contract: {
|
|
100
|
+
screen_name: EXTERNAL_DATA_CONNECTION_SCREEN_NAME,
|
|
101
|
+
screen_path: EXTERNAL_DATA_CONNECTION_PATH,
|
|
102
|
+
rule:
|
|
103
|
+
"Value data that no SDK call can capture — because it lives only in an external system the customer app never sees (a billing platform, a CRM, a spreadsheet, an analytics export) — is ingested by connecting that system on the Cluebase external data connection screen, not by fabricating a cluebase.track call. During guided business-event instrumentation, milestones with no authoritative in-repo code path are recorded as external_source_only and the customer is pointed at this screen.",
|
|
104
|
+
customer_navigation:
|
|
105
|
+
"Cluebase web app sidebar Settings → 外部データ, at " +
|
|
106
|
+
EXTERNAL_DATA_CONNECTION_PATH,
|
|
107
|
+
},
|
|
108
|
+
setup_verification: {
|
|
109
|
+
owner: "ai_or_user",
|
|
110
|
+
ai_agent_may_run: true,
|
|
111
|
+
command: cluebaseCliCommand("setup-doctor --local"),
|
|
112
|
+
rule: "Run setup-doctor after local frontend/backend services and required env are available. Missing inputs are reported as skipped/pending; real connectivity failures block verified completion unless setup-doctor is explicitly skipped. It checks API connectivity plus downstream published batch status before the user verifies real product-flow logs.",
|
|
113
|
+
checked_hops: Object.keys(API_CONNECTIVITY_CONTRACT.hops),
|
|
114
|
+
},
|
|
115
|
+
completion_boundary: {
|
|
116
|
+
ai_may_claim: [
|
|
117
|
+
"Cluebase setup code changes were applied",
|
|
118
|
+
"static setup-check passed",
|
|
119
|
+
"SDK dependency install/import/build/typecheck checks passed when actually run",
|
|
120
|
+
],
|
|
121
|
+
ai_must_not_claim: [
|
|
122
|
+
"setup completed",
|
|
123
|
+
"event delivery verified",
|
|
124
|
+
"Cluebase setup screen verification passed",
|
|
125
|
+
],
|
|
126
|
+
final_status_when_real_flow_not_user_verified:
|
|
127
|
+
"user_verification_pending",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
CLUEBASE_CLI_INVOCATION_CONTRACT,
|
|
5
|
+
cluebaseCliCommand,
|
|
6
|
+
} from "./cli-invocation.mjs";
|
|
7
|
+
import {
|
|
8
|
+
API_CONNECTIVITY_CONTRACT,
|
|
9
|
+
IDENTITY_BOUNDARY_CONTRACT,
|
|
10
|
+
OTEL_FIRST_SETUP_CONTRACT,
|
|
11
|
+
} from "./setup-ai-contract.mjs";
|
|
12
|
+
import { buildSetupDocumentationContract } from "./setup-documents.mjs";
|
|
13
|
+
import { discoverSetupRepository } from "./setup-repository-discovery.mjs";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_SETUP_MANIFEST_PATH = ".cluebase/setup-manifest.json";
|
|
16
|
+
const BROWSER_INGEST_PATH =
|
|
17
|
+
API_CONNECTIVITY_CONTRACT.hops.browser_ingest.path;
|
|
18
|
+
const BACKEND_INGEST_PATH = "/api/v1/ingest/backend";
|
|
19
|
+
// setup runtime で各 service が読む env 名一覧。
|
|
20
|
+
// 実際の値の提示は setup 画面 (web app の setup wizard) が担当する。
|
|
21
|
+
// CLI はサービス検出と manifest 出力のみを担う。
|
|
22
|
+
const FRONTEND_PUBLIC_ENV_NAMES = [
|
|
23
|
+
"CLUEBASE_API_BASE_URL",
|
|
24
|
+
"CLUEBASE_PROJECT_KEY",
|
|
25
|
+
];
|
|
26
|
+
const FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK = new Map([
|
|
27
|
+
["nextjs", "NEXT_PUBLIC_"],
|
|
28
|
+
["vite", "VITE_"],
|
|
29
|
+
["vue", "VITE_"],
|
|
30
|
+
["react", "REACT_APP_"],
|
|
31
|
+
["sveltekit", "PUBLIC_"],
|
|
32
|
+
["nuxt", "NUXT_PUBLIC_"],
|
|
33
|
+
]);
|
|
34
|
+
const BACKEND_RUNTIME_ENV_NAMES = [
|
|
35
|
+
"CLUEBASE_PROJECT_KEY",
|
|
36
|
+
"CLUEBASE_INGEST_ENDPOINT",
|
|
37
|
+
"CLUEBASE_API_KEY",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const writeJson = async ({ repoRoot, path, value }) => {
|
|
41
|
+
const absolutePath = join(resolve(repoRoot), path);
|
|
42
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
43
|
+
await writeFile(absolutePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const buildDetectedServices = (detection) => {
|
|
48
|
+
const frontendServices = Array.isArray(detection.services?.frontend)
|
|
49
|
+
? detection.services.frontend
|
|
50
|
+
: [];
|
|
51
|
+
const backendServices = Array.isArray(detection.services?.backend)
|
|
52
|
+
? detection.services.backend
|
|
53
|
+
: [];
|
|
54
|
+
return [...frontendServices, ...backendServices].map((service) => {
|
|
55
|
+
const targetId =
|
|
56
|
+
service.kind === "frontend" ? service.target_id : service.service_key;
|
|
57
|
+
if (typeof targetId !== "string" || !targetId.trim()) {
|
|
58
|
+
throw new Error("setup service target_id is required");
|
|
59
|
+
}
|
|
60
|
+
const targetIdForEnv = targetId.trim();
|
|
61
|
+
return {
|
|
62
|
+
kind: service.kind,
|
|
63
|
+
framework: service.framework,
|
|
64
|
+
root_path: service.root_path,
|
|
65
|
+
target_id: targetIdForEnv,
|
|
66
|
+
local_url_candidates: service.local_url_candidates ?? [],
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const optionalString = (value) =>
|
|
72
|
+
typeof value === "string" && value.trim() ? value.trim() : null;
|
|
73
|
+
|
|
74
|
+
const trimTrailingSlash = (value) => String(value).replace(/\/+$/, "");
|
|
75
|
+
|
|
76
|
+
const buildEndpoint = (baseUrl, path) => `${trimTrailingSlash(baseUrl)}${path}`;
|
|
77
|
+
|
|
78
|
+
const setupContextFromInput = (input = {}) => {
|
|
79
|
+
const projectKey = optionalString(input.projectKey);
|
|
80
|
+
return {
|
|
81
|
+
cluebase_api_key: optionalString(input.cluebaseApiKey),
|
|
82
|
+
cluebase_api_base_url: optionalString(input.cluebaseApiBaseUrl),
|
|
83
|
+
documents_url: optionalString(input.documentsUrl),
|
|
84
|
+
project_key: projectKey,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const frontendEnvName = ({ target, name }) => {
|
|
89
|
+
if (target.kind !== "frontend") return null;
|
|
90
|
+
const prefix = FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK.get(
|
|
91
|
+
String(target.framework ?? "").toLowerCase(),
|
|
92
|
+
);
|
|
93
|
+
return prefix === undefined ? null : `${prefix}${name}`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const requiredFrontendEnvNames = (detectedServices) => [
|
|
97
|
+
...new Set(
|
|
98
|
+
detectedServices
|
|
99
|
+
.filter((target) => target.kind === "frontend")
|
|
100
|
+
.flatMap((target) =>
|
|
101
|
+
FRONTEND_PUBLIC_ENV_NAMES.map((name) =>
|
|
102
|
+
frontendEnvName({ target, name }),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
.filter(Boolean),
|
|
106
|
+
),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const requiredBackendEnvNames = () => [...BACKEND_RUNTIME_ENV_NAMES];
|
|
110
|
+
|
|
111
|
+
// 環境変数は CLI 側で .env.cluebase ファイルを生成せず、setup 画面 (web app の setup wizard)
|
|
112
|
+
// に表示・誘導を任せる方針。 CLI はサービス検出結果と manifest 出力だけを担う。
|
|
113
|
+
// この関数は CLI が detect した service 一覧と「Step 2 は setup 画面で行ってください」
|
|
114
|
+
// という deferred 状態を返すのみ。
|
|
115
|
+
const buildEnvironmentInstructions = ({ manifest, setupContext }) => {
|
|
116
|
+
const missingFlags = [
|
|
117
|
+
["cluebase_api_key", "--cluebase-api-key"],
|
|
118
|
+
["cluebase_api_base_url", "--cluebase-api-base-url"],
|
|
119
|
+
["project_key", "--project-key"],
|
|
120
|
+
]
|
|
121
|
+
.filter(([key]) => !setupContext[key])
|
|
122
|
+
.map(([, flag]) => flag);
|
|
123
|
+
|
|
124
|
+
if (missingFlags.length > 0 || manifest.status !== "ready_for_ai") {
|
|
125
|
+
return {
|
|
126
|
+
status: "missing_setup_arguments",
|
|
127
|
+
required_flags: missingFlags,
|
|
128
|
+
message:
|
|
129
|
+
"Run setup with Cluebase values from the setup screen to view target-specific env blocks.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const detectedServices = Array.isArray(manifest.detected_services)
|
|
134
|
+
? manifest.detected_services
|
|
135
|
+
: [];
|
|
136
|
+
return {
|
|
137
|
+
status: "deferred_to_setup_wizard",
|
|
138
|
+
message:
|
|
139
|
+
"環境変数は setup 画面の Step 2 を見て、各サービスの env ファイルに反映してください。",
|
|
140
|
+
detected_services: detectedServices.map((target) => ({
|
|
141
|
+
kind: target.kind,
|
|
142
|
+
framework: target.framework,
|
|
143
|
+
root_path: target.root_path,
|
|
144
|
+
})),
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const summarizeEnvironmentInstructions = (instructions) => {
|
|
149
|
+
if (!instructions || instructions.status !== "deferred_to_setup_wizard") {
|
|
150
|
+
return instructions;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
status: "deferred_to_setup_wizard",
|
|
154
|
+
message: instructions.message,
|
|
155
|
+
detected_services: instructions.detected_services,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const runSetupPrepare = async ({
|
|
160
|
+
repoRoot,
|
|
161
|
+
setupContext: setupContextInput,
|
|
162
|
+
setupManifestPath = DEFAULT_SETUP_MANIFEST_PATH,
|
|
163
|
+
}) => {
|
|
164
|
+
const resolvedRepoRoot = resolve(repoRoot ?? ".");
|
|
165
|
+
const setupContext = setupContextFromInput(setupContextInput);
|
|
166
|
+
const detection = await discoverSetupRepository({ repoRoot: resolvedRepoRoot });
|
|
167
|
+
if (!detection.detected) {
|
|
168
|
+
const manifest = {
|
|
169
|
+
status: "blocked",
|
|
170
|
+
documentation: buildSetupDocumentationContract({
|
|
171
|
+
documentsUrl: setupContext.documents_url,
|
|
172
|
+
frameworks: [],
|
|
173
|
+
}),
|
|
174
|
+
blockers: detection.blockers,
|
|
175
|
+
detection,
|
|
176
|
+
ai_next_scope: "blocked_until_an_instrumentable_service_is_detected",
|
|
177
|
+
machine_owned_artifacts: [],
|
|
178
|
+
ai_owned_workstreams: [
|
|
179
|
+
"sdk_lifecycle_placement_after_blockers_are_resolved",
|
|
180
|
+
],
|
|
181
|
+
ai_implementation_scope: {
|
|
182
|
+
rule: "AI implementation is limited to placing cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset in existing lifecycle boundaries after blockers are resolved.",
|
|
183
|
+
lifecycle_apis: [
|
|
184
|
+
"cluebase.init",
|
|
185
|
+
"cluebase.identify",
|
|
186
|
+
"cluebase.group",
|
|
187
|
+
"cluebase.reset",
|
|
188
|
+
],
|
|
189
|
+
out_of_scope_by_default: ["cluebase.track"],
|
|
190
|
+
identity_boundary_contract: IDENTITY_BOUNDARY_CONTRACT,
|
|
191
|
+
instrumentation_contract: OTEL_FIRST_SETUP_CONTRACT,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
await writeJson({
|
|
195
|
+
repoRoot: resolvedRepoRoot,
|
|
196
|
+
path: setupManifestPath,
|
|
197
|
+
value: manifest,
|
|
198
|
+
});
|
|
199
|
+
const environmentInstructions = buildEnvironmentInstructions({
|
|
200
|
+
manifest,
|
|
201
|
+
setupContext,
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
...manifest,
|
|
205
|
+
environment_instructions: summarizeEnvironmentInstructions(
|
|
206
|
+
environmentInstructions,
|
|
207
|
+
),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const candidate = detection.candidates[0] ?? null;
|
|
212
|
+
const backendExists = detection.services.backend.length > 0;
|
|
213
|
+
const detectedServices = buildDetectedServices(detection);
|
|
214
|
+
const primaryFramework =
|
|
215
|
+
candidate?.framework ??
|
|
216
|
+
detection.services.frontend[0]?.framework ??
|
|
217
|
+
null;
|
|
218
|
+
const detectedFrameworks = [
|
|
219
|
+
...(candidate ? [candidate.framework] : []),
|
|
220
|
+
...detectedServices.map((target) => target.framework),
|
|
221
|
+
];
|
|
222
|
+
const frontendRuntimeEnvNames = requiredFrontendEnvNames(detectedServices);
|
|
223
|
+
const backendRuntimeEnvNames = backendExists ? requiredBackendEnvNames() : [];
|
|
224
|
+
const serviceRuntimeEnvNames = [
|
|
225
|
+
...new Set([...frontendRuntimeEnvNames, ...backendRuntimeEnvNames]),
|
|
226
|
+
];
|
|
227
|
+
const finalSetupCheckCommand = candidate
|
|
228
|
+
? cluebaseCliCommand(
|
|
229
|
+
`setup-check --framework ${candidate.framework} ` +
|
|
230
|
+
`--backend-root-path ${candidate.backend_root_path} --repo . --require-sdk-lifecycle`,
|
|
231
|
+
)
|
|
232
|
+
: cluebaseCliCommand("setup-check --repo . --require-sdk-lifecycle");
|
|
233
|
+
|
|
234
|
+
const manifest = {
|
|
235
|
+
status: "ready_for_ai",
|
|
236
|
+
detected: {
|
|
237
|
+
framework: primaryFramework,
|
|
238
|
+
...(candidate
|
|
239
|
+
? {
|
|
240
|
+
backend_root_path: candidate.backend_root_path,
|
|
241
|
+
service_key: candidate.service_key,
|
|
242
|
+
}
|
|
243
|
+
: {}),
|
|
244
|
+
},
|
|
245
|
+
documentation: buildSetupDocumentationContract({
|
|
246
|
+
documentsUrl: setupContext.documents_url,
|
|
247
|
+
framework: primaryFramework,
|
|
248
|
+
frameworks: detectedFrameworks,
|
|
249
|
+
}),
|
|
250
|
+
cli_invocation: CLUEBASE_CLI_INVOCATION_CONTRACT,
|
|
251
|
+
cluebase_context: {
|
|
252
|
+
project_key: setupContext.project_key,
|
|
253
|
+
cluebase_api_base_url: setupContext.cluebase_api_base_url,
|
|
254
|
+
api_connectivity_contract: API_CONNECTIVITY_CONTRACT,
|
|
255
|
+
ingest_endpoints: setupContext.cluebase_api_base_url
|
|
256
|
+
? {
|
|
257
|
+
browser: buildEndpoint(
|
|
258
|
+
setupContext.cluebase_api_base_url,
|
|
259
|
+
BROWSER_INGEST_PATH,
|
|
260
|
+
),
|
|
261
|
+
backend: buildEndpoint(
|
|
262
|
+
setupContext.cluebase_api_base_url,
|
|
263
|
+
BACKEND_INGEST_PATH,
|
|
264
|
+
),
|
|
265
|
+
}
|
|
266
|
+
: null,
|
|
267
|
+
},
|
|
268
|
+
detected_services: detectedServices,
|
|
269
|
+
lifecycle_verification: {
|
|
270
|
+
owner: "user",
|
|
271
|
+
rule: "After setup-doctor passes, run the local customer frontend/backend, perform the real product flow, and verify the resulting Cluebase logs in the setup screen or published batch evidence.",
|
|
272
|
+
},
|
|
273
|
+
artifacts: {
|
|
274
|
+
setup_manifest_path: setupManifestPath,
|
|
275
|
+
},
|
|
276
|
+
machine_owned_artifacts: [setupManifestPath],
|
|
277
|
+
ai_must_not_edit: [],
|
|
278
|
+
ai_owned_workstreams: ["sdk_lifecycle_placement"],
|
|
279
|
+
ai_implementation_scope: {
|
|
280
|
+
rule: "AI implementation is limited to placing cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset in existing lifecycle boundaries plus the minimal SDK wiring required for those calls.",
|
|
281
|
+
lifecycle_apis: [
|
|
282
|
+
"cluebase.init",
|
|
283
|
+
"cluebase.identify",
|
|
284
|
+
"cluebase.group",
|
|
285
|
+
"cluebase.reset",
|
|
286
|
+
],
|
|
287
|
+
out_of_scope_by_default: ["cluebase.track"],
|
|
288
|
+
identity_boundary_contract: IDENTITY_BOUNDARY_CONTRACT,
|
|
289
|
+
instrumentation_contract: OTEL_FIRST_SETUP_CONTRACT,
|
|
290
|
+
},
|
|
291
|
+
required_final_check: {
|
|
292
|
+
command: finalSetupCheckCommand,
|
|
293
|
+
},
|
|
294
|
+
required_final_verification: [
|
|
295
|
+
{
|
|
296
|
+
id: "static_setup_check",
|
|
297
|
+
command: finalSetupCheckCommand,
|
|
298
|
+
completion_meaning:
|
|
299
|
+
"static_passed_only_dependency_install_import_app_startup_and_event_delivery_still_required",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "sdk_dependency_install_and_import",
|
|
303
|
+
command:
|
|
304
|
+
"run the repository package-manager install plus frontend/backend SDK import checks in the target environments",
|
|
305
|
+
completion_meaning:
|
|
306
|
+
"required before claiming SDK lifecycle setup is complete",
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: "app_startup",
|
|
310
|
+
command:
|
|
311
|
+
"start the affected frontend/backend services and verify their configured local URLs respond",
|
|
312
|
+
completion_meaning:
|
|
313
|
+
"required before real product-flow event delivery can be trusted",
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "local_api_connectivity_preflight",
|
|
317
|
+
command: cluebaseCliCommand("setup-doctor --local"),
|
|
318
|
+
completion_meaning:
|
|
319
|
+
"required before real product-flow verification; verifies direct browser-token issuance, canonical browser observation-source-event batch ingest, and backend ingest connectivity when local services and required env are available",
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "local_event_delivery",
|
|
323
|
+
command:
|
|
324
|
+
"user runs the local customer frontend/backend, performs the real product flow, and checks Cluebase setup logs or published batch evidence",
|
|
325
|
+
completion_meaning:
|
|
326
|
+
"requires user-operated local services plus expected lifecycle event delivery; AI agents must report user_verification_pending when user evidence is not provided",
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
// server / SDK は project_key の prefix (pk_dev_ / pk_prod_) から
|
|
330
|
+
// environment を derive する。
|
|
331
|
+
required_env_names: [
|
|
332
|
+
...new Set([
|
|
333
|
+
...serviceRuntimeEnvNames,
|
|
334
|
+
...(backendExists ? ["CLUEBASE_API_KEY"] : []),
|
|
335
|
+
"CLUEBASE_PROJECT_KEY",
|
|
336
|
+
"CLUEBASE_API_BASE_URL",
|
|
337
|
+
]),
|
|
338
|
+
],
|
|
339
|
+
required_env_scopes: {
|
|
340
|
+
service_runtime: serviceRuntimeEnvNames,
|
|
341
|
+
frontend_runtime: frontendRuntimeEnvNames,
|
|
342
|
+
backend_runtime: backendRuntimeEnvNames,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
const environmentInstructions = buildEnvironmentInstructions({
|
|
346
|
+
manifest,
|
|
347
|
+
setupContext,
|
|
348
|
+
});
|
|
349
|
+
await writeJson({
|
|
350
|
+
repoRoot: resolvedRepoRoot,
|
|
351
|
+
path: setupManifestPath,
|
|
352
|
+
value: manifest,
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
...manifest,
|
|
356
|
+
environment_instructions: summarizeEnvironmentInstructions(
|
|
357
|
+
environmentInstructions,
|
|
358
|
+
),
|
|
359
|
+
};
|
|
360
|
+
};
|