@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.
@@ -97,6 +97,9 @@ const PURPOSE_CHANGE_STATES = new Set([
97
97
  "new_route",
98
98
  ]);
99
99
 
100
+ const shouldPurposeRecheckAllRoutes = (env) =>
101
+ env.CLUE_SEMANTIC_PURPOSE_RECHECK_ALL === "1";
102
+
100
103
  const semanticSnapshotHashScope = (request) =>
101
104
  [
102
105
  "clue_tools_semantic_snapshot",
@@ -641,6 +644,9 @@ const generateAiRoutesPerRoute = async ({
641
644
  );
642
645
  }
643
646
  } catch (error) {
647
+ if (error instanceof SyntaxError) {
648
+ throw error;
649
+ }
644
650
  aiRoutes.set(
645
651
  route.operation_source_key,
646
652
  unavailableAiRoute(
@@ -2063,31 +2069,37 @@ const buildSnapshot = ({
2063
2069
  };
2064
2070
  }
2065
2071
 
2072
+ const effectivePlan =
2073
+ !aiRoute?.semantics && plan.active_semantic_source === "new_confirmed"
2074
+ ? unconfirmedFallbackPlan({ plan, reason: unavailableReason })
2075
+ : plan;
2076
+
2066
2077
  return withStabilityMetadata({
2067
2078
  currentRoute: route,
2068
- plan,
2079
+ plan: effectivePlan,
2069
2080
  route: {
2070
- ...baseRoute,
2071
- operation_effects: assignmentCollections.operationEffects,
2072
- unresolved_operation_effects:
2073
- assignmentCollections.unresolvedOperationEffects,
2074
- source_evidence_refs: routeEvidenceRefs.map((entry) => entry.id),
2075
- route_input_hash: plan.route_input_hash,
2076
- previous_route_input_hash: plan.previous_route_input_hash,
2077
- previous_route_semantic_hash: plan.previous_route_semantic_hash,
2078
- semantic_origin:
2079
- plan.origin === "changed_route_needs_review" && aiRoute?.semantics
2080
- ? "changed_route_needs_review"
2081
- : plan.origin,
2082
- semantic_change_reason: plan.semantic_change_reason,
2083
- previous_semantic_snapshot_version:
2084
- plan.previous_route?.semantic_snapshot_version,
2085
- route_semantic_hash: routeSemanticHash({
2086
2081
  ...baseRoute,
2087
2082
  operation_effects: assignmentCollections.operationEffects,
2088
2083
  unresolved_operation_effects:
2089
2084
  assignmentCollections.unresolvedOperationEffects,
2090
- }),
2085
+ source_evidence_refs: routeEvidenceRefs.map((entry) => entry.id),
2086
+ route_input_hash: effectivePlan.route_input_hash,
2087
+ previous_route_input_hash: effectivePlan.previous_route_input_hash,
2088
+ previous_route_semantic_hash: effectivePlan.previous_route_semantic_hash,
2089
+ semantic_origin:
2090
+ effectivePlan.origin === "changed_route_needs_review" &&
2091
+ aiRoute?.semantics
2092
+ ? "changed_route_needs_review"
2093
+ : effectivePlan.origin,
2094
+ semantic_change_reason: effectivePlan.semantic_change_reason,
2095
+ previous_semantic_snapshot_version:
2096
+ effectivePlan.previous_route?.semantic_snapshot_version,
2097
+ route_semantic_hash: routeSemanticHash({
2098
+ ...baseRoute,
2099
+ operation_effects: assignmentCollections.operationEffects,
2100
+ unresolved_operation_effects:
2101
+ assignmentCollections.unresolvedOperationEffects,
2102
+ }),
2091
2103
  },
2092
2104
  });
2093
2105
  });
@@ -2460,8 +2472,16 @@ const routeSourceForPolicy = (route) =>
2460
2472
  const classifyClueInfrastructureRoute = (route) => {
2461
2473
  const source = routeSourceForPolicy(route);
2462
2474
  const reservedPath = isReservedClueBrowserTokenPath(route.path_template);
2463
- const proxiesToClueBrowserTokenApi =
2475
+ const directlyCallsClueBrowserTokenApi =
2464
2476
  /\/(?:api\/v[0-9]+\/)?ingest\/browser-tokens\b/i.test(source);
2477
+ const usesClueBrowserTokenRequestContract =
2478
+ /\bCLUE_API_BASE_URL\b/.test(source) &&
2479
+ /\bCLUE_PROJECT_KEY\b/.test(source) &&
2480
+ /\bCLUE_ENVIRONMENT\b/.test(source) &&
2481
+ /\bserviceKey\b/.test(source) &&
2482
+ /\b(?:client|httpx)\.post\b/.test(source);
2483
+ const proxiesToClueBrowserTokenApi =
2484
+ directlyCallsClueBrowserTokenApi || usesClueBrowserTokenRequestContract;
2465
2485
  const usesServerSideClueApiKey = /\bCLUE_API_KEY\b|x-clue-api-key/i.test(
2466
2486
  source,
2467
2487
  );
@@ -2478,6 +2498,8 @@ const classifyClueInfrastructureRoute = (route) => {
2478
2498
  evidence: {
2479
2499
  reserved_clue_path: true,
2480
2500
  proxies_to_clue_browser_token_api: true,
2501
+ uses_clue_browser_token_request_contract:
2502
+ usesClueBrowserTokenRequestContract,
2481
2503
  uses_server_side_clue_api_key: true,
2482
2504
  },
2483
2505
  };
@@ -2488,6 +2510,8 @@ const classifyClueInfrastructureRoute = (route) => {
2488
2510
  evidence: {
2489
2511
  reserved_clue_path: reservedPath,
2490
2512
  proxies_to_clue_browser_token_api: proxiesToClueBrowserTokenApi,
2513
+ uses_clue_browser_token_request_contract:
2514
+ usesClueBrowserTokenRequestContract,
2491
2515
  uses_server_side_clue_api_key: usesServerSideClueApiKey,
2492
2516
  },
2493
2517
  };
@@ -2537,6 +2561,7 @@ const classifyRoutesForSnapshot = async ({
2537
2561
  hashScope,
2538
2562
  generationContract,
2539
2563
  agentSkills,
2564
+ purposeRecheckAllRoutes = false,
2540
2565
  }) => {
2541
2566
  const previousRoutes = previousRouteMap(previousSnapshot);
2542
2567
  const plans = new Map();
@@ -2562,6 +2587,80 @@ const classifyRoutesForSnapshot = async ({
2562
2587
  previousRoute.route_input_hash === currentHash &&
2563
2588
  hasReusablePreviousRouteSemantics(previousRoute)
2564
2589
  ) {
2590
+ if (purposeRecheckAllRoutes) {
2591
+ const decision = await callAiReuseDecisionProvider({
2592
+ request,
2593
+ env,
2594
+ apiKey: aiProviderApiKey,
2595
+ route: buildRouteEvidencePromptEntry({ route, hashScope }),
2596
+ previousRoute,
2597
+ currentHash,
2598
+ generationContract,
2599
+ agentSkills,
2600
+ });
2601
+ if (
2602
+ decision.purpose_change_state === "same_purpose" &&
2603
+ decision.confidence >= PURPOSE_STABILITY_CONFIDENCE_THRESHOLD
2604
+ ) {
2605
+ plans.set(route.operation_source_key, {
2606
+ origin: "unchanged_route_reused",
2607
+ purpose_change_state: "same_purpose",
2608
+ active_semantic_source: "previous_reused",
2609
+ stability_confidence: decision.confidence,
2610
+ stability_missing_context: decision.missing_context,
2611
+ route_input_hash: currentHash,
2612
+ previous_route: previousRoute,
2613
+ previous_route_input_hash: previousRoute.route_input_hash,
2614
+ previous_route_semantic_hash:
2615
+ previousRoute.route_semantic_hash ??
2616
+ routeSemanticHash(previousRoute),
2617
+ semantic_change_reason: sanitizeText(
2618
+ `Periodic full purpose check confirmed previous semantics still apply: ${decision.reason}`,
2619
+ route,
2620
+ ),
2621
+ });
2622
+ continue;
2623
+ }
2624
+ if (
2625
+ decision.decision === "regenerate" &&
2626
+ decision.confidence >= PURPOSE_STABILITY_CONFIDENCE_THRESHOLD
2627
+ ) {
2628
+ plans.set(route.operation_source_key, {
2629
+ origin: "changed_route_semantic_regenerated",
2630
+ purpose_change_state: decision.purpose_change_state,
2631
+ active_semantic_source: "new_confirmed",
2632
+ stability_confidence: decision.confidence,
2633
+ stability_missing_context: decision.missing_context,
2634
+ route_input_hash: currentHash,
2635
+ previous_route: previousRoute,
2636
+ previous_route_input_hash: previousRoute.route_input_hash,
2637
+ previous_route_semantic_hash:
2638
+ previousRoute.route_semantic_hash ??
2639
+ routeSemanticHash(previousRoute),
2640
+ semantic_change_reason: sanitizeText(decision.reason, route),
2641
+ });
2642
+ routesRequiringGeneration.push(route);
2643
+ continue;
2644
+ }
2645
+ plans.set(route.operation_source_key, {
2646
+ origin: "changed_route_needs_review",
2647
+ purpose_change_state: "insufficient_evidence",
2648
+ active_semantic_source: "previous_kept_pending_review",
2649
+ stability_confidence: decision.confidence,
2650
+ stability_missing_context: decision.missing_context,
2651
+ route_input_hash: currentHash,
2652
+ previous_route: previousRoute,
2653
+ previous_route_input_hash: previousRoute.route_input_hash,
2654
+ previous_route_semantic_hash:
2655
+ previousRoute.route_semantic_hash ??
2656
+ routeSemanticHash(previousRoute),
2657
+ semantic_change_reason: sanitizeText(
2658
+ `Periodic full purpose check could not prove a purpose-level change: ${decision.reason}`,
2659
+ route,
2660
+ ),
2661
+ });
2662
+ continue;
2663
+ }
2565
2664
  plans.set(route.operation_source_key, {
2566
2665
  origin: "unchanged_route_reused",
2567
2666
  purpose_change_state: "same_purpose",
@@ -2775,6 +2874,19 @@ const withStabilityMetadata = ({ route, currentRoute, plan }) => ({
2775
2874
  evidence_packet_summary: buildEvidencePacketSummary(currentRoute),
2776
2875
  });
2777
2876
 
2877
+ const unconfirmedFallbackPlan = ({ plan, reason }) => ({
2878
+ ...plan,
2879
+ origin: "fallback",
2880
+ purpose_change_state: "insufficient_evidence",
2881
+ active_semantic_source: "new_unconfirmed",
2882
+ stability_confidence: 0,
2883
+ stability_missing_context: [
2884
+ ...safeArray(plan.stability_missing_context),
2885
+ "AI route semantic generation did not produce usable active semantics.",
2886
+ ],
2887
+ semantic_change_reason: reason,
2888
+ });
2889
+
2778
2890
  const fieldPathMatchesKeys = ({
2779
2891
  fieldPath,
2780
2892
  routeKeys,
@@ -3168,6 +3280,31 @@ const sendSnapshot = async ({ request, env, snapshot }) => {
3168
3280
  }
3169
3281
  };
3170
3282
 
3283
+ const routeHasUsableActiveSemantics = (route) =>
3284
+ route.semantics.route_confidence > 0 ||
3285
+ safeArray(route.operation_effects).length > 0 ||
3286
+ ["previous_reused", "previous_kept_pending_review"].includes(
3287
+ route.active_semantic_source,
3288
+ );
3289
+
3290
+ const assertSemanticSnapshotUploadable = (snapshot) => {
3291
+ const authFailureRoute = snapshot.routes.find((route) =>
3292
+ /provider_(401|403)\b/.test(route.confidence_reason ?? ""),
3293
+ );
3294
+ if (authFailureRoute) {
3295
+ throw new Error(
3296
+ `semantic snapshot generation failed before upload: AI provider authentication failed for ${authFailureRoute.operation_source_key}`,
3297
+ );
3298
+ }
3299
+ const usableRouteCount = snapshot.routes.filter(routeHasUsableActiveSemantics)
3300
+ .length;
3301
+ if (snapshot.routes.length > 0 && usableRouteCount === 0) {
3302
+ throw new Error(
3303
+ "semantic snapshot generation produced zero usable route semantics; refusing upload",
3304
+ );
3305
+ }
3306
+ };
3307
+
3171
3308
  const assertSemanticSnapshotAudit = ({
3172
3309
  routes,
3173
3310
  snapshot,
@@ -3186,12 +3323,17 @@ const assertSemanticSnapshotAudit = ({
3186
3323
  const routeByKey = new Map(
3187
3324
  routes.map((route) => [route.operation_source_key, route]),
3188
3325
  );
3326
+ const previousActiveSources = new Set([
3327
+ "previous_reused",
3328
+ "previous_kept_pending_review",
3329
+ ]);
3189
3330
  const allowedOrigins = new Set([
3190
3331
  "new_route_ai_generated",
3191
3332
  "unchanged_route_reused",
3192
3333
  "changed_route_semantic_reused",
3193
3334
  "changed_route_semantic_regenerated",
3194
3335
  "changed_route_needs_review",
3336
+ "fallback",
3195
3337
  ]);
3196
3338
  for (const route of auditedSnapshot.routes) {
3197
3339
  const currentRoute = routeByKey.get(route.operation_source_key);
@@ -3216,6 +3358,75 @@ const assertSemanticSnapshotAudit = ({
3216
3358
  `semantic snapshot audit found stale route_semantic_hash: ${route.operation_source_key}`,
3217
3359
  );
3218
3360
  }
3361
+ if (auditedSnapshot.schema_version >= 3) {
3362
+ if (route.semantic_stability.purpose_change_state !== route.purpose_change_state) {
3363
+ throw new Error(
3364
+ `semantic snapshot audit found mismatched purpose stability metadata: ${route.operation_source_key}`,
3365
+ );
3366
+ }
3367
+ if (
3368
+ previousActiveSources.has(route.active_semantic_source) &&
3369
+ (!route.previous_route_input_hash ||
3370
+ !route.previous_route_semantic_hash ||
3371
+ !route.previous_semantic_snapshot_version)
3372
+ ) {
3373
+ throw new Error(
3374
+ `semantic snapshot audit found incomplete previous active semantic metadata: ${route.operation_source_key}`,
3375
+ );
3376
+ }
3377
+ if (
3378
+ previousActiveSources.has(route.active_semantic_source) &&
3379
+ route.route_semantic_hash !== route.previous_route_semantic_hash
3380
+ ) {
3381
+ throw new Error(
3382
+ `semantic snapshot audit found changed active semantics for previous-backed route: ${route.operation_source_key}`,
3383
+ );
3384
+ }
3385
+ if (
3386
+ route.active_semantic_source === "previous_kept_pending_review" &&
3387
+ (route.purpose_change_state !== "insufficient_evidence" ||
3388
+ route.semantic_origin !== "changed_route_needs_review")
3389
+ ) {
3390
+ throw new Error(
3391
+ `semantic snapshot audit found inconsistent pending-review semantics: ${route.operation_source_key}`,
3392
+ );
3393
+ }
3394
+ if (
3395
+ route.active_semantic_source === "previous_reused" &&
3396
+ route.purpose_change_state !== "same_purpose"
3397
+ ) {
3398
+ throw new Error(
3399
+ `semantic snapshot audit found inconsistent reused semantics: ${route.operation_source_key}`,
3400
+ );
3401
+ }
3402
+ if (
3403
+ route.purpose_change_state === "purpose_added" &&
3404
+ (!route.previous_route_input_hash || !route.previous_route_semantic_hash)
3405
+ ) {
3406
+ throw new Error(
3407
+ `semantic snapshot audit found missing previous metadata for added purpose: ${route.operation_source_key}`,
3408
+ );
3409
+ }
3410
+ if (
3411
+ route.active_semantic_source === "new_confirmed" &&
3412
+ route.semantics.route_confidence === 0 &&
3413
+ safeArray(route.operation_effects).length === 0
3414
+ ) {
3415
+ throw new Error(
3416
+ `semantic snapshot audit found confirmed semantics without usable evidence: ${route.operation_source_key}`,
3417
+ );
3418
+ }
3419
+ if (
3420
+ route.active_semantic_source === "new_unconfirmed" &&
3421
+ (route.semantic_origin !== "fallback" ||
3422
+ route.purpose_change_state !== "insufficient_evidence" ||
3423
+ route.semantic_stability.confidence !== 0)
3424
+ ) {
3425
+ throw new Error(
3426
+ `semantic snapshot audit found inconsistent unconfirmed fallback semantics: ${route.operation_source_key}`,
3427
+ );
3428
+ }
3429
+ }
3219
3430
  if (
3220
3431
  (route.semantic_origin === "unchanged_route_reused" ||
3221
3432
  route.semantic_origin === "changed_route_semantic_reused") &&
@@ -3272,12 +3483,14 @@ export const runSemanticCi = async ({
3272
3483
  ? await fetchLatestSnapshot({ request, env })
3273
3484
  : validatePreviousSnapshotForReuse(providedPreviousSnapshot);
3274
3485
  const previousRoutes = previousRouteMap(previousSnapshot);
3486
+ const purposeRecheckAllRoutes = shouldPurposeRecheckAllRoutes(env);
3275
3487
  const routeNeedsAi = routes.some((route) => {
3276
3488
  const previousRoute = previousRoutes.get(route.operation_source_key);
3277
3489
  return (
3278
3490
  !previousRoute ||
3279
3491
  previousRoute.route_input_hash !== routeInputHash(route) ||
3280
- !hasReusablePreviousRouteSemantics(previousRoute)
3492
+ !hasReusablePreviousRouteSemantics(previousRoute) ||
3493
+ purposeRecheckAllRoutes
3281
3494
  );
3282
3495
  });
3283
3496
  if (routeNeedsAi && !env.CLUE_AI_PROVIDER_API_KEY) {
@@ -3310,6 +3523,7 @@ export const runSemanticCi = async ({
3310
3523
  hashScope,
3311
3524
  generationContract,
3312
3525
  agentSkills,
3526
+ purposeRecheckAllRoutes,
3313
3527
  });
3314
3528
  const promptRoutes = routesRequiringGeneration.map((route) =>
3315
3529
  buildRouteEvidencePromptEntry({ route, hashScope }),
@@ -3360,6 +3574,7 @@ export const runSemanticCi = async ({
3360
3574
  generationContract,
3361
3575
  aiRuntime,
3362
3576
  });
3577
+ assertSemanticSnapshotUploadable(snapshot);
3363
3578
  const upload = await sendSnapshot({ request, env, snapshot });
3364
3579
  return {
3365
3580
  accepted: upload.accepted === true,