@clue-ai/cli 0.0.6 → 0.0.7

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 CHANGED
@@ -16,7 +16,7 @@ clue-ai lifecycle-apply --plan clue-lifecycle-plan.json --repo .
16
16
  clue-ai setup-check --framework fastapi --backend-root-path backend --repo . --target codex --require-sdk-lifecycle
17
17
  clue-ai setup-watch --project-key <key> --environment dev --clue-api-base-url <clue-api-base-url> --watch-targets frontend:web[init,identify,set-account,event-sent]=<frontend-url>,backend:api[init,identify,set-account,logout,event-sent]=<backend-url>
18
18
  clue-ai init --request clue-init-request.json --repo .
19
- clue-ai semantic-ci --request clue-semantic-request.json --repo .
19
+ clue-ai semantic-gen --request clue-semantic-request.json --repo .
20
20
  ```
21
21
 
22
22
  `/clue-init` is the standard user-facing command for Codex and Claude Code
@@ -25,7 +25,7 @@ tool request/report contract as `clue-ai init`.
25
25
 
26
26
  `clue-ai semantic-workflow` mechanically writes the GitHub Actions workflow that
27
27
  generates `.clue/semantic-request.runtime.json` only inside CI and then runs
28
- `clue-ai semantic-ci`. Do not commit runtime semantic request files to client
28
+ `clue-ai semantic-gen`. Do not commit runtime semantic request files to client
29
29
  repositories.
30
30
 
31
31
  `clue-ai setup` installs the target AI-tool skills and, when backend routes are
@@ -67,21 +67,18 @@ detected service.
67
67
  ## Required Environment
68
68
 
69
69
  - `CLUE_API_KEY`
70
- - `CLUE_AI_PROVIDER`
71
70
  - `CLUE_AI_PROVIDER_API_KEY`
71
+ - `CLUE_AI_PROVIDER`
72
72
  - `CLUE_AI_MODEL`
73
73
 
74
- `CLUE_AI_PROVIDER` selects the AI provider used by semantic snapshot generation and
75
- lifecycle implementation planning. Use `openai` for OpenAI-compatible Chat
76
- Completions providers and `anthropic` for Claude Messages API. `CLUE_AI_PROVIDER_API_KEY`
77
- must match the selected provider. `CLUE_AI_MODEL` is the selected provider's model
78
- name. Keep it in environment configuration so provider/model upgrades do not
79
- require CLI code changes.
74
+ `clue-ai semantic-gen` runs semantic AI inside the customer repository CI with
75
+ the customer-configured AI provider key. Clue receives only the final
76
+ privacy-safe semantic snapshot.
80
77
 
81
78
  ## Boundaries
82
79
 
83
80
  - The tool may read allowed source paths in the client repository.
84
81
  - The tool must not read `.env`, secrets, logs, dumps, build output, or vendor directories.
85
- - Raw source code is sent only from the client CI runner to the configured AI provider.
86
- - Raw source code, raw SQL, bind values, function names, class names, file paths, and import graphs are not sent to Clue by default.
82
+ - Semantic generation calls the configured AI provider from customer CI.
83
+ - Raw source code, raw SQL, bind values, function names, class names, file paths, and import graphs are not sent to Clue.
87
84
  - `clue-layer.yaml` and `clue-semantic.yaml` are not committed to client repositories.
package/bin/clue-cli.mjs CHANGED
@@ -15,6 +15,10 @@ import { runSetupCheck } from "../src/setup-check.mjs";
15
15
  import { runSetupDetect } from "../src/setup-detect.mjs";
16
16
  import { runSetupPrepare } from "../src/setup-prepare.mjs";
17
17
  import { installSetupSkills } from "../src/setup-tool.mjs";
18
+ import {
19
+ semanticAgentSkillBundle,
20
+ validateSemanticAgentSkillBundle,
21
+ } from "../src/semantic-agent-runner.mjs";
18
22
 
19
23
  const parseArgs = (argv) => {
20
24
  const [command = "help", ...tokens] = argv;
@@ -36,6 +40,15 @@ const parseArgs = (argv) => {
36
40
 
37
41
  const readJson = async (path) => JSON.parse(await readFile(path, "utf8"));
38
42
 
43
+ const readTextIfExists = async (path) => {
44
+ try {
45
+ return await readFile(path, "utf8");
46
+ } catch (error) {
47
+ if (error?.code === "ENOENT") return "";
48
+ throw error;
49
+ }
50
+ };
51
+
39
52
  const writeJsonIfRequested = async ({ repoRoot, outputPath, value }) => {
40
53
  if (typeof outputPath !== "string") return false;
41
54
  const resolvedRepoRoot = resolve(repoRoot);
@@ -219,6 +232,73 @@ const resolveTargetUrlFromEnv = ({ target, env }) => {
219
232
  const shouldAskQuestions = ({ flags }) =>
220
233
  !flags.has("yes") && process.stdin.isTTY && process.stdout.isTTY;
221
234
 
235
+ const isGitignoreEntryPresent = (content, entry) =>
236
+ content
237
+ .split(/\r?\n/)
238
+ .map((line) => line.trim())
239
+ .includes(entry);
240
+
241
+ const appendGitignoreEntry = async ({ repoRoot, entry }) => {
242
+ const gitignorePath = resolve(repoRoot, ".gitignore");
243
+ const current = await readTextIfExists(gitignorePath);
244
+ if (isGitignoreEntryPresent(current, entry)) {
245
+ return "already_ignored";
246
+ }
247
+ const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
248
+ await writeFile(gitignorePath, `${current}${prefix}${entry}\n`, "utf8");
249
+ return "added";
250
+ };
251
+
252
+ const maybeProtectEnvironmentGuide = async ({
253
+ repoRoot,
254
+ envFilePath,
255
+ flags,
256
+ }) => {
257
+ if (typeof envFilePath !== "string" || !envFilePath.trim()) {
258
+ return null;
259
+ }
260
+ const gitignorePath = resolve(repoRoot, ".gitignore");
261
+ const current = await readTextIfExists(gitignorePath);
262
+ if (isGitignoreEntryPresent(current, envFilePath)) {
263
+ return {
264
+ env_file_path: envFilePath,
265
+ gitignore_status: "already_ignored",
266
+ };
267
+ }
268
+ if (flags.has("yes")) {
269
+ await appendGitignoreEntry({ repoRoot, entry: envFilePath });
270
+ return {
271
+ env_file_path: envFilePath,
272
+ gitignore_status: "added",
273
+ };
274
+ }
275
+ if (shouldAskQuestions({ flags })) {
276
+ const rl = createInterface({
277
+ input: process.stdin,
278
+ output: process.stdout,
279
+ });
280
+ try {
281
+ const answer = await rl.question(
282
+ `${envFilePath} は秘密情報を含みます。.gitignore に追加しますか? [Y/n] `,
283
+ );
284
+ if (!answer.trim() || /^y(es)?$/i.test(answer.trim())) {
285
+ await appendGitignoreEntry({ repoRoot, entry: envFilePath });
286
+ return {
287
+ env_file_path: envFilePath,
288
+ gitignore_status: "added",
289
+ };
290
+ }
291
+ } finally {
292
+ rl.close();
293
+ }
294
+ }
295
+ return {
296
+ env_file_path: envFilePath,
297
+ gitignore_status: "not_ignored",
298
+ warning: `${envFilePath} contains secrets. Do not commit it.`,
299
+ };
300
+ };
301
+
222
302
  const confirmTargetUrls = async ({ flags, watchTargets, env }) => {
223
303
  const initialTargets = watchTargets.map((target) => ({
224
304
  ...target,
@@ -670,40 +750,28 @@ const usage = () =>
670
750
  " clue-ai setup --clue-api-key <key> --clue-api-base-url <url> --project-key <key> --environment dev",
671
751
  " clue-ai setup-detect --repo .",
672
752
  " clue-ai semantic-inventory --framework fastapi --backend-root-path backend --repo . --output .clue/semantic-routes.json",
753
+ " clue-ai semantic-agent-skills --output .clue/semantic-agent-skills.json",
673
754
  " clue-ai semantic-workflow --framework fastapi --backend-root-path backend --repo .",
674
755
  " clue-ai lifecycle-apply --plan clue-lifecycle-plan.json --repo .",
675
756
  " clue-ai setup-check --framework fastapi --backend-root-path backend --repo .",
676
757
  " clue-ai setup-watch --local",
677
758
  " clue-ai setup-watch --project-key <key> --environment dev --clue-api-base-url <clue-api-base-url> --watch-targets frontend:web[init,identify,set-account,logout,event-sent]=<frontend-url>,backend:api[init,identify,set-account,logout,event-sent]=<backend-url>",
678
759
  " clue-ai init --request clue-init-request.json --repo .",
679
- " clue-ai semantic-ci --request clue-semantic-request.json --repo . [--previous-snapshot-file previous.json]",
680
- " clue-ai semantic-ci --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .",
760
+ " clue-ai semantic-gen --request clue-semantic-request.json --repo . [--previous-snapshot-file previous.json]",
761
+ " clue-ai semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .",
681
762
  ].join("\n");
682
763
 
683
764
  const renderEnvironmentInstructions = (instructions) => {
684
765
  if (!instructions || instructions.status !== "ready") {
685
766
  return "";
686
767
  }
687
- const lines = ["", instructions.message, ""];
688
- for (const block of instructions.service_env_blocks) {
689
- lines.push(
690
- `[${block.kind}] ${block.root_path} (${block.env_file_candidates.join(" or ")})`,
691
- block.env_block,
692
- "",
693
- );
694
- }
695
- lines.push(
696
- "GitHub Secrets",
697
- ...instructions.ci_github.secrets.map(
698
- (entry) => `${entry.name}=${entry.value}`,
699
- ),
768
+ const lines = [
700
769
  "",
701
- "GitHub Variables",
702
- ...instructions.ci_github.variables.map(
703
- (entry) => `${entry.name}=${entry.value}`,
704
- ),
770
+ `環境変数の設定内容を ${instructions.env_file_path} に書き出しました。`,
771
+ `Step2で ${instructions.env_file_path} を開き、各サービスの env と GitHub Secrets/Variables に反映してください。`,
772
+ `${instructions.env_file_path} は秘密情報を含むため、コミットしないでください。`,
705
773
  "",
706
- );
774
+ ];
707
775
  return `${lines.join("\n")}\n`;
708
776
  };
709
777
 
@@ -754,6 +822,14 @@ const main = async () => {
754
822
  environment: flags.get("environment"),
755
823
  },
756
824
  });
825
+ const environmentFileProtection = await maybeProtectEnvironmentGuide({
826
+ repoRoot,
827
+ envFilePath: preparation.environment_instructions?.env_file_path,
828
+ flags,
829
+ });
830
+ if (environmentFileProtection) {
831
+ preparation.environment_file_protection = environmentFileProtection;
832
+ }
757
833
  const environmentInstructions = renderEnvironmentInstructions(
758
834
  preparation.environment_instructions,
759
835
  );
@@ -818,6 +894,17 @@ const main = async () => {
818
894
  return;
819
895
  }
820
896
 
897
+ if (command === "semantic-agent-skills") {
898
+ const bundle = semanticAgentSkillBundle();
899
+ await writeJsonIfRequested({
900
+ repoRoot,
901
+ outputPath: flags.get("output"),
902
+ value: bundle,
903
+ });
904
+ process.stdout.write(`${JSON.stringify(bundle, null, 2)}\n`);
905
+ return;
906
+ }
907
+
821
908
  if (command === "lifecycle-apply") {
822
909
  const planPath = flags.get("plan");
823
910
  if (typeof planPath !== "string") {
@@ -874,18 +961,24 @@ const main = async () => {
874
961
  return;
875
962
  }
876
963
 
877
- if (command === "semantic-ci") {
964
+ if (command === "semantic-gen") {
878
965
  const previousSnapshotPath =
879
966
  flags.get("previous-snapshot") ?? flags.get("previous-snapshot-file");
880
967
  const previousSnapshot =
881
968
  typeof previousSnapshotPath === "string"
882
969
  ? await readJson(resolve(previousSnapshotPath))
883
970
  : undefined;
971
+ const agentSkillsPath = flags.get("agent-skills-file");
972
+ const agentSkills =
973
+ typeof agentSkillsPath === "string"
974
+ ? validateSemanticAgentSkillBundle(await readJson(resolve(agentSkillsPath)))
975
+ : undefined;
884
976
  const result = await runSemanticCi({
885
977
  repoRoot,
886
978
  request,
887
979
  env: process.env,
888
980
  previousSnapshot,
981
+ agentSkills,
889
982
  });
890
983
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
891
984
  return;
@@ -14,13 +14,14 @@ Required fields:
14
14
  Required environment:
15
15
 
16
16
  - `CLUE_API_KEY`
17
- - `AI_PROVIDER`
18
- - `AI_PROVIDER_API_KEY`
17
+ - `CLUE_AI_PROVIDER_API_KEY`
18
+ - `CLUE_AI_PROVIDER`
19
19
  - `CLUE_AI_MODEL`
20
20
 
21
- `AI_PROVIDER` must be `openai` or `anthropic`. `AI_PROVIDER_API_KEY` must be the
22
- API key for the selected provider. `CLUE_AI_MODEL` must be the model name for the
23
- selected provider.
21
+ Semantic snapshot generation runs inside the customer repository CI with the
22
+ customer-configured AI provider key. Clue must receive only the final
23
+ privacy-safe semantic snapshot, not raw source code, prompts, completions, or AI
24
+ provider keys.
24
25
 
25
26
  Behavior:
26
27
 
@@ -14,13 +14,14 @@ Required fields:
14
14
  Required environment:
15
15
 
16
16
  - `CLUE_API_KEY`
17
- - `AI_PROVIDER`
18
- - `AI_PROVIDER_API_KEY`
17
+ - `CLUE_AI_PROVIDER_API_KEY`
18
+ - `CLUE_AI_PROVIDER`
19
19
  - `CLUE_AI_MODEL`
20
20
 
21
- `AI_PROVIDER` must be `openai` or `anthropic`. `AI_PROVIDER_API_KEY` must be the
22
- API key for the selected provider. `CLUE_AI_MODEL` must be the model name for the
23
- selected provider.
21
+ Semantic snapshot generation runs inside the customer repository CI with the
22
+ customer-configured AI provider key. Clue must receive only the final
23
+ privacy-safe semantic snapshot, not raw source code, prompts, completions, or AI
24
+ provider keys.
24
25
 
25
26
  Behavior:
26
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clue-ai/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "clue-ai": "bin/clue-cli.mjs"
@@ -15,8 +15,7 @@ const normalizeProvider = (value) => {
15
15
  const trimTrailingSlash = (value) => String(value).replace(/\/+$/, "");
16
16
 
17
17
  const providerBaseUrl = ({ provider, env = {}, request = {} }) => {
18
- const explicit =
19
- request.ai_provider_base_url || env.CLUE_AI_PROVIDER_BASE_URL || null;
18
+ const explicit = env.CLUE_AI_PROVIDER_BASE_URL || null;
20
19
  if (explicit) {
21
20
  return trimTrailingSlash(explicit);
22
21
  }
@@ -106,6 +105,7 @@ export const callJsonAiProvider = async ({
106
105
  body: JSON.stringify({
107
106
  model: config.model,
108
107
  max_tokens: 4096,
108
+ temperature: 0,
109
109
  system,
110
110
  messages: [{ role: "user", content: user }],
111
111
  tools: [
@@ -129,6 +129,7 @@ export const callJsonAiProvider = async ({
129
129
  },
130
130
  body: JSON.stringify({
131
131
  model: config.model,
132
+ temperature: 0,
132
133
  messages: [
133
134
  { role: "system", content: system },
134
135
  { role: "user", content: user },
@@ -4,6 +4,7 @@ export const REQUIRED_SECRET_NAMES = [
4
4
  "CLUE_API_KEY",
5
5
  "CLUE_AI_PROVIDER_API_KEY",
6
6
  ];
7
+ export const REQUIRED_VARIABLE_NAMES = ["CLUE_AI_PROVIDER", "CLUE_AI_MODEL"];
7
8
 
8
9
  export const CLUE_INIT_COMMAND_FIELDS = [
9
10
  "project_key",
@@ -22,6 +23,7 @@ export const CLUE_INIT_COMMAND = {
22
23
  report_contract: "clueInitToolReportSchema",
23
24
  required_fields: CLUE_INIT_COMMAND_FIELDS,
24
25
  required_secret_names: REQUIRED_SECRET_NAMES,
26
+ required_variable_names: REQUIRED_VARIABLE_NAMES,
25
27
  };
26
28
 
27
29
  export const commandSpecs = [CLUE_INIT_COMMAND];
package/src/contracts.mjs CHANGED
@@ -2,7 +2,6 @@ import { createRequire } from "node:module";
2
2
 
3
3
  const require = createRequire(import.meta.url);
4
4
  const schemaPackage = require("./public-schema.cjs");
5
-
6
5
  const {
7
6
  clueInitToolRequestSchema,
8
7
  clueInitToolReportSchema,
@@ -63,6 +62,7 @@ export const validateInitRequest = (input) => {
63
62
  export const buildInitReport = ({
64
63
  request,
65
64
  lifecycleInsertions,
65
+ requiredVariables = [],
66
66
  warnings = [],
67
67
  }) =>
68
68
  parseWithSchema(
@@ -72,6 +72,7 @@ export const buildInitReport = ({
72
72
  ci_workflow_path: request.ci_workflow_path,
73
73
  ci_workflow_added: true,
74
74
  required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
75
+ required_variables: requiredVariables,
75
76
  lifecycle_insertions: lifecycleInsertions,
76
77
  semantic_generation_timing: "after_merge_ci",
77
78
  semantic_preview_generated: false,
@@ -89,7 +90,6 @@ export const validateSemanticCiRequest = (input) => ({
89
90
  project_key: nonEmpty(input.project_key, "project_key"),
90
91
  environment: nonEmpty(input.environment, "environment"),
91
92
  service_key: nonEmpty(input.service_key, "service_key"),
92
- ai_provider: optionalNonEmpty(input.ai_provider) ?? "openai",
93
93
  repository: {
94
94
  provider: nonEmpty(input.repository?.provider, "repository.provider"),
95
95
  repository_id: nonEmpty(
@@ -139,8 +139,9 @@ export const validateSemanticCiRequest = (input) => ({
139
139
  "excluded_source_paths",
140
140
  ),
141
141
  clue_api_base_url: nonEmpty(input.clue_api_base_url, "clue_api_base_url"),
142
- ai_provider_base_url: optionalNonEmpty(input.ai_provider_base_url) ?? null,
143
- ai_model: nonEmpty(input.ai_model, "ai_model"),
142
+ ai_provider_base_url: null,
143
+ ai_provider: optionalNonEmpty(input.ai_provider),
144
+ ai_model: optionalNonEmpty(input.ai_model),
144
145
  });
145
146
 
146
147
  export const buildOperationSourceKey = (method, pathTemplate) =>
package/src/init-tool.mjs CHANGED
@@ -110,7 +110,6 @@ const workflowRequestPayload = (request) =>
110
110
  },
111
111
  service: {
112
112
  service_key: request.service_key,
113
- root_path: request.allowed_source_paths[0],
114
113
  framework: request.framework,
115
114
  language: "python",
116
115
  },
@@ -118,8 +117,6 @@ const workflowRequestPayload = (request) =>
118
117
  excluded_source_paths: request.excluded_source_paths,
119
118
  clue_api_base_url:
120
119
  request.clue_api_base_url ?? "${{ vars.CLUE_API_BASE_URL }}",
121
- ai_provider: "${{ vars.CLUE_AI_PROVIDER }}",
122
- ai_model: "${{ vars.CLUE_AI_MODEL }}",
123
120
  },
124
121
  null,
125
122
  10,
@@ -157,10 +154,11 @@ jobs:
157
154
  CLUE_API_KEY: \${{ secrets.CLUE_API_KEY }}
158
155
  CLUE_AI_PROVIDER_API_KEY: \${{ secrets.CLUE_AI_PROVIDER_API_KEY }}
159
156
  CLUE_AI_PROVIDER: \${{ vars.CLUE_AI_PROVIDER }}
157
+ CLUE_AI_MODEL: \${{ vars.CLUE_AI_MODEL }}
160
158
  CLUE_SEMANTIC_REQUEST_JSON: |
161
159
  ${indentMultiline(workflowRequestPayload(request), 12)}
162
160
  run: |
163
- npx @clue-ai/cli semantic-ci --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .
161
+ npx @clue-ai/cli semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .
164
162
  `;
