@moda-ai/cli 1.55.14 → 1.55.16

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.
Files changed (31) hide show
  1. package/README.md +12 -0
  2. package/dist/{agent-y47qgmf1.js → agent-a2xwfeeh.js} +9 -9
  3. package/dist/{cli-ryd7nzke.js → cli-2ars875d.js} +2 -2
  4. package/dist/{cli-bfmn3bsk.js → cli-3gwbg0xp.js} +2 -2
  5. package/dist/{cli-4qe6n8am.js → cli-438g4nwh.js} +122 -2
  6. package/dist/{cli-k7vwt56h.js → cli-53mvsfgn.js} +3 -3
  7. package/dist/{cli-ssqb8bfw.js → cli-6ca1sz0a.js} +1 -1
  8. package/dist/{cli-k9nae6t9.js → cli-7fc44hhq.js} +1123 -187
  9. package/dist/{cli-ndn5m6wn.js → cli-86q35fpq.js} +8 -8
  10. package/dist/{cli-09rr5smg.js → cli-atx672xv.js} +1 -1
  11. package/dist/{cli-kwz900s4.js → cli-b3cxcxgn.js} +5 -4
  12. package/dist/{cli-4x9w6ey7.js → cli-kf3d6y8c.js} +1 -1
  13. package/dist/{cli-weqp7xc1.js → cli-xy6zn80e.js} +5 -5
  14. package/dist/{cli-y8gbbe9k.js → cli-zdeywyn9.js} +4 -4
  15. package/dist/cli.js +504 -150
  16. package/dist/{doctor-grvx6xx0.js → doctor-e2xrbpmp.js} +9 -9
  17. package/dist/{fix-91sr76gw.js → fix-v8xka487.js} +7 -7
  18. package/dist/{harness-vm0xd80p.js → harness-28wbzncq.js} +6 -6
  19. package/dist/{harness-github-actions-tmy3gqwa.js → harness-github-actions-301nak95.js} +6 -6
  20. package/dist/{index-f6cydfxg.js → index-ccdpy9ma.js} +11 -11
  21. package/dist/{production-intelligence-mg3qp2dg.js → production-intelligence-0wqcvvxk.js} +6 -5
  22. package/dist/{profile-commands-2g5bjzj2.js → profile-commands-f5kysjrv.js} +3 -3
  23. package/dist/prompt-source-mcp.js +6 -6
  24. package/dist/{prompts-kw5kzm39.js → prompts-jg0stjfb.js} +7 -7
  25. package/dist/{provision-rm0yc08f.js → provision-25bw5rd7.js} +4 -4
  26. package/dist/{registry-mbz65bjy.js → registry-39xzetsc.js} +4 -4
  27. package/dist/{skills-sagxq9sc.js → skills-gjesa81c.js} +6 -6
  28. package/dist/{sync-xv04wnth.js → sync-7ts9psky.js} +8 -8
  29. package/package.json +1 -1
  30. package/skills/integration/index.json +2 -2
  31. package/skills/moda-cli/SKILL.md +31 -4
@@ -4,16 +4,16 @@ import {
4
4
  import {
5
5
  createCommandContext,
6
6
  stringOption
7
- } from "./cli-4x9w6ey7.js";
7
+ } from "./cli-kf3d6y8c.js";
8
8
  import {
9
9
  assertFlags,
10
10
  assertPositionals,
11
11
  requireTenant
12
- } from "./cli-k7vwt56h.js";
12
+ } from "./cli-53mvsfgn.js";
13
13
  import {
14
14
  ApiError,
15
15
  callControlAPI
16
- } from "./cli-ryd7nzke.js";
16
+ } from "./cli-2ars875d.js";
17
17
  import {
18
18
  CliInputError,
19
19
  CliInputRequiredError,
@@ -39,7 +39,7 @@ import {
39
39
  resolveModaBaseUrl,
40
40
  resolveTenantId,
41
41
  shouldInventoryFile
42
- } from "./cli-kwz900s4.js";
42
+ } from "./cli-b3cxcxgn.js";
43
43
  import {
44
44
  __commonJS,
45
45
  __require,
@@ -180406,6 +180406,22 @@ function isCommentLine(line) {
180406
180406
  const t = line.trimStart();
180407
180407
  return t.startsWith("//") || t.startsWith("#") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("--") || t.startsWith("<!--");
180408
180408
  }
180409
+ function globRegex(glob) {
180410
+ let out = "";
180411
+ for (let i = 0;i < glob.length; i++) {
180412
+ const ch = glob[i];
180413
+ if (ch === "*" && glob[i + 1] === "*" && glob[i + 2] === "/") {
180414
+ out += "(?:.*/)?";
180415
+ i += 2;
180416
+ } else if (ch === "*")
180417
+ out += "[^/]*";
180418
+ else if (ch === "?")
180419
+ out += "[^/]";
180420
+ else
180421
+ out += ch.replace(/[.+^${}()|[\]\\]/, "\\$&");
180422
+ }
180423
+ return new RegExp(`^${out}$`);
180424
+ }
180409
180425
 
180410
180426
  // ../../packages/harness-bundle/src/detect/files.ts
180411
180427
  var DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
@@ -180727,6 +180743,7 @@ function pyStringStateAfter(line, open) {
180727
180743
  }
180728
180744
  return open;
180729
180745
  }
