@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/semantic-ci.mjs
CHANGED
|
@@ -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
|
|
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,
|