165
163
 
166
164
  export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
@@ -173,6 +171,7 @@ export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
173
171
  required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
174
172
  required_variables: [
175
173
  "CLUE_AI_PROVIDER",
174
+ "CLUE_AI_MODEL",
176
175
  ...(usesGithubVariable(
177
176
  request.project_key ?? "${{ vars.CLUE_PROJECT_KEY }}",
178
177
  )
@@ -188,7 +187,6 @@ export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
188
187
  )
189
188
  ? ["CLUE_API_BASE_URL"]
190
189
  : []),
191
- "CLUE_AI_MODEL",
192
190
  ],
193
191
  runtime_request_committed: false,
194
192
  semantic_generation_timing: "after_merge_ci",
@@ -204,7 +202,7 @@ export const runInitTool = async ({
204
202
  lifecyclePlanner = planLifecycleInsertions,
205
203
  }) => {
206
204
  const request = validateInitRequest(rawRequest);
207
- await writeSemanticWorkflow({ repoRoot, request });
205
+ const workflowResult = await writeSemanticWorkflow({ repoRoot, request });
208
206
  const lifecyclePlan = await lifecyclePlanner({ repoRoot, request, env });
209
207
  const lifecycleResult = await applyLifecyclePlan({
210
208
  repoRoot,
@@ -214,6 +212,7 @@ export const runInitTool = async ({
214
212
  return buildInitReport({
215
213
  request,
216
214
  lifecycleInsertions: lifecycleResult.lifecycleInsertions,
215
+ requiredVariables: workflowResult.required_variables,
217
216
  warnings: [...lifecycleResult.warnings],
218
217
  });
219
218
  };
@@ -22,6 +22,29 @@ const clueInitToolRequiredSecretsSchema = zod_1.z
22
22
  }
23
23
  })
24
24
  .transform((value) => Array.from(new Set(value)));
25
+ const clueInitToolRequiredVariableSchema = zod_1.z.enum([
26
+ "CLUE_AI_PROVIDER",
27
+ "CLUE_AI_MODEL",
28
+ "CLUE_PROJECT_KEY",
29
+ "CLUE_ENVIRONMENT",
30
+ "CLUE_API_BASE_URL",
31
+ ]);
32
+ const clueInitToolRequiredVariablesSchema = zod_1.z
33
+ .array(clueInitToolRequiredVariableSchema)
34
+ .superRefine((value, context) => {
35
+ for (const variable of [
36
+ "CLUE_AI_PROVIDER",
37
+ "CLUE_AI_MODEL",
38
+ ]) {
39
+ if (!value.includes(variable)) {
40
+ context.addIssue({
41
+ code: zod_1.z.ZodIssueCode.custom,
42
+ message: `${variable} is required`,
43
+ });
44
+ }
45
+ }
46
+ })
47
+ .transform((value) => Array.from(new Set(value)));
25
48
  const clueInitToolRequestSchema = zod_1.z.object({
26
49
  target_tool: clueInitToolTargetSchema,
27
50
  project_key: nonEmptyStringSchema,
@@ -48,6 +71,7 @@ const clueInitToolReportSchema = zod_1.z.object({
48
71
  ci_workflow_path: nonEmptyStringSchema,
49
72
  ci_workflow_added: zod_1.z.literal(true),
50
73
  required_secrets: clueInitToolRequiredSecretsSchema,
74
+ required_variables: clueInitToolRequiredVariablesSchema,
51
75
  lifecycle_insertions: zod_1.z.array(clueInitToolLifecycleInsertionSchema),
52
76
  semantic_generation_timing: zod_1.z.literal("after_merge_ci"),
53
77
  semantic_preview_generated: zod_1.z.literal(false),
@@ -211,6 +235,27 @@ const semanticSnapshotAnalysisSummarySchema = zod_1.z
211
235
  .default(0),
212
236
  })
213
237
  .strict();
238
+ const semanticSnapshotGenerationContractSchema = zod_1.z
239
+ .object({
240
+ schema_version: zod_1.z.number().int().positive(),
241
+ analyzer_version: nonEmptyStringSchema,
242
+ route_prompt_contract_version: nonEmptyStringSchema,
243
+ reuse_prompt_contract_version: nonEmptyStringSchema,
244
+ selector_prompt_contract_version: nonEmptyStringSchema,
245
+ privacy_sanitizer_version: nonEmptyStringSchema,
246
+ })
247
+ .strict();
248
+ const semanticSnapshotAiRuntimeSchema = zod_1.z
249
+ .object({
250
+ mode: zod_1.z.enum(["client_ci"]),
251
+ provider: nonEmptyStringSchema,
252
+ model: nonEmptyStringSchema,
253
+ model_source: zod_1.z.literal("customer_ci_secret"),
254
+ runner_version: nonEmptyStringSchema,
255
+ role_ids: zod_1.z.array(nonEmptyStringSchema).min(1),
256
+ temperature: zod_1.z.literal(0),
257
+ })
258
+ .strict();
214
259
  const semanticCandidateSchema = zod_1.z
215
260
  .object({
216
261
  label: nonEmptyStringSchema,
@@ -460,6 +505,8 @@ const semanticSnapshotRequestSchema = zod_1.z
460
505
  idempotency_key: nonEmptyStringSchema,
461
506
  schema_version: zod_1.z.number().int().positive().default(1),
462
507
  semantic_snapshot_version: nonEmptyStringSchema.optional(),
508
+ generation_contract: semanticSnapshotGenerationContractSchema.optional(),
509
+ ai_runtime: semanticSnapshotAiRuntimeSchema.optional(),
463
510
  generated_at: zod_1.z.string().datetime({ offset: true }),
464
511
  repository: semanticSnapshotRepositorySchema,
465
512
  service: semanticSnapshotServiceSchema,