180746
+ var PY_HEADER = /^(?:if|elif|else|try|except|finally|with|for|while|async\s+with|async\s+for)\b[^:]*:\s*/;
180730
180747
  function pyImports(text) {
180731
180748
  const out = [];
180732
180749
  const lines = text.split(/\r?\n/);
@@ -180737,24 +180754,34 @@ function pyImports(text) {
180737
180754
  inString = pyStringStateAfter(line, inString);
180738
180755
  if (wasInString !== undefined)
180739
180756
  continue;
180740
- let m = /^\s*from\s+([A-Za-z_][\w.]*)\s+import\s+(.+)/.exec(line);
180741
- if (m) {
180742
- out.push({ spec: m[1], line: i + 1 });
180743
- let list = m[2].replace(/#.*$/, "");
180744
- for (let j = i + 1;j < lines.length && (list.includes("(") && !list.includes(")") || /\\\s*$/.test(list)); j++)
180745
- list += ` ${lines[j].replace(/#.*$/, "")}`;
180746
- const names = list.replace(/[()\\]/g, "").split(",");
180747
- for (const raw of names) {
180748
- const name = raw.trim().split(/\s+as\s+/)[0];
180749
- if (/^[A-Za-z_]\w*$/.test(name))
180750
- out.push({ spec: `${m[1]}.${name}`, line: i + 1 });
180757
+ const literals = [];
180758
+ const code = line.replace(/(?:\b[rRbBuUfF]{1,2})?(['"])((?:\\.|(?!\1).)*)\1/g, (_m, _q, body) => `\x00${literals.push(body) - 1}\x00`).replace(/#.*$/, "");
180759
+ for (const m of code.matchAll(/\b(?:importlib\.import_module|__import__)\(\s*\u0000(\d+)\u0000/g)) {
180760
+ const spec = literals[Number(m[1])];
180761
+ if (/^[A-Za-z_][\w.]*$/.test(spec))
180762
+ out.push({ spec, line: i + 1 });
180763
+ }
180764
+ for (let stmt of code.split(";")) {
180765
+ stmt = stmt.trim();
180766
+ for (let h = PY_HEADER.exec(stmt);h; h = PY_HEADER.exec(stmt))
180767
+ stmt = stmt.slice(h[0].length);
180768
+ let m = /^from\s+([A-Za-z_][\w.]*)\s+import\s+(.+)/.exec(stmt);
180769
+ if (m) {
180770
+ out.push({ spec: m[1], line: i + 1 });
180771
+ let list = m[2];
180772
+ for (let j = i + 1;j < lines.length && (list.includes("(") && !list.includes(")") || /\\\s*$/.test(list)); j++)
180773
+ list += ` ${lines[j].replace(/#.*$/, "")}`;
180774
+ for (const raw of list.replace(/[()\\]/g, "").split(",")) {
180775
+ const name = raw.trim().split(/\s+as\s+/)[0];
180776
+ if (/^[A-Za-z_]\w*$/.test(name))
180777
+ out.push({ spec: `${m[1]}.${name}`, line: i + 1 });
180778
+ }
180779
+ continue;
180751
180780
  }
180752
- continue;
180753
- }
180754
- m = /^\s*import\s+([A-Za-z_][\w.]*(?:\s+as\s+\w+)?(?:\s*,\s*[A-Za-z_][\w.]*(?:\s+as\s+\w+)?)*)/.exec(line);
180755
- if (m) {
180756
- for (const raw of m[1].split(","))
180757
- out.push({ spec: raw.trim().split(/\s+as\s+/)[0], line: i + 1 });
180781
+ m = /^import\s+([A-Za-z_][\w.]*(?:\s+as\s+\w+)?(?:\s*,\s*[A-Za-z_][\w.]*(?:\s+as\s+\w+)?)*)/.exec(stmt);
180782
+ if (m)
180783
+ for (const raw of m[1].split(","))
180784
+ out.push({ spec: raw.trim().split(/\s+as\s+/)[0], line: i + 1 });
180758
180785
  }
180759
180786
  }
180760
180787
  return out;
@@ -180873,7 +180900,7 @@ var PROVIDERS = [
180873
180900
  p("libsql", "database", "engine", { npm: ["@libsql/client", "drizzle-orm/libsql", "@prisma/adapter-libsql"], py: ["libsql_client", "libsql_experimental", "libsql"], pypi: ["libsql-client", "libsql-experimental"], hosts: [".turso.io"], env: ["TURSO_*", "LIBSQL_*"], schemes: ["libsql"], images: ["ghcr.io/tursodatabase/libsql-server"] }),
180874
180901
  p("mssql", "database", "engine", { npm: ["mssql", "tedious"], py: ["pyodbc", "pymssql"], go: ["github.com/microsoft/go-mssqldb", "github.com/denisenkom/go-mssqldb"], hosts: [".database.windows.net"], env: ["MSSQL_*"], schemes: ["mssql", "sqlserver"], images: ["mcr.microsoft.com/mssql/server", "mcr.microsoft.com/azure-sql-edge"] }),
180875
180902
  p("cloudflare.d1", "database", "emulated", { npm: ["drizzle-orm/d1", "@prisma/adapter-d1"] }),
180876
- p("redis", "kv", "engine", { npm: ["redis", "ioredis", "@redis/client", "@upstash/redis", "@vercel/kv"], py: ["redis", "aioredis", "upstash_redis", "valkey"], pypi: ["redis", "aioredis", "upstash-redis", "valkey"], go: ["github.com/redis/go-redis", "github.com/go-redis/redis", "github.com/gomodule/redigo"], gems: ["redis"], hosts: [".upstash.io", ".redis.cache.windows.net", ".redislabs.com", ".redis-cloud.com", ".cache.amazonaws.com"], env: ["REDIS_*", "UPSTASH_REDIS_*", "KV_REST_API_*", "KV_URL", "VALKEY_*", "*_REDIS_URL"], schemes: ["redis", "rediss", "valkey"], images: ["redis", "valkey/valkey", "redislabs/redismod", "redis/redis-stack", "redis/redis-stack-server", "bitnami/redis", "eqalpha/keydb", "docker.dragonflydb.io/dragonflydb/dragonfly"] }),
180903
+ p("redis", "kv", "engine", { npm: ["redis", "ioredis", "@redis/client", "@upstash/redis", "@vercel/kv", "bullmq", "bull", "bee-queue", "@nestjs/bull", "@nestjs/bullmq"], py: ["redis", "aioredis", "upstash_redis", "valkey"], pypi: ["redis", "aioredis", "upstash-redis", "valkey"], go: ["github.com/redis/go-redis", "github.com/go-redis/redis", "github.com/gomodule/redigo"], gems: ["redis"], hosts: [".upstash.io", ".redis.cache.windows.net", ".redislabs.com", ".redis-cloud.com", ".cache.amazonaws.com"], env: ["REDIS_*", "UPSTASH_REDIS_*", "KV_REST_API_*", "KV_URL", "VALKEY_*", "*_REDIS_URL"], schemes: ["redis", "rediss", "valkey"], images: ["redis", "valkey/valkey", "redislabs/redismod", "redis/redis-stack", "redis/redis-stack-server", "bitnami/redis", "eqalpha/keydb", "docker.dragonflydb.io/dragonflydb/dragonfly"] }),
180877
180904
  p("memcached", "kv", "engine", { npm: ["memjs", "memcached"], py: ["pymemcache", "pylibmc"], env: ["MEMCACHE_*", "MEMCACHED_*"], schemes: ["memcached"], images: ["memcached"] }),
180878
180905
  p("mongodb", "document", "engine", { npm: ["mongodb", "mongoose"], py: ["pymongo", "motor", "mongoengine", "beanie"], go: ["go.mongodb.org/mongo-driver"], gems: ["mongoid", "mongo"], hosts: [".mongodb.net"], env: ["MONGODB_*", "MONGO_*"], schemes: ["mongodb", "mongodb+srv"], images: ["mongo", "mongodb/mongodb-community-server", "bitnami/mongodb"] }),
180879
180906
  p("clickhouse", "database", "engine", { npm: ["@clickhouse/client", "@clickhouse/client-web"], py: ["clickhouse_connect", "clickhouse_driver"], pypi: ["clickhouse-connect", "clickhouse-driver"], go: ["github.com/ClickHouse/clickhouse-go"], hosts: [".clickhouse.cloud"], env: ["CLICKHOUSE_*"], schemes: ["clickhouse"], images: ["clickhouse/clickhouse-server", "yandex/clickhouse-server"] }),
@@ -180887,7 +180914,7 @@ var PROVIDERS = [
180887
180914
  p("snowflake", "database", "none", { npm: ["snowflake-sdk"], py: ["snowflake.connector", "snowflake.snowpark"], pypi: ["snowflake-connector-python"], hosts: [".snowflakecomputing.com"], env: ["SNOWFLAKE_*"] }),
180888
180915
  p("aws.s3", "object_store", "emulated", { npm: ["@aws-sdk/client-s3", "@aws-sdk/lib-storage", "@aws-sdk/s3-request-presigner", "@aws-sdk/s3-presigned-post", "minio"], py: ["minio", "s3fs"], go: [...goAws("s3"), "github.com/minio/minio-go"], hosts: [/(^|\.)s3([.-][a-z0-9-]+)?\.amazonaws\.com$/], env: ["S3_*", "AWS_S3_*", "*_S3_BUCKET*", "MINIO_*"], schemes: ["s3"], images: ["minio/minio", "quay.io/minio/minio", "bitnami/minio"], code: [botoTracked("s3"), awsV2("S3")] }),
180889
180916
  p("cloudflare.r2", "object_store", "emulated", { hosts: [".r2.cloudflarestorage.com", ".r2.dev"], env: ["R2_*", "*_R2_*"] }),
180890
- p("gcp.gcs", "object_store", "emulated", { npm: ["@google-cloud/storage", "firebase-admin/storage"], py: ["google.cloud.storage", "firebase_admin.storage"], pypi: ["google-cloud-storage"], go: ["cloud.google.com/go/storage"], hosts: ["storage.googleapis.com"], env: ["GCS_*", "*_GCS_*"], schemes: ["gs"], images: ["fsouza/fake-gcs-server"] }),
180917
+ p("gcp.storage", "object_store", "emulated", { npm: ["@google-cloud/storage", "firebase-admin/storage"], py: ["google.cloud.storage", "firebase_admin.storage"], pypi: ["google-cloud-storage"], go: ["cloud.google.com/go/storage"], hosts: ["storage.googleapis.com"], env: ["GCS_*", "*_GCS_*"], schemes: ["gs"], images: ["fsouza/fake-gcs-server"] }),
180891
180918
  p("azure.blob", "object_store", "emulated", { npm: ["@azure/storage-blob"], py: ["azure.storage.blob"], pypi: ["azure-storage-blob"], go: ["github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"], hosts: [/\.blob\.core\.windows\.net$/], env: ["AZURE_STORAGE_*", "AzureWebJobsStorage"], images: ["mcr.microsoft.com/azure-storage/azurite"] }),
180892
180919
  p("vercel.blob", "object_store", "saas", { npm: ["@vercel/blob"], hosts: [".blob.vercel-storage.com"], env: ["BLOB_READ_WRITE_TOKEN"] }),
180893
180920
  p("uploadthing", "object_store", "saas", { npm: ["uploadthing", "@uploadthing/react"], env: ["UPLOADTHING_*"], hosts: ["api.uploadthing.com"] }),
@@ -180905,7 +180932,6 @@ var PROVIDERS = [
180905
180932
  p("kafka", "queue", "engine", { npm: ["kafkajs", "node-rdkafka", "@confluentinc/kafka-javascript"], py: ["kafka", "confluent_kafka", "aiokafka"], pypi: ["kafka-python", "confluent-kafka", "aiokafka"], go: ["github.com/segmentio/kafka-go", "github.com/confluentinc/confluent-kafka-go", "github.com/IBM/sarama", "github.com/Shopify/sarama"], hosts: [".confluent.cloud"], env: ["KAFKA_*"], schemes: ["kafka"], images: ["confluentinc/cp-kafka", "bitnami/kafka", "apache/kafka", "wurstmeister/kafka", "redpandadata/redpanda", "docker.redpanda.com/redpandadata/redpanda", "vectorized/redpanda"] }),
180906
180933
  p("rabbitmq", "queue", "engine", { npm: ["amqplib", "amqp-connection-manager"], py: ["pika", "aio_pika", "kombu"], pypi: ["pika", "aio-pika", "kombu"], go: ["github.com/rabbitmq/amqp091-go", "github.com/streadway/amqp"], hosts: [".cloudamqp.com"], env: ["RABBITMQ_*", "AMQP_*"], schemes: ["amqp", "amqps"], images: ["rabbitmq", "bitnami/rabbitmq"] }),
180907
180934
  p("nats", "queue", "engine", { npm: ["nats"], py: ["nats"], pypi: ["nats-py"], go: ["github.com/nats-io/nats.go"], env: ["NATS_*"], schemes: ["nats"], images: ["nats"] }),
180908
- p("bullmq", "queue", "engine", { npm: ["bullmq", "bull", "bee-queue"] }),
180909
180935
  p("celery", "queue", "engine", { py: ["celery"], env: ["CELERY_*"] }),
180910
180936
  p("inngest", "queue", "emulated", { npm: ["inngest"], py: ["inngest"], env: ["INNGEST_*"], hosts: ["api.inngest.com", "inn.gs"] }),
180911
180937
  p("trigger.dev", "queue", "saas", { npm: ["@trigger.dev/sdk"], env: ["TRIGGER_*"], hosts: ["api.trigger.dev"] }),
@@ -180921,7 +180947,11 @@ var PROVIDERS = [
180921
180947
  p("aws.bedrock_agentcore", "function.invoke", "none", { npm: ["bedrock-agentcore", "@aws-sdk/client-bedrock-agentcore", "@aws-sdk/client-bedrock-agentcore-control"], py: ["bedrock_agentcore"], pypi: ["bedrock-agentcore"], env: ["BEDROCK_AGENTCORE_*"], code: [botoTracked("bedrock-agentcore", "bedrock-agentcore-control")] }),
180922
180948
  p("cloud.metadata", "other", "emulated", { hosts: ["metadata.google.internal", "169.254.169.254", "metadata"] }),
180923
180949
  p("gcp.secretmanager", "other", "saas", { npm: ["@google-cloud/secret-manager"], py: ["google.cloud.secretmanager"], pypi: ["google-cloud-secret-manager"], hosts: ["secretmanager.googleapis.com"] }),
180924
- p("gcp.ml_apis", "http.request", "saas", { npm: ["@google-cloud/vision", "@google-cloud/language", "@google-cloud/translate", "@google-cloud/speech", "@google-cloud/text-to-speech", "@google-cloud/video-intelligence", "@google-cloud/documentai", "@google-cloud/dialogflow", "@google-cloud/dlp"], py: ["google.cloud.vision", "google.cloud.language", "google.cloud.translate", "google.cloud.translate_v2", "google.cloud.translate_v3", "google.cloud.speech", "google.cloud.texttospeech", "google.cloud.videointelligence", "google.cloud.documentai", "google.cloud.dialogflow"], hosts: ["vision.googleapis.com", "translation.googleapis.com", "language.googleapis.com", "speech.googleapis.com", "texttospeech.googleapis.com", "documentai.googleapis.com"] }),
180950
+ p("gcp.vision", "http.request", "saas", { npm: ["@google-cloud/vision"], py: ["google.cloud.vision"], pypi: ["google-cloud-vision"], hosts: ["vision.googleapis.com"] }),
180951
+ p("gcp.translate", "http.request", "saas", { npm: ["@google-cloud/translate"], py: ["google.cloud.translate", "google.cloud.translate_v2", "google.cloud.translate_v3"], pypi: ["google-cloud-translate"], hosts: ["translation.googleapis.com"] }),
180952
+ p("gcp.speech", "http.request", "saas", { npm: ["@google-cloud/speech", "@google-cloud/text-to-speech"], py: ["google.cloud.speech", "google.cloud.texttospeech"], hosts: ["speech.googleapis.com", "texttospeech.googleapis.com"] }),
180953
+ p("gcp.language", "http.request", "saas", { npm: ["@google-cloud/language"], py: ["google.cloud.language"], hosts: ["language.googleapis.com"] }),
180954
+ p("gcp.documentai", "http.request", "saas", { npm: ["@google-cloud/documentai", "@google-cloud/video-intelligence", "@google-cloud/dialogflow", "@google-cloud/dlp"], py: ["google.cloud.documentai", "google.cloud.videointelligence", "google.cloud.dialogflow"], hosts: ["documentai.googleapis.com"] }),
180925
180955
  p("cloudflare.api", "http.request", "saas", { npm: ["cloudflare"], py: ["cloudflare"], hosts: ["api.cloudflare.com"], env: ["CLOUDFLARE_API_TOKEN", "CF_API_TOKEN"] }),
180926
180956
  p("pinecone", "vector_store", "emulated", { npm: ["@pinecone-database/pinecone", "@langchain/pinecone"], py: ["pinecone", "langchain_pinecone"], pypi: ["pinecone", "pinecone-client"], hosts: [".pinecone.io"], env: ["PINECONE_*"], images: ["ghcr.io/pinecone-io/pinecone-local"] }),
180927
180957
  p("weaviate", "vector_store", "engine", { npm: ["weaviate-client", "weaviate-ts-client", "@langchain/weaviate"], py: ["weaviate"], pypi: ["weaviate-client"], hosts: [".weaviate.network", ".weaviate.cloud"], env: ["WEAVIATE_*"], images: ["semitechnologies/weaviate", "cr.weaviate.io/semitechnologies/weaviate"] }),
@@ -180943,7 +180973,7 @@ var PROVIDERS = [
180943
180973
  p("anthropic", "model.inference", "model", { npm: ["@anthropic-ai/sdk", "@ai-sdk/anthropic", "@langchain/anthropic", "@anthropic-ai/claude-agent-sdk", "@anthropic-ai/claude-code", "@anthropic-ai/bedrock-sdk", "@anthropic-ai/vertex-sdk"], py: ["anthropic", "langchain_anthropic", "claude_agent_sdk", "claude_code_sdk", "llama_index.llms.anthropic"], pypi: ["langchain-anthropic", "claude-agent-sdk", "claude-code-sdk"], go: ["github.com/anthropics/anthropic-sdk-go"], hosts: ["api.anthropic.com"], env: ["ANTHROPIC_*"] }),
180944
180974
  p("openrouter", "model.inference", "model", { npm: ["@openrouter/ai-sdk-provider", "@openrouter/sdk"], hosts: ["openrouter.ai"], env: ["OPENROUTER_*"] }),
180945
180975
  p("google.genai", "model.inference", "model", { npm: ["@google/genai", "@google/generative-ai", "@ai-sdk/google", "@langchain/google-genai", "@genkit-ai/googleai", "@genkit-ai/google-genai"], py: ["google.genai", "google.generativeai", "langchain_google_genai"], pypi: ["google-genai", "google-generativeai", "langchain-google-genai"], go: ["google.golang.org/genai", "github.com/google/generative-ai-go"], hosts: ["generativelanguage.googleapis.com"], env: ["GEMINI_*", "GOOGLE_API_KEY", "GOOGLE_GENERATIVE_AI_*", "GOOGLE_GENAI_*"] }),
180946
- p("google.vertex", "model.inference", "model", { npm: ["@google-cloud/vertexai", "@ai-sdk/google-vertex", "@langchain/google-vertexai", "@genkit-ai/vertexai"], py: ["vertexai", "google.cloud.aiplatform", "langchain_google_vertexai"], pypi: ["google-cloud-aiplatform", "langchain-google-vertexai"], hosts: [/aiplatform\.googleapis\.com$/], env: ["VERTEX_*", "GOOGLE_VERTEX_*"] }),
180976
+ p("google.vertex", "model.inference", "model", { npm: ["@google-cloud/vertexai", "@ai-sdk/google-vertex", "@langchain/google-vertexai", "@genkit-ai/vertexai"], py: ["vertexai", "google.cloud.aiplatform", "langchain_google_vertexai", "pydantic_ai.models.vertexai"], pypi: ["google-cloud-aiplatform", "langchain-google-vertexai"], hosts: [/aiplatform\.googleapis\.com$/], env: ["VERTEX_*", "GOOGLE_VERTEX_*", "GOOGLE_GENAI_USE_VERTEXAI"], code: [/\bvertexai\.init\s*\(/, /\bClient\s*\([^)]*\bvertexai\s*=\s*True/, /\bnew\s+GoogleGenAI\s*\(\s*\{[^}]*\bvertexai\s*:\s*true/, /GOOGLE_GENAI_USE_VERTEXAI['"]?\s*\]?\s*=\s*['"]?(?:True|true|TRUE|1)\b/] }),
180947
180977
  p("aws.bedrock", "model.inference", "model", { npm: ["@aws-sdk/client-bedrock-runtime", "@aws-sdk/client-bedrock-agent-runtime", "@ai-sdk/amazon-bedrock", "@langchain/aws"], py: ["langchain_aws"], pypi: ["langchain-aws"], go: goAws("bedrockruntime"), hosts: [/^bedrock(-runtime|-agent-runtime)?\.[a-z0-9-]+\.amazonaws\.com$/], env: ["BEDROCK_*", "AWS_BEDROCK_*"], code: [botoTracked("bedrock-runtime", "bedrock-agent-runtime", "bedrock")] }),
180948
180978
  p("azure.openai", "model.inference", "model", { npm: ["@azure/openai", "@ai-sdk/azure"], py: ["azure.ai.inference", "azure.ai.openai"], hosts: [/\.openai\.azure\.com$/, /\.services\.ai\.azure\.com$/], env: ["AZURE_OPENAI_*", "AZURE_AI_*"], code: [/\bAzureOpenAI\s*\(/, /\bAzureChatOpenAI\s*\(/, /\bnew\s+AzureOpenAI\s*\(/] }),
180949
180979
  p("groq", "model.inference", "model", { npm: ["groq-sdk", "@ai-sdk/groq", "@langchain/groq"], py: ["groq", "langchain_groq"], hosts: ["api.groq.com"], env: ["GROQ_*"] }),
@@ -180999,7 +181029,7 @@ var PROVIDERS = [
180999
181029
  p("shopify", "http.request", "saas", { npm: ["@shopify/shopify-api", "@shopify/admin-api-client", "shopify-api-node"], py: ["shopify"], pypi: ["ShopifyAPI"], hosts: [".myshopify.com"], env: ["SHOPIFY_*"] }),
181000
181030
  p("jira", "http.request", "saas", { npm: ["jira-client", "jira.js"], py: ["jira", "atlassian"], pypi: ["atlassian-python-api"], hosts: [".atlassian.net"], env: ["JIRA_*", "ATLASSIAN_*", "CONFLUENCE_*"] }),
181001
181031
  p("airtable", "http.request", "saas", { npm: ["airtable"], py: ["pyairtable"], hosts: ["api.airtable.com"], env: ["AIRTABLE_*"] }),
181002
- p("google.apis", "http.request", "saas", { npm: ["googleapis", "@googleapis/sheets", "@googleapis/gmail", "@googleapis/drive", "@googleapis/calendar"], py: ["googleapiclient", "google_auth_oauthlib", "google.oauth2", "google.auth"], pypi: ["google-api-python-client", "google-auth-oauthlib"], go: ["google.golang.org/api"], hosts: ["www.googleapis.com", "sheets.googleapis.com", "gmail.googleapis.com", "oauth2.googleapis.com", "accounts.google.com", ".googleapis.com"], env: ["GOOGLE_CLIENT_*", "GOOGLE_SERVICE_ACCOUNT*"] }),
181032
+ p("google.apis", "http.request", "saas", { npm: ["googleapis", "@googleapis/sheets", "@googleapis/gmail", "@googleapis/drive", "@googleapis/calendar"], py: ["googleapiclient", "google_auth_oauthlib"], pypi: ["google-api-python-client", "google-auth-oauthlib"], go: ["google.golang.org/api"], hosts: ["www.googleapis.com", "sheets.googleapis.com", "gmail.googleapis.com", "oauth2.googleapis.com", "accounts.google.com", ".googleapis.com"], env: ["GOOGLE_CLIENT_*", "GOOGLE_SERVICE_ACCOUNT*"] }),
181003
181033
  p("google.maps", "http.request", "saas", { npm: ["@googlemaps/google-maps-services-js"], py: ["googlemaps"], hosts: ["maps.googleapis.com", "places.googleapis.com"], env: ["GOOGLE_MAPS_*"] }),
181004
181034
  p("serpapi", "http.request", "saas", { npm: ["serpapi"], py: ["serpapi"], pypi: ["google-search-results"], hosts: ["serpapi.com"], env: ["SERPAPI_*", "SERP_API_*"] }),
181005
181035
  p("serper", "http.request", "saas", { hosts: ["google.serper.dev"], env: ["SERPER_*"], code: [/\bSerperDevTool\s*\(/] }),
@@ -181025,22 +181055,52 @@ var PROVIDERS = [
181025
181055
  p("arxiv", "http.request", "saas", { py: ["arxiv"], hosts: ["export.arxiv.org"], code: [/\bArxiv(?:Retriever|APIWrapper|QueryRun|Loader)\s*\(/] }),
181026
181056
  p("linkup", "http.request", "saas", { npm: ["linkup-sdk"], py: ["linkup"], pypi: ["linkup-sdk"], hosts: ["api.linkup.so"], env: ["LINKUP_*"] }),
181027
181057
  p("ncbi", "http.request", "saas", { hosts: ["eutils.ncbi.nlm.nih.gov", "pubmed.ncbi.nlm.nih.gov"], code: [/\bPubMed(?:APIWrapper|QueryRun|Retriever)\s*\(/], env: ["NCBI_*", "PUBMED_*"] }),
181058
+ p("mcp", "function.invoke", "saas", { npm: ["@modelcontextprotocol/sdk/client", "@ai-sdk/mcp", "@langchain/mcp-adapters"], py: ["mcp.client", "langchain_mcp_adapters", "fastmcp.client"], code: [/\baddMcpServer\s*\(/, /\bcreateSdkMcpServer\s*\(/, /\bexperimental_createMCPClient\s*\(|\bcreateMCPClient\s*\(/, /\bmcpServers\b/, /\bMultiServerMCPClient\s*\(/, /\bClientSession\s*\(\s*read/] }),
181028
181059
  p("pusher", "http.request", "saas", { npm: ["pusher"], py: ["pusher"], hosts: [".pusher.com"], env: ["PUSHER_*"] }),
181029
181060
  p("ably", "http.request", "saas", { npm: ["ably"], py: ["ably"], hosts: ["rest.ably.io", "realtime.ably.io"], env: ["ABLY_*"] }),
181030
181061
  p("mapbox", "http.request", "saas", { hosts: ["api.mapbox.com"], env: ["MAPBOX_*", "*_MAPBOX_*"] }),
181031
181062
  p("openweathermap", "http.request", "saas", { hosts: ["api.openweathermap.org"], env: ["OPENWEATHER*"] }),
181032
181063
  p("open-meteo", "http.request", "saas", { hosts: ["api.open-meteo.com", "geocoding-api.open-meteo.com", "archive-api.open-meteo.com"] }),
181033
- p("telemetry.sentry", "other", "saas", { npm: ["@sentry/node", "@sentry/nextjs", "@sentry/cloudflare", "@sentry/serverless", "@sentry/aws-serverless", "@sentry/bun", "@sentry/react"], py: ["sentry_sdk"], pypi: ["sentry-sdk"], hosts: [".ingest.sentry.io", ".ingest.us.sentry.io"], env: ["SENTRY_*", "*_SENTRY_*"] }),
181034
- p("telemetry.langsmith", "other", "saas", { npm: ["langsmith"], py: ["langsmith"], hosts: ["api.smith.langchain.com"], env: ["LANGSMITH_*", "LANGCHAIN_API_KEY", "LANGCHAIN_TRACING*", "LANGCHAIN_ENDPOINT", "LANGCHAIN_PROJECT"] }),
181035
- p("telemetry.langfuse", "other", "saas", { npm: ["langfuse", "@langfuse/tracing", "@langfuse/otel", "langfuse-vercel"], py: ["langfuse"], hosts: ["cloud.langfuse.com", "us.cloud.langfuse.com"], env: ["LANGFUSE_*"] }),
181036
- p("telemetry.posthog", "other", "saas", { npm: ["posthog-node", "posthog-js"], py: ["posthog"], hosts: [".posthog.com"], env: ["POSTHOG_*", "*_POSTHOG_*"] }),
181037
- p("telemetry.datadog", "other", "saas", { npm: ["dd-trace"], py: ["ddtrace", "datadog"], hosts: [".datadoghq.com", ".datadoghq.eu"], env: ["DD_API_KEY", "DD_SITE", "DATADOG_*"] }),
181038
- p("telemetry.braintrust", "other", "saas", { npm: ["braintrust"], py: ["braintrust"], hosts: ["api.braintrust.dev"], env: ["BRAINTRUST_*"] }),
181064
+ p("sentry", "other", "saas", { npm: ["@sentry/node", "@sentry/nextjs", "@sentry/cloudflare", "@sentry/serverless", "@sentry/aws-serverless", "@sentry/bun", "@sentry/react"], py: ["sentry_sdk"], pypi: ["sentry-sdk"], hosts: [".ingest.sentry.io", ".ingest.us.sentry.io"], env: ["SENTRY_*", "*_SENTRY_*"] }),
181065
+ p("langsmith", "other", "saas", { npm: ["langsmith"], py: ["langsmith"], hosts: ["api.smith.langchain.com"], env: ["LANGSMITH_*", "LANGCHAIN_API_KEY", "LANGCHAIN_TRACING*", "LANGCHAIN_ENDPOINT", "LANGCHAIN_PROJECT"] }),
181066
+ p("langfuse", "other", "saas", { npm: ["langfuse", "@langfuse/tracing", "@langfuse/otel", "langfuse-vercel"], py: ["langfuse"], hosts: ["cloud.langfuse.com", "us.cloud.langfuse.com"], env: ["LANGFUSE_*"] }),
181067
+ p("posthog", "other", "saas", { npm: ["posthog-node", "posthog-js"], py: ["posthog"], hosts: [".posthog.com"], env: ["POSTHOG_*", "*_POSTHOG_*"] }),
181068
+ p("datadog", "other", "saas", { npm: ["dd-trace"], py: ["ddtrace", "datadog"], hosts: [".datadoghq.com", ".datadoghq.eu"], env: ["DD_API_KEY", "DD_SITE", "DATADOG_*"] }),
181069
+ p("braintrust", "other", "saas", { npm: ["braintrust"], py: ["braintrust"], hosts: ["api.braintrust.dev"], env: ["BRAINTRUST_*"] }),
181039
181070
  p("telemetry.gcp", "other", "saas", { npm: ["@google-cloud/logging", "@google-cloud/logging-winston", "@google-cloud/monitoring", "@google-cloud/opentelemetry-cloud-trace-exporter", "@google-cloud/trace-agent"], py: ["google.cloud.logging"], pypi: ["google-cloud-logging"] }),
181040
181071
  p("telemetry.azure_monitor", "other", "saas", { npm: ["@azure/monitor-opentelemetry", "applicationinsights"], py: ["azure.monitor.opentelemetry", "opencensus.ext.azure"], pypi: ["azure-monitor-opentelemetry"], hosts: [".applicationinsights.azure.com", "dc.services.visualstudio.com"], env: ["APPLICATIONINSIGHTS_*"] }),
181041
- p("telemetry.otlp", "other", "saas", { npm: ["@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/exporter-trace-otlp-grpc", "@vercel/otel"], py: ["opentelemetry.exporter.otlp"], pypi: ["opentelemetry-exporter-otlp"], env: ["OTEL_EXPORTER_OTLP_*"] }),
181042
181072
  p("local_fs", "file", "engine", { code: [/\b(?:fs\.)?(?:writeFile|writeFileSync|appendFile|appendFileSync)\s*\(/, /\bBun\.write\s*\(/, /\bopen\([^)\n]*,\s*['"](?:w|a|wb|ab|w\+|a\+)['"]/, /\.write_(?:text|bytes)\s*\(/] })
181043
181073
  ];
181074
+ var SDK_DEFAULT_ENV = {
181075
+ "npm:openai": ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_ORG_ID", "OPENAI_PROJECT_ID"],
181076
+ "pypi:openai": ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_ORG_ID", "OPENAI_PROJECT_ID"],
181077
+ "npm:@openai/agents": ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_DEFAULT_MODEL", "OPENAI_AGENTS_DISABLE_TRACING", "OPENAI_AGENTS_DONT_LOG_MODEL_DATA", "OPENAI_AGENTS_DONT_LOG_TOOL_DATA"],
181078
+ "pypi:openai-agents": ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_DEFAULT_MODEL", "OPENAI_AGENTS_DISABLE_SENSITIVE_DATA", "OPENAI_AGENTS_DONT_LOG_MODEL_DATA", "OPENAI_AGENTS_DONT_LOG_TOOL_DATA"],
181079
+ "npm:@anthropic-ai/sdk": ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"],
181080
+ "pypi:anthropic": ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"],
181081
+ "npm:@anthropic-ai/claude-agent-sdk": ["ANTHROPIC_API_KEY"],
181082
+ "pypi:claude-agent-sdk": ["ANTHROPIC_API_KEY"],
181083
+ "npm:@google/genai": ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
181084
+ "pypi:google-genai": ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
181085
+ "npm:groq-sdk": ["GROQ_API_KEY"],
181086
+ "pypi:groq": ["GROQ_API_KEY"],
181087
+ "npm:@mistralai/mistralai": ["MISTRAL_API_KEY"],
181088
+ "pypi:mistralai": ["MISTRAL_API_KEY"],
181089
+ "npm:cohere-ai": ["CO_API_KEY"],
181090
+ "pypi:cohere": ["CO_API_KEY"],
181091
+ "npm:together-ai": ["TOGETHER_API_KEY"],
181092
+ "pypi:together": ["TOGETHER_API_KEY"],
181093
+ "npm:replicate": ["REPLICATE_API_TOKEN"],
181094
+ "pypi:replicate": ["REPLICATE_API_TOKEN"],
181095
+ "npm:@pinecone-database/pinecone": ["PINECONE_API_KEY"],
181096
+ "pypi:pinecone": ["PINECONE_API_KEY"],
181097
+ "pypi:logfire": ["LOGFIRE_TOKEN"],
181098
+ "npm:@pydantic/logfire-node": ["LOGFIRE_TOKEN"],
181099
+ "npm:langsmith": ["LANGSMITH_API_KEY"],
181100
+ "pypi:langsmith": ["LANGSMITH_API_KEY"],
181101
+ "npm:@livekit/agents": ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"],
181102
+ "pypi:livekit-agents": ["LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"]
181103
+ };
181044
181104
  var WORKERS_BINDINGS = {
181045
181105
  d1_databases: { capability: "database", provider: "cloudflare.d1", cls: "emulated", type: "D1" },
181046
181106
  kv_namespaces: { capability: "kv", provider: "cloudflare.kv", cls: "emulated", type: "KV" },
@@ -181075,8 +181135,42 @@ var CFN_RESOURCES = {
181075
181135
  "AWS::ElastiCache::CacheCluster": { capability: "kv", provider: "redis", cls: "engine" },
181076
181136
  "AWS::ElastiCache::ReplicationGroup": { capability: "kv", provider: "redis", cls: "engine" },
181077
181137
  "AWS::ElastiCache::ServerlessCache": { capability: "kv", provider: "redis", cls: "engine" },
181078
- "AWS::OpenSearchService::Domain": { capability: "search", provider: "opensearch", cls: "engine" }
181138
+ "AWS::OpenSearchService::Domain": { capability: "search", provider: "opensearch", cls: "engine" },
181139
+ "AWS::Cognito::UserPoolClient": { capability: "auth", provider: "aws.cognito", cls: "emulated" },
181140
+ "AWS::Cognito::IdentityPool": { capability: "auth", provider: "aws.cognito", cls: "emulated" },
181141
+ "AWS::Bedrock::Agent": { capability: "model.inference", provider: "aws.bedrock", cls: "model" },
181142
+ "AWS::Bedrock::KnowledgeBase": { capability: "vector_store", provider: "aws.bedrock", cls: "none" },
181143
+ "AWS::BedrockAgentCore::Runtime": { capability: "function.invoke", provider: "aws.bedrock_agentcore", cls: "none" },
181144
+ "AWS::BedrockAgentCore::Gateway": { capability: "function.invoke", provider: "aws.bedrock_agentcore", cls: "none" },
181145
+ "AWS::BedrockAgentCore::Memory": { capability: "document", provider: "aws.bedrock_agentcore", cls: "none" }
181146
+ };
181147
+ var CFN_SHORT = {
181148
+ dynamodb: "dynamodb",
181149
+ s3: "s3",
181150
+ sqs: "sqs",
181151
+ sns: "sns",
181152
+ events: "eventbridge",
181153
+ kinesis: "kinesis",
181154
+ stepfunctions: "stepfunctions",
181155
+ cognito: "cognito",
181156
+ secretsmanager: "secretsmanager",
181157
+ ssm: "ssm",
181158
+ elasticache: "elasticache",
181159
+ opensearchservice: "opensearch",
181160
+ rds: "rds"
181079
181161
  };
181162
+ function cfnBindingType(type) {
181163
+ const [, service = "", resource = ""] = type.split("::");
181164
+ if (type === "AWS::Serverless::SimpleTable")
181165
+ return "dynamodb";
181166
+ if (type === "AWS::Serverless::StateMachine")
181167
+ return "stepfunctions";
181168
+ const svc = service.toLowerCase();
181169
+ if (svc === "bedrock" || svc === "bedrockagentcore") {
181170
+ return `${svc === "bedrock" ? "bedrock" : "agentcore"}_${resource.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase()}`;
181171
+ }
181172
+ return CFN_SHORT[svc] ?? svc;
181173
+ }
181080
181174
  function rdsEngineProvider(engine) {
181081
181175
  const e = (engine ?? "").toLowerCase();
181082
181176
  if (e.includes("postgres"))
@@ -181573,6 +181667,9 @@ function scanSources(ctx, declaredPypi, workspaceNames) {
181573
181667
  evidence: evidence.slice(0, 3)
181574
181668
  };
181575
181669
  ctx.gaps.push(gap);
181670
+ if (evidence.some((e) => !isTestPath(e.path))) {
181671
+ ctx.signals.push({ kind: "code", capability: "http.request", provider: "http", host: undefined, evidence: evidence[0] });
181672
+ }
181576
181673
  }
181577
181674
  return { frameworks };
181578
181675
  }
@@ -181597,6 +181694,7 @@ var CODE_ENV_RES = [
181597
181694
  /\bos\.environ\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/g,
181598
181695
  /\bos\.environ\.get\(\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g,
181599
181696
  /\bos\.getenv\(\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]/g,
181697
+ /['"]([A-Z_][A-Z0-9_]*)['"]\s+(?:not\s+)?in\s+os\.environ\b/g,
181600
181698
  /\bos\.Getenv\(\s*"([A-Za-z_][A-Za-z0-9_]*)"/g,
181601
181699
  /\bos\.LookupEnv\(\s*"([A-Za-z_][A-Za-z0-9_]*)"/g,
181602
181700
  /\bENV\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/g,
@@ -181604,11 +181702,15 @@ var CODE_ENV_RES = [
181604
181702
  /\benv\.(?:str|int|bool|url|db|list|float|json)\(\s*['"]([A-Z_][A-Z0-9_]*)['"]/g,
181605
181703
  /\bconfig\(\s*['"]([A-Z_][A-Z0-9_]*)['"]/g
181606
181704
  ];
181607
- var ENV_EXAMPLE_FILE = /^(?:\.env\.(?:example|sample|template|dist|defaults|local\.example|development\.example)|\.env-(?:example|sample)|example\.env|sample\.env|env\.example|\.dev\.vars\.example|\.dev\.vars\.sample)$/;
181705
+ var ENV_EXAMPLE_FILE = /^(?:\.env\.(?:example|sample|template|dist|defaults?|local\.example|development\.example)|\.env-(?:example|sample)|example\.env|sample\.env|env\.example|\.dev\.vars\.example|\.dev\.vars\.sample)$/;
181608
181706
  var ENV_SECRET_FILE = /^(?:\.env(?:\.[\w.-]+)?|\.dev\.vars)$/;
181609
181707
  function isEnvExampleFile(path) {
181610
181708
  return ENV_EXAMPLE_FILE.test(basename2(path));
181611
181709
  }
181710
+ function isEnvFile(path) {
181711
+ const name = basename2(path);
181712
+ return (ENV_EXAMPLE_FILE.test(name) || ENV_SECRET_FILE.test(name)) && !/\.test\b/.test(name);
181713
+ }
181612
181714
  function scanEnv(ctx) {
181613
181715
  for (const file of ctx.files) {
181614
181716
  const name = basename2(file.path);
@@ -181634,6 +181736,8 @@ function scanEnv(ctx) {
181634
181736
  continue;
181635
181737
  const at = lineIndex(file.text);
181636
181738
  const lines = splitLines(file.text);
181739
+ if (file.path.endsWith(".py") && /\bBaseSettings\b/.test(file.text))
181740
+ settingsFields(ctx, file.path, lines);
181637
181741
  const seen = new Set;
181638
181742
  for (const re of CODE_ENV_RES) {
181639
181743
  re.lastIndex = 0;
@@ -181655,6 +181759,63 @@ function scanEnv(ctx) {
181655
181759
  }
181656
181760
  }
181657
181761
  }
181762
+ function sdkDefaultEnv(ctx, declared) {
181763
+ const seen = new Set;
181764
+ for (const d of declared) {
181765
+ const file = ctx.byPath.get(d.path);
181766
+ if (d.dev || file?.isTest)
181767
+ continue;
181768
+ const key = `${d.eco}:${d.eco === "pypi" ? d.name.toLowerCase().replace(/[_.]/g, "-") : d.name}`;
181769
+ for (const name of SDK_DEFAULT_ENV[key] ?? []) {
181770
+ if (seen.has(`${name}:${d.path}`))
181771
+ continue;
181772
+ seen.add(`${name}:${d.path}`);
181773
+ const line = d.line ?? 1;
181774
+ ctx.env.push({
181775
+ name,
181776
+ source: `${d.path}:${line}`,
181777
+ evidence: { path: d.path, line, excerpt: excerpt(file ? splitLines(file.text)[line - 1] : undefined), detector: `${DETECTOR2}.sdk_default` }
181778
+ });
181779
+ }
181780
+ }
181781
+ }
181782
+ function settingsFields(ctx, path, lines) {
181783
+ const settings = new Map([["BaseSettings", ""]]);
181784
+ for (let i = 0;i < lines.length; i++) {
181785
+ const cls = /^class\s+(\w+)\s*\(([^)]*)\)/.exec(lines[i]);
181786
+ const base = cls?.[2].split(",").map((b) => b.trim().split(".").pop() ?? "").find((b) => settings.has(b));
181787
+ if (!cls || base === undefined)
181788
+ continue;
181789
+ const body = [];
181790
+ let prefix = settings.get(base) ?? "";
181791
+ let docstring = false;
181792
+ for (let j = i + 1;j < lines.length; j++) {
181793
+ const line = lines[j];
181794
+ if (line.trim() !== "" && !/^\s/.test(line))
181795
+ break;
181796
+ const quotes = (line.match(/"""|'''/g) ?? []).length;
181797
+ if (docstring || quotes > 0) {
181798
+ if (quotes % 2 === 1)
181799
+ docstring = !docstring;
181800
+ continue;
181801
+ }
181802
+ const p2 = /\benv_prefix\s*=\s*['"]([^'"]*)['"]/.exec(line);
181803
+ if (p2)
181804
+ prefix = p2[1];
181805
+ const m = /^ {4}([A-Za-z][A-Za-z0-9_]*)\s*:/.exec(line);
181806
+ if (m && m[1] !== "model_config" && !isCommentLine(line))
181807
+ body.push({ name: m[1], line: j + 1 });
181808
+ }
181809
+ settings.set(cls[1], prefix);
181810
+ for (const f of body) {
181811
+ ctx.env.push({
181812
+ name: `${prefix}${f.name}`.toUpperCase(),
181813
+ source: `${path}:${f.line}`,
181814
+ evidence: { path, line: f.line, excerpt: excerpt(lines[f.line - 1]), detector: `${DETECTOR2}.settings` }
181815
+ });
181816
+ }
181817
+ }
181818
+ }
181658
181819
  var GENERIC_DB_URL = /^(?:[A-Z0-9]+_)*(?:DATABASE|DB)_(?:URL|URI|DSN|CONNECTION(?:_STRING)?)(?:_[A-Z0-9]+)*$|^SQLALCHEMY_DATABASE_UR[IL]$|^(?:DATABASE|DB)$/;
181659
181820
  function isGenericDatabaseUrl(name) {
181660
181821
  return GENERIC_DB_URL.test(name);
@@ -181689,6 +181850,8 @@ function envSignals(ctx) {
181689
181850
  }
181690
181851
  if (resolved)
181691
181852
  continue;
181853
+ if (/(?:^|_)(?:USE|ENABLE|ENABLED)(?:_|$)/.test(obs.name) && obs.value !== undefined && /^(?:false|0|no|off)$/i.test(obs.value.trim()))
181854
+ continue;
181692
181855
  const sig = matchEnvName(obs.name);
181693
181856
  if (sig) {
181694
181857
  ctx.signals.push({ kind: "env", capability: sig.capability, provider: sig.provider, envVar: obs.name, evidence: ev, weak: isTestPath(ev.path) });
@@ -181698,14 +181861,18 @@ function envSignals(ctx) {
181698
181861
  var SECRET_NAME = /(?:^|_)(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD|PASS|CREDENTIALS?|PRIVATE|AUTH|SIGNING|SALT|CERT|DSN)(?:_|$)|APIKEY|_PAT$/;
181699
181862
  var ENDPOINT_NAME = /(?:^|_)(?:URL|URI|ENDPOINT|HOST|HOSTNAME|BASE|BASE_URL|ADDR|ADDRESS|PORT|DSN|CONNECTION(?:_STRING)?|SERVER|REGION|BUCKET|TABLE|QUEUE|TOPIC|DATABASE|DB|INDEX|NAMESPACE|PROJECT_ID|ACCOUNT_ID)(?:_|$)/;
181700
181863
  var SELF_CONFIG = /^(?:NODE_ENV|ENV|ENVIRONMENT|APP_ENV|DEBUG|LOG_LEVEL|LOGLEVEL|PORT|HOST|HOSTNAME|TZ|PYTHONPATH|PYTHONUNBUFFERED|PATH|HOME|CI|VERCEL_ENV|VERCEL_URL|NEXT_PUBLIC_APP_URL|APP_URL|SITE_URL|PUBLIC_URL|BASE_URL|NEXTAUTH_URL|AUTH_URL|DJANGO_SETTINGS_MODULE|FLASK_APP|FLASK_ENV|WORKERS|WEB_CONCURRENCY|ALLOWED_HOSTS|DJANGO_ALLOWED_HOSTS|CORS_ORIGINS?)$/;
181864
+ var PUBLIC_NAME = /^(?:NEXT_PUBLIC_|VITE_|PUBLIC_|EXPO_PUBLIC_|REACT_APP_|NUXT_PUBLIC_|GATSBY_)|(?:^|_)(?:PUBLISHABLE|ANON)_KEY$/;
181865
+ var CREDENTIAL_SETTING = /^(?:ENABLE|ENABLED|ALLOW|DISABLE|USE|MIN|MAX)_|_(?:LENGTH|EXPIRY|EXPIRES_IN|INTERVAL|INTERVAL_\w+|SECONDS|SECS|HOURS|MINUTES|MS|TTL|MAX|WINDOW|PREFIX|PREFIX_VAR|KIND|PATH|FORMAT|PROVIDER|DURATION|VIOLATIONS|COUNT|LIMIT)$/;
181701
181866
  function classifyName(name) {
181702
- if (SELF_CONFIG.test(name))
181867
+ if (SELF_CONFIG.test(name) || PUBLIC_NAME.test(name))
181703
181868
  return "config";
181704
181869
  if (SECRET_NAME.test(name))
181705
181870
  return "secret";
181706
181871
  return "unknown";
181707
181872
  }
181708
181873
  function classifyLinked(name, deps) {
181874
+ if (PUBLIC_NAME.test(name))
181875
+ return "config";
181709
181876
  const isModel = deps.some((d) => d.capability === "model.inference");
181710
181877
  const secret = SECRET_NAME.test(name) && !/(?:^|_)(?:URL|URI|HOST|ENDPOINT)$/.test(name);
181711
181878
  if (isModel) {
@@ -181721,6 +181888,21 @@ function classifyLinked(name, deps) {
181721
181888
  return "dependency_endpoint";
181722
181889
  return "config";
181723
181890
  }
181891
+ var AMBIENT_ENV = /^(?:PATH|HOME|USER|LOGNAME|SHELL|PWD|OLDPWD|TERM|LANG|LANGUAGE|LC_\w+|TMPDIR|TEMP|TMP|EDITOR|PAGER|LD_PRELOAD|LD_LIBRARY_PATH|PYTHONDONTWRITEBYTECODE|PYTHONASYNCIODEBUG|PYTHONHASHSEED|PYTHONFAULTHANDLER|NX_\w+|APPDATA|LOCALAPPDATA|PROGRAMDATA|USERPROFILE|COMSPEC|SYSTEMROOT|WINDIR|COLORTERM|COLORFGBG|NO_COLOR|FORCE_COLOR|TERM_PROGRAM|DEBIAN_FRONTEND|BUILDPLATFORM|TARGETPLATFORM|TARGETOS|TARGETARCH|TARGETVARIANT|BUILDOS|BUILDARCH|BUILDKIT_\w+|DOCKER_BUILDKIT|DOCKER_METADATA_\w+|GITHUB_(?:ACTIONS|WORKSPACE|SHA|REF|REF_NAME|REF_TYPE|HEAD_REF|BASE_REF|RUN_ID|RUN_NUMBER|RUN_ATTEMPT|REPOSITORY|REPOSITORY_OWNER|EVENT_NAME|EVENT_PATH|ENV|OUTPUT|STEP_SUMMARY|PATH|JOB|ACTOR|SERVER_URL|API_URL|GRAPHQL_URL|WORKFLOW|ACTION|ACTION_PATH|RETENTION_DAYS)|RUNNER_\w+|ACTIONS_\w+|CARGO_\w+|RUSTFLAGS|BASH_\w+|BASH|ZSH_\w+|CONDA_\w+|VIRTUAL_ENV|PYENV_\w+|NVM_\w+|npm_\w+|CODECOV_TOKEN|DOCKERHUB_\w+|UV_\w+|PIP_\w+|POETRY_\w+|CMAKE_\w+|CC|CXX|CFLAGS|CXXFLAGS|LDFLAGS|MAKEFLAGS|ANDROID_\w+|XDG_\w+|SSH_\w+|DISPLAY|SHLVL|_)$/;
181892
+ var TOOLING_SOURCE = /(^|\/)(\.github|\.gitlab|\.circleci|\.buildkite|ci|\.devcontainer)\/|(^|\/)(Makefile|GNUmakefile|CMakeLists\.txt|Justfile|Jenkinsfile)$|(^|\/)(?:jest|vitest|playwright|karma|cypress)\.config\.[cm]?[jt]s$|\.(cmake|mk|bat|ps1)$/;
181893
+ function isRuntimeEnvSource(source) {
181894
+ if (/ ARG$/.test(source))
181895
+ return false;
181896
+ const path = source.split(/[: ]/)[0];
181897
+ if (TOOLING_SOURCE.test(path))
181898
+ return false;
181899
+ if (!/[/.]/.test(path))
181900
+ return true;
181901
+ return !isTestPath(path);
181902
+ }
181903
+ function isAmbientEnv(name) {
181904
+ return AMBIENT_ENV.test(name);
181905
+ }
181724
181906
  function buildEnvReferences(ctx, deps) {
181725
181907
  const byName = new Map;
181726
181908
  for (const ref of ctx.manifest?.env ?? []) {
@@ -181747,12 +181929,16 @@ function buildEnvReferences(ctx, deps) {
181747
181929
  const entry = byName.get(name);
181748
181930
  const linked = depsByEnv.get(name) ?? [];
181749
181931
  let classification;
181750
- if (linked.length > 0)
181932
+ if (PUBLIC_NAME.test(name))
181933
+ classification = "config";
181934
+ else if (linked.length > 0)
181751
181935
  classification = classifyLinked(name, linked);
181752
181936
  else if (entry.manifest && entry.manifest.classification !== "unknown")
181753
181937
  classification = entry.manifest.classification;
181754
181938
  else
181755
181939
  classification = classifyName(name);
181940
+ if (classification === "secret" && CREDENTIAL_SETTING.test(name))
181941
+ classification = "config";
181756
181942
  const ref = {
181757
181943
  name,
181758
181944
  sources: uniqSorted(entry.sources).slice(0, 20),
@@ -182107,6 +182293,9 @@ function detectMigrations(ctx) {
182107
182293
  if (env)
182108
182294
  ctx.envLinks.push({ envVar: env, capability: providerName === "mongodb" ? "document" : "database", provider: providerName, evidence });
182109
182295
  }
182296
+ if (/\bextensions\s*=\s*\[[^\]]*\b(?:pgvector|vector)\b/.test(file.text)) {
182297
+ ctx.signals.push({ kind: "config", capability: "vector_store", provider: "pgvector", evidence: evidenceAt(file.path, file.text, findLine(file.text, /\bextensions\s*=/), `${DETECTOR3}.prisma`) });
182298
+ }
182110
182299
  continue;
182111
182300
  }
182112
182301
  if (/^drizzle\.config\.(ts|js|mjs|cjs|json)$/.test(name)) {
@@ -182119,10 +182308,21 @@ function detectMigrations(ctx) {
182119
182308
  b.schemaFiles.add(file.path);
182120
182309
  for (const s of schemas) {
182121
182310
  const p2 = joinPath(dir, s);
182122
- if (ctx.byPath.has(p2) || ctx.files.some((f) => isUnder(f.path, p2)))
182311
+ if (/[*?]/.test(s)) {
182312
+ const re = globRegex(p2);
182313
+ for (const f of ctx.files)
182314
+ if (re.test(f.path))
182315
+ b.schemaFiles.add(f.path);
182316
+ } else if (ctx.byPath.has(p2) || ctx.files.some((f) => isUnder(f.path, p2)))
182123
182317
  b.schemaFiles.add(p2);
182124
182318
  }
182125
- const outDir = joinPath(dir, out2);
182319
+ let outDir = joinPath(dir, out2);
182320
+ if (!ctx.files.some((f) => isUnder(f.path, outDir))) {
182321
+ const nestedRoots = ctx.files.filter((f) => /^(?:package\.json|pyproject\.toml|go\.mod)$/.test(basename2(f.path)) && dirname2(f.path) !== dir && isUnder(f.path, dir)).map((f) => dirname2(f.path));
182322
+ const sql = ctx.files.find((f) => isUnder(f.path, dir) && /(^|\/)migrations?\/[^/]+\.(?:sql|[cm]?[jt]s)$/.test(f.path) && !isTestPath(f.path) && !nestedRoots.some((r) => isUnder(f.path, r)));
182323
+ if (sql)
182324
+ outDir = dirname2(sql.path);
182325
+ }
182126
182326
  if (ctx.files.some((f) => isUnder(f.path, outDir))) {
182127
182327
  b.paths.add(outDir);
182128
182328
  claimDir(outDir);
@@ -182276,13 +182476,50 @@ function detectMigrations(ctx) {
182276
182476
  b.paths.add(dir);
182277
182477
  }
182278
182478
  }
182479
+ for (const f of ctx.files) {
182480
+ if (!/^wrangler\.(?:toml|jsonc?)$/.test(basename2(f.path)) || !/new_sqlite_classes/.test(f.text) || isTestPath(f.path))
182481
+ continue;
182482
+ ctx.gaps.push({
182483
+ kind: "durable_object_migrations",
182484
+ message: `${f.path} declares SQLite-backed Durable Object migrations; the workerd runtime adapter applies them`,
182485
+ evidence: [{ path: f.path, line: findLine(f.text, /new_sqlite_classes/), detector: DETECTOR3 }]
182486
+ });
182487
+ }
182488
+ const all = [...sets.values()];
182489
+ for (const b of all) {
182490
+ if (b.tool !== "raw_sql" || b.paths.size > 0)
182491
+ continue;
182492
+ const parent = [...b.schemaFiles].map(dirname2)[0];
182493
+ const sibling = all.find((o) => o !== b && o.tool === "raw_sql" && [...o.paths].some((p2) => isUnder(p2, parent ?? "\x00")));
182494
+ if (sibling) {
182495
+ for (const f of b.schemaFiles)
182496
+ sibling.schemaFiles.add(f);
182497
+ sibling.first = [...sibling.first ?? [], ...uniqSorted(b.schemaFiles)];
182498
+ if (sibling.engine === "unknown")
182499
+ sibling.engine = b.engine;
182500
+ b.schemaFiles.clear();
182501
+ }
182502
+ }
182279
182503
  const out = [];
182504
+ const testOnly = (p2) => isTestPath(p2) || /(^|\/)(?:conformance|e2e|[\w-]+-tests?|tests?-[\w-]+|e2e-[\w-]+)\//.test(p2);
182280
182505
  for (const b of [...sets.values()].sort((x, y) => cmp(`${x.tool}:${x.key}`, `${y.tool}:${y.key}`))) {
182506
+ if (b.tool === "django" && b.paths.size === 0)
182507
+ continue;
182281
182508
  if (b.paths.size === 0 && b.schemaFiles.size === 0)
182282
182509
  continue;
182283
- if (b.tool === "django" && b.paths.size === 0)
182510
+ if ([...b.paths, ...b.schemaFiles].every(testOnly))
182284
182511
  continue;
182285
- out.push({ engine: b.engine, tool: b.tool, paths: uniqSorted(b.paths), schemaFiles: uniqSorted(b.schemaFiles) });
182512
+ if (b.paths.size === 0) {
182513
+ for (const f of b.schemaFiles)
182514
+ b.paths.add(f);
182515
+ ctx.gaps.push({
182516
+ kind: "migration_schema_only",
182517
+ message: `${b.tool} has no migration files at ${[...b.schemaFiles][0]}; the schema is applied directly`,
182518
+ evidence: [...b.schemaFiles].slice(0, 1).map((path) => ({ path, detector: DETECTOR3 }))
182519
+ });
182520
+ }
182521
+ const first = b.first ?? [];
182522
+ out.push({ engine: b.engine, tool: b.tool, paths: [...first, ...uniqSorted(b.paths).filter((x) => !first.includes(x))], schemaFiles: uniqSorted(b.schemaFiles) });
182286
182523
  }
182287
182524
  return out;
182288
182525
  }
@@ -183290,6 +183527,19 @@ function fromPyproject(f, out) {
183290
183527
  for (const req of asArray(uv["dev-dependencies"]))
183291
183528
  if (typeof req === "string")
183292
183529
  add(req, true);
183530
+ const hatch = isRecord2(tool.hatch) && isRecord2(tool.hatch.metadata) && isRecord2(tool.hatch.metadata.hooks) ? tool.hatch.metadata.hooks : {};
183531
+ for (const hook of Object.values(hatch)) {
183532
+ if (!isRecord2(hook))
183533
+ continue;
183534
+ for (const req of asArray(hook.dependencies))
183535
+ if (typeof req === "string")
183536
+ add(req, false);
183537
+ const extra = isRecord2(hook["optional-dependencies"]) ? hook["optional-dependencies"] : {};
183538
+ for (const [group, reqs] of Object.entries(extra))
183539
+ for (const req of asArray(reqs))
183540
+ if (typeof req === "string")
183541
+ add(req, /dev|test|lint|doc/i.test(group));
183542
+ }
183293
183543
  }
183294
183544
  function fromPipfile(f, out) {
183295
183545
  let doc;
@@ -183553,7 +183803,7 @@ function cfnResources(ctx, resources, file, runtime) {
183553
183803
  if (!spec)
183554
183804
  continue;
183555
183805
  const resource = asString(props.TableName) ?? asString(props.BucketName) ?? asString(props.QueueName) ?? asString(props.TopicName) ?? asString(props.Name) ?? asString(props.StateMachineName) ?? logicalId;
183556
- runtime.bindings.push({ name: logicalId, type, resource });
183806
+ runtime.bindings.push({ name: logicalId, type: cfnBindingType(type), resource });
183557
183807
  bindingSignal(ctx, { ...spec, binding: logicalId, resource, evidence: ev(file, logicalId) });
183558
183808
  }
183559
183809
  }
@@ -183603,6 +183853,30 @@ function detectSam(ctx, file, doc) {
183603
183853
  envFromVariables(ctx, isRecord2(props.Environment) ? props.Environment.Variables : undefined, file, logicalId);
183604
183854
  }
183605
183855
  cfnResources(ctx, resources, file, runtime);
183856
+ const ids = Object.keys(resources);
183857
+ const direct = (text, self) => ids.filter((id) => id !== self && new RegExp(`\\b${escapeRe(id)}\\b`).test(text));
183858
+ const globalsText = JSON.stringify(isRecord2(doc.Globals) && isRecord2(doc.Globals.Function) ? doc.Globals.Function : {});
183859
+ const refs = new Map;
183860
+ for (const [fnId, res] of Object.entries(resources)) {
183861
+ if (!isRecord2(res) || !/^AWS::(?:Serverless|Lambda)::Function$/.test(asString(res.Type) ?? ""))
183862
+ continue;
183863
+ const seen = new Set(direct(`${JSON.stringify(res.Properties ?? {})} ${globalsText}`, fnId));
183864
+ const queue = [...seen];
183865
+ while (queue.length > 0) {
183866
+ const id = queue.shift();
183867
+ const other = resources[id];
183868
+ if (!isRecord2(other) || /^AWS::(?:Serverless|Lambda)::Function$/.test(asString(other.Type) ?? ""))
183869
+ continue;
183870
+ for (const next of direct(JSON.stringify(other.Properties ?? {}), id)) {
183871
+ if (next !== fnId && !seen.has(next)) {
183872
+ seen.add(next);
183873
+ queue.push(next);
183874
+ }
183875
+ }
183876
+ }
183877
+ refs.set(fnId, seen);
183878
+ }
183879
+ ctx.functionRefs.set(file.path, refs);
183606
183880
  if (runtime.entrypoints.length === 0)
183607
183881
  return;
183608
183882
  return runtime;
@@ -183666,6 +183940,17 @@ var CDK_RESOURCE_TYPES = {
183666
183940
  StateMachine: "AWS::StepFunctions::StateMachine",
183667
183941
  UserPool: "AWS::Cognito::UserPool"
183668
183942
  };
183943
+ function cdkTrigger(text, variable) {
183944
+ const v = `(?:this\\.|self\\.)?${escapeRe(variable)}\\b`;
183945
+ const re = (pattern) => new RegExp(pattern);
183946
+ if (re(`LambdaIntegration\\(\\s*(?:['"][^'"]*['"]\\s*,\\s*)?${v}`).test(text) || re(`LambdaRestApi\\([^)]*?handler\\s*[:=]\\s*${v}`).test(text) || re(`${v}\\s*\\.\\s*(?:addFunctionUrl|add_function_url)\\(`).test(text) || re(`FunctionUrl\\([^)]*?function\\s*[:=]\\s*${v}`).test(text))
183947
+ return "http";
183948
+ if (re(`${v}\\s*\\.\\s*(?:addEventSource|add_event_source)\\(\\s*(?:new\\s+)?(?:\\w+\\.)?SqsEventSource`).test(text))
183949
+ return "queue";
183950
+ if (re(`Schedule\\.[\\s\\S]{0,400}LambdaFunction\\(\\s*${v}`).test(text))
183951
+ return "cron";
183952
+ return "event";
183953
+ }
183669
183954
  function detectCdk(ctx, cdkJson) {
183670
183955
  const root = dirname2(cdkJson.path);
183671
183956
  const runtime = newRuntime("aws_lambda", root, [cdkJson.path], [{ path: cdkJson.path, detector: `${DETECTOR4}.cdk` }], 0.8);
@@ -183691,7 +183976,8 @@ function detectCdk(ctx, cdkJson) {
183691
183976
  runtime.runtimeVersion = lang.version;
183692
183977
  }
183693
183978
  const handlerRef = entry ? joinPath(root, entry) + (handler ? `#${handler}` : "") : asset ? `${joinPath(root, asset)}/${handler ?? ""}` : handler;
183694
- runtime.entrypoints.push({ name: id, handler: handlerRef, trigger: "event" });
183979
+ const variable = /(?:\b|\.)(\w+)\s*=\s*$/.exec(file.text.slice(Math.max(0, m.index - 120), m.index))?.[1];
183980
+ runtime.entrypoints.push({ name: id, handler: handlerRef, trigger: variable ? cdkTrigger(file.text, variable) : "event" });
183695
183981
  runtime.evidence.push({ path: file.path, line: at(m.index), excerpt: excerpt(splitLines(file.text)[at(m.index) - 1]), detector: `${DETECTOR4}.cdk:${construct}` });
183696
183982
  }
183697
183983
  CDK_RESOURCE.lastIndex = 0;
@@ -183703,12 +183989,12 @@ function detectCdk(ctx, cdkJson) {
183703
183989
  const type = CDK_RESOURCE_TYPES[kind];
183704
183990
  if (type) {
183705
183991
  const spec = CFN_RESOURCES[type];
183706
- runtime.bindings.push({ name: id, type });
183992
+ runtime.bindings.push({ name: id, type: cfnBindingType(type) });
183707
183993
  bindingSignal(ctx, { ...spec, binding: id, evidence });
183708
183994
  } else if (kind === "DatabaseInstance" || kind === "DatabaseCluster") {
183709
183995
  const engine = /DatabaseInstanceEngine\.(\w+)|DatabaseClusterEngine\.(\w+)/.exec(file.text.slice(m.index, m.index + 800));
183710
183996
  const provider = rdsEngineProvider(engine?.[1] ?? engine?.[2]);
183711
- runtime.bindings.push({ name: id, type: `AWS::RDS::${kind.replace("Database", "DB")}` });
183997
+ runtime.bindings.push({ name: id, type: "rds" });
183712
183998
  bindingSignal(ctx, { capability: "database", provider, cls: "engine", binding: id, evidence });
183713
183999
  }
183714
184000
  }
@@ -183842,7 +184128,10 @@ function detectCloudflare(input) {
183842
184128
  if (seen.has(key))
183843
184129
  return false;
183844
184130
  seen.add(key);
183845
- runtime.bindings.push(resource ? { name, type, resource } : { name, type });
184131
+ if (type === "QueueConsumer")
184132
+ return true;
184133
+ const normalized = type.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
184134
+ runtime.bindings.push(resource ? { name, type: normalized, resource } : { name, type: normalized });
183846
184135
  return true;
183847
184136
  };
183848
184137
  const produced = new Set(sections(config).flatMap((section) => asArray(isRecord2(section.queues) ? section.queues.producers : undefined).map((p2) => isRecord2(p2) ? asString(p2.queue) : undefined)));
@@ -183915,7 +184204,9 @@ function detectCloudflare(input) {
183915
184204
  const script = asString(item.script_name);
183916
184205
  if (!addBinding(name, "DurableObject", className))
183917
184206
  continue;
183918
- if (script && !workerNames.has(script)) {
184207
+ if (!script || workerNames.has(script)) {
184208
+ bindingSignal(ctx, { capability: "document", provider: "cloudflare.durable_objects", cls: "emulated", binding: name, resource: className, evidence: ev2(file, name) });
184209
+ } else {
183919
184210
  bindingSignal(ctx, { capability: "function.invoke", provider: "cloudflare.durable_object", cls: "saas", binding: name, resource: className, evidence: ev2(file, name) });
183920
184211
  ctx.gaps.push({ kind: "external_service_binding", message: `Durable Object ${name} lives in worker \`${script}\`, which is not in the bundle`, evidence: [ev2(file, name)] });
183921
184212
  }
@@ -183961,6 +184252,21 @@ var DETECTOR6 = "runtime.container";
183961
184252
  var DOCKERFILE = /^(?:Dockerfile|Containerfile)(?:\.[\w.-]+)?$|\.(?:Dockerfile|dockerfile)$/;
183962
184253
  var COMPOSE = /^(?:docker-)?compose(?:\.[\w-]+)*\.ya?ml$/;
183963
184254
  var INFRA_IMAGES = /^(?:nginx|traefik|caddy|haproxy|envoyproxy\/envoy|busybox|alpine|ubuntu|debian|node|python|golang|grafana\/.*|prom\/.*|otel\/.*|jaegertracing\/.*|adminer|dpage\/pgadmin4|portainer\/.*|.*\/?(?:loki|promtail|tempo))$/;
184255
+ var PROJECT_MANIFEST = /^(?:package\.json|pyproject\.toml|requirements\.txt|setup\.py|go\.mod|Gemfile|Cargo\.toml|pom\.xml|build\.gradle(?:\.kts)?)$/;
184256
+ function buildContext(ctx, dir, copies) {
184257
+ if (copies.length === 0 || ctx.files.some((f) => dirname2(f.path) === dir && PROJECT_MANIFEST.test(basename2(f.path))))
184258
+ return dir;
184259
+ const has = (base, src) => {
184260
+ const p2 = joinPath(base, src);
184261
+ return ctx.byPath.has(p2) || ctx.files.some((f) => f.path.startsWith(`${p2}/`));
184262
+ };
184263
+ for (let base = dir;; base = dirname2(base)) {
184264
+ if (copies.every((src) => has(base, src)))
184265
+ return base;
184266
+ if (base === ".")
184267
+ return dir;
184268
+ }
184269
+ }
183964
184270
  function parseInstructionArgs(rest) {
183965
184271
  const t = rest.trim();
183966
184272
  if (t.startsWith("[")) {
@@ -183973,7 +184279,7 @@ function parseInstructionArgs(rest) {
183973
184279
  return ["sh", "-c", t];
183974
184280
  }
183975
184281
  function parseDockerfile(file) {
183976
- const out = { path: file.path, from: [], expose: [], env: [], args: [], stages: {} };
184282
+ const out = { path: file.path, from: [], expose: [], env: [], args: [], copies: [], stages: {} };
183977
184283
  const raw = splitLines(file.text);
183978
184284
  const stages = new Map;
183979
184285
  let stage = { base: "", expose: [], env: [] };
@@ -184018,6 +184324,11 @@ function parseDockerfile(file) {
184018
184324
  for (const [name, value] of pairs)
184019
184325
  if (name)
184020
184326
  stage.env.push({ name, value, line: startLine });
184327
+ } else if ((instr === "COPY" || instr === "ADD") && !/--from=/.test(rest)) {
184328
+ const parts = rest.trim().startsWith("[") ? parseInstructionArgs(rest) : rest.split(/\s+/).filter((w) => !w.startsWith("--"));
184329
+ for (const src of parts.slice(0, -1))
184330
+ if (src && !/^https?:/.test(src) && !/[*?$]/.test(src) && src !== "." && src !== "./")
184331
+ out.copies.push(src.replace(/^\.\//, "").replace(/\/$/, ""));
184021
184332
  } else if (instr === "ARG") {
184022
184333
  const name = rest.split("=")[0].trim();
184023
184334
  if (name)
@@ -184051,7 +184362,7 @@ function detectContainers(input) {
184051
184362
  continue;
184052
184363
  const df = parseDockerfile(file);
184053
184364
  dockerfiles.push(df);
184054
- const root = dirname2(file.path);
184365
+ const root = buildContext(ctx, dirname2(file.path), df.copies);
184055
184366
  const base = df.from[df.from.length - 1] ?? "";
184056
184367
  const lambda = /^(?:public\.ecr\.aws\/lambda\/|amazon\/aws-lambda-)/.test(base);
184057
184368
  const kind = lambda ? "aws_lambda" : "container";
@@ -184136,12 +184447,16 @@ function composeServices(ctx, file, byRoot, contributions, relocated) {
184136
184447
  const ports = asArray(svc.ports);
184137
184448
  const svcEntrypoint = composeArgs(svc.entrypoint);
184138
184449
  const svcCommand = composeArgs(svc.command);
184139
- const serviceRoot = build !== undefined ? joinPath(base, typeof build === "string" ? build : isRecord2(build) ? asString(build.context) ?? "." : ".") : undefined;
184450
+ let serviceRoot = build !== undefined ? joinPath(base, typeof build === "string" ? build : isRecord2(build) ? asString(build.context) ?? "." : ".") : undefined;
184140
184451
  if (build !== undefined) {
184141
184452
  const context = typeof build === "string" ? build : isRecord2(build) ? asString(build.context) ?? "." : ".";
184142
- const root = joinPath(base, context);
184453
+ const contextRoot = joinPath(base, context);
184454
+ const dockerfile = joinPath(contextRoot, (isRecord2(build) ? asString(build.dockerfile) : undefined) ?? "Dockerfile");
184455
+ const dfDir = dirname2(dockerfile);
184456
+ const projectDir = dfDir !== contextRoot && isUnder(dfDir, contextRoot) && ctx.files.some((f) => dirname2(f.path) === dfDir && PROJECT_MANIFEST.test(basename2(f.path)));
184457
+ const root = projectDir ? dfDir : contextRoot;
184458
+ serviceRoot = root;
184143
184459
  const key = `container:${root}`;
184144
- const dockerfile = joinPath(root, (isRecord2(build) ? asString(build.dockerfile) : undefined) ?? "Dockerfile");
184145
184460
  const contribution = contributions.get(dockerfile);
184146
184461
  const runtime = byRoot.get(key) ?? newRuntime("container", root, [], [], 0.85);
184147
184462
  runtime.configFiles.push(file.path);
@@ -184178,28 +184493,11 @@ function composeServices(ctx, file, byRoot, contributions, relocated) {
184178
184493
  if (sig) {
184179
184494
  ctx.signals.push({ kind: "image", capability: sig.capability, provider: sig.provider, resource: name, evidence });
184180
184495
  } else if (!INFRA_IMAGES.test(image.split(":")[0])) {
184181
- if (svcEntrypoint || svcCommand || ports.length > 0) {
184182
- const key = `container:${base}`;
184183
- const runtime = byRoot.get(key) ?? newRuntime("container", base, [], [], 0.75);
184184
- runtime.configFiles.push(file.path);
184185
- runtime.evidence.push(evidence);
184186
- if (!runtime.entrypoints.some((e) => e.name === name)) {
184187
- const entry = { name, handler: image, trigger: ports.length > 0 ? "http" : "unknown" };
184188
- if (svcEntrypoint)
184189
- entry.command = [...svcEntrypoint, ...svcCommand ?? []];
184190
- else if (svcCommand) {
184191
- ctx.gaps.push({
184192
- kind: "unresolved_command",
184193
- message: `Compose service \`${name}\` overrides the CMD of image \`${image}\`, whose ENTRYPOINT is not in the bundle; the full launch command is unknown`,
184194
- evidence: [evidence]
184195
- });
184196
- }
184197
- runtime.entrypoints.push(entry);
184198
- }
184199
- byRoot.set(key, runtime);
184200
- } else {
184201
- ctx.gaps.push({ kind: "unrecognized_service", message: `Compose service \`${name}\` uses image \`${image}\`, which maps to no known provider`, evidence: [evidence] });
184202
- }
184496
+ ctx.gaps.push({
184497
+ kind: "unrecognized_service",
184498
+ message: `Compose service \`${name}\` runs prebuilt image \`${image}\`, which maps to no known provider; treated as a sidecar, not a harness entrypoint`,
184499
+ evidence: [evidence]
184500
+ });
184203
184501
  }
184204
184502
  }
184205
184503
  const env = svc.environment;
@@ -184223,7 +184521,8 @@ function composeServices(ctx, file, byRoot, contributions, relocated) {
184223
184521
  source: `${file.path} services.${name}.environment`,
184224
184522
  value: literal,
184225
184523
  evidence: { path: file.path, line: l, excerpt: l ? excerpt(lines[l - 1]) : undefined, detector: `${DETECTOR6}.compose` },
184226
- scopeRoot: serviceRoot
184524
+ scopeRoot: serviceRoot,
184525
+ service: name
184227
184526
  });
184228
184527
  }
184229
184528
  }
@@ -184246,14 +184545,21 @@ var ROOT_MARKERS = new Set([
184246
184545
  "host.json",
184247
184546
  "deno.json",
184248
184547
  "deno.jsonc",
184249
- "Cargo.toml"
184548
+ "Cargo.toml",
184549
+ "project.json"
184250
184550
  ]);
184251
184551
  function projectRoots(files) {
184252
- const out = new Set;
184253
- for (const f of files)
184254
- if (ROOT_MARKERS.has(basename2(f.path)))
184255
- out.add(dirname2(f.path));
184256
- return [...out].sort();
184552
+ const markers = new Map;
184553
+ for (const f of files) {
184554
+ if (!ROOT_MARKERS.has(basename2(f.path)))
184555
+ continue;
184556
+ const dir = dirname2(f.path);
184557
+ if (!markers.has(dir))
184558
+ markers.set(dir, new Set);
184559
+ markers.get(dir).add(basename2(f.path));
184560
+ }
184561
+ const pyProjects = [...markers.entries()].filter(([, m]) => m.has("pyproject.toml") || m.has("setup.py")).map(([d]) => d);
184562
+ return [...markers.entries()].filter(([dir, m]) => !(m.size === 1 && m.has("requirements.txt") && pyProjects.some((p2) => p2 !== dir && isUnder(dir, p2)))).map(([dir]) => dir).sort();
184257
184563
  }
184258
184564
  function nearestRoot(path, roots) {
184259
184565
  let best;
@@ -184291,6 +184597,15 @@ function cloudRunMarkers(ctx) {
184291
184597
  out.push({ root: dirname2(file.path), path: file.path, line: findLine(file.text, /serving\.knative\.dev/) });
184292
184598
  continue;
184293
184599
  }
184600
+ if (name.endsWith(".tf")) {
184601
+ const services = (file.text.match(/resource\s+"google_cloud_run(?:_v2)?_service"/g) ?? []).length;
184602
+ if (services > 0) {
184603
+ const dir = dirname2(file.path);
184604
+ const root = /^(?:terraform|infra|infrastructure|deploy|deployment)$/.test(basename2(dir)) ? dirname2(dir) : dir;
184605
+ out.push({ root, path: file.path, line: findLine(file.text, /google_cloud_run/), services });
184606
+ }
184607
+ continue;
184608
+ }
184294
184609
  const re = /gcloud\s+(?:beta\s+)?run\s+(?:deploy|services\s+replace)\b([^\n]*)/g;
184295
184610
  let m;
184296
184611
  while ((m = re.exec(file.text)) !== null) {
@@ -184495,76 +184810,72 @@ function decoratedDefs(text, decorator) {
184495
184810
  // ../../packages/harness-bundle/src/detect/runtimes/azure.ts
184496
184811
  var DETECTOR8 = "runtime.azure";
184497
184812
  var BINDINGS = {
184498
- httpTrigger: { trigger: "http" },
184499
- timerTrigger: { trigger: "cron" },
184500
- queueTrigger: { trigger: "queue", dep: { capability: "queue", provider: "azure.queue", cls: "emulated" } },
184501
- queue: { dep: { capability: "queue", provider: "azure.queue", cls: "emulated" } },
184502
- blobTrigger: { trigger: "event", dep: { capability: "object_store", provider: "azure.blob", cls: "emulated" } },
184503
- blob: { dep: { capability: "object_store", provider: "azure.blob", cls: "emulated" } },
184813
+ http: { trigger: "http" },
184814
+ timer: { trigger: "cron" },
184815
+ queue: { trigger: "queue", dep: { capability: "queue", provider: "azure.queue", cls: "emulated" } },
184816
+ blob: { trigger: "event", dep: { capability: "object_store", provider: "azure.blob", cls: "emulated" } },
184504
184817
  table: { dep: { capability: "document", provider: "azure.table", cls: "emulated" } },
184505
- cosmosDBTrigger: { trigger: "event", dep: { capability: "document", provider: "azure.cosmos", cls: "emulated" } },
184506
- cosmosDB: { dep: { capability: "document", provider: "azure.cosmos", cls: "emulated" } },
184507
- serviceBusTrigger: { trigger: "queue", dep: { capability: "queue", provider: "azure.servicebus", cls: "emulated" } },
184508
- serviceBus: { dep: { capability: "queue", provider: "azure.servicebus", cls: "emulated" } },
184509
- eventHubTrigger: { trigger: "queue", dep: { capability: "queue", provider: "azure.eventhubs", cls: "emulated" } },
184510
- eventHub: { dep: { capability: "queue", provider: "azure.eventhubs", cls: "emulated" } },
184511
- eventGridTrigger: { trigger: "event" },
184512
- eventGrid: { dep: { capability: "queue", provider: "azure.eventgrid", cls: "saas" } },
184513
- sql: { dep: { capability: "database", provider: "mssql", cls: "engine" } },
184514
- sqlTrigger: { trigger: "event", dep: { capability: "database", provider: "mssql", cls: "engine" } }
184818
+ cosmosdb: { trigger: "event", dep: { capability: "document", provider: "azure.cosmos", cls: "emulated" } },
184819
+ servicebus: { trigger: "queue", dep: { capability: "queue", provider: "azure.servicebus", cls: "emulated" } },
184820
+ eventhub: { trigger: "queue", dep: { capability: "queue", provider: "azure.eventhubs", cls: "emulated" } },
184821
+ eventgrid: { trigger: "event", dep: { capability: "queue", provider: "azure.eventgrid", cls: "saas" } },
184822
+ sql: { trigger: "event", dep: { capability: "database", provider: "mssql", cls: "engine" } },
184823
+ durable_client: {},
184824
+ durable_orchestration: { trigger: "event" },
184825
+ durable_activity: { trigger: "event" },
184826
+ durable_entity: { trigger: "event" },
184827
+ embeddings_store: { dep: { capability: "vector_store", provider: "azure.search", cls: "saas" } },
184828
+ semantic_search: { dep: { capability: "vector_store", provider: "azure.search", cls: "saas" } }
184515
184829
  };
184516
- var PY_DECORATORS = {
184517
- route: "httpTrigger",
184518
- timer_trigger: "timerTrigger",
184519
- schedule: "timerTrigger",
184520
- queue_trigger: "queueTrigger",
184521
- queue_output: "queue",
184522
- blob_trigger: "blobTrigger",
184523
- blob_input: "blob",
184524
- blob_output: "blob",
184525
- cosmos_db_trigger: "cosmosDBTrigger",
184526
- cosmos_db_input: "cosmosDB",
184527
- cosmos_db_output: "cosmosDB",
184528
- service_bus_queue_trigger: "serviceBusTrigger",
184529
- service_bus_topic_trigger: "serviceBusTrigger",
184530
- service_bus_queue_output: "serviceBus",
184531
- service_bus_topic_output: "serviceBus",
184532
- event_hub_message_trigger: "eventHubTrigger",
184533
- event_hub_output: "eventHub",
184534
- event_grid_trigger: "eventGridTrigger",
184535
- event_grid_output: "eventGrid",
184536
- table_input: "table",
184537
- table_output: "table",
184538
- sql_input: "sql",
184539
- sql_output: "sql",
184540
- sql_trigger: "sqlTrigger"
184541
- };
184542
- var NODE_REGISTRATIONS = {
184543
- http: "httpTrigger",
184544
- get: "httpTrigger",
184545
- post: "httpTrigger",
184546
- put: "httpTrigger",
184547
- patch: "httpTrigger",
184548
- deleteRequest: "httpTrigger",
184549
- timer: "timerTrigger",
184550
- storageQueue: "queueTrigger",
184551
- storageBlob: "blobTrigger",
184552
- cosmosDB: "cosmosDBTrigger",
184553
- serviceBusQueue: "serviceBusTrigger",
184554
- serviceBusTopic: "serviceBusTrigger",
184555
- eventHub: "eventHubTrigger",
184556
- eventGrid: "eventGridTrigger",
184557
- sql: "sqlTrigger"
184830
+ var TYPE_ALIASES = {
184831
+ httptrigger: "http",
184832
+ route: "http",
184833
+ get: "http",
184834
+ post: "http",
184835
+ put: "http",
184836
+ patch: "http",
184837
+ deleterequest: "http",
184838
+ timertrigger: "timer",
184839
+ schedule: "timer",
184840
+ queuetrigger: "queue",
184841
+ storagequeue: "queue",
184842
+ blobtrigger: "blob",
184843
+ storageblob: "blob",
184844
+ cosmosdbtrigger: "cosmosdb",
184845
+ cosmos_db: "cosmosdb",
184846
+ servicebustrigger: "servicebus",
184847
+ service_bus_queue: "servicebus",
184848
+ service_bus_topic: "servicebus",
184849
+ servicebusqueue: "servicebus",
184850
+ servicebustopic: "servicebus",
184851
+ eventhubtrigger: "eventhub",
184852
+ event_hub_message: "eventhub",
184853
+ event_hub: "eventhub",
184854
+ eventgridtrigger: "eventgrid",
184855
+ event_grid: "eventgrid",
184856
+ sqltrigger: "sql",
184857
+ durableclient: "durable_client",
184858
+ orchestrationtrigger: "durable_orchestration",
184859
+ orchestration: "durable_orchestration",
184860
+ activitytrigger: "durable_activity",
184861
+ activity: "durable_activity",
184862
+ entitytrigger: "durable_entity",
184863
+ entity: "durable_entity"
184558
184864
  };
184865
+ function azureBindingType(raw) {
184866
+ const key = raw.toLowerCase();
184867
+ const t = TYPE_ALIASES[key] ?? key;
184868
+ return t in BINDINGS ? t : undefined;
184869
+ }
184559
184870
  var NODE_IO = {
184560
184871
  storageBlob: "blob",
184561
184872
  storageQueue: "queue",
184562
184873
  table: "table",
184563
- cosmosDB: "cosmosDB",
184564
- serviceBusQueue: "serviceBus",
184565
- serviceBusTopic: "serviceBus",
184566
- eventHub: "eventHub",
184567
- eventGrid: "eventGrid",
184874
+ cosmosDB: "cosmosdb",
184875
+ serviceBusQueue: "servicebus",
184876
+ serviceBusTopic: "servicebus",
184877
+ eventHub: "eventhub",
184878
+ eventGrid: "eventgrid",
184568
184879
  sql: "sql"
184569
184880
  };
184570
184881
  function detectAzure(input) {
@@ -184583,12 +184894,12 @@ function detectAzure(input) {
184583
184894
  const seenDeps = new Set;
184584
184895
  const addDep = (type, name, rawConnection, evidence) => {
184585
184896
  const connection = rawConnection && /^[A-Za-z_][A-Za-z0-9_]*$/.test(rawConnection) ? rawConnection : undefined;
184897
+ if (!runtime.bindings.some((b) => b.name === name && b.type === type))
184898
+ runtime.bindings.push(connection ? { name, type, resource: connection } : { name, type });
184586
184899
  const spec = BINDINGS[type]?.dep;
184587
184900
  if (!spec)
184588
184901
  return;
184589
184902
  const key = `${spec.provider}:${connection ?? ""}`;
184590
- if (!runtime.bindings.some((b) => b.name === name && b.type === type))
184591
- runtime.bindings.push({ name, type, resource: connection });
184592
184903
  if (seenDeps.has(key))
184593
184904
  return;
184594
184905
  seenDeps.add(key);
@@ -184611,7 +184922,7 @@ function detectAzure(input) {
184611
184922
  continue;
184612
184923
  if (basename2(file.path) === "function.json") {
184613
184924
  handleFunctionJson(file, runtime, addDep);
184614
- } else if (file.path.endsWith(".py") && /azure\.functions|func\.FunctionApp|func\.Blueprint/.test(file.text)) {
184925
+ } else if (file.path.endsWith(".py") && /azure\.functions|azure\.durable_functions|func\.FunctionApp|func\.Blueprint|\bDFApp\(/.test(file.text)) {
184615
184926
  handlePython(file, runtime, addDep);
184616
184927
  } else if (/\.[cm]?[jt]s$/.test(file.path) && /@azure\/functions/.test(file.text)) {
184617
184928
  handleNode(file, runtime, addDep);
@@ -184632,17 +184943,22 @@ function handleFunctionJson(file, runtime, addDep) {
184632
184943
  for (const b of asArray(json.bindings)) {
184633
184944
  if (!isRecord2(b))
184634
184945
  continue;
184635
- const type = asString(b.type) ?? "";
184636
- const spec = BINDINGS[type];
184637
- if (spec?.trigger)
184638
- trigger = spec.trigger;
184946
+ const raw = asString(b.type) ?? "";
184947
+ const type = azureBindingType(raw);
184948
+ if (!type)
184949
+ continue;
184950
+ if (/trigger$/i.test(raw) && BINDINGS[type].trigger)
184951
+ trigger = BINDINGS[type].trigger;
184639
184952
  const name = asString(b.name) ?? type;
184640
- addDep(type, name, asString(b.connection) ?? (type.startsWith("blob") || type.startsWith("queue") ? "AzureWebJobsStorage" : undefined), evidenceAt(file.path, file.text, findLine(file.text, new RegExp(`"${escapeRe(type)}"`)), DETECTOR8));
184953
+ addDep(type, name, asString(b.connection) ?? defaultConnection(type), evidenceAt(file.path, file.text, findLine(file.text, new RegExp(`"${escapeRe(raw)}"`)), DETECTOR8));
184641
184954
  }
184642
184955
  const script = asString(json.scriptFile);
184643
184956
  runtime.entrypoints.push({ name: fnName, handler: script ? joinPath(dirname2(file.path), script) : dirname2(file.path), trigger });
184644
184957
  runtime.language ??= script ? languageFromPath(script) : undefined;
184645
184958
  }
184959
+ function defaultConnection(type) {
184960
+ return type === "blob" || type === "queue" || type === "table" ? "AzureWebJobsStorage" : undefined;
184961
+ }
184646
184962
  function parenDepth(text) {
184647
184963
  let depth = 0;
184648
184964
  for (const ch of text.replace(/'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"|#[^\n]*/g, "")) {
@@ -184665,24 +184981,34 @@ function handlePython(file, runtime, addDep) {
184665
184981
  while (parenDepth(lines.slice(start, i + 1).join(`
184666
184982
  `)) > 0 && i + 1 < lines.length)
184667
184983
  i++;
184668
- deco[2] = [deco[2], ...lines.slice(start + 1, i + 1)].join(`
184984
+ const args = [deco[2], ...lines.slice(start + 1, i + 1)].join(`
184669
184985
  `);
184670
184986
  const kind = deco[1];
184671
184987
  if (kind === "function_name")
184672
- pending.name = /name\s*=\s*['"]([^'"]+)['"]/.exec(deco[2])?.[1];
184673
- const type = PY_DECORATORS[kind];
184988
+ pending.name = /name\s*=\s*['"]([^'"]+)['"]/.exec(args)?.[1];
184989
+ const stem = kind === "route" || kind === "schedule" ? kind : /^(\w+?)_(?:trigger|input|output)$/.exec(kind)?.[1];
184990
+ const type = stem ? azureBindingType(stem) : undefined;
184674
184991
  if (type) {
184675
- if (BINDINGS[type]?.trigger)
184992
+ const isTrigger = kind === "route" || kind === "schedule" || kind.endsWith("_trigger");
184993
+ if (isTrigger && BINDINGS[type].trigger)
184676
184994
  pending.trigger = BINDINGS[type].trigger;
184677
184995
  if (kind === "route")
184678
- pending.name ??= /route\s*=\s*['"]([^'"]+)['"]/.exec(deco[2])?.[1];
184679
- const connection = /connection\s*=\s*['"]([^'"]+)['"]/.exec(deco[2])?.[1];
184680
- addDep(type, kind, connection ?? (type.startsWith("blob") || type.startsWith("queue") ? "AzureWebJobsStorage" : undefined), { path: file.path, line: start + 1, excerpt: excerpt(line), detector: DETECTOR8 });
184996
+ pending.name ??= /route\s*=\s*['"]([^'"]+)['"]/.exec(args)?.[1];
184997
+ const connection = /connection\s*=\s*['"]([^'"]+)['"]/.exec(args)?.[1] ?? defaultConnection(type);
184998
+ const argName = /\b(?:arg_name|client_name|context_name|input_name|name)\s*=\s*['"]([^'"]+)['"]/.exec(args)?.[1];
184999
+ const evidence = { path: file.path, line: start + 1, excerpt: excerpt(line), detector: DETECTOR8 };
185000
+ if (type === "http" && !argName)
185001
+ pending.httpBinding = { line: start + 1, connection };
185002
+ else
185003
+ addDep(type, argName ?? kind, connection, evidence);
184681
185004
  }
184682
185005
  continue;
184683
185006
  }
184684
- const def = /^\s*(?:async\s+)?def\s+(\w+)/.exec(line);
185007
+ const def = /^\s*(?:async\s+)?def\s+(\w+)\s*\(\s*(\w+)?/.exec(line);
184685
185008
  if (def && pending) {
185009
+ if (pending.httpBinding) {
185010
+ addDep("http", def[2] ?? "req", undefined, { path: file.path, line: pending.httpBinding.line, excerpt: excerpt(lines[pending.httpBinding.line - 1]), detector: DETECTOR8 });
185011
+ }
184686
185012
  if (pending.trigger) {
184687
185013
  runtime.entrypoints.push({ name: pending.name ?? def[1], handler: `${file.path}#${def[1]}`, trigger: pending.trigger });
184688
185014
  runtime.language ??= "python";
@@ -184696,10 +185022,10 @@ function handlePython(file, runtime, addDep) {
184696
185022
  function handleNode(file, runtime, addDep) {
184697
185023
  const at = lineIndex(file.text);
184698
185024
  const lines = splitLines(file.text);
184699
- const reg = /\bapp\.(\w+)\(\s*['"]([^'"]+)['"]/g;
185025
+ const reg = /\b(?:app|df\.app)\.(\w+)\(\s*['"]([^'"]+)['"]/g;
184700
185026
  let m;
184701
185027
  while ((m = reg.exec(file.text)) !== null) {
184702
- const type = NODE_REGISTRATIONS[m[1]];
185028
+ const type = azureBindingType(m[1]);
184703
185029
  if (!type)
184704
185030
  continue;
184705
185031
  const line = at(m.index);
@@ -184707,7 +185033,7 @@ function handleNode(file, runtime, addDep) {
184707
185033
  runtime.language ??= languageFromPath(file.path);
184708
185034
  const window2 = callArgs(file.text, m.index);
184709
185035
  const connection = /connection\s*:\s*['"]([^'"]+)['"]/.exec(window2)?.[1];
184710
- addDep(type, m[2], connection ?? (type.startsWith("blob") || type.startsWith("queue") ? "AzureWebJobsStorage" : undefined), { path: file.path, line, excerpt: excerpt(lines[line - 1]), detector: DETECTOR8 });
185036
+ addDep(type, m[2], connection ?? defaultConnection(type), { path: file.path, line, excerpt: excerpt(lines[line - 1]), detector: DETECTOR8 });
184711
185037
  }
184712
185038
  const io = /\b(?:input|output)\.(\w+)\(\s*\{/g;
184713
185039
  while ((m = io.exec(file.text)) !== null) {
@@ -184717,7 +185043,8 @@ function handleNode(file, runtime, addDep) {
184717
185043
  const line = at(m.index);
184718
185044
  const window2 = callArgs(file.text, m.index);
184719
185045
  const connection = /connection\s*:\s*['"]([^'"]+)['"]/.exec(window2)?.[1];
184720
- addDep(type, m[1], connection ?? (type === "blob" || type === "queue" || type === "table" ? "AzureWebJobsStorage" : undefined), { path: file.path, line, excerpt: excerpt(lines[line - 1]), detector: DETECTOR8 });
185046
+ const name = /(?:const|let|var)\s+(\w+)\s*=\s*$/.exec(file.text.slice(Math.max(0, m.index - 80), m.index))?.[1] ?? m[1];
185047
+ addDep(type, name, connection ?? defaultConnection(type), { path: file.path, line, excerpt: excerpt(lines[line - 1]), detector: DETECTOR8 });
184721
185048
  }
184722
185049
  }
184723
185050
 
@@ -184771,7 +185098,10 @@ function detectProcesses(input, roots) {
184771
185098
  continue;
184772
185099
  if (json.workspaces && !start && !bin)
184773
185100
  continue;
184774
- const rt = upsert(map, "node_process", pkg.root, pkg.path);
185101
+ const runsOnBun = /^\s*bun\s+(?:run\s+|--\S+\s+)*[\w./-]+\.[cm]?[jt]sx?\b/.test(start ?? asString(scripts.dev) ?? "");
185102
+ const rt = upsert(map, runsOnBun ? "other_process" : "node_process", pkg.root, pkg.path);
185103
+ if (runsOnBun)
185104
+ rt.language = "typescript";
184775
185105
  const pkgFile = ctx.byPath.get(pkg.path);
184776
185106
  rt.evidence.push(evidenceAt(pkg.path, pkgFile?.text ?? "", findLine(pkgFile?.text ?? "", /"(start|bin|main)"/), DETECTOR9));
184777
185107
  const trigger = server2 ? "http" : bin ? "cli" : "unknown";
@@ -184956,6 +185286,10 @@ function pythonFile(file, map, roots) {
184956
185286
  hits.push({ entry: { name: `mcp ${mod}:${m[1]}`, handler: `${file.path}:${m[1]}`, command: ["python", fromRoot(file.path, root)], trigger: /sse|streamable_http/.test(text) ? "http" : "cli" }, offset: m.index, detector: `${DETECTOR9}.mcp` });
184957
185287
  }
184958
185288
  }
185289
+ const agentcore = /\bBedrockAgentCoreApp\s*\(/.exec(text);
185290
+ if (agentcore && /bedrock_agentcore/.test(text)) {
185291
+ hits.push({ entry: { name: `agentcore ${file.path}`, handler: file.path, command: ["python", rel], trigger: "http" }, offset: agentcore.index, detector: `${DETECTOR9}.agentcore` });
185292
+ }
184959
185293
  const main = /^if\s+__name__\s*==\s*['"]__main__['"]\s*:/m.exec(text);
184960
185294
  if (main && hits.length === 0) {
184961
185295
  const serves = /uvicorn\.run|\.run\(\s*(?:host|port|debug)|app\.run\(|web\.run_app|serve_forever/.test(text);
@@ -184972,10 +185306,24 @@ function pythonFile(file, map, roots) {
184972
185306
  }
184973
185307
  }
184974
185308
  }
185309
+ var SUPABASE_EDGE = /^(.*?supabase\/functions)\/([^_./][^/]*)\/index\.[cm]?[jt]s$/;
184975
185310
  function nodeFile(file, map, roots) {
184976
185311
  const text = file.text;
185312
+ const edge = SUPABASE_EDGE.exec(file.path);
185313
+ if (edge) {
185314
+ const rt2 = upsert(map, "other_process", edge[1]);
185315
+ rt2.language = "typescript";
185316
+ rt2.frameworks = [...new Set([...rt2.frameworks ?? [], "supabase-edge-functions"])].sort();
185317
+ addEntry(rt2, { name: `edge ${edge[2]}`, handler: file.path, command: ["supabase", "functions", "serve", edge[2]], trigger: "http" });
185318
+ if (rt2.evidence.length < 5)
185319
+ rt2.evidence.push(evidenceAt(file.path, text, 1, `${DETECTOR9}.supabase_edge`));
185320
+ return;
185321
+ }
184977
185322
  const hits = [];
184978
185323
  let m;
185324
+ if (/bedrock-agentcore/.test(text) && (m = /\bBedrockAgentCoreApp\b|\.entrypoint\s*\(/.exec(text))) {
185325
+ hits.push({ entry: { name: `agentcore ${file.path}`, handler: file.path, trigger: "http" }, offset: m.index, detector: `${DETECTOR9}.agentcore` });
185326
+ }
184979
185327
  if (/@modelcontextprotocol\/sdk/.test(text) && (m = /new\s+(McpServer|Server)\s*\(/.exec(text))) {
184980
185328
  const http = /StreamableHTTPServerTransport|SSEServerTransport/.test(text);
184981
185329
  hits.push({ entry: { name: `mcp ${file.path}`, handler: file.path, trigger: http ? "http" : "cli" }, offset: m.index, detector: `${DETECTOR9}.mcp` });
@@ -184985,10 +185333,15 @@ function nodeFile(file, map, roots) {
184985
185333
  }
184986
185334
  if (m = /\b(?:app|server|fastify)\.listen\s*\(|\bserve\(\s*\{\s*fetch|\bBun\.serve\s*\(|\bDeno\.serve\s*\(|\bcreateServer\s*\([^)]*\)\s*\.listen/.exec(text)) {
184987
185335
  hits.push({ entry: { name: `listen ${file.path}`, handler: file.path, trigger: "http" }, offset: m.index, detector: `${DETECTOR9}.listen` });
185336
+ const bun = /\bBun\.serve\s*\(/.exec(text);
185337
+ if (bun && /\bwebsocket\s*[:,}]/.test(callArgs(text, bun.index + bun[0].length - 1, 50000))) {
185338
+ hits.push({ entry: { name: `websocket ${file.path}`, handler: file.path, trigger: "websocket" }, offset: bun.index, detector: `${DETECTOR9}.listen` });
185339
+ }
184988
185340
  }
184989
185341
  if (hits.length === 0)
184990
185342
  return;
184991
- const rt = upsert(map, "node_process", nearestRoot(file.path, roots));
185343
+ const kind = /\bBun\.serve\s*\(/.test(text) ? "other_process" : "node_process";
185344
+ const rt = upsert(map, kind, nearestRoot(file.path, roots));
184992
185345
  rt.language = languageFromPath(file.path) ?? rt.language;
184993
185346
  const at = lineIndex(text);
184994
185347
  const lines = splitLines(text);
@@ -185068,7 +185421,9 @@ function detectVercel(input) {
185068
185421
  if (info.next && /^(?:src\/)?app\/(?:.*\/)?route\.[cm]?[jt]sx?$/.test(r)) {
185069
185422
  const methods = exportedMethods(f.text);
185070
185423
  const path = routePath(r.replace(/^src\//, "").replace(/^app\//, ""), "app");
185071
- entries.push({ name: `${methods.join(",") || "ANY"} ${path}`, handler: f.path, trigger: "http" });
185424
+ entries.push({ name: `${methods.length > 0 ? methods.join(",") : "ANY"} ${path}`, handler: f.path, trigger: "http" });
185425
+ } else if (info.next && /^(?:src\/)?middleware\.[cm]?[jt]s$/.test(r)) {
185426
+ entries.push({ name: "middleware", handler: `${f.path}#middleware`, trigger: "http" });
185072
185427
  } else if (info.next && /^(?:src\/)?pages\/api\/.+\.[cm]?[jt]sx?$/.test(r)) {
185073
185428
  entries.push({ name: `ANY ${routePath(r.replace(/^src\//, "").replace(/^pages\//, ""), "pages")}`, handler: f.path, trigger: "http" });
185074
185429
  } else if (!info.next && /^api\/.+\.([cm]?[jt]s|py|go)$/.test(r) && !/\/_/.test(r)) {
@@ -185097,11 +185452,277 @@ function detectVercel(input) {
185097
185452
  function rel(path, root) {
185098
185453
  return root === "." ? path : path.slice(root.length + 1);
185099
185454
  }
185455
+
185456
+ // ../../packages/harness-bundle/src/detect/scope.ts
185457
+ var DEPLOY_CONFIG = /^(?:wrangler\.(?:toml|json|jsonc)|template\.ya?ml|serverless\.ya?ml|vercel\.json|firebase\.json|cloudbuild\.ya?ml|service\.ya?ml|cdk\.json|langgraph\.json|Procfile|render\.ya?ml|fly\.toml|railway\.(?:json|toml)|netlify\.toml|(?:docker-)?compose(?:\.[\w-]+)*\.ya?ml)$/;
185458
+ var PROJECT_MANIFEST2 = /^(?:package\.json|pyproject\.toml|requirements\.txt|go\.mod|Cargo\.toml|Gemfile)$/;
185459
+ var ANCESTOR_ORM = [
185460
+ { uses: /@prisma\/client|\bPrismaClient\b/, files: /^(?:prisma\/.+|[\w.-]+\.prisma|(?:\.config\/)?prisma(?:\.config)?\.[cm]?[jt]s)$/ },
185461
+ { uses: /\bdrizzle-orm\b/, files: /^drizzle\.config\.[cm]?[jt]s$/ },
185462
+ { uses: /\b(?:sqlalchemy|sqlmodel|alembic)\b/, files: /^(?:alembic\.ini|alembic\/.+)$/ }
185463
+ ];
185464
+ var CODE_FILE = /\.(?:[cm]?[jt]sx?|py)$/;
185465
+ function pyDependencyNames(doc) {
185466
+ const out = [];
185467
+ const project = isRecord2(doc.project) ? doc.project : {};
185468
+ for (const req of asArray(project.dependencies)) {
185469
+ const m = typeof req === "string" ? /^\s*([A-Za-z0-9][\w.-]*)/.exec(req) : null;
185470
+ if (m)
185471
+ out.push(m[1]);
185472
+ }
185473
+ const poetry = isRecord2(doc.tool) && isRecord2(doc.tool.poetry) ? doc.tool.poetry : {};
185474
+ if (isRecord2(poetry.dependencies))
185475
+ out.push(...Object.keys(poetry.dependencies));
185476
+ return out.map(pyName);
185477
+ }
185478
+ function pyName(name) {
185479
+ return name.toLowerCase().replace(/[_.]/g, "-");
185480
+ }
185481
+ function npmDeps(json) {
185482
+ const out = [];
185483
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
185484
+ const deps = json[field];
185485
+ if (isRecord2(deps))
185486
+ out.push(...Object.keys(deps));
185487
+ }
185488
+ return out;
185489
+ }
185490
+ function tomlDoc(file) {
185491
+ if (!file)
185492
+ return;
185493
+ try {
185494
+ return parse(file.text);
185495
+ } catch {
185496
+ return;
185497
+ }
185498
+ }
185499
+ function directDeps(dir, byPath, npmNames, pyNames, goModules, workspaces) {
185500
+ const out = [];
185501
+ const pkg = byPath.get(joinPath(dir, "package.json"));
185502
+ if (pkg) {
185503
+ const json = parseJsonLoose(pkg.text);
185504
+ if (isRecord2(json)) {
185505
+ for (const name of npmDeps(json)) {
185506
+ const target = npmNames.get(name);
185507
+ if (target !== undefined)
185508
+ out.push(target);
185509
+ }
185510
+ }
185511
+ }
185512
+ const pyproject = tomlDoc(byPath.get(joinPath(dir, "pyproject.toml")));
185513
+ if (pyproject) {
185514
+ const tool = isRecord2(pyproject.tool) ? pyproject.tool : {};
185515
+ const uv = isRecord2(tool.uv) ? tool.uv : {};
185516
+ const sources = isRecord2(uv.sources) ? uv.sources : {};
185517
+ for (const [name, src] of Object.entries(sources)) {
185518
+ if (!isRecord2(src))
185519
+ continue;
185520
+ if (src.workspace === true) {
185521
+ const target = pyNames.get(pyName(name));
185522
+ if (target !== undefined)
185523
+ out.push(target);
185524
+ }
185525
+ const path = asString(src.path);
185526
+ if (path)
185527
+ out.push(joinPath(dir, path));
185528
+ }
185529
+ for (const ws of workspaces) {
185530
+ if (!isUnder(dir, ws.root))
185531
+ continue;
185532
+ for (const name of pyDependencyNames(pyproject)) {
185533
+ const target = ws.sources.get(name);
185534
+ if (target !== undefined)
185535
+ out.push(target);
185536
+ }
185537
+ }
185538
+ const poetry = isRecord2(tool.poetry) ? tool.poetry : {};
185539
+ const groups = [poetry.dependencies, ...isRecord2(poetry.group) ? Object.values(poetry.group).map((g) => isRecord2(g) ? g.dependencies : undefined) : []];
185540
+ for (const deps of groups) {
185541
+ if (!isRecord2(deps))
185542
+ continue;
185543
+ for (const spec of Object.values(deps)) {
185544
+ const path = isRecord2(spec) ? asString(spec.path) : undefined;
185545
+ if (path)
185546
+ out.push(joinPath(dir, path));
185547
+ }
185548
+ }
185549
+ }
185550
+ for (const name of ["requirements.txt", "requirements.in"]) {
185551
+ const req = byPath.get(joinPath(dir, name));
185552
+ if (!req)
185553
+ continue;
185554
+ for (const line of splitLines(req.text)) {
185555
+ const m = /^\s*(?:-e\s+)?(\.{1,2}\/[^\s#;]+)/.exec(line);
185556
+ if (m)
185557
+ out.push(joinPath(dir, m[1]));
185558
+ }
185559
+ }
185560
+ const gomod = byPath.get(joinPath(dir, "go.mod"));
185561
+ if (gomod) {
185562
+ for (const m of gomod.text.matchAll(/=>\s*(\.{1,2}\/\S+)/g))
185563
+ out.push(joinPath(dir, m[1]));
185564
+ for (const m of gomod.text.matchAll(/^\s*(?:require\s+)?([\w.-]+\.[\w./-]+)\s+v[\w.+-]+/gm)) {
185565
+ const target = goModules.get(m[1]);
185566
+ if (target !== undefined)
185567
+ out.push(target);
185568
+ }
185569
+ const own = /^module\s+(\S+)/m.exec(gomod.text)?.[1];
185570
+ const others = [...goModules].filter(([mod]) => mod !== own);
185571
+ if (others.length > 0) {
185572
+ for (const f of byPath.values()) {
185573
+ if (!f.path.endsWith(".go") || !isUnder(f.path, dir))
185574
+ continue;
185575
+ for (const [mod, target] of others)
185576
+ if (f.text.includes(`"${mod}"`) || f.text.includes(`"${mod}/`))
185577
+ out.push(target);
185578
+ }
185579
+ }
185580
+ }
185581
+ return out;
185582
+ }
185583
+ function composeBuildsUnder(svc, base, root) {
185584
+ if (!isRecord2(svc) || svc.build === undefined)
185585
+ return false;
185586
+ const build = svc.build;
185587
+ const context = joinPath(base, typeof build === "string" ? build : isRecord2(build) ? asString(build.context) ?? "." : ".");
185588
+ if (isUnder(context, root))
185589
+ return true;
185590
+ const dockerfile = isRecord2(build) ? asString(build.dockerfile) : undefined;
185591
+ return dockerfile !== undefined && isUnder(joinPath(context, dockerfile), root);
185592
+ }
185593
+ function computeScope(files, root) {
185594
+ const byPath = new Map(files.map((f) => [f.path, f]));
185595
+ if (root === ".")
185596
+ return { root, closure: [], configs: [] };
185597
+ const npmNames = new Map;
185598
+ const pyNames = new Map;
185599
+ const goModules = new Map;
185600
+ for (const f of files) {
185601
+ const name = basename2(f.path);
185602
+ if (name === "package.json") {
185603
+ const json = parseJsonLoose(f.text);
185604
+ if (isRecord2(json) && typeof json.name === "string")
185605
+ npmNames.set(json.name, dirname2(f.path));
185606
+ } else if (name === "pyproject.toml") {
185607
+ const doc = tomlDoc(f);
185608
+ const project = doc && isRecord2(doc.project) ? doc.project : undefined;
185609
+ const poetry = doc && isRecord2(doc.tool) && isRecord2(doc.tool.poetry) ? doc.tool.poetry : undefined;
185610
+ const projectName = asString(project?.name) ?? asString(poetry?.name);
185611
+ if (projectName)
185612
+ pyNames.set(pyName(projectName), dirname2(f.path));
185613
+ } else if (name === "go.mod") {
185614
+ const mod = /^module\s+(\S+)/m.exec(f.text)?.[1];
185615
+ if (mod)
185616
+ goModules.set(mod, dirname2(f.path));
185617
+ }
185618
+ }
185619
+ const workspaces = [];
185620
+ for (const f of files) {
185621
+ if (basename2(f.path) !== "pyproject.toml")
185622
+ continue;
185623
+ const doc = tomlDoc(f);
185624
+ const uv = doc && isRecord2(doc.tool) && isRecord2(doc.tool.uv) ? doc.tool.uv : undefined;
185625
+ if (!uv || !isRecord2(uv.workspace))
185626
+ continue;
185627
+ const base = dirname2(f.path);
185628
+ const members = asArray(uv.workspace.members).filter((m) => typeof m === "string").map((m) => globRegex(joinPath(base, m)));
185629
+ if (!members.some((re) => re.test(root) || [...pyNames.values()].some((d) => isUnder(root, d) && re.test(d))))
185630
+ continue;
185631
+ const sources = new Map;
185632
+ for (const [name, src] of Object.entries(isRecord2(uv.sources) ? uv.sources : {})) {
185633
+ if (!isRecord2(src))
185634
+ continue;
185635
+ const target = src.workspace === true ? pyNames.get(pyName(name)) : asString(src.path) ? joinPath(base, asString(src.path)) : undefined;
185636
+ if (target !== undefined)
185637
+ sources.set(pyName(name), target);
185638
+ }
185639
+ workspaces.push({ root: base, sources });
185640
+ }
185641
+ const MANIFEST = /^(?:package\.json|pyproject\.toml|requirements\.(?:txt|in)|go\.mod)$/;
185642
+ const nested = files.filter((f) => MANIFEST.test(basename2(f.path)) && isUnder(f.path, root) && !/(^|\/)node_modules\//.test(f.path)).map((f) => dirname2(f.path));
185643
+ const closure = new Set;
185644
+ const queue = [...new Set([root, ...nested])];
185645
+ while (queue.length > 0) {
185646
+ const dir = queue.shift();
185647
+ for (const dep of directDeps(dir, byPath, npmNames, pyNames, goModules, workspaces)) {
185648
+ if (dep === root || isUnder(dep, root) || closure.has(dep) || dep === ".")
185649
+ continue;
185650
+ closure.add(dep);
185651
+ queue.push(dep);
185652
+ }
185653
+ }
185654
+ const owned = (p2) => isUnder(p2, root) || [...closure].some((c) => isUnder(p2, c));
185655
+ const ancestors = [];
185656
+ for (let d = dirname2(root);; d = dirname2(d)) {
185657
+ ancestors.push(d);
185658
+ if (d === ".")
185659
+ break;
185660
+ }
185661
+ const harnessFiles = files.filter((f) => owned(f.path));
185662
+ const configs = new Set;
185663
+ if (!harnessFiles.some((f) => dirname2(f.path) === root && PROJECT_MANIFEST2.test(basename2(f.path)))) {
185664
+ const dir = ancestors.find((a) => files.some((f) => dirname2(f.path) === a && PROJECT_MANIFEST2.test(basename2(f.path))));
185665
+ for (const f of files)
185666
+ if (dir !== undefined && dirname2(f.path) === dir && PROJECT_MANIFEST2.test(basename2(f.path)))
185667
+ configs.add(f.path);
185668
+ }
185669
+ const users = [...harnessFiles, ...[...configs].map((p2) => byPath.get(p2))];
185670
+ const relToRoot = (p2) => p2.slice(root.length + 1);
185671
+ const orms = ANCESTOR_ORM.filter((o) => users.some((f) => (CODE_FILE.test(f.path) || PROJECT_MANIFEST2.test(basename2(f.path))) && o.uses.test(f.text)) && !files.some((f) => isUnder(f.path, root) && o.files.test(relToRoot(f.path))));
185672
+ const ormDirs = [];
185673
+ for (const f of files) {
185674
+ if (owned(f.path))
185675
+ continue;
185676
+ const name = basename2(f.path);
185677
+ const base = dirname2(f.path);
185678
+ if (ancestors.includes(base) && isEnvFile(f.path))
185679
+ configs.add(f.path);
185680
+ for (const a of ancestors) {
185681
+ if (!isUnder(f.path, a))
185682
+ continue;
185683
+ const rel3 = a === "." ? f.path : f.path.slice(a.length + 1);
185684
+ if (!orms.some((o) => o.files.test(rel3)))
185685
+ continue;
185686
+ configs.add(f.path);
185687
+ if (!/^(?:drizzle\.config\.|alembic\.ini$)/.test(name))
185688
+ continue;
185689
+ if (name.startsWith("drizzle.config.") && !/\bout\s*:/.test(f.text))
185690
+ ormDirs.push(joinPath(a, "drizzle"));
185691
+ for (const m of f.text.matchAll(/\b(?:out|schema|script_location)\s*[:=]\s*['"]?([\w./-]+)/g))
185692
+ ormDirs.push(joinPath(a, m[1].replace(/\/?\*.*$/, "")));
185693
+ }
185694
+ if (!DEPLOY_CONFIG.test(name) || !ancestors.includes(base))
185695
+ continue;
185696
+ const rel2 = base === "." ? root : root.slice(base.length + 1);
185697
+ if (/compose/.test(name)) {
185698
+ const doc = loadYaml(f.text);
185699
+ const services = isRecord2(doc) && isRecord2(doc.services) ? doc.services : {};
185700
+ if (Object.values(services).some((svc) => composeBuildsUnder(svc, base, root)))
185701
+ configs.add(f.path);
185702
+ } else if (new RegExp(`(?:^|[\\s"'=:/])\\.?/?${rel2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[/"'\\s]|$)`, "m").test(f.text)) {
185703
+ configs.add(f.path);
185704
+ }
185705
+ }
185706
+ for (const f of files)
185707
+ if (!owned(f.path) && ormDirs.some((d) => d !== "." && (f.path === d || isUnder(f.path, d))))
185708
+ configs.add(f.path);
185709
+ return { root, closure: [...closure].sort(), configs: [...configs].sort() };
185710
+ }
185711
+ var configSets = new WeakMap;
185712
+ function inScope(path, scope) {
185713
+ if (scope.root === ".")
185714
+ return true;
185715
+ let configs = configSets.get(scope);
185716
+ if (!configs)
185717
+ configSets.set(scope, configs = new Set(scope.configs));
185718
+ return isUnder(path, scope.root) || scope.closure.some((c) => isUnder(path, c)) || configs.has(path);
185719
+ }
185100
185720
  // ../../packages/harness-bundle/src/detect/index.ts
185101
185721
  var DETECTOR_NAME = "@moda/harness-bundle/detect";
185102
185722
  var DETECTOR_VERSION = "0.1.0";
185103
185723
  var SERVER_FRAMEWORKS = new Set(FRAMEWORKS.filter((f) => f.kind === "server").map((f) => f.name));
185104
185724
  var MIGRATION_ENGINE_PROVIDERS = new Set(["postgres", "mysql", "sqlite", "libsql", "mssql", "clickhouse", "cloudflare.d1", "supabase", "mongodb"]);
185725
+ var CLOSURE_CAPABILITIES = new Set(["database", "document", "kv", "vector_store", "object_store", "queue"]);
185105
185726
  var PLATFORM_KINDS = new Set(["cloudflare_workers", "aws_lambda", "gcp_cloud_functions", "gcp_cloud_run", "azure_functions", "vercel_functions"]);
185106
185727
  var PROCESS_KINDS = new Set(["node_process", "python_process", "other_process"]);
185107
185728
  function packageSignals(ctx, declared) {
@@ -185122,6 +185743,237 @@ function packageSignals(ctx, declared) {
185122
185743
  });
185123
185744
  }
185124
185745
  }
185746
+ function normalizeRoot(raw) {
185747
+ if (!raw)
185748
+ return ".";
185749
+ const root = joinPath(raw);
185750
+ return root === "" ? "." : root;
185751
+ }
185752
+ var RUNTIME_CONFIG = /^(?:wrangler\.(?:toml|jsonc?)|template\.ya?ml|serverless\.ya?ml|host\.json|vercel\.json|next\.config\.\w+|Dockerfile[\w.-]*|(?:docker-)?compose[\w.-]*\.ya?ml|langgraph\.json|firebase\.json|cdk\.json)$/;
185753
+ function outsideContext(files, scope) {
185754
+ const configs = files.filter((f) => RUNTIME_CONFIG.test(basename2(f.path))).map((f) => f.path);
185755
+ const providers = new Set;
185756
+ for (const d of collectDeclaredPackages(files)) {
185757
+ const m = matchModule(d.eco === "pypi" ? "pypi" : d.eco, d.name);
185758
+ if (m && !d.dev)
185759
+ providers.add(m.signature.provider);
185760
+ }
185761
+ const list = [...providers].sort();
185762
+ return {
185763
+ kind: "outside_harness_root",
185764
+ message: `${files.length} file(s) outside harness root ${scope.root}${scope.closure.length > 0 ? ` and its workspace dependencies (${scope.closure.join(", ")})` : ""} were not analyzed` + (configs.length > 0 ? `; runtime configs there: ${configs.slice(0, 5).join(", ")}${configs.length > 5 ? `, +${configs.length - 5} more` : ""}` : "") + (list.length > 0 ? `; providers declared there: ${list.slice(0, 10).join(", ")}${list.length > 10 ? `, +${list.length - 10} more` : ""}` : ""),
185765
+ evidence: configs.slice(0, 3).map((path) => ({ path, detector: "scope" }))
185766
+ };
185767
+ }
185768
+ function scopeRuntimes(ctx, runtimes, scope) {
185769
+ if (scope.root === ".")
185770
+ return runtimes;
185771
+ const out = [];
185772
+ for (const rt of runtimes) {
185773
+ if (isUnder(rt.root, scope.root)) {
185774
+ out.push(rt);
185775
+ continue;
185776
+ }
185777
+ if (!isUnder(scope.root, rt.root))
185778
+ continue;
185779
+ const pointsIn = (ep) => [ep.handler ?? "", ...ep.command ?? []].some((p2) => isUnder(joinPath(rt.root, p2.split(/[#:]/)[0]), scope.root) || isUnder(p2.split(/[#:]/)[0], scope.root));
185780
+ const entrypoints = rt.entrypoints.filter(pointsIn);
185781
+ if (entrypoints.length === 0)
185782
+ continue;
185783
+ const kept = new Set(entrypoints.map((e) => e.name));
185784
+ let bindings = rt.bindings;
185785
+ for (const [template, refs] of ctx.functionRefs) {
185786
+ if (!rt.configFiles.includes(template))
185787
+ continue;
185788
+ const dropped = [...refs.keys()].filter((fn) => !kept.has(fn));
185789
+ const allowed = new Set([...refs.entries()].filter(([fn]) => kept.has(fn)).flatMap(([, ids]) => [...ids]));
185790
+ ctx.env = ctx.env.filter((e) => !dropped.some((fn) => e.source === `${template} ${fn}`));
185791
+ ctx.signals = ctx.signals.filter((s) => s.kind !== "binding" || s.evidence.path !== template || allowed.has(s.binding ?? ""));
185792
+ bindings = bindings.filter((b) => allowed.has(b.name));
185793
+ }
185794
+ out.push({ ...rt, entrypoints, bindings });
185795
+ }
185796
+ return out;
185797
+ }
185798
+ function scopeComposeImages(ctx, scope) {
185799
+ const optional = new Set;
185800
+ for (const f of ctx.files) {
185801
+ if (!/^(?:docker-)?compose(?:\.[\w-]+)*\.ya?ml$/.test(basename2(f.path)))
185802
+ continue;
185803
+ const base = f.path.includes("/") ? f.path.slice(0, f.path.lastIndexOf("/")) : ".";
185804
+ if (base === scope.root)
185805
+ continue;
185806
+ const doc = loadYaml(f.text);
185807
+ const services = isRecord2(doc) && isRecord2(doc.services) ? Object.values(doc.services) : [];
185808
+ const builds = services.some((svc) => isRecord2(svc) && svc.build !== undefined);
185809
+ if (!builds)
185810
+ optional.add(f.path);
185811
+ }
185812
+ for (const s of ctx.signals)
185813
+ if (s.kind === "image" && optional.has(s.evidence.path))
185814
+ s.weak = true;
185815
+ if (scope.root === ".")
185816
+ return ctx.signals;
185817
+ const allowed = new Map;
185818
+ for (const path of scope.configs) {
185819
+ if (!/compose/.test(basename2(path)))
185820
+ continue;
185821
+ const doc = loadYaml(ctx.byPath.get(path)?.text ?? "");
185822
+ const services = isRecord2(doc) && isRecord2(doc.services) ? doc.services : {};
185823
+ const base = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : ".";
185824
+ const deps = new Set;
185825
+ let declared = false;
185826
+ for (const svc of Object.values(services)) {
185827
+ if (!isRecord2(svc) || !composeBuildsUnder(svc, base, scope.root))
185828
+ continue;
185829
+ const names = [...Array.isArray(svc.depends_on) ? svc.depends_on : isRecord2(svc.depends_on) ? Object.keys(svc.depends_on) : [], ...Array.isArray(svc.links) ? svc.links : []];
185830
+ for (const n of names)
185831
+ if (typeof n === "string") {
185832
+ declared = true;
185833
+ deps.add(n.split(":")[0]);
185834
+ }
185835
+ }
185836
+ if (declared)
185837
+ allowed.set(path, deps);
185838
+ }
185839
+ ctx.env = ctx.env.filter((e) => e.service === undefined || e.scopeRoot !== undefined || !e.evidence || !allowed.has(e.evidence.path) || allowed.get(e.evidence.path).has(e.service));
185840
+ return ctx.signals.filter((s) => s.kind !== "image" || !allowed.has(s.evidence.path) || allowed.get(s.evidence.path).has(s.resource ?? ""));
185841
+ }
185842
+ var SERVERLESS_KINDS = new Set(["cloudflare_workers", "aws_lambda", "gcp_cloud_functions", "azure_functions", "vercel_functions"]);
185843
+ function isAuxiliary(path, harnessRoot) {
185844
+ if (!isUnder(path, harnessRoot))
185845
+ return false;
185846
+ const rel2 = harnessRoot === "." ? path : path === harnessRoot ? "." : path.slice(harnessRoot.length + 1);
185847
+ return AUXILIARY_DIR.test(rel2);
185848
+ }
185849
+ var AUXILIARY_DIR = /(^|\/)(vendor|third_party|examples?|samples?|demos?|docs?|tests?|e2e|benchmarks?|fixtures|\.devops|\.github|cloud-deployments|hack|playground)(\/|$)/;
185850
+ function foldNestedRuntimes(ctx, runtimes, harnessRoot) {
185851
+ const rel2 = (root) => harnessRoot === "." ? root : root === harnessRoot ? "." : root.slice(harnessRoot.length + 1);
185852
+ const auxiliary = runtimes.filter((r) => isUnder(r.root, harnessRoot) && AUXILIARY_DIR.test(rel2(r.root)));
185853
+ if (auxiliary.length > 0) {
185854
+ ctx.gaps.push({
185855
+ kind: "auxiliary_runtime",
185856
+ message: `${auxiliary.length} runtime(s) under example, vendored, test, or deployment-variant directories were not treated as the harness: ${auxiliary.slice(0, 5).map((r) => `${r.kind}@${r.root}`).join(", ")}${auxiliary.length > 5 ? ", ..." : ""}`,
185857
+ evidence: auxiliary.slice(0, 3).map((r) => r.evidence[0]).filter((e) => e !== undefined)
185858
+ });
185859
+ }
185860
+ let out = runtimes.filter((r) => !auxiliary.includes(r));
185861
+ const merge = (into, from) => {
185862
+ into.configFiles = uniqSorted([...into.configFiles, ...from.configFiles]);
185863
+ into.frameworks = uniqSorted([...into.frameworks ?? [], ...from.frameworks ?? []]);
185864
+ for (const ep of from.entrypoints) {
185865
+ const moved = ep.command && from.root !== into.root ? { ...ep, command: rebaseCommand(ep.command, from.root, into.root, ctx) } : ep;
185866
+ if (into.entrypoints.some((e) => e.name === moved.name && e.handler === moved.handler && (e.command ?? []).join(" ") === (moved.command ?? []).join(" ")))
185867
+ continue;
185868
+ into.entrypoints.push(moved);
185869
+ }
185870
+ into.language ??= from.language;
185871
+ };
185872
+ for (const v of out.filter((r) => r.kind === "vercel_functions")) {
185873
+ const container = out.find((r) => (r.kind === "container" || r.kind === "gcp_cloud_run") && r.root === v.root);
185874
+ if (container && !v.configFiles.some((f) => basename2(f) === "vercel.json")) {
185875
+ merge(container, v);
185876
+ out = out.filter((r) => r !== v);
185877
+ }
185878
+ }
185879
+ for (const p2 of out.filter((r) => PROCESS_KINDS.has(r.kind))) {
185880
+ const owners = out.filter((o) => o !== p2 && !PROCESS_KINDS.has(o.kind) && isUnder(p2.root, o.root) && o.root !== p2.root).sort((a, b) => b.root.length - a.root.length);
185881
+ const owner = owners.find((o) => o.kind === "container" || o.kind === "gcp_cloud_run" || SERVERLESS_KINDS.has(o.kind) && !p2.entrypoints.some((e) => e.trigger === "http"));
185882
+ if (owner) {
185883
+ merge(owner, p2);
185884
+ out = out.filter((r) => r !== p2);
185885
+ }
185886
+ }
185887
+ return out;
185888
+ }
185889
+ function resolveCommandHandlers(rt, ctx) {
185890
+ const exists = (p2) => ctx.byPath.has(p2);
185891
+ const under = (p2) => joinPath(rt.root, p2.replace(/^\/(?:app|usr\/src\/app|code|workspace|srv)\//, ""));
185892
+ for (const ep of rt.entrypoints) {
185893
+ if (ep.handler || !ep.command)
185894
+ continue;
185895
+ const words = ep.command.flatMap((w) => w.split(/\s+/)).filter(Boolean);
185896
+ for (let i = 0;i < words.length && !ep.handler; i++) {
185897
+ const w = words[i];
185898
+ const mod = /^([A-Za-z_][\w.]*):([A-Za-z_]\w*)(?:\(\))?$/.exec(w) ?? (words[i - 1] === "-m" && /^[A-Za-z_][\w.]*$/.test(w) ? [w, w, ""] : null);
185899
+ if (mod) {
185900
+ const base = mod[1].replace(/\./g, "/");
185901
+ const file = [`${base}.py`, `${base}/__init__.py`, `${base}/__main__.py`, `src/${base}.py`].map(under).find(exists);
185902
+ if (file)
185903
+ ep.handler = mod[2] ? `${file}:${mod[2]}` : file;
185904
+ continue;
185905
+ }
185906
+ if (/\.(?:py|[cm]?[jt]sx?|sh)$/.test(w)) {
185907
+ const file = [under(w), joinPath(w)].find(exists);
185908
+ if (file)
185909
+ ep.handler = file;
185910
+ }
185911
+ }
185912
+ }
185913
+ }
185914
+ var INFERRED_ENTRY = /^(?:fastapi|starlette|litestar|quart|sanic|flask) /;
185915
+ function dedupeEntrypoints(rt) {
185916
+ const handlerFile = (h) => h.split(/[#:]/)[0];
185917
+ const kept = rt.entrypoints.filter((e) => !e.handler || !isAuxiliary(handlerFile(e.handler), rt.root));
185918
+ const pool = kept.length > 0 ? kept : rt.entrypoints;
185919
+ const commanded = new Set(pool.filter((e) => e.command && e.handler).map((e) => `${e.trigger}:${e.handler}`));
185920
+ const explicit = new Set(pool.filter((e) => e.command && e.handler && !INFERRED_ENTRY.test(e.name)).map((e) => `${e.trigger}:${e.handler}`));
185921
+ const seen = new Set;
185922
+ const out = [];
185923
+ for (const ep of pool) {
185924
+ if (!ep.command && ep.handler && commanded.has(`${ep.trigger}:${ep.handler}`))
185925
+ continue;
185926
+ if (ep.handler && INFERRED_ENTRY.test(ep.name) && explicit.has(`${ep.trigger}:${ep.handler}`))
185927
+ continue;
185928
+ const key = ep.command ? `${ep.trigger}:${ep.handler ?? ""}:${ep.command.join(" ")}` : `${ep.trigger}:${ep.handler ?? ""}:${ep.name}`;
185929
+ if (seen.has(key))
185930
+ continue;
185931
+ seen.add(key);
185932
+ out.push(ep);
185933
+ }
185934
+ return out;
185935
+ }
185936
+ function scriptLocalFilter(ctx, runtimes) {
185937
+ const scripts = ctx.files.filter((f) => /\.(?:sh|bash)$/.test(f.path));
185938
+ if (scripts.length === 0)
185939
+ return () => false;
185940
+ const launched = new Set(runtimes.flatMap((r) => r.entrypoints.flatMap((e) => [...(e.command ?? []).flatMap((w) => w.split(/[\s/;&|]+/)), basename2(e.handler ?? "")])));
185941
+ const runners = ctx.files.filter((f) => /^(?:Dockerfile[\w.-]*|Procfile|package\.json|azure\.ya?ml)$/.test(basename2(f.path)) || !f.isDoc && !f.isTest && languageOf(f.path) !== undefined).map((f) => f.text);
185942
+ for (const s of scripts)
185943
+ if (runners.some((t) => t.includes(basename2(s.path))))
185944
+ launched.add(basename2(s.path));
185945
+ for (let changed = true;changed; ) {
185946
+ changed = false;
185947
+ for (const s of scripts) {
185948
+ if (!launched.has(basename2(s.path)))
185949
+ continue;
185950
+ for (const m of s.text.matchAll(/(?:^|[\s;&|])(?:source|\.)\s+["']?([^\s"';&|]+\.(?:sh|bash))/gm)) {
185951
+ const name = basename2(m[1]);
185952
+ if (!launched.has(name)) {
185953
+ launched.add(name);
185954
+ changed = true;
185955
+ }
185956
+ }
185957
+ }
185958
+ }
185959
+ return (path) => /\.(?:sh|bash)$/.test(path) && !launched.has(basename2(path));
185960
+ }
185961
+ function rebaseCommand(command, fromRoot2, toRoot, ctx) {
185962
+ const sub = toRoot === "." ? fromRoot2 : fromRoot2.slice(toRoot.length + 1);
185963
+ let rebased = false;
185964
+ const out = command.map((arg) => {
185965
+ const path = joinPath(fromRoot2, arg);
185966
+ if (/[./]/.test(arg) && !arg.startsWith("-") && !arg.startsWith("/") && (ctx.byPath.has(path) || ctx.files.some((f) => f.path.startsWith(`${path}/`)))) {
185967
+ rebased = true;
185968
+ return joinPath(sub, arg);
185969
+ }
185970
+ return arg;
185971
+ });
185972
+ if (rebased)
185973
+ return out;
185974
+ const quote = (a) => /^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`;
185975
+ return ["sh", "-c", `cd ${quote(sub)} && ${command.map(quote).join(" ")}`];
185976
+ }
185125
185977
  function consolidateRuntimes(runtimes) {
185126
185978
  const out = [];
185127
185979
  const owners = runtimes.filter((r) => !PROCESS_KINDS.has(r.kind));
@@ -185151,8 +186003,17 @@ function applyCloudRun(ctx, runtimes) {
185151
186003
  for (const marker of markers) {
185152
186004
  const candidates = runtimes.filter((r) => (r.kind === "container" || PROCESS_KINDS.has(r.kind) || r.kind === "gcp_cloud_run") && isUnder(r.root, marker.root));
185153
186005
  const exact = candidates.filter((r) => r.root === marker.root);
185154
- const pool = exact.length > 0 ? exact : candidates;
186006
+ const pool = exact.length > 0 && (marker.services ?? 1) <= 1 ? exact : candidates;
185155
186007
  const roots = uniqSorted(pool.map((r) => r.root));
186008
+ if (roots.length > 1 && (marker.services ?? 1) >= roots.length) {
186009
+ for (const rt of pool.filter((r) => r.kind === "container" || PROCESS_KINDS.has(r.kind))) {
186010
+ rt.kind = "gcp_cloud_run";
186011
+ if (!rt.configFiles.includes(marker.path))
186012
+ rt.configFiles = uniqSorted([...rt.configFiles, marker.path]);
186013
+ rt.evidence.push({ path: marker.path, line: marker.line, detector: "runtime.gcp.cloud_run" });
186014
+ }
186015
+ continue;
186016
+ }
185156
186017
  if (roots.length > 1) {
185157
186018
  ctx.gaps.push({
185158
186019
  kind: "ambiguous_deploy_target",
@@ -185190,6 +186051,9 @@ function applyCloudRun(ctx, runtimes) {
185190
186051
  function dedupeGaps(gaps) {
185191
186052
  const seen = new Map;
185192
186053
  for (const g of gaps) {
186054
+ g.message = redact(g.message);
186055
+ if (g.evidence)
186056
+ g.evidence = g.evidence.map((e) => ({ ...e, path: redact(e.path) }));
185193
186057
  const key = `${g.kind}:${g.message}`;
185194
186058
  if (!seen.has(key))
185195
186059
  seen.set(key, g);
@@ -185199,9 +186063,13 @@ function dedupeGaps(gaps) {
185199
186063
  function detectDependencies(input) {
185200
186064
  const opts = typeof input === "string" ? { dir: input } : input;
185201
186065
  const loaded = loadFiles(opts.dir, { maxFileBytes: opts.maxFileBytes, maxFiles: opts.maxFiles });
186066
+ const harnessRoot = normalizeRoot(opts.harnessRoot ?? loaded.manifest?.harness?.root);
186067
+ const scope = computeScope(loaded.files, harnessRoot);
186068
+ const scoped = loaded.files.filter((f) => inScope(f.path, scope));
186069
+ const outside = loaded.files.filter((f) => !inScope(f.path, scope));
185202
186070
  const ctx = {
185203
- files: loaded.files,
185204
- byPath: new Map(loaded.files.map((f) => [f.path, f])),
186071
+ files: scoped,
186072
+ byPath: new Map(scoped.map((f) => [f.path, f])),
185205
186073
  manifest: loaded.manifest,
185206
186074
  signals: [],
185207
186075
  runtimes: [],
@@ -185209,12 +186077,28 @@ function detectDependencies(input) {
185209
186077
  gaps: [],
185210
186078
  env: [],
185211
186079
  envLinks: [],
185212
- migrationHints: []
186080
+ migrationHints: [],
186081
+ functionRefs: new Map
185213
186082
  };
185214
- for (const ref of ctx.manifest?.env ?? []) {
185215
- for (const source of ref.sources.length > 0 ? ref.sources : ["manifest.json"])
185216
- ctx.env.push({ name: ref.name, source });
186083
+ if (ctx.manifest) {
186084
+ const scopedEnv = [];
186085
+ for (const ref of ctx.manifest.env ?? []) {
186086
+ if (isAmbientEnv(ref.name))
186087
+ continue;
186088
+ const sources = ref.sources.filter((src) => {
186089
+ const path = src.split(/[: ]/)[0];
186090
+ return (!/[/.]/.test(path) || inScope(path, scope)) && isRuntimeEnvSource(src) && !isAuxiliary(path, harnessRoot);
186091
+ });
186092
+ if (ref.sources.length > 0 && sources.length === 0)
186093
+ continue;
186094
+ scopedEnv.push({ ...ref, sources });
186095
+ for (const source of sources.length > 0 ? sources : ["manifest.json"])
186096
+ ctx.env.push({ name: ref.name, source });
186097
+ }
186098
+ ctx.manifest = { ...ctx.manifest, env: scopedEnv };
185217
186099
  }
186100
+ if (outside.length > 0)
186101
+ ctx.gaps.push(outsideContext(outside, scope));
185218
186102
  if (loaded.skipped.length > 0) {
185219
186103
  const byReason = new Map;
185220
186104
  for (const sk of loaded.skipped) {
@@ -185233,13 +186117,25 @@ function detectDependencies(input) {
185233
186117
  }
185234
186118
  if (loaded.truncated)
185235
186119
  ctx.gaps.push({ kind: "truncated", message: `Only the first ${ctx.files.length} files were analyzed` });
185236
- const declared = collectDeclaredPackages(ctx.files);
186120
+ const harnessFiles = ctx.files.filter((f) => isUnder(f.path, scope.root) && !f.isDoc && !f.isTest);
186121
+ const harnessCode = harnessFiles.map((f) => f.text);
186122
+ const pyImported = new Set(harnessFiles.filter((f) => languageOf(f.path) === "python").flatMap((f) => extractImports("python", f.text).map((r) => r.spec)));
186123
+ const usesPackage = (d) => {
186124
+ if (d.eco !== "pypi") {
186125
+ const re = new RegExp(`['"\`]${escapeRe(d.name)}(?:/[^'"\`]*)?['"\`]`);
186126
+ return harnessCode.some((t) => re.test(t));
186127
+ }
186128
+ const modules = [d.name.toLowerCase().replace(/-/g, "_"), ...matchModule("pypi", d.name)?.signature.py ?? []];
186129
+ return [...pyImported].some((spec) => modules.some((m) => spec === m || spec.startsWith(`${m}.`)));
186130
+ };
186131
+ const declared = collectDeclaredPackages(ctx.files).filter((d) => isUnder(d.path, scope.root) || !scope.configs.includes(d.path) || usesPackage(d));
185237
186132
  const packageJsons = parsePackageJsons(ctx.files);
185238
186133
  packageSignals(ctx, declared);
185239
186134
  const declaredPypi = new Set(declared.filter((d) => d.eco === "pypi").map((d) => d.name));
185240
186135
  const workspaceNames = new Set(packageJsons.map((p2) => typeof p2.json.name === "string" ? p2.json.name : "").filter(Boolean));
185241
186136
  const scan = scanSources(ctx, declaredPypi, workspaceNames);
185242
186137
  scanEnv(ctx);
186138
+ sdkDefaultEnv(ctx, declared);
185243
186139
  const roots = projectRoots(ctx.files);
185244
186140
  const runtimeInput = { ctx, packageJsons, declared, frameworks: scan.frameworks };
185245
186141
  let runtimes = [
@@ -185253,6 +186149,12 @@ function detectDependencies(input) {
185253
186149
  ];
185254
186150
  runtimes = applyCloudRun(ctx, runtimes);
185255
186151
  runtimes = consolidateRuntimes(runtimes);
186152
+ runtimes = foldNestedRuntimes(ctx, runtimes, harnessRoot);
186153
+ runtimes = scopeRuntimes(ctx, runtimes, scope);
186154
+ const runtimeConfigs = new Set(runtimes.flatMap((r) => r.configFiles));
186155
+ ctx.signals = ctx.signals.filter((s) => s.kind !== "binding" || isUnder(s.evidence.path, scope.root) || runtimeConfigs.has(s.evidence.path));
186156
+ ctx.signals = scopeComposeImages(ctx, scope);
186157
+ ctx.env = ctx.env.filter((e) => e.scopeRoot === undefined || inScope(e.scopeRoot, scope) || isUnder(scope.root, e.scopeRoot));
185256
186158
  runtimes.sort((a, b) => cmp(a.root, b.root) || cmp(a.kind, b.kind));
185257
186159
  for (const rt of runtimes) {
185258
186160
  if (PLATFORM_KINDS.has(rt.kind) || rt.kind === "container" || PROCESS_KINDS.has(rt.kind)) {
@@ -185261,11 +186163,15 @@ function detectDependencies(input) {
185261
186163
  if (all.length > 0)
185262
186164
  rt.frameworks = all;
185263
186165
  }
186166
+ resolveCommandHandlers(rt, ctx);
186167
+ if (rt.entrypoints.some((e) => !e.name.startsWith("__main__ ")))
186168
+ rt.entrypoints = rt.entrypoints.filter((e) => !e.name.startsWith("__main__ ") || e.trigger !== "cli");
185264
186169
  const serves = (rt.frameworks ?? []).some((f) => SERVER_FRAMEWORKS.has(f));
185265
186170
  for (const ep of rt.entrypoints) {
185266
186171
  if (ep.trigger === "unknown" && (serves || /gunicorn|uvicorn|hypercorn|daphne|waitress|next start|runserver|flask run|serve/.test((ep.command ?? []).join(" "))))
185267
186172
  ep.trigger = "http";
185268
186173
  }
186174
+ rt.entrypoints = dedupeEntrypoints(rt);
185269
186175
  for (const ep of rt.entrypoints)
185270
186176
  if (ep.command)
185271
186177
  ep.command = ep.command.map((arg, i) => i === 2 && ep.command[0] === "sh" && ep.command[1] === "-c" ? redactScript(arg) : redactArg(arg));
@@ -185281,7 +186187,28 @@ function detectDependencies(input) {
185281
186187
  }
185282
186188
  }
185283
186189
  const migrations = detectMigrations(ctx);
186190
+ const scriptLocal = scriptLocalFilter(ctx, runtimes);
186191
+ ctx.env = ctx.env.filter((e) => !scriptLocal(e.source.split(/[: ]/)[0]));
186192
+ if (ctx.manifest?.env) {
186193
+ const env2 = ctx.manifest.env.flatMap((ref) => {
186194
+ const sources = ref.sources.filter((src) => !scriptLocal(src.split(/[: ]/)[0]));
186195
+ return ref.sources.length > 0 && sources.length === 0 ? [] : [{ ...ref, sources }];
186196
+ });
186197
+ ctx.manifest = { ...ctx.manifest, env: env2 };
186198
+ }
185284
186199
  envSignals(ctx);
186200
+ const touched = new Set(ctx.signals.filter((s) => isUnder(s.evidence.path, scope.root)).map((s) => s.provider));
186201
+ for (const sig of ctx.signals) {
186202
+ if (!scope.closure.some((c) => isUnder(sig.evidence.path, c)) || sig.capability === "model.inference")
186203
+ continue;
186204
+ if (sig.kind === "host" && sig.provider === "http" && !scope.closure.some((c) => isAuxiliary(sig.evidence.path, c)))
186205
+ continue;
186206
+ if (["host", "env", "env_value", "code"].includes(sig.kind) || !CLOSURE_CAPABILITIES.has(sig.capability) && !touched.has(sig.provider))
186207
+ sig.weak = true;
186208
+ }
186209
+ for (const sig of ctx.signals)
186210
+ if (isAuxiliary(sig.evidence.path, harnessRoot))
186211
+ sig.weak = true;
185285
186212
  const dependencies = mergeSignals(ctx, runtimes);
185286
186213
  linkEnv(ctx, dependencies, runtimes);
185287
186214
  for (const m of migrations) {
@@ -185290,6 +186217,15 @@ function detectDependencies(input) {
185290
186217
  const candidates = dependencies.filter((d) => MIGRATION_ENGINE_PROVIDERS.has(d.provider) && (root === undefined || (d.runtimeRoots ?? []).includes(root)));
185291
186218
  resolveMigrationEngines([m], candidates.map((d) => d.provider), ctx);
185292
186219
  }
186220
+ const keepSource = (source) => isRuntimeEnvSource(source) && !isAuxiliary(source.split(/[: ]/)[0], harnessRoot);
186221
+ ctx.env = ctx.env.filter((e) => !isAmbientEnv(e.name) && keepSource(e.source));
186222
+ if (ctx.manifest?.env) {
186223
+ const env2 = ctx.manifest.env.flatMap((ref) => {
186224
+ const sources = ref.sources.filter(keepSource);
186225
+ return ref.sources.length > 0 && sources.length === 0 ? [] : [{ ...ref, sources }];
186226
+ });
186227
+ ctx.manifest = { ...ctx.manifest, env: env2 };
186228
+ }
185293
186229
  const env = buildEnvReferences(ctx, dependencies);
185294
186230
  return {
185295
186231
  schema: DEPENDENCY_MAP_SCHEMA,
@@ -193545,19 +194481,19 @@ function assertHarnessGraphSyncSize(graph) {
193545
194481
  // ../../packages/messaging/messaging.json
193546
194482
  var messaging_default = {
193547
194483
  $schema: "./messaging.schema.json",
193548
- version: "1.0.3",
193549
- locked_on: "2026-10-14",
194484
+ version: "1.0.4",
194485
+ locked_on: "2026-10-15",
193550
194486
  approved_by: "mo",
193551
- source: "Branding project docs/positioning.md v1.0.3",
194487
+ source: "Branding project docs/positioning.md v1.0.4",
193552
194488
  canonical_strings: {
193553
- version: "1.0.3",
193554
- locked_on: "2026-10-14",
194489
+ version: "1.0.4",
194490
+ locked_on: "2026-10-15",
193555
194491
  approved_by: "mo",
193556
194492
  strings: [
193557
194493
  {
193558
194494
  id: "one_liner",
193559
194495
  source: "approved",
193560
- text: "Moda gives engineers the infrastructure to monitor AI agents in production, find the mistakes they keep repeating, and test every change against real traces before it ships.",
194496
+ text: "Infrastructure for catching AI agent mistakes in production and turning them into evals.",
193561
194497
  match: "exact",
193562
194498
  surfaces: [
193563
194499
  "apps/moda-frontend/components/landing/hero.tsx (subhead under the H1)",
@@ -193617,7 +194553,7 @@ var messaging_default = {
193617
194553
  {
193618
194554
  id: "meta_description",
193619
194555
  source: "derived",
193620
- text: "Production monitoring and evals for AI agents. Find the mistakes your agents keep repeating and test every change against real traces before it ships.",
194556
+ text: "Production monitoring and evals for AI agents. Infrastructure for catching AI agent mistakes in production and turning them into evals.",
193621
194557
  match: "exact",
193622
194558
  max_chars: 160,
193623
194559
  surfaces: [
@@ -193642,7 +194578,7 @@ var messaging_default = {
193642
194578
  {
193643
194579
  id: "long_description",
193644
194580
  source: "derived",
193645
- text: "Moda gives engineers the infrastructure to monitor AI agents in production, find the mistakes they keep repeating, and test every change against real traces before it ships. Agents fail in ways that never throw an error: frustrated users, failing tools, lazy or hallucinated answers. Moda reads every production trace end to end, detects these failures, and groups them into problems ranked by how many users they affect, with the traces behind each one. When your team changes a prompt, tool, or model, Moda replays the change against those traces and shows whether it worked, then keeps monitoring after release. Works with any model provider through SDKs, OpenTelemetry, or HTTP. Backed by Y Combinator.",
194581
+ text: "Infrastructure for catching AI agent mistakes in production and turning them into evals. Agents fail in ways that never throw an error: frustrated users, failing tools, lazy or hallucinated answers. Moda reads every production trace end to end, detects these failures, and groups them into problems ranked by how many users they affect, with the traces behind each one. When your team changes a prompt, tool, or model, Moda replays the change against those traces and shows whether it worked, then keeps monitoring after release. Works with any model provider through SDKs, OpenTelemetry, or HTTP. Backed by Y Combinator.",
193646
194582
  match: "exact",
193647
194583
  surfaces: [
193648
194584
  "LinkedIn About",
@@ -193756,7 +194692,7 @@ var messaging_default = {
193756
194692
  ]
193757
194693
  },
193758
194694
  banned_phrases: {
193759
- version: "1.0.3",
194695
+ version: "1.0.4",
193760
194696
  phrases: [
193761
194697
  {
193762
194698
  id: "fixes_your_agent",
@@ -193933,7 +194869,7 @@ var messaging_default = {
193933
194869
  ]
193934
194870
  },
193935
194871
  terminology: {
193936
- version: "1.0.3",
194872
+ version: "1.0.4",
193937
194873
  terms: [
193938
194874
  {
193939
194875
  use: "trace",
@@ -194935,7 +195871,7 @@ async function runHarnessCommand(context) {
194935
195871
  throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
194936
195872
  }
194937
195873
  if (context.flags["github-actions"] === "true") {
194938
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-tmy3gqwa.js");
195874
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-301nak95.js");
194939
195875
  const result = await runGithubActionsAnalyze(rootDir, {
194940
195876
  writeReport: (report2) => {
194941
195877
  const normalized = normalizeHarnessReport(report2);