diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index e4511b85..5bf37b41 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -10,7 +10,7 @@
"name": "semctx",
"source": "./plugins/claude-code",
"description": "Trace repository impact, authored intent and fail-closed migration plans in Claude Code.",
- "version": "0.4.2",
+ "version": "0.4.3",
"author": {
"name": "hoklims",
"email": "hoklims@gmail.com"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2b790cb2..25da324c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -63,6 +63,14 @@ jobs:
- run: bun install --frozen-lockfile
- name: Run selected verification lane
run: bun scripts/ci-gate.ts
+ - name: Archive complete worker benchmark report
+ if: matrix.smoke == 'true'
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ with:
+ name: multicore-index-${{ matrix.os }}-${{ github.run_attempt }}-${{ strategy.job-index }}
+ path: .semctx/multicore-index.json
+ include-hidden-files: true
+ if-no-files-found: error
semctx-required:
name: semctx-required
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 626270fb..ae161b81 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -41,7 +41,7 @@ jobs:
version="${GITHUB_REF_NAME#v}"
notes_file="docs/releases/v$version.md"
test -s "$notes_file"
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
@@ -82,7 +82,7 @@ jobs:
)
- id: upload
name: Upload the verified npm package
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: npm-package
path: ${{ runner.temp }}/npm-package/
@@ -99,13 +99,13 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- run: npm install --global npm@12.0.1 # zizmor: ignore[adhoc-packages] exact OIDC publisher pin
- - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: npm-package
path: ${{ runner.temp }}/npm-package
@@ -178,7 +178,7 @@ jobs:
timeout-minutes: 35
permissions: {}
steps:
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
@@ -342,7 +342,7 @@ jobs:
with:
bun-version: 1.4.0
no-cache: true
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
package-manager-cache: false
@@ -386,7 +386,7 @@ jobs:
SEMCTX_PROOF_TOOL_SHA="$verifier_sha" bun scripts/prove-stable-delivery.ts
- name: Upload the delivery proof
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: stable-delivery-proof
path: ${{ runner.temp }}/delivery-proof/stable-delivery-proof.json
diff --git a/.github/workflows/stable-delivery-proof.yml b/.github/workflows/stable-delivery-proof.yml
index c5bb6154..2c689691 100644
--- a/.github/workflows/stable-delivery-proof.yml
+++ b/.github/workflows/stable-delivery-proof.yml
@@ -144,7 +144,7 @@ jobs:
with:
bun-version: 1.4.0
no-cache: true
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
package-manager-cache: false
@@ -190,7 +190,7 @@ jobs:
bun scripts/prove-stable-delivery.ts
- name: Upload the delivery proof
if: always()
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: stable-delivery-proof-rerun
path: ${{ runner.temp }}/delivery-proof/stable-delivery-proof.json
diff --git a/.omp-plugin/marketplace.json b/.omp-plugin/marketplace.json
index 46a7177c..051437f1 100644
--- a/.omp-plugin/marketplace.json
+++ b/.omp-plugin/marketplace.json
@@ -12,10 +12,10 @@
"source": "git-subdir",
"url": "https://github.com/hoklims/semctx.git",
"path": "plugins/claude-code",
- "ref": "v0.4.2"
+ "ref": "v0.4.3"
},
"description": "Trace repository impact, authored intent and fail-closed migration plans in Oh My Pi.",
- "version": "0.4.2",
+ "version": "0.4.3",
"author": {
"name": "hoklims",
"email": "hoklims@gmail.com"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcb10e72..b537d721 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,24 @@ GitHub Release advance together through the tag-driven lockstep workflow documen
## [Unreleased]
+## [0.4.3] - 2026-10-12
+
+### Added
+
+- Observed setup scope and bounded include proposals, without automatic configuration changes.
+- Opt-in qualified JavaScript ESM analysis with retained-input, analyzed-scope and Git identity
+ requirements; unsupported implicit execution remains an explicit refusal.
+
+### Fixed
+
+- Committed-range verification recovers guards removed, moved or stripped from its base tree.
+ Base blobs are read without checkout filters; unsafe paths and incomplete selected base
+ analysis refuse. Previously passing guard removals can now report WARN or BLOCK.
+- SQLite writer contention is bounded and CLI commands reliably release repository stores.
+- Multicore CI reports are archived separately for each OS, shard and workflow attempt.
+- Pre-commit examples keep indexing outside the hook and launch Bun from a trusted installation.
+- MCP SDK, lint tools and GitHub Actions receive maintenance updates with regenerated bundles.
+
## [0.4.2] - 2026-10-06
### Fixed
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aac63872..47861d21 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ public journey without writing authored declarations or installing a global plug
Control Handoff v2 capture/explain, staleness detection, and a refused resume, end to end.
-Semctx **0.4.2** requires **Bun >=1.4.0**.
+Semctx **0.4.3** requires **Bun >=1.4.0**.
The supported, tested host baseline is **Codex 0.147.0** and
**Claude Code 2.1.229**. Other host versions are **unknown** until tested;
these pins do not claim the earliest historically compatible versions.
diff --git a/README.fr.md b/README.fr.md
index 2e368d93..562feca6 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -220,8 +220,8 @@ peut pas être analysé, et avertit quand l’analyse de ce fichier n’est que
### Comment semctx relie un test au code
-semctx n’exécute jamais vos tests. Il regarde les imports. Un test couvre un symbole quand un
-fichier de test l’importe par son nom, directement depuis le fichier qui le déclare :
+semctx n’exécute jamais vos tests. Dans le parcours TypeScript seul par défaut, il regarde les
+imports directs qui conservent le nom déclaré du symbole :
| Import dans un fichier de test, avec `greet` déclaré dans `src/index.ts` | Couvre `greet` |
| --- | --- |
@@ -230,6 +230,12 @@ fichier de test l’importe par son nom, directement depuis le fichier qui le d
| `import * as m from "../src/index";` | non, import d’espace de noms |
| `import { greet } from "../src/barrel";` où `src/barrel.ts` le réexporte | non, réexport |
+Le profil optionnel `qualified-static-v1`, en config v2, suit aussi les imports renommés, les
+membres d’espaces de noms modélisés et les réexports jusqu’à leur déclaration d’origine. Ces
+liens restent statiques ; une cible inconnue ou non prise en charge ne prouve pas la couverture.
+Voir le [profil qualifié](docs/qualification/modelo-suite-static-v1.md) et son
+[contrat d’admission](docs/adr/0035-qualified-static-analysis-admission.md), en anglais.
+
Les imports de type seul (`import type`) ne comptent pas non plus. Un fichier est considéré comme
un test quand son nom se termine par `.test.ts` ou `.spec.ts` (ou leurs variantes `.tsx`, `.mts`,
`.cts`), quand il se trouve dans un dossier `test/`, `tests/` ou `__tests__`, ou quand il importe
diff --git a/README.md b/README.md
index abdae31a..120ec270 100644
--- a/README.md
+++ b/README.md
@@ -210,8 +210,8 @@ file's analysis is only partial.
### What counts as a covering test
-semctx never runs your tests. It checks imports. A test covers a symbol when a test file imports it
-by name, directly from the file that declares it:
+semctx never runs your tests. In the default TypeScript-only flow, it checks direct imports
+under the symbol's declared name:
| Import in a test file, with `greet` declared in `src/index.ts` | Covers `greet` |
| --- | --- |
@@ -220,6 +220,12 @@ by name, directly from the file that declares it:
| `import * as m from "../src/index";` | no, namespace import |
| `import { greet } from "../src/barrel";` where `src/barrel.ts` re-exports it | no, re-export |
+The opt-in config v2 `qualified-static-v1` profile also follows canonical renamed imports,
+modeled namespace members and re-exports to their original declarations. These are static test
+associations; unknown or unsupported targets do not establish coverage. See the
+[qualified profile](docs/qualification/modelo-suite-static-v1.md) and its
+[admission contract](docs/adr/0035-qualified-static-analysis-admission.md).
+
Type-only imports do not count either. A file is a test when its name ends in `.test.ts` or
`.spec.ts` (or the `.tsx`, `.mts`, `.cts` variants), when it sits in a `test/`, `tests/` or
`__tests__` folder, or when it imports `vitest`, `bun:test` or `node:test`.
@@ -295,7 +301,7 @@ and never adds any. A marker must fit on one line.
| Surface | Start with | What you get | Guide |
| --- | --- | --- | --- |
| CLI | `semctx verify diff` | The verdict as text, GitHub annotations (`--format github`) or a versioned JSON report (`--format json`) | [CLI reference](docs/reference/cli.md) |
-| GitHub Actions | `uses: hoklims/semctx/packages/github-action@v0.4.2` | An annotation at each symbol a finding names, a job summary and a `verdict` output; BLOCK fails the job | [GitHub Actions](docs/integrations/github-actions.md) |
+| GitHub Actions | `uses: hoklims/semctx/packages/github-action@v0.4.3` | An annotation at each symbol a finding names, a job summary and a `verdict` output; BLOCK fails the job | [GitHub Actions](docs/integrations/github-actions.md) |
| Claude Code | `bunx semctx@latest install` | The semctx MCP tools and skills, plus an optional guarded mode that holds `git commit` until the diff is verified | [Claude Code](docs/integrations/claude-code.md) |
| Codex | `bunx semctx@latest install --host codex` | The same MCP tools and skills | [Codex](docs/integrations/codex-control-plane.md) |
| Any MCP client | tool `semctx_verify_change` | The same diff check over MCP, next to the server's other typed tools | [MCP setup](docs/integrations/claude-code.md#mcp-without-the-plugin) |
@@ -319,7 +325,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # semctx needs the merge base and never fetches
- - uses: hoklims/semctx/packages/github-action@v0.4.2
+ - uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
@@ -375,7 +381,7 @@ Plugins update from the `stable` branch, which moves only when a release is publ
Tested host versions
-Semctx **0.4.2** requires **Bun >=1.4.0**.
+Semctx **0.4.3** requires **Bun >=1.4.0**.
The supported, tested host baseline is **Codex 0.147.0** and
**Claude Code 2.1.229**. Other host versions are **unknown** until tested;
these pins do not claim the earliest historically compatible versions.
diff --git a/ROADMAP.md b/ROADMAP.md
index 3f040179..da6790fe 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,6 +1,6 @@
# Semctx roadmap
-> Revised 2026-10-06. Source baseline: **v0.4.2** (candidate). Publication and host delivery are gated separately.
+> Revised 2026-10-12. Source baseline: **v0.4.3** (candidate). Publication and host delivery are gated separately.
> Future versions are outcome targets, not available features or promised dates.
## Understand the risk of a change before running the checks
@@ -32,8 +32,12 @@ without deciding any proof. The release briefs for [0.3.0](docs/releases/v0.3.0.
budgets, Mac/lobby performance and independent human outcomes remain open; publication does not
complete the whole 0.3 roadmap.
-Version 0.4.1 remains the current public release. The 0.4.2 candidate fixes Codex installation metadata, native cache selection and Windows launchers;
-it is not public until the release workflow, registry read-back and fresh-host delivery complete.
+The current npm release and `latest` channel are **0.4.2**, with registry `gitHead`
+`745bac21925dd96c936e99ad62dd6b6b266525b0` observed on 2026-10-12. Its patch fixes Codex
+installation metadata, native cache selection and Windows launchers. This registry observation
+establishes npm publication, not that a host installed it or an active session loaded it.
+The source candidate is **0.4.3**; it is not public until its own release workflow, registry
+read-back and fresh-host delivery complete.
TypeScript is the semantic baseline. Python support is bounded through Python 3.12; Markdown and
SQL provide structural facts, not equivalent semantic analysis. Suggested tests are inferred from
diff --git a/apps/cli/README.md b/apps/cli/README.md
index ef1b89eb..b58755ef 100644
--- a/apps/cli/README.md
+++ b/apps/cli/README.md
@@ -1,7 +1,7 @@
# semctx
-Semctx **0.4.2** requires **Bun >=1.4.0**.
+Semctx **0.4.3** requires **Bun >=1.4.0**.
The supported, tested host baseline is **Codex 0.147.0** and
**Claude Code 2.1.229**. Other host versions are **unknown** until tested;
these pins do not claim the earliest historically compatible versions.
diff --git a/apps/cli/package.json b/apps/cli/package.json
index ae2394bd..05f62707 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "semctx",
- "version": "0.4.2",
+ "version": "0.4.3",
"type": "module",
"description": "Deterministic, local-first repository change-impact analyzer: computes the semantic blast radius of a git diff and enforces contracts and invariants (PASS/WARN/BLOCK). Not a code-search replacement (ADR 0005).",
"license": "Apache-2.0",
diff --git a/apps/cli/src/commands/bench.ts b/apps/cli/src/commands/bench.ts
index 4833a2fd..e10e65d4 100644
--- a/apps/cli/src/commands/bench.ts
+++ b/apps/cli/src/commands/bench.ts
@@ -1,7 +1,7 @@
import { readFileSync, existsSync } from "node:fs";
import { join, resolve } from "node:path";
import { SemctxError } from "@semantic-context/core";
-import { openStore } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import { runBench, type BenchCase } from "@semantic-context/eval";
import type { ParsedArgs } from "../args";
import { flagString, flagBool } from "../args";
@@ -37,15 +37,15 @@ function loadSuite(root: string, args: ParsedArgs): BenchCase[] {
export function runBenchCmd(root: string, args: ParsedArgs): number {
const cases = loadSuite(root, args);
- const store = openStore(root);
- if (!store.isIndexed()) {
- store.close();
- throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
- }
- const graph = store.loadGraph();
- const evidence = store.loadEvidence();
- const claims = store.loadClaims();
- store.close();
+ const { graph, evidence, claims } = withStore(root, (store) => {
+ if (!store.isIndexed()) {
+ throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
+ }
+ const graph = store.loadGraph();
+ const evidence = store.loadEvidence();
+ const claims = store.loadClaims();
+ return { graph, evidence, claims };
+ });
const thresholdRaw = flagString(args, "threshold");
const threshold = thresholdRaw !== undefined && Number.isFinite(Number(thresholdRaw)) ? Number(thresholdRaw) : 1;
diff --git a/apps/cli/src/commands/context.ts b/apps/cli/src/commands/context.ts
index ed7a01de..307d6bec 100644
--- a/apps/cli/src/commands/context.ts
+++ b/apps/cli/src/commands/context.ts
@@ -2,11 +2,12 @@ import { join } from "node:path";
import { SemctxError } from "@semantic-context/core";
import type { TaskFrame } from "@semantic-context/core";
import { trustedControlSealHash } from "@semantic-context/app-services";
-import { openStore, contextPacksDir, loadConfig, writeFileNoFollow } from "@semantic-context/repository-store";
+import { contextPacksDir, loadConfig, writeFileNoFollow } from "@semantic-context/repository-store";
import { prepareContextPack, fetchProviderCandidates, stableProviderSourceSeal } from "@semantic-context/context-engine";
import type { ParsedArgs } from "../args";
import { flagBool } from "../args";
import { info, success, json, c, nowIso } from "../output";
+import { withStoreAsync } from "../store";
import { renderPackMarkdown, renderPackConsole } from "../render-pack";
/** `semctx context prepare ` — compile a TaskFrame into a ContextPack. */
@@ -14,57 +15,54 @@ export async function runContextPrepare(root: string, args: ParsedArgs): Promise
const taskId = args.positionals[2];
const config = loadConfig(root);
const sourceSealBefore = trustedControlSealHash(root);
- const store = openStore(root);
-
- if (!store.isIndexed()) {
- store.close();
- throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
- }
-
- let taskFrame: TaskFrame | undefined;
- if (taskId !== undefined) {
- taskFrame = store.getTaskFrame(taskId);
- if (taskFrame === undefined) {
- store.close();
- throw new SemctxError("TASK_NOT_FOUND", `no task frame with id ${taskId}`, { taskId });
+ const { pack, taskFrame } = await withStoreAsync(root, async (store) => {
+ if (!store.isIndexed()) {
+ throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
}
- } else {
- const frames = store.listTaskFrames();
- taskFrame = frames[0];
- if (taskFrame === undefined) {
- store.close();
- throw new SemctxError("TASK_NOT_FOUND", "no task frames exist; run 'semctx task create' first");
+
+ let taskFrame: TaskFrame | undefined;
+ if (taskId !== undefined) {
+ taskFrame = store.getTaskFrame(taskId);
+ if (taskFrame === undefined) {
+ throw new SemctxError("TASK_NOT_FOUND", `no task frame with id ${taskId}`, { taskId });
+ }
+ } else {
+ const frames = store.listTaskFrames();
+ taskFrame = frames[0];
+ if (taskFrame === undefined) {
+ throw new SemctxError("TASK_NOT_FOUND", "no task frames exist; run 'semctx task create' first");
+ }
}
- }
- const graph = store.loadGraph();
- const evidence = store.loadEvidence();
- const claims = store.loadClaims();
- const capturedAt = nowIso();
- const providerInput = { query: taskFrame.rawTask, repositoryRoot: config.repositoryRoot, limit: 20 };
- const providerCandidates = await fetchProviderCandidates(
- config,
- providerInput.query,
- providerInput.limit,
- sourceSealBefore !== undefined ? { sourceRepositorySealHash: sourceSealBefore, capturedAt } : undefined,
- );
- const sourceSealAfter = trustedControlSealHash(root);
- const stableSourceSeal = stableProviderSourceSeal(sourceSealBefore, sourceSealAfter);
- let pack = prepareContextPack({
- graph,
- evidence,
- claims,
- taskFrame,
- now: capturedAt,
- providerCandidates,
- providerInput,
- ...(stableSourceSeal !== undefined ? { expectedProviderSourceSealHash: stableSourceSeal } : {}),
+ const graph = store.loadGraph();
+ const evidence = store.loadEvidence();
+ const claims = store.loadClaims();
+ const capturedAt = nowIso();
+ const providerInput = { query: taskFrame.rawTask, repositoryRoot: config.repositoryRoot, limit: 20 };
+ const providerCandidates = await fetchProviderCandidates(
+ config,
+ providerInput.query,
+ providerInput.limit,
+ sourceSealBefore !== undefined ? { sourceRepositorySealHash: sourceSealBefore, capturedAt } : undefined,
+ );
+ const sourceSealAfter = trustedControlSealHash(root);
+ const stableSourceSeal = stableProviderSourceSeal(sourceSealBefore, sourceSealAfter);
+ let pack = prepareContextPack({
+ graph,
+ evidence,
+ claims,
+ taskFrame,
+ now: capturedAt,
+ providerCandidates,
+ providerInput,
+ ...(stableSourceSeal !== undefined ? { expectedProviderSourceSealHash: stableSourceSeal } : {}),
+ });
+ if (stableSourceSeal !== undefined && stableProviderSourceSeal(stableSourceSeal, trustedControlSealHash(root)) === undefined) {
+ pack = prepareContextPack({ graph, evidence, claims, taskFrame, now: capturedAt, providerCandidates, providerInput });
+ }
+ store.saveContextPack(pack);
+ return { pack, taskFrame };
});
- if (stableSourceSeal !== undefined && stableProviderSourceSeal(stableSourceSeal, trustedControlSealHash(root)) === undefined) {
- pack = prepareContextPack({ graph, evidence, claims, taskFrame, now: capturedAt, providerCandidates, providerInput });
- }
- store.saveContextPack(pack);
- store.close();
const dir = contextPacksDir(root);
const safeId = taskFrame.id.replace(/[^A-Za-z0-9._-]/g, "_");
diff --git a/apps/cli/src/commands/init.ts b/apps/cli/src/commands/init.ts
index bc1c9b75..d6c5a4d9 100644
--- a/apps/cli/src/commands/init.ts
+++ b/apps/cli/src/commands/init.ts
@@ -1,7 +1,8 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { createGlobSelectionConfig } from "@semantic-context/core";
-import { initWorkspace, openStore, isInitialized } from "@semantic-context/repository-store";
+import { initWorkspace, isInitialized } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import { ensureSemanticGitignore } from "@semantic-context/semantic-engine";
import type { ParsedArgs } from "../args";
import { flagBool, flagString } from "../args";
@@ -27,7 +28,7 @@ export function runInit(root: string, args: ParsedArgs): number {
root,
flagBool(args, "polyglot") ? createGlobSelectionConfig(root) : undefined,
);
- openStore(root).close();
+ withStore(root, () => undefined);
ensureSemanticGitignore(root);
if (flagBool(args, "json")) {
diff --git a/apps/cli/src/commands/inspect.ts b/apps/cli/src/commands/inspect.ts
index c003865c..486cc4b0 100644
--- a/apps/cli/src/commands/inspect.ts
+++ b/apps/cli/src/commands/inspect.ts
@@ -1,5 +1,5 @@
import { SemctxError } from "@semantic-context/core";
-import { openStore } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import { inspectGraph, type InspectKind } from "@semantic-context/context-engine";
import type { ParsedArgs } from "../args";
import { flagBool } from "../args";
@@ -19,19 +19,19 @@ export function runInspect(root: string, args: ParsedArgs): number {
warn(`unknown inspect kind "${kindArg}", searching all kinds`);
}
- const store = openStore(root);
- if (!store.isIndexed()) {
- store.close();
- throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
- }
- const result = inspectGraph({
- graph: store.loadGraph(),
- claims: store.loadClaims(),
- evidence: store.loadEvidence(),
- query,
- kind,
+ const result = withStore(root, (store) => {
+ if (!store.isIndexed()) {
+ throw new SemctxError("REPO_NOT_INDEXED", "repository is not indexed; run 'semctx index' first");
+ }
+ const result = inspectGraph({
+ graph: store.loadGraph(),
+ claims: store.loadClaims(),
+ evidence: store.loadEvidence(),
+ query,
+ kind,
+ });
+ return result;
});
- store.close();
if (flagBool(args, "json")) {
json(result);
diff --git a/apps/cli/src/commands/migrate.ts b/apps/cli/src/commands/migrate.ts
index 59bb87b6..57715c1f 100644
--- a/apps/cli/src/commands/migrate.ts
+++ b/apps/cli/src/commands/migrate.ts
@@ -12,7 +12,7 @@
*/
import { SemctxError } from "@semantic-context/core";
-import { openStore } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import {
anchorMigrationAuthority,
applyConfigMigration,
@@ -110,15 +110,12 @@ function renderText(report: AnchorMigrationReport): void {
}
function loadFacts(root: string): RepositoryFacts {
- const store = openStore(root);
- try {
+ return withStore(root, (store) => {
if (!store.isIndexed()) {
throw new SemctxError("REPO_NOT_INDEXED", "run 'semctx index' before migrating anchors");
}
return { graph: store.loadGraph(), claims: store.loadClaims(), evidence: store.loadEvidence() };
- } finally {
- store.close();
- }
+ });
}
const CONFIG_MIGRATION_REFUSAL_HELP: Record = {
diff --git a/apps/cli/src/commands/preset.ts b/apps/cli/src/commands/preset.ts
index 0fdeb256..b9e5fede 100644
--- a/apps/cli/src/commands/preset.ts
+++ b/apps/cli/src/commands/preset.ts
@@ -44,7 +44,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - uses: hoklims/semctx/packages/github-action@v0.4.2
+ - uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: \${{ github.event.pull_request.base.sha }}
head: \${{ github.sha }}
diff --git a/apps/cli/src/commands/semantic.ts b/apps/cli/src/commands/semantic.ts
index a5eb4bb9..36995e30 100644
--- a/apps/cli/src/commands/semantic.ts
+++ b/apps/cli/src/commands/semantic.ts
@@ -1,6 +1,6 @@
import { SemctxError } from "@semantic-context/core";
import { checkSemanticState } from "@semantic-context/app-services";
-import { openStore } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import {
initSemanticScaffold,
loadModelWithWorking,
@@ -36,15 +36,12 @@ Usage: semctx semantic [options]
`;
function loadFacts(root: string): { facts: RepositoryFacts | undefined; indexed: boolean } {
- const store = openStore(root);
- const indexed = store.isIndexed();
- if (!indexed) {
- store.close();
- return { facts: undefined, indexed: false };
- }
- const facts: RepositoryFacts = { graph: store.loadGraph(), claims: store.loadClaims(), evidence: store.loadEvidence() };
- store.close();
- return { facts, indexed };
+ return withStore(root, (store) => {
+ const indexed = store.isIndexed();
+ if (!indexed) return { facts: undefined, indexed: false };
+ const facts: RepositoryFacts = { graph: store.loadGraph(), claims: store.loadClaims(), evidence: store.loadEvidence() };
+ return { facts, indexed };
+ });
}
export function runSemantic(root: string, args: ParsedArgs): number {
diff --git a/apps/cli/src/commands/task.ts b/apps/cli/src/commands/task.ts
index 77857e8d..bb64e871 100644
--- a/apps/cli/src/commands/task.ts
+++ b/apps/cli/src/commands/task.ts
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { SemctxError, TaskFrameInputSchema, TaskModeSchema } from "@semantic-context/core";
import type { TaskFrameInput, TaskMode } from "@semantic-context/core";
-import { openStore } from "@semantic-context/repository-store";
+import { withStore } from "../store";
import { parseTaskDocument, defaultTaskExtractor, extractionContext } from "@semantic-context/context-engine";
import type { ParsedArgs } from "../args";
import { flagString, flagBool } from "../args";
@@ -46,12 +46,13 @@ export function runTaskCreate(root: string, args: ParsedArgs): number {
else warn(`ignoring invalid --mode "${modeOverride}"`);
}
- const store = openStore(root);
- const graph = store.loadGraph();
- if (graph.nodes.length === 0) warn("repository not indexed; run 'semctx index' first for capability/invariant matching");
- const taskFrame = defaultTaskExtractor.extract(input, extractionContext(graph, nowIso()));
- store.saveTaskFrame(taskFrame);
- store.close();
+ const taskFrame = withStore(root, (store) => {
+ const graph = store.loadGraph();
+ if (graph.nodes.length === 0) warn("repository not indexed; run 'semctx index' first for capability/invariant matching");
+ const taskFrame = defaultTaskExtractor.extract(input, extractionContext(graph, nowIso()));
+ store.saveTaskFrame(taskFrame);
+ return taskFrame;
+ });
if (flagBool(args, "json")) {
json(taskFrame);
diff --git a/apps/cli/src/commands/verify.ts b/apps/cli/src/commands/verify.ts
index 7fb35493..4f943a2c 100644
--- a/apps/cli/src/commands/verify.ts
+++ b/apps/cli/src/commands/verify.ts
@@ -1,11 +1,14 @@
-import { readFileSync } from "node:fs";
-import { resolve } from "node:path";
+import { existsSync, readFileSync } from "node:fs";
+import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
import { SemctxError } from "@semantic-context/core";
import { replaceLocalReportFile } from "../report-output";
+import { writeFileNoFollow } from "@semantic-context/repository-store";
+import { typeScriptLibraryDirectory } from "@semantic-context/ts-analyzer";
import type { VerifyReport } from "@semantic-context/core";
import type { VerifyResult, VerifyReportGitMeta, CoChange } from "@semantic-context/context-engine";
import {
captureRecordableVerificationGitState,
+ canonicalRepositoryRoot,
evaluatePreCommitHook,
evaluatePrePushHook,
parsePrePushRefs,
@@ -162,7 +165,51 @@ function renderText(
else fail("blocking violations present");
}
+/** Validate prospective qualified publication without performing filesystem writes. */
+export function assertQualifiedReportOutput(root: string, path: string): boolean {
+ // Classify prospective paths as well as existing inputs; canonicalize the nearest
+ // existing ancestor so aliases above the checkout cannot bypass this boundary.
+ let ancestor = resolve(path); const missing: string[] = [];
+ while (!existsSync(ancestor) && dirname(ancestor) !== ancestor) {
+ missing.unshift(basename(ancestor)); ancestor = dirname(ancestor);
+ }
+ const output = resolve(canonicalRepositoryRoot(ancestor), ...missing);
+ const canonicalRoot = canonicalRepositoryRoot(root);
+ const under = (directory: string, target = output): boolean => {
+ const relPath = relative(directory, target).replaceAll("\\", "/");
+ return relPath !== ".." && !relPath.startsWith("../") && !isAbsolute(relPath);
+ };
+ const dependencyRoot = resolve(root, "node_modules");
+ if (under(canonicalRepositoryRoot(typeScriptLibraryDirectory()))) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified report output cannot change SDK inputs", { path });
+ }
+ if (existsSync(dependencyRoot) && under(canonicalRepositoryRoot(dependencyRoot))) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified report output cannot change dependency inputs", { path });
+ }
+ for (const args of [["rev-parse", "--absolute-git-dir"], ["rev-parse", "--path-format=absolute", "--git-common-dir"]]) {
+ const result = Bun.spawnSync(["git", "-C", canonicalRoot, ...args], { stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new SemctxError("INVALID_TASK_INPUT", "cannot establish qualified output Git control boundary", { path });
+ if (under(canonicalRepositoryRoot(new TextDecoder().decode(result.stdout).trim()))) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified report output cannot change Git control inputs", { path });
+ }
+ }
+ if (under(canonicalRoot) || under(resolve(root), resolve(path))) {
+ const relPaths = [under(canonicalRoot) ? relative(canonicalRoot, output) : undefined,
+ under(resolve(root), resolve(path)) ? relative(resolve(root), resolve(path)) : undefined].filter((value): value is string => value !== undefined);
+ if (relPaths.some(value => !value.replaceAll("\\", "/").startsWith(".semctx/reports/"))) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified report output cannot change repository inputs; use .semctx/reports or a location outside the repository", { path });
+ }
+ return true;
+ }
+ return false;
+}
+
function writeReportAtomic(root: string, path: string, report: VerifyReport): void {
+ const reservedReport = relative(resolve(root), resolve(path)).replaceAll("\\", "/").startsWith(".semctx/reports/");
+ if (report.analysisAdmission !== undefined ? assertQualifiedReportOutput(root, path) : reservedReport) {
+ writeFileNoFollow(root, path, `${JSON.stringify(report, null, 2)}\n`);
+ return;
+ }
replaceLocalReportFile(path, `${JSON.stringify(report, null, 2)}\n`, root);
}
@@ -230,7 +277,7 @@ export function runVerifyDiff(root: string, args: ParsedArgs): number {
if (recordedPath !== undefined && format === "text") info(c.dim(`recorded verification state -> ${recordedPath}`));
- return exitCode(report.verdict, failOn);
+ return report.analysisAdmission?.status === "rejected" ? 3 : exitCode(report.verdict, failOn);
}
/**
diff --git a/apps/cli/src/store.ts b/apps/cli/src/store.ts
new file mode 100644
index 00000000..c504772c
--- /dev/null
+++ b/apps/cli/src/store.ts
@@ -0,0 +1,36 @@
+import { attachSuppressedError } from "@semantic-context/core";
+import { openStore, type RepositoryStore } from "@semantic-context/repository-store";
+
+function closeStore(store: RepositoryStore, failure: { error: unknown } | undefined): void {
+ try {
+ store.close();
+ } catch (error) {
+ throw failure === undefined ? error : attachSuppressedError(failure.error, error);
+ }
+}
+
+export function withStore(root: string, operation: (store: RepositoryStore) => T): T {
+ const store = openStore(root);
+ let failure: { error: unknown } | undefined;
+ try {
+ return operation(store);
+ } catch (error) {
+ failure = { error };
+ throw error;
+ } finally {
+ closeStore(store, failure);
+ }
+}
+
+export async function withStoreAsync(root: string, operation: (store: RepositoryStore) => Promise): Promise {
+ const store = openStore(root);
+ let failure: { error: unknown } | undefined;
+ try {
+ return await operation(store);
+ } catch (error) {
+ failure = { error };
+ throw error;
+ } finally {
+ closeStore(store, failure);
+ }
+}
diff --git a/apps/cli/test/constructor-provenance.test.ts b/apps/cli/test/constructor-provenance.test.ts
new file mode 100644
index 00000000..0cefa17c
--- /dev/null
+++ b/apps/cli/test/constructor-provenance.test.ts
@@ -0,0 +1,61 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository } from "@semantic-context/app-services";
+
+const cli = join(import.meta.dir, "../src/index.ts");
+const localConstructor = (extension: string): string => extension === "ts"
+ ? "class LocalDate {constructor(_value:unknown){}}\nconst env:typeof globalThis={Date:LocalDate} as unknown as typeof globalThis;\nexport function run(){return new env.Date(1); }\n"
+ : "class LocalDate {constructor(value){}}\n/** @type {typeof globalThis} */\nconst env=/** @type {any} */ ({Date:LocalDate});\nexport function run(){return new env.Date(1); }\n";
+function verify(source: string, extension: string, excluded: boolean, hiddenOnly = false): { exitCode: number; report: { analysisAdmission: { status: string; reasons: string[]; binding: { status: string } }; verdict: string } } {
+ const root = mkdtempSync(join(tmpdir(), "semctx-constructor-origin-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const main = join(root, `src/main.${extension}`);
+ writeFileSync(main, hiddenOnly && !excluded ? source : "export function main(){return 1;}\n");
+ if (hiddenOnly) {
+ writeFileSync(join(root, `src/leaf.${extension}`), "export function hidden(){return 1;}\n");
+ writeFileSync(join(root, `src/runner.test.${extension}`), `import {run} from '${excluded ? "../hidden" : "./main"}.${extension}';export function check(){return run();}\n`);
+ }
+ if (excluded) writeFileSync(join(root, `hidden.${extension}`), source);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ if (hiddenOnly) writeFileSync(join(root, `src/leaf.${extension}`), "export function hidden(){return 2;}\n");
+ else writeFileSync(main, excluded ? "export function main(){return 2;}\n" : source);
+ indexRepository(root, "2026-10-12T10:00:00.000Z");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ return { exitCode: result.exitCode, report: JSON.parse(new TextDecoder().decode(result.stdout)) };
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}
+for (const extension of ["ts", "mjs"]) {
+ for (const excluded of [false, true]) test(`qualified CLI rejects SDK-shaped local constructor in ${excluded ? "excluded" : "selected"} ${extension}`, () => {
+ const { exitCode, report } = verify(localConstructor(extension), extension, excluded);
+ expect(exitCode).toBe(3);
+ expect(report.verdict).toBe("BLOCK");
+ expect(report.analysisAdmission.status).toBe("rejected");
+ expect(report.analysisAdmission.reasons.some(reason => reason.includes("SOURCE_CONSTRUCTION_UNSUPPORTED"))).toBe(true);
+ }, 60_000);
+ test(`qualified CLI preserves true SDK aliases in ${extension}`, () => {
+ const { exitCode, report } = verify("const C=Date;const env=globalThis;const P=globalThis.Date;export function run(){new C(1);new env.Date(1);return new P(1); }\n", extension, false);
+ expect(exitCode).toBe(0);
+ expect(report.analysisAdmission.status).toBe("admitted");
+ expect(report.analysisAdmission.reasons).toEqual([]);
+ }, 60_000);
+}
+
+for (const extension of ["ts", "mjs"]) for (const excluded of [false, true]) test(`qualified CLI rejects hidden-only edit behind SDK-shaped ${excluded ? "excluded" : "selected"} ${extension} constructor`, () => {
+ const source = `import {hidden} from '${excluded ? "./src/leaf" : "./leaf"}.${extension}';\n${localConstructor(extension).replace("constructor(_value:unknown){}", "constructor(_value:unknown){hidden();}").replace("constructor(value){}", "constructor(value){hidden();}")}`;
+ const { exitCode, report } = verify(source, extension, excluded, true);
+ expect(exitCode).toBe(3);
+ expect(report.verdict).toBe("BLOCK");
+ expect(report.analysisAdmission.status).toBe("rejected");
+ expect(report.analysisAdmission.binding.status).toBe("valid");
+ expect(report.analysisAdmission.reasons.some(reason => reason.includes("SOURCE_CONSTRUCTION_UNSUPPORTED"))).toBe(true);
+}, 60_000);
diff --git a/apps/cli/test/init-preset.test.ts b/apps/cli/test/init-preset.test.ts
index 0850ab50..7744a5bd 100644
--- a/apps/cli/test/init-preset.test.ts
+++ b/apps/cli/test/init-preset.test.ts
@@ -74,7 +74,7 @@ describe("init --preset github-claude", () => {
// the workflow uses least privilege and the safe trigger
const wf = readFileSync(join(repo, ".github/workflows/semctx.yml"), "utf8");
expect(wf).toContain("contents: read");
- expect(wf).toContain("uses: hoklims/semctx/packages/github-action@v0.4.2");
+ expect(wf).toContain("uses: hoklims/semctx/packages/github-action@v0.4.3");
expect(wf).not.toContain("pull_request_target");
});
diff --git a/apps/cli/test/ordinary-reserved-report.test.ts b/apps/cli/test/ordinary-reserved-report.test.ts
new file mode 100644
index 00000000..aba0ee28
--- /dev/null
+++ b/apps/cli/test/ordinary-reserved-report.test.ts
@@ -0,0 +1,27 @@
+import { expect, test } from "bun:test";
+import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+test("ordinary reserved-report parent refuses a linked ancestor before directory creation", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ordinary-reserved-report-"));
+ const outside = mkdtempSync(join(tmpdir(), "semctx-ordinary-report-outside-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main(){return 1;}\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ expect(Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }).exitCode).toBe(0);
+ }
+ const cli = join(import.meta.dir, "../src/index.ts");
+ for (const args of [["init"], ["index"]]) expect(Bun.spawnSync([process.execPath, cli, ...args, "--root", root], { cwd: join(import.meta.dir, "../../.."), stdout: "pipe", stderr: "pipe" }).exitCode).toBe(0);
+ writeFileSync(join(outside, "sentinel"), "outside bytes");
+ symlinkSync(outside, join(root, ".semctx/reports"), process.platform === "win32" ? "junction" : "dir");
+ const output = join(root, ".semctx/reports/missing-child/verify.json");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: join(import.meta.dir, "../../.."), stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(1);
+ expect(new TextDecoder().decode(result.stderr)).toContain("[CONFIG_INVALID] linked .semctx entries are unsupported");
+ expect(existsSync(join(outside, "missing-child"))).toBe(false);
+ expect(readdirSync(outside)).toEqual(["sentinel"]);
+ expect(readFileSync(join(outside, "sentinel"), "utf8")).toBe("outside bytes");
+ } finally { rmSync(root, { recursive: true, force: true }); rmSync(outside, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/apps/cli/test/qualified-output.test.ts b/apps/cli/test/qualified-output.test.ts
new file mode 100644
index 00000000..b06ab661
--- /dev/null
+++ b/apps/cli/test/qualified-output.test.ts
@@ -0,0 +1,124 @@
+import { expect, test } from "bun:test";
+import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository } from "@semantic-context/app-services";
+import { typeScriptLibraryDirectory } from "@semantic-context/ts-analyzer";
+import { assertQualifiedReportOutput } from "../src/commands/verify";
+
+const cli = join(import.meta.dir, "../src/index.ts");
+function fixture(): { parent: string; root: string } {
+ const parent = mkdtempSync(join(tmpdir(), "semctx-qualified-output-")); const root = join(parent, "repo");
+ mkdirSync(join(root, "src"), { recursive: true });
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nreport.json\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ return { parent, root };
+}
+test("qualified output cannot overwrite workspace configuration", () => {
+ const { parent, root } = fixture();
+ try {
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const output = join(root, ".semctx/config.json");
+ const original = readFileSync(output, "utf8");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ exitCode: result.exitCode, changed: readFileSync(output, "utf8") !== original }));
+ expect(result.exitCode).not.toBe(0);
+ expect(readFileSync(output, "utf8")).toBe(original);
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+for (const destination of ["state", "git"] as const) test(`qualified output protects ${destination} control paths`, () => {
+ const { parent, root } = fixture();
+ try {
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const output = destination === "state" ? join(root, ".semctx/verification-state.json") : join(root, ".git/round18-report.json");
+ expect(existsSync(output)).toBe(false);
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).not.toBe(0);
+ expect(existsSync(output)).toBe(false);
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+test("qualified SDK output decision refuses before any writer is invoked", () => {
+ const { parent, root } = fixture();
+ try {
+ expect(() => assertQualifiedReportOutput(root, join(typeScriptLibraryDirectory(), "round18-report.json"))).toThrow("cannot change SDK inputs");
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+});
+test("qualified output protects external worktree and common Git directories", () => {
+ const { parent, root } = fixture(); const linkedRoot = join(parent, "linked");
+ try {
+ expect(Bun.spawnSync(["git", "worktree", "add", "--detach", linkedRoot, "HEAD"], { cwd: root, stdout: "pipe", stderr: "pipe" }).exitCode).toBe(0);
+ initWorkspace(linkedRoot, { ...createGlobSelectionConfig(linkedRoot), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(linkedRoot, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(linkedRoot, "2026-10-11T10:00:00.000Z");
+ for (const args of [["--absolute-git-dir"], ["--path-format=absolute", "--git-common-dir"]]) {
+ const gitDir = new TextDecoder().decode(Bun.spawnSync(["git", "rev-parse", ...args], { cwd: linkedRoot, stdout: "pipe" }).stdout).trim();
+ const output = join(gitDir, "round18-report.json");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", linkedRoot, "--format", "json", "--fail-on", "none", "--output", output], { cwd: linkedRoot, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).not.toBe(0);
+ expect(new TextDecoder().decode(result.stderr)).toContain("cannot change Git control inputs");
+ expect(existsSync(output)).toBe(false);
+ }
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+for (const control of ["reports", "dependency"] as const) test(`qualified ${control} junction preserves its outside fixture`, () => {
+ const { parent, root } = fixture(); const outside = join(parent, "outside");
+ const linked = join(root, control === "reports" ? ".semctx/reports" : "node_modules");
+ try {
+ if (control === "dependency") writeFileSync(join(root, ".gitignore"), ".semctx/\nreport.json\nnode_modules\n");
+ mkdirSync(outside); writeFileSync(join(outside, "sentinel.json"), "unchanged");
+ symlinkSync(outside, linked, "junction"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const output = control === "reports" ? join(linked, "sentinel.json") : join(outside, "sentinel.json");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).not.toBe(0);
+ if (control === "dependency") {
+ const error = new TextDecoder().decode(result.stderr);
+ expect(error).toContain("INVALID_TASK_INPUT");
+ expect(error).toContain("cannot change dependency inputs");
+ }
+ expect(readFileSync(join(outside, "sentinel.json"), "utf8")).toBe("unchanged");
+ } finally { if (existsSync(linked)) unlinkSync(linked); rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+for (const existing of [false, true]) test(`qualified output cannot ${existing ? "replace" : "create"} a retained input`, () => {
+ const { parent, root } = fixture();
+ try {
+ const output = join(root, "report.json"); const original = '{"sentinel":true}\n';
+ if (existing) writeFileSync(output, original);
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ existing, exitCode: result.exitCode, stdout: new TextDecoder().decode(result.stdout), stderr: new TextDecoder().decode(result.stderr) }));
+ expect(result.exitCode).not.toBe(0);
+ expect(new TextDecoder().decode(result.stderr)).toContain("qualified report output cannot change repository inputs");
+ if (existing) expect(readFileSync(output, "utf8")).toBe(original);
+ else expect(existsSync(output)).toBe(false);
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+
+test("qualified prospective output through a checkout alias is still a retained input", () => {
+ const { parent, root } = fixture(); const alias = join(parent, "alias");
+ try {
+ symlinkSync(parent, alias, "junction"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const output = join(root, "report.json");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", join(alias, "repo"), "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).not.toBe(0);
+ expect(new TextDecoder().decode(result.stderr)).toContain("qualified report output cannot change repository inputs");
+ expect(existsSync(output)).toBe(false);
+ } finally { if (existsSync(alias)) unlinkSync(alias); rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
+for (const location of ["internal", "outside"] as const) test(`qualified output ${location} preserves report publication`, () => {
+ const { parent, root } = fixture();
+ try {
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const output = location === "internal" ? join(root, ".semctx/reports/verify.json") : join(parent, "verify.json");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none", "--output", output], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0);
+ expect(JSON.parse(readFileSync(output, "utf8")).analysisAdmission.status).toBe("admitted");
+ } finally { rmSync(parent, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/apps/cli/test/store-lifecycle.test.ts b/apps/cli/test/store-lifecycle.test.ts
new file mode 100644
index 00000000..060ee0d1
--- /dev/null
+++ b/apps/cli/test/store-lifecycle.test.ts
@@ -0,0 +1,160 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { Database } from "bun:sqlite";
+import { chmodSync, existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { pathToFileURL } from "node:url";
+import { dbPath, initWorkspace, loadConfig, saveConfig, openStore, SqliteRepositoryReader } from "@semantic-context/repository-store";
+import { defaultTaskExtractor, extractionContext } from "@semantic-context/context-engine";
+import { SemctxError } from "@semantic-context/core";
+import { parseArgs } from "../src/args";
+import { withStore, withStoreAsync } from "../src/store";
+import { runContextPrepare } from "../src/commands/context";
+import { runBenchCmd } from "../src/commands/bench";
+import { runInspect } from "../src/commands/inspect";
+import { runTaskCreate } from "../src/commands/task";
+import { runSemantic } from "../src/commands/semantic";
+
+const directories: string[] = [];
+afterEach(() => {
+ for (const directory of directories.splice(0)) rmSync(directory, { recursive: true, force: true });
+});
+
+function workspace(): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-cli-store-"));
+ directories.push(root);
+ initWorkspace(root);
+ const store = openStore(root);
+ try {
+ store.saveGraph({ nodes: [{ id: "mod:probe.ts", kind: "module", name: "probe.ts", filePath: "probe.ts", tags: [], evidence: [], metadata: {} }], edges: [] }, []);
+ } finally {
+ store.close();
+ }
+ return root;
+}
+
+function assertReadable(root: string): void {
+ const path = dbPath(root);
+ expect(existsSync(`${path}-wal`)).toBe(false);
+ expect(existsSync(`${path}-shm`)).toBe(false);
+ const reader = SqliteRepositoryReader.openExisting(path);
+ try {
+ expect(reader.isIndexed()).toBe(true);
+ } finally {
+ reader.close();
+ }
+}
+
+function corruptClaims(root: string): void {
+ const db = new Database(dbPath(root));
+ try {
+ db.exec("INSERT INTO claims VALUES ('broken', 'behavior', 'probe', '[]', '[]', 1, 1, 1, 'invalid', NULL, NULL, '[]');");
+ } finally {
+ db.close();
+ }
+}
+
+describe("CLI writer lifecycle", () => {
+ it("closes context preparation after an available provider fails, before the process exits", () => {
+ const root = workspace();
+ saveConfig(root, { ...loadConfig(root), semanticProvider: "cocoindex" });
+ const frame = withStore(root, (store) => {
+ const task = defaultTaskExtractor.extract({ rawTask: "probe" }, extractionContext(store.loadGraph(), "2026-10-10T00:00:00Z"));
+ store.saveTaskFrame(task);
+ return task;
+ });
+ const executable = join(root, process.platform === "win32" ? "ccc.cmd" : "ccc");
+ writeFileSync(executable, process.platform === "win32"
+ ? '@echo off\r\nif "%1"=="version" (\r\n echo 1.0.0\r\n exit /b 0\r\n)\r\nexit /b 9\r\n'
+ : '#!/bin/sh\nif [ "$1" = "version" ]; then echo 1.0.0; exit 0; fi\nexit 9\n');
+ if (process.platform !== "win32") chmodSync(executable, 0o755);
+ const contextModule = pathToFileURL(join(import.meta.dir, "../src/commands/context.ts")).href;
+ const script = `
+ import { runContextPrepare } from ${JSON.stringify(contextModule)};
+ import { dbPath, SqliteRepositoryReader } from "@semantic-context/repository-store";
+ import { existsSync } from "node:fs";
+ let failure;
+ try {
+ await runContextPrepare(${JSON.stringify(root)}, { positionals: ["context", "prepare", ${JSON.stringify(frame.id)}], flags: new Map([["json", true]]) });
+ } catch (error) { failure = { name: error.name, code: error.code }; }
+ const path = dbPath(${JSON.stringify(root)});
+ const sidecars = [existsSync(path + "-wal"), existsSync(path + "-shm")];
+ let nodes = null;
+ try {
+ const reader = SqliteRepositoryReader.openExisting(path);
+ try { nodes = reader.loadGraph().nodes.length; } finally { reader.close(); }
+ } catch {}
+ console.log(JSON.stringify({ failure, sidecars, nodes }));
+ `;
+ const environment = Object.fromEntries(Object.entries(process.env).filter(([key]) => key.toUpperCase() !== "PATH"));
+ const result = Bun.spawnSync([process.execPath, "--eval", script], {
+ cwd: join(import.meta.dir, "../../.."), env: { ...environment, PATH: root },
+ stdout: "pipe", stderr: "pipe", timeout: 10_000,
+ });
+ expect(result.exitCode).toBe(0);
+ expect(new TextDecoder().decode(result.stderr)).toBe("");
+ expect(JSON.parse(new TextDecoder().decode(result.stdout))).toEqual({
+ failure: { name: "CocoIndexProviderError", code: "PROCESS_FAILURE" }, sidecars: [false, false], nodes: 1,
+ });
+ });
+
+ for (const asynchronous of [false, true]) {
+ it(`retains both operation and cleanup failures (${asynchronous ? "async" : "sync"})`, async () => {
+ const root = workspace();
+ let blocker: Database | undefined;
+ const operation = () => {
+ blocker = new Database(dbPath(root), { readonly: true });
+ blocker.query("SELECT * FROM meta").all();
+ throw new SemctxError("TASK_NOT_FOUND", "primary failure");
+ };
+ let failure: unknown;
+ try {
+ if (asynchronous) await withStoreAsync(root, async () => operation());
+ else withStore(root, operation);
+ } catch (error) {
+ failure = error;
+ } finally {
+ blocker?.close();
+ }
+ expect(failure).toMatchObject({
+ code: "TASK_NOT_FOUND", message: "primary failure",
+ suppressed: [expect.objectContaining({ code: "STORE_ERROR", message: "repository store cannot leave WAL mode" })],
+ });
+ openStore(root).close();
+ assertReadable(root);
+ });
+ }
+
+ it("closes context preparation after persisted task validation throws", async () => {
+ const root = workspace();
+ const db = new Database(dbPath(root));
+ db.exec("INSERT INTO task_frames VALUES ('broken', 'probe', '{}', '2026-10-10T00:00:00Z');");
+ db.close();
+ const failure = await runContextPrepare(root, parseArgs(["context", "prepare", "broken", "--json"]))
+ .then(() => undefined, (error: unknown) => error);
+ expect(failure).toBeInstanceOf(Error);
+ assertReadable(root);
+ });
+
+ for (const command of ["bench", "inspect", "semantic"]) {
+ it(`closes ${command} after persisted claims validation throws`, () => {
+ const root = workspace();
+ corruptClaims(root);
+ writeFileSync(join(root, "semctx-bench.json"), "[]");
+ const invoke = command === "bench" ? () => runBenchCmd(root, parseArgs(["bench", "--json"]))
+ : command === "inspect" ? () => runInspect(root, parseArgs(["inspect", "any", "probe", "--json"]))
+ : () => runSemantic(root, parseArgs(["semantic", "inspect", "invariant.probe", "--json"]));
+ expect(invoke).toThrow();
+ assertReadable(root);
+ });
+ }
+
+ it("closes task creation after SQLite rejects persistence", () => {
+ const root = workspace();
+ const db = new Database(dbPath(root));
+ db.exec("CREATE TRIGGER reject_task BEFORE INSERT ON task_frames BEGIN SELECT RAISE(ABORT, 'task rejected'); END;");
+ db.close();
+ expect(() => runTaskCreate(root, parseArgs(["task", "create", "--text", "probe", "--json"]))).toThrow("task rejected");
+ assertReadable(root);
+ });
+});
diff --git a/bun.lock b/bun.lock
index 8394dd5a..45078bd3 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,15 +7,15 @@
"devDependencies": {
"@eslint/js": "10.0.1",
"@types/bun": "^1.4.2",
- "eslint": "10.11.0",
- "globals": "17.12.0",
+ "eslint": "10.12.0",
+ "globals": "17.13.0",
"typescript": "^5.6.3",
- "typescript-eslint": "8.71.0",
+ "typescript-eslint": "8.71.1",
},
},
"apps/cli": {
"name": "semctx",
- "version": "0.4.2",
+ "version": "0.4.3",
"bin": {
"semctx": "./dist/index.js",
},
@@ -37,7 +37,7 @@
},
"packages/app-services": {
"name": "@semantic-context/app-services",
- "version": "0.4.2",
+ "version": "0.4.3",
"dependencies": {
"@semantic-context/context-engine": "workspace:*",
"@semantic-context/control-engine": "workspace:*",
@@ -124,16 +124,16 @@
},
"packages/github-action": {
"name": "@semantic-context/github-action",
- "version": "0.4.2",
+ "version": "0.4.3",
},
"packages/mcp-server": {
"name": "@semantic-context/mcp-server",
- "version": "0.4.2",
+ "version": "0.4.3",
"bin": {
"semctx-mcp": "./src/index.ts",
},
"dependencies": {
- "@modelcontextprotocol/server": "2.2.0",
+ "@modelcontextprotocol/server": "2.3.1",
"@semantic-context/app-services": "workspace:*",
"@semantic-context/change-authorization-verifier": "workspace:*",
"@semantic-context/context-engine": "workspace:*",
@@ -150,7 +150,7 @@
"zod-v4": "npm:zod@4.2.0",
},
"devDependencies": {
- "@modelcontextprotocol/client": "2.2.0",
+ "@modelcontextprotocol/client": "2.3.1",
"@semantic-context/test-fixtures": "workspace:*",
},
},
@@ -279,11 +279,11 @@
"@lezer/python": ["@lezer/python@1.1.19", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ=="],
- "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.2.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.2.0", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "jose": "^6.1.3", "pkce-challenge": "^5.0.0", "zod": "^4.2.0" } }, "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig=="],
+ "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.3.1", "", { "dependencies": { "@modelcontextprotocol/core": "2.3.1", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.8", "jose": "^6.1.3", "pkce-challenge": "^5.0.0", "zod": "^4.2.0" } }, "sha512-mIGZXpHsjnZ6lD+gD/WCMpR5k8yVQQ8nNFH1N0Srf7AvnwTUMYD6pvTY8ng2+He5FfV7YMZLmrjL7cLa/cc3dQ=="],
- "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.2.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw=="],
+ "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.3.1", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g=="],
- "@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.2.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.2.0", "zod": "^4.2.0" } }, "sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ=="],
+ "@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.3.1", "", { "dependencies": { "@modelcontextprotocol/core": "2.3.1", "zod": "^4.2.0" } }, "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw=="],
"@semantic-context/app-services": ["@semantic-context/app-services@workspace:packages/app-services"],
@@ -333,25 +333,25 @@
"@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.71.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.71.0", "@typescript-eslint/type-utils": "8.71.0", "@typescript-eslint/utils": "8.71.0", "@typescript-eslint/visitor-keys": "8.71.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.71.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw=="],
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.71.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.71.1", "@typescript-eslint/type-utils": "8.71.1", "@typescript-eslint/utils": "8.71.1", "@typescript-eslint/visitor-keys": "8.71.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.71.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw=="],
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.71.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.71.0", "@typescript-eslint/types": "8.71.0", "@typescript-eslint/typescript-estree": "8.71.0", "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A=="],
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.71.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.71.1", "@typescript-eslint/types": "8.71.1", "@typescript-eslint/typescript-estree": "8.71.1", "@typescript-eslint/visitor-keys": "8.71.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw=="],
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.71.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.71.0", "@typescript-eslint/types": "^8.71.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ=="],
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.71.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.71.1", "@typescript-eslint/types": "^8.71.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g=="],
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.71.0", "", { "dependencies": { "@typescript-eslint/types": "8.71.0", "@typescript-eslint/visitor-keys": "8.71.0" } }, "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw=="],
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.71.1", "", { "dependencies": { "@typescript-eslint/types": "8.71.1", "@typescript-eslint/visitor-keys": "8.71.1" } }, "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w=="],
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.71.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg=="],
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.71.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg=="],
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.71.0", "", { "dependencies": { "@typescript-eslint/types": "8.71.0", "@typescript-eslint/typescript-estree": "8.71.0", "@typescript-eslint/utils": "8.71.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw=="],
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.71.1", "", { "dependencies": { "@typescript-eslint/types": "8.71.1", "@typescript-eslint/typescript-estree": "8.71.1", "@typescript-eslint/utils": "8.71.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.71.0", "", {}, "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.71.1", "", {}, "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ=="],
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.71.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.71.0", "@typescript-eslint/tsconfig-utils": "8.71.0", "@typescript-eslint/types": "8.71.0", "@typescript-eslint/visitor-keys": "8.71.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw=="],
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.71.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.71.1", "@typescript-eslint/tsconfig-utils": "8.71.1", "@typescript-eslint/types": "8.71.1", "@typescript-eslint/visitor-keys": "8.71.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ=="],
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.71.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.71.0", "@typescript-eslint/types": "8.71.0", "@typescript-eslint/typescript-estree": "8.71.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ=="],
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.71.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.71.1", "@typescript-eslint/types": "8.71.1", "@typescript-eslint/typescript-estree": "8.71.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ=="],
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.71.0", "", { "dependencies": { "@typescript-eslint/types": "8.71.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A=="],
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.71.1", "", { "dependencies": { "@typescript-eslint/types": "8.71.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w=="],
"acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
@@ -375,7 +375,7 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@10.11.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ=="],
+ "eslint": ["eslint@10.12.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA=="],
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
@@ -413,7 +413,7 @@
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
- "globals": ["globals@17.12.0", "", {}, "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA=="],
+ "globals": ["globals@17.13.0", "", {}, "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg=="],
"hashery": ["hashery@1.5.1", "", { "dependencies": { "hookified": "^1.15.0" } }, "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ=="],
@@ -483,7 +483,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
- "typescript-eslint": ["typescript-eslint@8.71.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.71.0", "@typescript-eslint/parser": "8.71.0", "@typescript-eslint/typescript-estree": "8.71.0", "@typescript-eslint/utils": "8.71.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fBdHYiqQ14RW6mOMXD14Svn82ZsCYAoQSzGRzyEjR59S5A2Krh/l7fGTOQ7iCr8gGy/mHVXtEF7s5fgjEdV0Pw=="],
+ "typescript-eslint": ["typescript-eslint@8.71.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.71.1", "@typescript-eslint/parser": "8.71.1", "@typescript-eslint/typescript-estree": "8.71.1", "@typescript-eslint/utils": "8.71.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ=="],
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
@@ -507,6 +507,7 @@
"@modelcontextprotocol/core/zod": ["zod@4.2.0", "", {}, "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw=="],
+
"@modelcontextprotocol/server/zod": ["zod@4.2.0", "", {}, "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
diff --git a/docs/adr/0034-javascript-static-esm.md b/docs/adr/0034-javascript-static-esm.md
new file mode 100644
index 00000000..0d5a3e12
--- /dev/null
+++ b/docs/adr/0034-javascript-static-esm.md
@@ -0,0 +1,83 @@
+# ADR 0034 — Opt-in JavaScript static ESM analysis
+
+- Status: accepted
+- Date: 2026-10-09
+- Related: [ADR 0010](0010-multilanguage-plane-a-capability-and-authority.md)
+
+## Decision
+
+Version 2 selection can explicitly enable `languages.javascript: "on"` and select
+`.js`, `.mjs`, `.jsx` and `.cjs`. Version 1 retains its existing selected file set.
+Extension recognition is discovery, not completed analysis.
+
+The existing pinned TypeScript 5.9.3 compiler extracts JavaScript function and class
+declarations, function-valued variables, imports, static reexports and resolved
+calls in the same program as TypeScript. Literal dynamic imports produce genuine
+module dependency edges. Mixed calls traverse in both language directions; the
+regression fixture proves three inbound call hops under an inherited tsconfig and
+an internal paths alias. Runtime `.mjs` sources remain the dependency endpoint when
+a `.d.mts` declaration companion is present. Named local export clauses are read
+through the compiler export table, including alias bindings.
+That runtime redirection does not extract or qualify the companion's API/dependency
+facts. Configuration v2 reports repository declarations as unsupported, and the
+named ESM profile refuses declaration-bearing scope (ADR 0035). The separately
+eligible public mixed fixture contains no declaration companion or CommonJS source.
+
+Named exported non-function variables are recorded in module `staticExports`
+metadata as a JSON string containing their names and declaration kinds. These are
+module export facts, not invented function nodes or resolved variable-reference
+edges. Ordinary graph metadata remains scalar and graph schema remains unchanged.
+Module `staticModuleLinks` metadata separately records resolved, external and
+unresolved module specifiers. Node builtins and installed dependencies under
+`node_modules` are explicit external links. Unknown bare package aliases remain
+unresolved and partial, rather than being silently classified as external.
+
+JavaScript capabilities use producer identity
+`@semantic-context/ts-analyzer/javascript`, version `0.1.0`, compiler dialect
+`5.9.3` and resolution semantics `javascript-static-esm-v1`. They register complete
+producer-declared static facts only. Partial JavaScript facts have no consumer
+capability registration and cannot admit a change. Negative evidence remains
+ineligible: this vertical does not prove the absence of tests or all runtime paths.
+
+Parsing failures, including TypeScript-only annotations in JavaScript files, are
+terminal failed outcomes. CommonJS, computed dynamic imports, dynamic evaluation,
+dynamic scope, computed calls, unresolved relative imports and exported
+destructuring remain explicit diagnostic limitations. CommonJS extensions are
+discovered and reported but are not included in the qualified ESM profile.
+TypeScript parsing failures and invalid tsconfig options are also terminal failures
+in version 2; compiler syntax recovery must not count as completed analysis.
+
+Nearest tsconfig options, inherited `extends`, `paths` and `baseUrl` inform mixed
+module resolution. Resolver configuration is read fresh, with no process-global
+configuration cache. Qualification must bind those configuration inputs and
+withdraw admission after they change. The analyzer does not create missing built
+outputs or establish arbitrary package/workspace aliases by directory name.
+Configuration outside the repository boundary, including inherited `extends`,
+is reported explicitly and cannot support a qualified positive result.
+The qualified profile uses ESNext/Bundler when module settings are absent and
+refuses explicitly incompatible direct or inherited settings, including NodeNext,
+Node16 and CommonJS. Those semantics are not silently replaced by defaults.
+Mixed JavaScript/TypeScript extraction uses one compiler Program. Explicit worker
+requests fall back to a single Program with a reported reason, preserving alias
+and declaration-companion semantics across the full selected source set.
+
+## Evidence and boundaries
+
+The independent Plane-A operation registry admits the 22 declared JavaScript
+fact kinds only for configuration v2, compiler dialect 5.9.3 and static
+`verify` / `change` analysis. Capability and completeness checks still reject
+partial CommonJS, computed imports and parsing failures. This registration
+grants no approval, execution, merge or deployment authority.
+
+Public fixtures contain only generated, anonymous sources. The JavaScript
+discovery regression fails on the previous source because all four JavaScript
+variants are absent from analyzed files. It passes after real symbol/call/module
+extraction. Runtime regressions separately demonstrate recovered-AST rejection,
+partial capability denial, mixed alias/companion resolution and transitive calls.
+
+CLI, MCP and packaged-consumer qualification, exact source/configuration identity,
+change coverage and index freshness remain responsibilities of the shared
+admission path. This analyzer ADR does not itself declare a consumer ready, an
+installed package, an active session, executed tests or functioning pipeline.
+Static PASS is not runtime or pipeline evidence. Approval gates, brokers and
+shared agent policy are outside this change.
diff --git a/docs/adr/0035-qualified-static-analysis-admission.md b/docs/adr/0035-qualified-static-analysis-admission.md
new file mode 100644
index 00000000..7fab5c4d
--- /dev/null
+++ b/docs/adr/0035-qualified-static-analysis-admission.md
@@ -0,0 +1,625 @@
+# ADR 0035 — Qualified static analysis admission
+
+- Status: accepted
+- Date: 2026-10-09
+- Related: ADR 0008 (additive reports), ADR 0010 (trust dimensions), ADR 0012
+ (transport parity), ADR 0028 (explicit configuration migration), ADR 0036 (scope)
+
+## Decision
+
+Configuration v2 can opt into `analysisProfile: "modelo-suite-static-v1"` together
+with `selectionMode: "qualified-static-v1"`. Both values require each other.
+Older runtimes reject the new selector instead of stripping an unknown profile
+and silently producing a legacy positive result.
+Legacy diagnostics retain their existing behavior. The named profile adds a versioned
+`analysisAdmission` to the shared verification report. CLI, MCP and plugin use the same
+application service. Rejection produces `BLOCK`; CLI returns 3 and the Action returns 1
+even under `fail-on: none`. The structured schema rejects contradictory positive admissions.
+
+Admission requires exact Git worktree-root selection, a Git-derived diff with resolved
+commit identities, a current bound index, completed reconstruction and a non-empty
+effectively analyzed change scope. The scope starts with both sides of the exact diff,
+including renames and deletions, then closes over statically extracted imports, exports,
+calls and dependency relations. Discovery outside configured selectors detects excluded
+importers. Static reexports and literal dynamic imports also contribute to the closure
+when the repository contains only TypeScript. Parse failure, unreadable sources,
+CommonJS loading (including literal `require()` and external `import = require()`),
+unresolved non-external imports and computed imports that could conceal
+an inbound dependency prevent admission. Exclusions remain visible and never discharge
+an obligated source. Unknown configuration/runtime semantics and deleted post-images
+are conservatively outside this bounded ESM/TypeScript profile.
+
+Output directory names are not source-eligibility evidence. Qualified discovery and retained
+input capture share a tracked/nonignored/originally-selected policy for sources beneath
+`build`, `dist`, `coverage`, `.turbo` and `.next`. Repository-local ignore controls are bound;
+host/shared excludes cannot silently narrow the scope. Ignored untracked generated outputs
+outside the original selector carry a named exclusion. Administrative metadata and installed
+dependencies remain explicit external boundaries.
+
+Source identity hashes raw bytes, including excluded/untracked source and repository
+manifests, lockfiles and JSON/YAML configuration. It includes the generated analyzer
+implementation digest; rebuilding a seal without consuming sources cannot restore it.
+Reconstruction marks the old index incomplete before analysis and completes that state
+atomically with the new snapshot. An interrupted rebuild cannot authorize the old graph.
+Working-tree results use refreshed post-image symbol ranges. A final qualified observation
+rejects retained-input, diff, resolved source/head, effective range merge-base or indexed
+snapshot changes during analysis.
+
+Binding, index freshness, repository coverage, change coverage, check freshness and
+control freshness are separate fields. `DIRTY_KNOWN` means the exact dirty source was
+indexed and sealed; `STALE`, `UNSEALED`, incompatible implementation/configuration or
+partial obligated analysis refuse admission. Repository coverage may remain partial
+outside the named change closure; the report enumerates both scopes. An admitted report must
+include every `changedFiles` path in `changeCoverage.expected`; dependency closure can add
+expected paths, so equality is not required. Complete repository coverage cannot contain an
+explicit failed, missing or unsupported outcome, or an analyzed record with reasons. Excluded
+records may coexist with complete selected coverage. These checks reject represented
+contradictions; they do not prove completeness of paths absent from every recorded inventory.
+
+A dirty qualified index supports working-tree verification only. Staged and range
+sources require a clean indexed post-image; a range destination must also match the
+indexed commit. Qualified committed ranges that remove base lines, files or paths are
+currently unsupported and reject with `QUALIFIED_BASE_RECOVERY_UNSUPPORTED`, a blocking
+`analysis_scope_incomplete` finding and a named unknown. This includes ordinary body-line
+replacements, not only deleted or stripped guards. Raw base blobs alone do not establish
+Git source eligibility, retained compiler inputs or capability admission for that base.
+No head configuration alone substitutes for those committed-range base inputs. Pure additions
+that remove no base lines keep their existing admission path.
+
+For refreshed working-tree diffs removing base lines, immutable HEAD guard capture runs only
+after current analysis is admitted. A current rejection remains rejected BLOCK with its own
+reasons and observed binding state, without this supplemental old capture. Otherwise-admitted
+removals capture resolved HEAD raw blobs and full safe inventory in a disposable private repository.
+They analyze retained old inputs under the exact current raw/parsed Semctx policy and pinned
+current SDK/compiler, then compare guards with the refreshed head graph. This is current-policy
+structural comparison, not historical configuration, SDK or old admission replay. Old obligated sources and their actual
+dependency/evidence closure must pass binding, producer, capability and authority checks; broad
+old loader/configuration/parse checks remain in force. An unavailable, unsafe, unbound or unsupported
+capture rejects with `QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED`, a blocking scope finding
+and named details. Technical capture failure is not mislabeled as a universal semantic diagnosis.
+Recovered guards retain configured severities; surviving current covering-test evidence remains
+structural, not observed test execution. Incomplete negative evidence stays advisory. No legacy
+base analysis can grant qualified absence or PASS. Full HEAD copying and selected analysis happen
+per otherwise-admitted removing working-tree diff; no overall latency, memory ceiling or speed gain
+is qualified.
+Staged/range support is unchanged by this working-tree path.
+
+The committed-range restriction remains until its own immutable base eligibility and
+compiler-input capture are implemented. Consumers must not retry with legacy configuration
+or relabel the rejection as admitted. Verify refreshed working-tree changes before committing
+and retain the explicit committed-range refusal in CI; a supported committed range requires
+a separately delivered base-capture implementation. Rejection produces the ordinary qualified
+report and nonzero transport status, not an internal snapshot/discovery exception. `DIRTY_KNOWN` proves retained worktree bytes, not equality with the
+Git index or a committed range destination. Exact dirty staged-post-image matching
+is not implemented and cannot be inferred from stable input observations.
+Clean Git status also does not prove post-image membership: ignored retained sources,
+manifests or empty workspace roots may be absent from the candidate. Staged/range
+admission compares retained repository inputs with Git blobs and consumed
+workspace roots with the selected directory inventory. Git >= 2.41 is required for
+qualified staged/range verification: retained payloads are converted through Git using
+the selected commit's attributes before object-identity comparison. An older Git produces
+an operational `UNSUPPORTED` diagnostic before conversion, not a qualified admission report.
+Attribute metadata is observed in batches and rechecked. At this retained post-image seam,
+failed attribute observation or metadata drift returns `QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND`;
+failed selected-attribute conversion returns `QUALIFIED_POST_IMAGE_CONVERSION_FAILED`.
+Both produce invalid binding, rejected admission and a structured `BLOCK` report. An already
+known `QUALIFIED_BASE_RECOVERY_UNSUPPORTED` refusal does not attempt this comparison.
+Unrelated Git status/freshness or I/O errors remain operational failures; these reasons do not
+catch every Git error. Raw retained bytes still bind the compiler snapshot and input digest.
+Missing inputs or differing converted objects refuse admission.
+Working-tree scope cannot rely on a Git diff that hides retained raw changes through an
+effective clean/process filter. Such retained inputs refuse with a precise invalid-binding
+reason. Batched attribute capture and read-only driver configuration establish this check;
+no new filter execution is introduced. Ordinary mode, filter-free text/eol normalization
+and an unconfigured filter name remain distinct. Configured sentinel-like names can be
+ambiguous with Git's attribute states and are conservatively unsupported. Staged/range
+comparison keeps its existing selected-attribute conversion and reobservation contract.
+
+Qualified verification refuses sparse checkout and relevant tracked inputs marked
+`skip-worktree` or `assume-unchanged`: these flags can hide an unmaterialized importer
+from working-tree discovery and diff coverage. Ordinary analysis and verification keep
+their existing behavior. Changed Python files already receive unsupported coverage with
+`OUTSIDE_BOUNDED_ESM_TYPESCRIPT_PROFILE`; the ordinary Python producer does not establish
+the broad Python dependency closure required to qualify those changes.
+
+The profile refuses actual global scripts, global declaration files, and global or
+string-named module augmentations because their cross-file bindings are not modeled.
+Explicit import/export boundaries and the `.mjs`/`.mts` modes marked external by the
+current extraction Program remain eligible. That Program does not propagate
+configured `moduleDetection` or infer bare TypeScript/JavaScript module scope solely
+from package `type: "module"` under ESNext/Bundler. Such bare files remain unsupported,
+including those configured with `moduleDetection: "force"`; configuration intent does
+not replace observed compiler scope.
+
+Repository declaration files (`.d.ts`, `.d.mts`, `.d.cts`) are unsupported in configuration
+v2 until their API and dependency facts are extracted. They remain visible as unsupported
+per-path outcomes and prevent qualified admission when retained in the broad repository
+scope, including excluded declarations. CommonJS TypeScript `.cts`/`.d.cts` format is
+outside the ESM profile even when it contains explicit exports. Legacy v1 behavior is
+unchanged. Existing runtime-companion resolution does not qualify declaration changes.
+
+Default export expression assignments are unsupported, including identifier aliases,
+inline functions, conditionals, calls, class/object expressions and literals. Transparent
+parentheses and TypeScript assertion wrappers cannot hide this boundary. This closed
+profile does not infer an export owner from an expression's apparent shape. Anonymous
+default function/class declarations retain their existing extracted symbols; legacy v1
+extraction behavior is unchanged.
+
+Opt-in semantic extraction resolves calls through transparent parentheses and
+TypeScript type-only wrappers to the same callable owner. JavaScript calls whose
+unwrapped callee is outside the modeled identifier/property/literal-import forms
+remain partial. Test associations use canonical checker-resolved value-binding
+coordinates, including import aliases and named/anonymous default declarations;
+the structurally imported module remains unchanged across reexports. These import
+associations do not establish test execution. Legacy v1 no-snapshot extraction keeps
+its previous call and import-name behavior.
+
+Semantic JavaScript JSDoc import types and import tags are outside this profile until
+their dependency links are extracted. Actual attached JSDoc AST nodes make selected
+producers partial and prevent broad qualified scope from silently omitting an importer.
+Ordinary prose mentions of imports and nonsemantic TypeScript JSDoc are not dependencies.
+
+Namespace test associations use static member references whose receiver is the
+actual namespace import binding and whose target has an existing modeled declaration.
+Canonical leaf/default coordinates are preserved through barrels. Dynamic whole reads,
+local shadows and unmodeled object members do not provide `tested_by`/`covers` evidence.
+These associations do not observe test execution. Whole-module impact reads use the
+indexed import target, including TypeScript extension substitution, before fallback probes.
+Object destructuring from the actual namespace binding also supplies canonical member
+associations, including renamed/barrel/default owners. Local binding-element names do
+not become fictional callee coordinates. This is semantic opt-in metadata with the
+existing worker DTO shape, without a new invocation extractor.
+
+TypeScript/TSX files classified as migrations retain legacy structural migration facts,
+but this profile refuses their selected producer scope and broad dependency scope because
+their executable symbols and calls are not extracted. Retained TypeScript export clauses
+and named default declarations use the same semantic export coordinates as JavaScript.
+
+Sources containing JSX are unsupported when the pinned compiler's configuration or
+semantic file pragmas introduce an implicit JSX runtime import. This includes automatic
+JSX modes and `jsxImportSource`; extraction still preserves JSX without modeling that
+runtime dependency. JSX-free files remain eligible under those options. Classic pragma
+overrides follow the pinned SDK semantics, including repeated pragma precedence.
+Retained classic JSX emission is also unsupported when the pinned emitter synthesizes a
+factory invocation at an original JSX element/fragment range. The guard uses the actual
+retained options and SDK transform, including factory/fragment settings, without choosing
+names such as `h` or `React` and without manufacturing call edges. Preserve emission and
+JSX-free sources keep their domain. A classic override can therefore refuse even though
+the separate automatic-runtime import guard is absent. The configuration guard runs when
+retained compiler inputs are supplied, including JavaScript diagnostics in that form;
+ordinary calls without retained inputs and existing extraction facts remain unchanged.
+
+For this named profile, already extracted calls whose named endpoint belongs to a
+modeled local module require exactly one grouped symbol coordinate. A missing or
+ambiguous caller/callee makes the caller file partial before capability and discovery
+ledger construction. Unmodeled object-literal callable members cannot reuse same-named
+file-level caller or callee coordinates. Module-level callers and opaque external/declaration boundaries
+remain distinct. This checks coherence of extracted local coordinates, not completeness
+of all possible calls; legacy and manually selected v2 analysis retain their behavior.
+The same private integrity seam rejects actually used runtime import bindings into modeled
+local modules when their canonical target has no unique modeled symbol, in production
+and test files. Namespace constant/enum reads retain their actual declaration origin;
+an unmodeled value cannot borrow a same-named function's `tested_by`/`covers` association.
+Plain constants remain module metadata. The importing path fails explicitly instead of
+silently losing the dependency. Unused bindings, erased type-only uses and lexical
+shadows do not prove runtime use. Optional private extraction DTO metadata is validated
+at worker transport and preserved during merging; it adds no public graph schema fields.
+No value symbols or approximate test edges are manufactured. Ordinary/no-snapshot
+extraction and legacy admission retain their policy; modeled direct namespace test
+associations remain supported, without a claim of complete transitive recommendations.
+
+Retained generator declarations/expressions/methods are outside this named qualification
+domain. A cached generator's `.next()` can execute its body without an extracted consumer
+resumption edge, even if initialization and yielded values are statically known. The
+existing iteration refusal covers this limit; ordinary extraction retains generator facts.
+The former intrinsic delegated-generator positive remains a visible unsupported witness.
+The shared JavaScript diagnostic API also reports this generator refusal without a named
+configuration or supplied snapshot. Ordinary JavaScript diagnostics therefore become more
+conservative; the ordinary extractor still returns the existing generator symbols/calls.
+
+Qualified TypeScript also refuses `CallExpression` callees outside the shared extractor
+and JavaScript domain after transparent parentheses/type wrappers are removed. Conditional,
+comma, inline-function and other unnamed call forms receive `SOURCE_DYNAMIC_CALL_UNSUPPORTED`
+for selected producers and broad excluded scope. Identifier/property calls keep their actual
+coordinates; literal `import()` remains a separately guarded module link. This shape check
+does not establish constructor call edges. Qualified construction now requires a proven
+SDK constructor value origin: internal and uncertain `NewExpression` targets are
+unsupported. Transparent immutable aliases of actual SDK values retain that opaque
+boundary; an annotation, assertion or call-return type alone cannot establish it.
+Ordinary/legacy construction facts and static namespace class-test associations remain
+available, but those associations do not qualify the constructor invocation itself.
+Actual SDK Worker/SharedWorker constructors are code loaders and remain unsupported,
+including transparent aliases and URL-valued arguments; no worker dependency edge is
+invented. Other known SDK constructor boundaries retain their domain.
+
+Browser SDK setTimeout/setInterval calls with string, possibly string, any/unknown or
+generic handlers are unsupported runtime evaluation. Known function callbacks are also
+unsupported because callback invocation edges are not extracted; earlier callback
+controls remain visible as explicit BLOCK cases. Timer aliases and destructuring are
+tracked; helpers and value escapes without a proven safe direct call are refused.
+Local/own methods and Node timer imports do not acquire the browser SDK origin merely
+from their spelling. Ordinary TypeScript without retained inputs remains unchanged.
+SDK Object.defineProperty accessor descriptors and unproved descriptor/function
+escapes are unsupported. Only a direct data-descriptor literal containing known own
+value/writable/enumerable/configurable fields retains its boundary; get/set, spreads,
+unknown fields and a prototype-setting field do not prove accessor absence. A mutable const descriptor
+alias is not an absence proof. Own homonyms and type-only SDK references stay distinct.
+SDK data descriptors replacing iterator/then protocols or SDK-callable properties are
+also unsupported; a direct descriptor does not restore intrinsic mutability safety.
+Nonprimitive writes to members whose callable/constructible coordinates come from
+the actual loaded SDK revoke that opaque call boundary. Own source declarations are
+distinct; an annotation is not proof of an unmodified native value.
+The current ES2022.full Program loads lib.webworker.importscripts.d.ts: importScripts
+is an actual SDK loader and is unsupported, including transparent value aliases.
+No worker library is added and no SDK origin is fabricated from its spelling.
+Retained bodyless-only function declarations are not evidence of a runtime lexical
+shadow and are unsupported. A binder overload set with a real implementation remains
+distinct; ordinary signature facts and SDK/declaration boundaries are preserved.
+
+Tagged-template invocations have no extracted callee edges and are explicitly unsupported:
+JavaScript producers report `JAVASCRIPT_TAGGED_TEMPLATE_UNSUPPORTED`, while qualified
+retained TS/JS scope reports `SOURCE_TAGGED_TEMPLATE_UNSUPPORTED`, including excluded
+sources. Ordinary template literals keep their existing call references and eligibility.
+
+Verification captures qualified inputs directly at each before-analysis, after-analysis
+and final stability boundary. The digest and retained files from the same after-analysis
+capture serve admission and Git post-image comparison together; no capture is reused across
+later temporal checks. Useful discovery for candidate/ledger scope remains separate. Source,
+configuration and inventory drift still refuses admission.
+
+Unmodeled JSX component invocations are unsupported; intrinsic lowercase/custom-element
+tags and ordinary literals retain their existing domain, and the automatic JSX-runtime
+gate remains separate. Actual SDK Function/CallableFunction/NewableFunction `call`, `apply`
+and `bind` helpers on retained callable receivers are refused, including aliases. Own class
+methods with those names do not acquire that prototype origin. Class heritage whose base
+resolves to an internal retained class/constructor is unsupported until dependency edges
+are modeled; plain classes and known SDK/external base boundaries retain their existing
+contract. No component, helper or heritage edge is invented. Semantic inspection shares
+one retained Program per snapshot identity and never reuses it across a new boundary;
+repository source reads remain limited to retained bytes plus the pinned SDK libraries.
+
+Decorator AST nodes are unsupported in qualified scope, including bare, property and
+factory-call forms: extracting the explicit factory call does not model the implicit
+application of the returned decorator. Exported callable or constructible bindings
+originating in destructuring, including local export clauses and identifier aliases,
+are unsupported because extraction does not create their symbol coordinates. Unknown,
+any and possibly callable union bindings receive the same refusal; known noncallable
+local bindings retain their module-metadata boundary. Modeled identifier declarations
+and ordinary TypeScript analysis without retained inputs remain unchanged. These guards
+apply to selected and broad retained sources, and to the JavaScript producer; no new
+decorator edge, destructured symbol or test-coverage fact is invented.
+
+Other exported variable bindings that are possibly callable or constructible also
+require an existing extraction coordinate. Only direct arrow/function initializers
+currently create variable callable symbols; class expressions, conditionals, aliases,
+calls and wrapped initializers are unsupported exports. Known noncallable bindings and
+modeled function/class declarations retain their existing domain. Actual SDK
+`Object.constructor` access on a callable/constructible receiver is refused as a
+dynamic-evaluation route, including subsequent helper-call chains; own constructor
+properties and noncallable object receivers do not acquire that intrinsic origin.
+Bun `import.meta.require`, including retained aliases and destructuring, is a runtime
+loader outside the qualified static-module domain. Computed metadata access and
+container escapes also refuse qualification when the loader origin cannot be excluded.
+Ordinary metadata such as
+`import.meta.url` remains supported. No coordinate, evaluation or loader edge is invented.
+
+Source getter/setter declarations are unsupported in qualified scope because implicit
+accessor invocation dependencies are not extracted. Ordinary data properties, methods
+and SDK/external boundaries keep their existing domain; ordinary TypeScript analysis
+without retained inputs is unchanged. Retained filesystem inputs must be regular files:
+symlinks, FIFOs, sockets and devices do not provide captured source bytes.
+
+Triple-slash `reference path` directives are unmodeled compiler-input dependencies and
+are unsupported in selected producers and broad retained scope, whether their targets
+are retained or absent. Missing-target diagnostics remain available. This refusal does
+not fabricate runtime imports, and ordinary TypeScript extraction remains unchanged.
+
+Qualified CLI report publication must not create or replace a potentially retained
+repository input after the final capture. The writer checks the prospective canonical
+path, including nonexistent destinations and aliases above the checkout. Use an output
+only under the dedicated `.semctx/reports` subtree or outside the repository; ordinary report
+publication and atomic symlink safeguards remain unchanged. The Action defaults to
+`.semctx/reports/verify.json`, preserving the qualified-rejection report and adapter hand-off.
+Workspace and Git control paths remain protected even when Git metadata lives outside
+the checkout. Reports cannot replace configuration, database or verification state.
+
+Actual SDK `Reflect.apply`/`Reflect.construct` and their aliases/escapes are unsupported
+reflective invocations. Iteration via ordinary `for...of` or iterable spread requires a
+proven intrinsic array/string or SDK collection value; internal or uncertain iterator origins
+are refused without inventing invocation edges. `for await...of` over a synchronous array
+additionally requires a fresh direct literal whose non-hole elements each satisfy the existing
+safe-await value proof: primitive values, actual native promises or supported async results.
+Mutable const array containers and spreads do not establish that element provenance. Proven
+primitive strings retain their boundary; this does not admit arbitrary async iterators.
+Ordinary facts remain unchanged. The same iteration refusal covers array binding/assignment,
+array-binding parameters and delegated `yield*`. `await` refuses internal or uncertain thenable
+origins; primitive values, actual native promises and genuine async-function results retain their
+supported boundary. The actual async callee binding, including resolved aliases, must not have
+been reassigned; a declared async origin alone is insufficient. Type annotations or assertions
+alone do not prove that boundary. `instanceof`
+requires an actual SDK constructor origin; internal or uncertain `Symbol.hasInstance`
+invocations are unsupported. No protocol invocation edges are added.
+Unknown or internal primitive coercion at addition/concatenation and template interpolation
+is unsupported, including numeric addition over unknown parameters. Primitive annotations
+or assertions do not prove value origin. Async return resolution, including expression-bodied
+async arrows, applies the same thenable-origin boundary as explicit await; nested ordinary
+function returns remain distinct. Actual SDK Proxy construction and ServiceWorkerContainer
+register references are unsupported implicit trap and code-loader routes. No protocol,
+proxy-trap or browser-loader edges are invented; actual primitive/native promise values
+and ordinary own-name methods retain their supported domains.
+
+The original declaration-bearing fixture and the historical declaration-free `eligible-esm`
+fixture retain their complete original source bytes. The latter's unknown `input + 1`
+coercion is now the explicit `eligible-esm-unknown-coercion-blocks` witness. Positive
+qualification uses the separately named `data-proven-esm` domain with literal `1 + 1`
+operands and guarded Node data, the same tool pins/topology/static call chain, and test copy
+describing its fixed primitive value. The historical `primitive-safe-esm` bytes remain a
+distinct opaque-data BLOCK witness. Artifact pairs use identical explicit fixture domains;
+earlier receipts remain bound to their original sources and are never relabeled.
+Array/collection initialization behind a const binding does not prove an immutable
+object or exclude a later `then` override. Those iterable objects are outside safe
+await/async-return eligibility; primitive values and unmodified native promise origins
+retain their boundary. Explicit nonprimitive `then` writes, including immutable static
+key aliases, are unsupported. A native promise's own `then` override is not invoked by
+direct native await, but it can be invoked by async-return assimilation; the mutation
+refusal conservatively prevents that missing dependency.
+The same object mutability limit applies to iteration: assignments through a proven
+SDK `Symbol.iterator` or `Symbol.asyncIterator` key (including const key aliases)
+are unsupported. This source-level refusal applies before for-of, await-for-of,
+destructuring, delegated yield or spread eligibility; it does not fabricate protocol
+edges. Unmodified intrinsic origins retain their boundary, and string properties
+named `iterator` or own homonyms are distinct from actual SDK protocol symbols.
+Actual SDK call and constructor signatures with callable callback parameters are
+unsupported when their supplied callback is internal or unproven: the extractor
+does not model the implicit invocation. This includes array callback APIs and a
+native Promise executor; eligibility of the resulting native promise value does
+not prove the executor dependency. Primitive/noncallback SDK operations and real
+own source APIs remain distinct. No callback or constructor edge is fabricated.
+JavaScript files under migration directories are currently classified as executable
+sources, unlike TypeScript migration-role files; directory spelling alone does not
+turn them into structural migrations or silently remove their executable facts.
+The same SDK signature boundary covers iterable parameters: internal or uncertain
+iterable arguments are unsupported because their protocol invocation has no edge.
+Literal arrays/strings and the existing proven SDK collection origins remain distinct.
+SDK `Object.assign` cannot restore native callable authority after an internal or
+unknown patch; callable/protocol writes and uncertain sources on SDK targets refuse.
+Ordinary data patches and actual own APIs keep their boundary. Numeric unary,
+arithmetic/bitwise/relational and loose-comparison coercions require primitive value
+proof; strict identity, logical truthiness, `typeof` and `void` are distinct.
+The `in` operator requires a proven primitive/property-symbol left key; its ordinary
+right-hand object is not itself a coercion refusal. Actual SDK global bindings cannot
+be replaced through data descriptors merely because the binding is a noncallable
+namespace. The same `SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED` rule applies to actual SDK-global
+replacement through `Object.defineProperties`, `Reflect.set` and `Object.assign`, not only a
+singular descriptor. Receiver identity and captured SDK variable declarations establish this
+boundary, including JavaScript's merged descriptor declarations; local homonyms and ordinary
+data targets remain distinct. This is a bounded set of mutator routes, not a complete global
+mutation model. These shared semantic diagnostics add no invocation edges and may also report
+the refusal through the JavaScript diagnostic API outside named qualification.
+Actual SDK WebAssembly `instantiate`, `instantiateStreaming` and `Instance` construction refuse as instantiation
+boundaries: native module-start execution can call an imported internal helper without
+an extracted invocation edge. Compile/validate data operations remain distinct.
+Actual SDK node insertion with a captured SDK `Document.createElement` argument whose
+constant tag spelling is `script` or an ASCII-case variant such as `SCRIPT` also refuses.
+Receiver provenance and default-library declaration identity
+establish the boundary; a type annotation, local lookalike or bare operation name does
+not. Standalone inert script creation and ordinary DOM/local controls remain eligible.
+The insertion boundary conservatively refuses empty/disconnected script instances too.
+Native Bun observed the WebAssembly callback, and isolated Chrome observed the appended
+script's output changing after a hidden-script-only edit. Those observations establish
+these specific runtime effects, not complete browser/runtime modeling or private
+consumer execution. Shared JavaScript diagnostics may also report these refusals.
+Insertion arguments are selected by actual default-library owner/member identity:
+`Node.appendChild/insertBefore/replaceChild` argument 0; every argument of
+`ParentNode.append/prepend/replaceChildren` and `ChildNode.before/after/replaceWith`;
+`Element.insertAdjacentElement` argument 1; `Range.insertNode` argument 0.
+Reference/replaced nodes and position strings are not insertion candidates.
+On the captured `strict: false` checker, SDK `onclick` has one call signature and its
+replacement already refuses through the existing callable-mutation guard. No generalized
+nullable callback-property rule or complete browser invocation model follows from that case.
+Actual SDK Date construction requires primitive provenance for the first seven arguments;
+a separately proven intrinsic Date copy remains eligible under this boundary. SDK origin
+cannot be supplied by a local value's annotation. Zero-argument construction and ordinary
+Date calls remain distinct; unknown coercion refuses without inventing an invocation edge.
+Actual imported cluster/node:cluster fork and setupPrimary runtime bindings are unsupported
+process launchers. Existing module-origin traversal preserves child_process/vm boundaries;
+inert cluster imports, isPrimary reads, type-only uses and local homonyms remain distinct.
+The setupPrimary refusal is conservative even without a subsequent fork. Runtime named reexports
+of fork/setupPrimary from cluster/node:cluster, including aliases through
+repository barrels, retain this refusal; type-only exports remain distinct.
+Actual SDK Document.write/writeln also refuse runtime script execution boundaries. Member
+declaration and receiver origin remain required; ordinary HTML/text arguments are refused
+conservatively too. Neither HTML parsing nor hidden-module dependency extraction is added.
+Plain argument data does not establish callee identity. Property/element invocations on
+an unresolved ambient receiver refuse without a captured runtime lexical origin, actual
+SDK origin or explicit supported imported boundary. Ordinary native process members and
+bare framework-call ownership retain their existing policy. Synthetic JavaScript binder
+declarations introduced by an assignment are not a runtime lexical binding.
+For a retained locally initialized receiver, the called member needs an actual retained callable
+target. A bounded exception preserves the string-member boundary when an actual dominating
+`typeof value !== "string"` throw guard proves the same unwritten receiver binding and the member
+has the loaded SDK String declaration origin. This removes only the local unresolved-callee
+refusal; callback and prototype checks remain, and no callable coordinate is fabricated.
+Annotations, casts or a guard for another primitive kind do not establish this exception.
+Otherwise, an erased or unresolved member cannot borrow the receiver's identity. An erased
+SDK-shaped annotation is not runtime callee authority: absent or erased local member origin
+without an actual retained coordinate receives `SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED`, not a
+fabricated WebAssembly or script origin. Known own methods and callable initializer aliases
+retain their separate proof.
+Caller-supplied parameters keep their existing opaque data-argument policy; no universal
+call-target completeness is inferred from this local-container refusal.
+The early producer refusal applies to absent, erased or synthetic-assignment-only member
+identity. Real captured literal/type member declarations proceed to the existing qualified
+coordinate check. Missing callable graph coordinates still refuse there with their precise
+coordinate reason, rather than being hidden by an earlier producer diagnostic.
+An actual uncomputed object-literal __proto__ setter requires the existing closed-data
+proof for its initializer; null/direct closed data remain separate. Callable or uncertain
+prototypes refuse without modeling inherited invocation. Computed properties, methods
+and shorthand named __proto__ do not use that setter syntax.
+Callable invocation/escape from import.meta refuses unmodeled runtime effects while
+ordinary metadata reads and supported primitive aliases remain distinct. The Vite glob
+case has direct/static admission observations and a documented transform, but no native
+Vite build observation in this qualification. It is not labeled as CommonJS.
+Actual SDK String and Number conversion requires existing primitive provenance for the first argument.
+The same String/Number, numeric Math, array join and locale-string obligations use effective
+SDK target, receiver and argument coordinates through actual `Function.call`, `Function.apply`
+and invoked immutable const-bound aliases. Apply argument lists must be finite direct literals
+(or absent/null) for this proof; unknown/spread lists cannot establish primitive coordinates.
+Creating an unused bound function is not a coercion invocation and is not refused for that alone.
+Local call/apply/bind homonyms retain their own domain. No hidden invocation edge is invented.
+SDK array join requires a fresh direct literal receiver with proven primitive elements and
+a primitive separator when supplied. Const array initializers and container aliases do not
+prove the current elements after possible mutation. Ordinary toString hooks are unmodeled invocation;
+unknown inputs conservatively refuse, while primitive/local controls retain their domain.
+SDK array toLocaleString also requires fresh direct literal primitive elements. Supplied
+locales must be primitive or a direct literal array of primitives; options must be
+primitive or a direct literal data record with primitive values. Unproven values refuse
+conservatively; this does not establish complete Intl option semantics or an implicit edge.
+Actual SDK Math calls also require primitive provenance for arguments consumed by
+numeric parameters in the loaded SDK signature, including rest parameters. Zero-argument
+signatures and ignored extra arguments remain distinct; local homonyms do not use this guard.
+Actual SDK parameters whose direct dictionary members declare callable types in the loaded
+default libraries require the existing closed literal-data proof for their supplied argument.
+Internal callbacks and unproven dictionaries refuse; this does not model their invocation edges.
+Writes to actual SDK Location.href require a proven constant inert URL. After URL-parser
+whitespace and case normalization, only HTTP/HTTPS and relative URLs have that proof.
+Other schemes, including JavaScript/data/VBScript/blob, and unknown values refuse because
+their code/module execution is unmodeled. Proven inert URLs and local homonyms retain their domain.
+Qualified tests with a resolved literal dynamic import and no canonical binding proof
+refuse through a private retained-source/AST source-line-specifier check. Ordinary/legacy
+module facts and non-test literal-import closure remain unchanged; no new metadata field
+or approximate tested_by edge is introduced.
+Actual SDK Worklet `addModule` loaders are unsupported without a module link.
+Retained configurations that actually emit imported runtime helpers are unsupported:
+the pinned SDK emitter observes helper metadata and the emitted `tslib` dependency,
+without manufacturing an edge. Configurations emitting no helpers remain eligible.
+
+Computed property access/names require a proven primitive key or actual SDK symbol
+origin, without relying on a Symbol/string annotation. Resource declarations carrying
+the SDK Using/AwaitUsing flags are unsupported implicit disposal, while ordinary
+identifiers named `using` remain distinct. CommonJS JavaScript `.cjs` is unsupported
+in retained broad module scope even with ESM-looking syntax; its existing selected
+JavaScript-producer refusal remains in place. No property-coercion/disposal edges are added.
+
+Retained named `tsconfig*.json` files whose options are not actually consumed are
+unsupported. Conventional `tsconfig.json` reads and their SDK-parsed `extends` read set
+are recognized across the retained sources; project references and inventory/hash
+membership alone do not establish consumption. There is no guessed named-config selector.
+This binds the publication operation only; it does not guarantee arbitrary future edits.
+
+Qualified inputs retain `package.json` and `pyproject.toml` workspace manifest bytes,
+including ignored manifests outside the reserved internal directories. The input
+identity also binds directory membership consumed by the workspace projection, so
+adding or removing an empty declared workspace invalidates freshness. Reserved
+`.semctx` artifacts and unrelated empty output directories do not supply workspace
+evidence. Workspace projection consumes retained manifest bytes; index reconstruction
+still compares its projection against the final current layout to detect drift.
+
+## Proof boundary
+
+`Symbol.species` is a callable protocol key: overrides through the existing SDK mutator
+routes refuse without inventing constructor edges. Comparator-free actual SDK sorting
+requires a fresh literal receiver with proven primitive elements; const containers and
+element annotations do not establish immutability. Actual SDK global-binding replacements
+invalidate native authority and refuse, while local bindings stay distinct. Promise
+combinator inputs must be fresh literal arrays whose individual values satisfy the existing
+safe-assimilation proof; internal/unknown thenables and mutable container/helper escapes
+are unsupported. Literal primitive/native promise values retain their boundary.
+The exact built-in `vm` / `node:vm` runtime bindings share captured import/alias provenance
+with process launchers and are unsupported code evaluation, including value escapes.
+Type-only/unused imports and own homonyms remain distinct; no Node declarations, implicit
+invocation edges or global SDK policy rewrite is added. The existing explicit
+`Symbol.toPrimitive` write refusal already covers the reported `String(value)` case.
+
+Runtime values actually imported from the built-in `child_process` / `node:child_process`
+module are outside qualification: an opaque launcher can execute retained repository code
+without a module link. Import bindings, immutable aliases, namespace members and helper/
+container escapes use captured AST provenance, not a new Node declaration provider or API
+catalogue. Unused/type-only imports, real local homonyms and unrelated primitive Node
+helpers remain distinct. No launch edge is manufactured. Internal calls through an
+unmodeled callable parameter already fail the existing exact call-coordinate guard when
+their file is in the effective change/closure; no function-pointer propagation is added.
+Explicit runtime re-exports from `child_process` / `node:child_process` and `vm` /
+`node:vm` are conservatively unsupported at the retained exporting source. This preserves
+the same execution boundary when raw import declarations in a consumer point at a barrel.
+Whole-declaration and per-specifier type-only re-exports remain distinct, as do ordinary
+Node data modules and local exports. No new barrel resolver, Node provider or invocation
+edge is introduced.
+
+Actual SDK `Object.setPrototypeOf` / `Reflect.setPrototypeOf` references are unsupported
+prototype mutations, including retained aliases and helper escapes. SDK `Object.create`
+and `Object.defineProperties` use the descriptor boundary: accessor/protocol installations,
+unknown descriptor maps or prototype values refuse; directly proven data descriptors stay
+distinct. The loaded SDK symbols `match`, `matchAll`, `replace`, `search` and `split` join the
+well-known callable protocol-key refusals. Own-name APIs and string properties are not
+classified as native hooks. No implicit invocation edges, new libraries or fixture changes
+are introduced; ordinary/v1 extraction remains unchanged.
+
+Opaque data arguments require actual primitive provenance or recursively proven direct
+literal data without callable/uncertain members. A const object/container initializer
+does not prove immutability or absence of callbacks; nested callable members are unsupported.
+Actual SDK `JSON.stringify` uses the same closed data domain to refuse unmodeled `toJSON`
+hooks, including aliased/helper escapes, while own APIs remain distinct. No callback or
+serialization edges are invented and the four public fixture outputs remain unchanged.
+
+Named qualified working-tree verification refuses relevant untracked inputs present in
+the retained inventory. Git diff does not describe their additions, so merely adding paths
+to expected coverage cannot establish their effects. The reason names each untracked
+retained path; actually excluded generated/hard-excluded artifacts and the separately
+sealed raw control config do not become obligations through this guard. Ordinary/v1 diff
+semantics stay unchanged. The check compares actual retained paths with Git's tracked
+index inventory, without recursively enumerating ignored trees. Repository `.gitignore`,
+`.git/info/exclude` and `core.excludesFile` cannot hide an authored file that capture
+actually retained. The existing binding classification is invalid with the named
+untracked-input reason, even when the indexed bytes have not drifted. A force-tracked
+ignored source remains in the supported tracked domain. The old outside-output
+`ignored.ts` fixture is now an explicit negative; the separate positive uses a generated
+output path genuinely omitted by capture, preserving both source domains visibly.
+
+Runtime object/class definitions of potentially callable hooks at actual SDK protocol
+symbol keys are unsupported as well as later mutations. Methods and unknown/nonprimitive
+initializers have no modeled implicit invocation edge; erased declarations, primitive
+data definitions, string keys and locally shadowed symbols remain distinct.
+
+Qualified identity separately binds the exact `.semctx/config.json` bytes, including an
+explicit absent state. Equivalent JSON rewrites invalidate the input seal; malformed
+configuration still refuses normal loading. These control bytes do not become retained
+repository files or Git post-image members, and database/report artifacts stay excluded.
+
+Actual SDK descriptor/mutator routes cannot overwrite `Symbol.iterator`,
+`Symbol.asyncIterator`, `Symbol.hasInstance` or `Symbol.toPrimitive` and then claim an
+intrinsic boundary. Formal primitive property-key slots are not iterable API contracts.
+Every actual SDK `Promise.resolve` route must establish safe assimilation of its value.
+Opaque calls cannot accept unproven callable data. Specific inline obligations may be
+discharged after assembly by existing uniquely owned calls or paired canonical test edges;
+the exact snapshot, callback span, current graph and indexed raw-input seal bind that proof.
+No new invocation edges, framework whitelist or public proof field is introduced.
+Runtime primitive guards are proof only for the same unmodified binding in the same
+executing scope. Type annotations and assertions alone do not establish value provenance.
+
+The three historical public consumer domains remain byte-identical and visible BLOCK
+witnesses. The fourth `data-proven-esm` domain preserves the primitive leaf and changes only
+the two Node helpers to check real string values before native calls. Current/historical
+artifact comparisons use identical explicitly selected domain inputs. Native pipeline
+behavior and source hashes are observed separately; prior receipts are never relabeled.
+
+Every required closure member must also have admitted Plane-A fact-use tuples.
+The JavaScript registration covers the 22 declared ESM fact kinds for v2,
+dialect 5.9.3 and `verify` / `change` only. It does not replace capability,
+completeness, binding or freshness checks and grants no execution or approval authority.
+
+`PASS` is a static analysis result. It does not prove test execution, Turbo cache
+invalidation, failure propagation or pipeline execution. The report names those as
+unobserved proof obligations and specifies the required observations. Static admission
+qualifies only the named source/dependency subset, never runtime dependency completeness.
+Built-in Node modules are an explicit external boundary; unresolved package/self-name
+imports require resolution or a separately qualified profile.
+
+Package qualification, installation, session loading and observed use are separate
+states. Qualification is invalidated by any source, selector/configuration, Git coordinate,
+analyzer, store or snapshot change, interrupted indexing or incomplete required coverage.
+Consumers must retain the actual report and exit code on the exact candidate. Independent
+aggregate review and negative witnesses are required before delivery; the modified
+mechanism is not its own admissibility authority. Public fixtures contain no private
+consumer source, documents or links. Approval gates, brokers and shared agent policy
+remain a distinct project.
diff --git a/docs/examples/pre-commit-hook.md b/docs/examples/pre-commit-hook.md
index 726713e2..d8f337ee 100644
--- a/docs/examples/pre-commit-hook.md
+++ b/docs/examples/pre-commit-hook.md
@@ -2,24 +2,31 @@
`semctx verify diff` analyses a git diff and exits **non-zero on `BLOCK`** (exit code 3), so it
drops straight into a pre-commit hook or a CI job. `WARN` and `PASS` exit 0 by default; add
-`--strict` to also fail on `WARN`.
+`--fail-on warn` to also fail on `WARN` (`--strict` is a legacy alias).
## Local pre-commit hook
`semctx verify diff --staged` analyses exactly what is staged for the commit.
+Bun loads `bunfig.toml` preloads and `.env` from its working directory before semctx can inspect
+source. A clean contributor checkout is not necessarily trusted. All Bun-backed commands below
+start from a trusted semctx installation, separate from the analysed checkout, and pass the
+captured absolute repository path through `--root`. Do not point `semctx_home` at an untrusted
+branch, or run a bare `semctx` command there. See [SECURITY.md](../../SECURITY.md#integrations).
+
Create `.git/hooks/pre-commit` (make it executable: `chmod +x .git/hooks/pre-commit`):
```sh
#!/bin/sh
# Block a commit whose staged diff violates a strict-tier rule
# (invariant or critical contract changed without a covering test).
-SEMCTX="bun /abs/path/semantic-context-compiler/apps/cli/src/index.ts"
+repo_root="$(git rev-parse --show-toplevel)" || exit 1
+semctx_home="/abs/path/semantic-context-compiler" # trusted installation, not the contributor checkout
# Skip if the repo was never indexed (nothing to verify against).
-[ -d .semctx ] || exit 0
+[ -d "$repo_root/.semctx" ] || exit 0
-$SEMCTX verify diff --staged --root .
+bun --cwd "$semctx_home" "$semctx_home/apps/cli/src/index.ts" verify diff --staged --root "$repo_root"
status=$?
if [ "$status" -eq 3 ]; then
echo "semctx: BLOCK — a strict-tier rule failed. Fix or add a test, then re-commit."
@@ -31,13 +38,26 @@ exit 0
Notes:
-- The hook re-uses the **already-indexed** `.semctx/` graph; it does not re-index. After large
- refactors run `semctx index` to refresh the graph (or add it to the hook if your repo is
- small — indexing the demo repo is ~sub-second, a 260-file monorepo ~3–4 s).
-- Use `--strict` in the hook if you want `WARN` (plain exported contract without a direct test)
- to block as well.
+- The hook re-uses the **already-indexed** `.semctx/` graph and must not re-index. A staged or
+ working-tree check reads the index as a snapshot of the committed `HEAD` and matches the diff's
+ old-side line numbers against it. Indexing the staged or edited tree first stores post-edit
+ ranges, which can stop overlapping those hunks and hide a `WARN` or `BLOCK` finding.
+- Re-index outside the hook, from a clean tree, after each commit, pull or branch switch, using
+ the trusted-directory command below. If `HEAD` changes without a refresh, the next check returns
+ `BLOCK` with `index_binding_stale`. Indexing was previously measured at sub-second on the demo
+ repo and about 3–4 s on a 260-file monorepo; these timings have not been re-measured here.
+- Use `--fail-on warn` in the hook if you want `WARN` (plain exported contract without a direct
+ test) to block as well.
- `git commit --no-verify` bypasses the hook for the rare intentional exception.
+To refresh the committed snapshot outside the hook, run this from the clean analysed checkout:
+
+```sh
+repo_root="$(git rev-parse --show-toplevel)" || exit 1
+semctx_home="/abs/path/semantic-context-compiler" # trusted installation
+bun --cwd "$semctx_home" "$semctx_home/apps/cli/src/index.ts" index --root "$repo_root"
+```
+
## Managed hooks (husky / lefthook / pre-commit framework)
lefthook (`lefthook.yml`):
@@ -46,7 +66,10 @@ lefthook (`lefthook.yml`):
pre-commit:
commands:
semctx-verify:
- run: bun /abs/path/apps/cli/src/index.ts verify diff --staged --root .
+ run: |
+ repo_root="$(git rev-parse --show-toplevel)" || exit 1
+ semctx_home="/abs/path/semantic-context-compiler" # trusted installation
+ bun --cwd "$semctx_home" "$semctx_home/apps/cli/src/index.ts" verify diff --staged --root "$repo_root"
```
## Guarded mode (Claude Code plugin)
@@ -54,7 +77,8 @@ pre-commit:
`verify diff --staged` above is an advisory gate: it exits on `BLOCK` but records nothing. When the
repository also runs the Claude Code plugin in guarded mode, declare `"hooks": "project-managed"` in
`.semctx/guard.json` and make `semctx verify hook pre-commit` the **last** pre-commit job, after any
-formatter or restager; put `semctx verify hook pre-push` last in pre-push. The pre-commit job
+formatter or restager; put `semctx verify hook pre-push` last in pre-push. Launch both jobs with
+Bun's trusted `--cwd` and the absolute `--root` as above. The pre-commit job
compares the post-writer tree with the recorded proof and re-records only on drift; the pre-push job
checks every pushed commit tree and never records. Configuration, Lefthook layout and accepted
residue: [`docs/integrations/claude-code-guarded-mode.md`](../integrations/claude-code-guarded-mode.md)
@@ -65,7 +89,9 @@ residue: [`docs/integrations/claude-code-guarded-mode.md`](../integrations/claud
Analyse the PR directly from its Git-backed range:
```sh
-semctx verify diff --base origin/main --head HEAD --root .
+repo_root="$(git rev-parse --show-toplevel)" || exit 1
+semctx_home="/abs/path/semantic-context-compiler" # trusted installation
+bun --cwd "$semctx_home" "$semctx_home/apps/cli/src/index.ts" verify diff --base origin/main --head HEAD --root "$repo_root"
# exit 3 → BLOCK → fail the job
```
@@ -78,7 +104,7 @@ therefore remains a deliberately blocking diagnostic, even when accompanied by `
| verdict | exit | default rules |
| --- | --- | --- |
| `BLOCK` | 3 | invariant touched without test · **critical** contract (tagged `critical`/`security`) without test · security surface without verification |
-| `WARN` | 0 (3 with `--strict`) | plain exported contract without a direct test · touched unresolved contradiction |
+| `WARN` | 0 (3 with `--fail-on warn`) | plain exported contract without a direct test · touched unresolved contradiction |
| `PASS` | 0 | none of the above |
Rules are configurable in `.semctx/config.json` (`blockingRules`). See
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 1232889c..5e9e51fa 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1,7 +1,7 @@
# Getting started
-Semctx **0.4.2** requires **Bun >=1.4.0**.
+Semctx **0.4.3** requires **Bun >=1.4.0**.
The supported, tested host baseline is **Codex 0.147.0** and
**Claude Code 2.1.229**. Other host versions are **unknown** until tested;
these pins do not claim the earliest historically compatible versions.
diff --git a/docs/integrations/github-actions.md b/docs/integrations/github-actions.md
index 86343244..9939c0fc 100644
--- a/docs/integrations/github-actions.md
+++ b/docs/integrations/github-actions.md
@@ -16,7 +16,7 @@ steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # required: the merge-base with the base must be local
- - uses: hoklims/semctx/packages/github-action@v0.4.2
+ - uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
diff --git a/docs/qualification/modelo-suite-static-v1.md b/docs/qualification/modelo-suite-static-v1.md
new file mode 100644
index 00000000..10f1c88c
--- /dev/null
+++ b/docs/qualification/modelo-suite-static-v1.md
@@ -0,0 +1,449 @@
+# Modelo Suite static profile qualification
+
+This qualification concerns `modelo-suite-static-v1`, selected explicitly by configuration
+version 2 with `selectionMode: "qualified-static-v1"`. It covers the analyzed ESM/TypeScript
+change and its static dependency closure. It does not qualify the entire consumer repository
+or its production pipeline. Public fixtures use generated anonymous sources.
+
+## Use and admission
+
+```json
+{
+ "version": 2,
+ "selectionMode": "qualified-static-v1",
+ "analysisProfile": "modelo-suite-static-v1",
+ "languages": { "typescript": "on", "javascript": "on" },
+ "include": ["scripts/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}", "suite/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
+ "exclude": ["**/node_modules/**", "**/dist/**", "**/.git/**", "**/.semctx/**"]
+}
+```
+
+Apply these fields to a complete existing configuration; preserve its blocking rules and other
+settings. This excerpt is not a replacement configuration. Select the exact Git worktree root,
+inspect the proposed source roots and configure the required paths explicitly. Existing
+configuration is never broadened automatically. Rebuild the index after editing the sources.
+
+Consumers must inspect `analysisAdmission.profile`, `analysisAdmission.status`, binding,
+index freshness, check freshness, change coverage and the actual exit status. A legacy report
+without this admission is not qualified. An older runtime rejects the new selection mode.
+Qualified committed ranges that remove base lines, files or paths reject with
+`QUALIFIED_BASE_RECOVERY_UNSUPPORTED`. Ordinary body replacements count as removals too.
+The report is `BLOCK`, admission is rejected, CLI exits 3 even under `--fail-on none`, and
+the Action fails. Immutable merge-base source eligibility and retained compiler inputs are
+not captured yet; raw blobs do not establish that base's admission. Pure additions removing
+no base lines keep the existing path. Use refreshed working-tree verification before committing,
+but do not present it as a qualified committed-range result or disable the profile to bypass CI.
+This temporary restriction reduces supported range workflows without claiming a performance gain.
+Refreshed working-tree removals attempt immutable HEAD guard capture only after current analysis
+is admitted. A current rejected report keeps its own reasons and observed binding state and remains
+BLOCK without supplemental old capture. Otherwise-admitted removals require full safe raw resolved-HEAD
+inventory and retained old inputs analyzed under the exact current raw/parsed Semctx policy
+and pinned current SDK/compiler, not historical policy or SDK admission. Actual old obligations
+and dependency/evidence closure need bound analyzed sources and passed factual capability/authority
+gates; broad old loader/configuration/parse checks remain required. Unavailable, unsafe, unbound
+or unsupported capture returns rejected `BLOCK` with invalid binding and
+`QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED` details. Recovered invariant/security/contract
+findings keep their configured severities, not a blanket new BLOCK severity for every old guard.
+Current covering-test evidence is structural; incomplete negative evidence remains advisory.
+Full HEAD capture and selected analysis run per otherwise-admitted removing diff, without a
+qualified overall resource/latency ceiling. This working-tree result does not qualify a committed
+removal range.
+Supported staged/range post-image comparison requires the already-declared Git >= 2.41 for
+`--attr-source`; an older Git returns an operational `UNSUPPORTED` diagnostic before conversion,
+not a qualified report. In retained post-image comparison, attribute observation failure or
+metadata drift produces `QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND`, and selected conversion
+failure produces `QUALIFIED_POST_IMAGE_CONVERSION_FAILED`. Both return invalid binding,
+rejected admission and a structured `BLOCK` report. An already-known unsupported base-removal
+range skips that unnecessary comparison. Unrelated Git status/freshness and I/O failures are
+not reclassified as these admission reasons.
+The analyzer digest identifies the implementation and actual compiler bytes independently
+of the package version. A source build carrying an existing release version is still a new
+unreleased candidate until its own artifacts are qualified.
+
+## Coverage and boundaries
+
+| Obligation | Evidence required | Scope and limitation |
+| --- | --- | --- |
+| JavaScript analysis | Actual `.mjs/.js/.jsx` functions, classes, exports, imports and resolved calls | Non-function exports are explicit module metadata; no invented variable call edges |
+| Mixed-language impact | Declaration-free TypeScript to JavaScript and JavaScript to TypeScript paths, transitive callers and inherited aliases | Resolved static ESM only; declarations and CommonJS TypeScript are outside qualification |
+| TypeScript-only closure | Static reexports and literal dynamic imports, including excluded inbound importers | Required links are extracted even when no JavaScript file is present |
+| Effective module configuration | Direct and inherited compiler options are inspected | The profile uses ESNext/Bundler defaults and refuses explicit incompatible module or resolution semantics |
+| Monorepo selection | Exact changed paths plus required dependencies, including excluded importers | A required exclusion, wrong root, empty selection or zero analysis rejects admission |
+| Source identity | Exact Git endpoints, diff, raw source/configuration input digest, analyzer and index identities | Selected and excluded/untracked source/configuration drift invalidates qualification |
+| Report scope | Admitted changed files are included in expected obligations; represented complete repository outcomes are analyzed without reasons or excluded | Closure may add expected paths; validation does not prove absent inventory or runtime completeness |
+| Reconstruction | Sources actually consumed; completed snapshot plus explicit build state | Interrupted rebuilding and partial/failed required analysis refuse admission |
+| Parsing and unresolved constructs | File outcome and diagnostic, with nonpositive admission | CommonJS, computed loading/evaluation and missing/deleted post-images require another profile |
+| CLI, MCP, plugin and Action | Actual structured outputs and process results | A qualified rejection returns nonzero even in advisory consumption |
+| Tests | Executed tests on the exact input with retained outcomes | Static test edges and recommendations do not establish execution |
+| Turbo cache | Miss, hit and invalidation observed on representative tasks with one fresh cache outside the fixture repository | The synthetic task graph qualifies only that task graph |
+| Failure propagation | Injected failed task produces an observed nonzero enclosing command | Static code analysis does not establish pipeline propagation |
+| Pipeline | Actual required pipeline execution on the exact candidate | Synthetic consumer observations do not establish the private production pipeline |
+
+The standalone witness uses Bun 1.4.2, pnpm 12.9.1, TypeScript 7.0.2, Vitest 5.0.3
+and Turbo 2.11.7 with nested `apps/*`, `contracts/*`, `design-system/*`, `domains/*/*`,
+`platform/*` and `tooling/*` workspaces. Semctx's analysis compiler is separately pinned
+to TypeScript 5.9.3. Newer consumer syntax that cannot be parsed remains a failure.
+The original `createConsumer` fixture retains its `.d.mts` companion and `.cts` source
+and is an explicit `original-declaration-bearing-consumer-blocks` witness. Positive
+qualification now uses the separately named `createDataProvenConsumer` fixture in
+the `data-proven-esm` domain: the same tool pins, topology and runtime call chain,
+with no declarations, an explicit ESM `port.ts`, and leaf operands literally `1 + 1`.
+Its test describes the fixed primitive fixture value, not input-increment semantics.
+The historical `createPrimitiveSafeConsumer` / `primitive-safe-esm` bytes remain unchanged
+as `primitive-safe-esm-opaque-data-blocks`: its Node helpers pass unproven data across
+opaque calls. The fourth domain changes only those two helpers, rejecting non-string
+values with actual runtime guards before the native APIs. Native output bytes, valid
+string behavior and the synthetic `+ 9` build failure remain the same.
+The original declaration-free `createEligibleConsumer` / `eligible-esm` output is preserved
+byte-for-byte and is the additional `eligible-esm-unknown-coercion-blocks` witness:
+an unknown `input + 1` can invoke an internal primitive-coercion protocol.
+Runtime companion redirection remains distinct from declaration-change
+qualification; v2 declaration files remain visible as unsupported. Selector globs
+can select `.cts`/declarations, but selection cannot admit their unsupported semantics.
+Qualification observations record each fixture domain. Qualified historical/current
+artifact pairs both use the explicit `data-proven-esm` domain; the executable path never
+selects fixture content. Earlier `eligible-esm` receipts retain their historical source identity.
+The original declaration-bearing negative is selected explicitly, and legacy diagnostics
+retain their separately recorded original domain.
+Observations bind each domain's initial leaf and two Node helper hashes to its Git inputs;
+historical receipts keep their original domain and source identity.
+Generators are unsupported in retained qualified source: initialization facts do not prove
+later cached resumption dependencies. Ordinary extraction still exposes their existing
+symbols and calls. Actually used runtime import targets in modeled local modules must
+have one exact modeled symbol coordinate, in production and test files. Namespace value
+reads retain their actual declaration origin: missing/ambiguous targets and unmodeled
+constants or enums fail the importing path rather than silently dropping a dependency
+or borrowing a same-named function's test association. Unused and erased type-only
+bindings remain distinct. Private extraction/worker metadata carries these checks;
+the public graph schema gains no fields, value symbols or approximate edges. Direct
+modeled namespace test associations remain supported; this does not establish complete
+transitive test recommendations. These limits preserve the four fixture outputs.
+The shared JavaScript diagnostic API reports the generator refusal even without this
+profile or a supplied snapshot. Its ordinary diagnostics become more conservative while
+ordinary extraction retains generator facts.
+Classic JSX configurations that actually emit an unmodeled element/fragment factory call
+are unsupported, as observed by the pinned SDK transform on retained options/source. The
+guard uses synthesized call ranges rather than factory names. Preserve/JSX-free sources
+remain eligible under this boundary, and automatic-runtime refusals keep their existing
+reason. JavaScript diagnostics report the configuration reason when supplied retained
+compiler inputs; ordinary no-snapshot diagnostics and extraction retain their behavior.
+Opaque callable arguments are unsupported unless existing facts prove ownership of every
+retained callable reference in that exact inline callback. This private proof adds no edges
+and requires the actual graph and raw inputs to match the indexed freshness seal; stale
+graphs, value escapes, unrelated edges and empty or uncertain references do not qualify.
+Primitive data guards must dominate the use in the same execution scope and reference the
+same unchanged binding. An annotation, assertion or guard in another function is not proof.
+Nested opaque data is limited to recursively proven direct literals without callable or
+uncertain members; mutable container aliases are unsupported. Actual SDK serialization
+refuses unproven `toJSON` inputs and helper escapes without adding implicit invocation edges.
+Qualified working-tree changes with relevant retained untracked inputs refuse explicitly:
+Git diff omits their additions. Track those inputs and rebuild before qualifying; ordinary
+analysis and ignored/control report artifacts keep their existing behavior.
+The check compares the actual retained input paths with Git's tracked index inventory.
+Repository `.gitignore`, `.git/info/exclude` and `core.excludesFile` cannot conceal a
+retained authored input from this refusal. Actual capture exclusions, including ignored
+generated output directories, remain separate. No recursive ignored-tree enumeration is
+needed. The existing binding policy marks such an untracked input invalid with its exact
+reason; tracking it and rebuilding restores the supported tracked-input domain.
+Retained ignore/attribute controls beneath ignored outputs follow this same membership
+rule. The tracked positive fixture still checks both control digests, stale rejection
+after edits, and recovery after reindexing; identical untracked controls refuse admission.
+Working-tree qualification also refuses effective Git clean/process filters on retained
+inputs: check-in conversion can hide a raw byte change from the Git diff used for scope.
+The driver is inspected without executing a new filter command. Ordinary verification,
+text/eol normalization without a filter and a filter name without a configured driver
+remain distinct. Ambiguous sentinel-like names with a configured driver are conservatively
+unsupported; this refusal does not replace the staged/range attribute-conversion contract.
+Object/class definitions of potentially callable hooks at actual SDK protocol symbol keys
+also refuse qualification. Erased type members, proven primitive data, string keys and
+local symbol homonyms remain distinct; no implicit invocation edges are manufactured.
+SDK prototype replacements and accessor/protocol installation through plural descriptors
+or `Object.create` are unsupported, including aliases/helper escapes. Actual SDK well-known
+RegExp symbol hooks are refused as unmodeled protocol mutations; own APIs, string properties
+and directly proven ordinary data descriptors keep their distinct boundary. These checks
+add no invocation edges and do not change the four public fixture outputs.
+Runtime `child_process` built-in values are unsupported code-launch boundaries without a
+retained module link, including aliases, members and helper escapes. This uses actual
+built-in import provenance; local homonyms, unused/type-only declarations and the existing
+guarded `fs`/`path` data helpers keep their domains. Internal higher-order parameter calls
+with missing coordinates remain refused by the existing change/closure guard rather than
+being presented as resolved function-pointer invocations.
+Explicit runtime re-exports from the existing execution built-ins (`child_process` and
+`vm`, including `node:` forms) are also unsupported, even when their consumer reaches
+them through a repository barrel. Type-only re-exports remain distinct. This refuses the
+unmodeled execution boundary without resolving new Node declarations or creating edges.
+Species overrides, SDK-global binding replacements and unproven default-sort coercion
+refuse qualification. Default sorting is limited to fresh primitive-element literals,
+and Promise combinators independently prove every element's safe assimilation rather
+than trusting a const container. Actual built-in VM runtime bindings are unsupported
+evaluation boundaries. These checks preserve ordinary local/Node data helpers and the
+four fixture outputs, without adding hidden invocation or module edges.
+Property membership (`in`) requires a proven primitive/property-symbol left operand;
+an ordinary right-hand object remains permitted. Descriptor replacement of an actual
+SDK global binding is unsupported even when that binding is a noncallable namespace.
+`Object.defineProperties`, `Reflect.set` and `Object.assign` apply the same
+`SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED` rule as a singular descriptor for these actual
+SDK-global replacements. Captured SDK declarations and receiver identity distinguish them
+from local homonyms and ordinary data targets. These bounded routes do not establish a
+complete global mutation model.
+Awaiting an async result requires the actual callee binding, including resolved aliases, to
+remain unreassigned. A declared async function that has been replaced does not prove native
+promise origin. `for await...of` over a synchronous array requires a fresh direct literal
+whose non-hole elements each satisfy the existing safe-await proof: primitive values, actual
+native promises or supported async results. Mutable const arrays and spreads refuse this
+assimilation proof; proven primitive strings retain their boundary. Ordinary synchronous
+iteration remains distinct, and this does not qualify arbitrary async iterators.
+The shared JavaScript semantic diagnostic API can also report these refusals; ordinary
+extraction receives no synthesized invocation facts.
+Captured SDK WebAssembly `instantiate`, `instantiateStreaming` and `Instance` construction are unsupported
+instantiation boundaries: a module start function may invoke imported internal code.
+Compile/validate data operations keep their existing domain. Captured SDK
+node insertion with an actual SDK-created constant `script` tag argument is also unsupported,
+including ASCII-case variants such as `SCRIPT`. The pinned `Document.createElement` origin and
+receiver proof distinguish ordinary objects and type annotations. Standalone script creation and ordinary inert DOM/local
+controls remain distinct. The insertion refusal is conservative even for an individual
+empty or disconnected script. These are the observed instantiation/insertion routes,
+not a claim of complete browser or WebAssembly runtime modeling. Shared JavaScript
+diagnostics can report the same refusals without adding invocation or module edges.
+The SDK insertion coordinates are `Node.appendChild/insertBefore/replaceChild` argument 0,
+all arguments of `ParentNode.append/prepend/replaceChildren` and
+`ChildNode.before/after/replaceWith`, `Element.insertAdjacentElement` argument 1, and
+`Range.insertNode` argument 0. Reference/replaced nodes and position strings are separate.
+The captured checker uses `strict: false`; its actual SDK `onclick` property has one
+call signature and replacement is already refused by the existing callable-mutation guard.
+That counterexample does not establish behavior for other compiler configurations.
+Actual SDK `Date` construction requires primitive provenance for the first seven
+arguments, with a separately proven intrinsic Date-copy path. An annotation does not
+establish SDK origin. Zero-argument construction and ordinary `Date(...)` calls remain
+distinct. Unknown constructor coercion refuses without adding an invocation edge.
+Actual `cluster`/`node:cluster` `fork` and `setupPrimary` runtime bindings are unsupported
+process-launch boundaries, including the existing alias/destructuring forms. The setup
+boundary is conservative even when no fork follows. Inert imports, `isPrimary`, type-only
+uses and local homonyms retain their separate domain. Runtime named reexports of fork/setupPrimary
+from cluster/node:cluster, including aliases through repository barrels, retain
+that refusal; type-only exports remain distinct.
+Actual SDK `Document.write` and `writeln` refuse as runtime script boundaries, with
+captured member and receiver origin. This is conservative for ordinary HTML/text too;
+no HTML parsing, hidden-module extraction or complete browser model is claimed.
+Property/element invocations on an ambient receiver without a captured runtime lexical
+origin, an actual SDK origin or an explicit supported external import refuse even when
+their arguments are plain data. The existing ordinary native `process` members remain
+distinct. Bare framework calls retain their existing ownership policy.
+A retained local container does not prove its invoked member. An actual dominating
+`typeof value !== "string"` throw guard on the same unwritten receiver binding, together with
+loaded SDK String member origin, preserves a bounded string-member boundary without inventing
+callable coordinates. This exception removes only the local unresolved-callee refusal; callback
+and prototype checks remain. It does not qualify every String method. Annotations, casts or
+other primitive-kind guards alone do not establish the exception.
+Otherwise, erased or unresolved members require an actual retained callable target before data
+exemptions apply. An erased SDK-shaped annotation or local SDK lookalike is not runtime callee
+authority. Absent or erased local member origin without an actual retained coordinate receives
+`SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED`, not an invented WebAssembly/script origin.
+Known own methods and callable initializer aliases retain their existing proof. Caller
+parameters keep their existing opaque boundary; this is not universal callable resolution.
+Members with real captured literal/type declarations continue to the qualified coordinate
+check after extraction. The early origin refusal targets absent, erased or synthetic-only
+members; a declared member still cannot qualify when its required graph coordinate is missing.
+Object-literal prototype setters require the existing closed-data proof for their
+initializer. Null/direct closed data remain distinct; an uncertain or callable prototype
+refuses. Computed `__proto__` properties, methods and shorthand are ordinary own properties.
+Invocation or callable escape of `import.meta` members is unsupported when its runtime
+effect is unmodeled. Metadata reads and supported primitive aliases remain distinct.
+The observed `import.meta.glob` admission hole is a static loader refusal, supported by
+the bundler's documented transform; no native Vite build was observed in this qualification.
+Actual SDK `String` and `Number` conversion requires primitive provenance for their first argument.
+The String/Number, numeric Math, array join and locale-string checks also use the effective target,
+receiver and argument coordinates of actual SDK call/apply and invoked immutable const-bound
+aliases. Finite direct apply lists (or absent/null lists) remain distinct from unknown/spread
+arguments. Unused bind creation is not itself a conversion refusal; local homonyms remain separate.
+Actual SDK array `join` requires a fresh direct literal array of proven primitive elements
+and a proven primitive separator when supplied. Const containers and their aliases do not
+prove current element provenance after mutation. Unknown ordinary conversion hooks refuse;
+local homonyms and primitive controls remain distinct. No implicit invocation edge is added.
+Actual SDK array `toLocaleString` requires fresh direct literal primitive elements. Supplied
+locales are primitive or a direct literal array of primitives; supplied options are
+primitive or a direct literal data record whose values are primitive. Unproven arguments
+refuse conservatively. Complete Intl option semantics are outside this qualification.
+Actual SDK Math calls require primitive provenance for arguments consumed by numeric
+parameters in the loaded SDK signature, including rest arguments. Zero-argument calls,
+ignored extra arguments and ordinary local homonyms retain their separate boundaries.
+Actual SDK callback dictionaries require closed literal-data proof when their direct members
+declare callable types in the loaded default libraries. Internal callbacks and unproven
+arguments refuse; ordinary data dictionaries and local homonyms retain their tested domain.
+Writes to actual SDK Location.href require constant HTTP/HTTPS or relative URLs after parser
+whitespace/case normalization. Other schemes and unknown URL values refuse; proven inert
+URLs and local homonyms stay distinct. No browser code-execution or module-link edge is
+invented for these navigation effects.
+Tests using resolved literal dynamic imports without canonical test-binding proof refuse
+qualified admission. Their ordinary module-link facts remain visible, and non-test literal
+dynamic imports retain their existing closure policy. No coarse test edge is fabricated.
+Unknown/internal coercion in addition or template interpolation, async thenable return
+assimilation, actual SDK Proxy traps and ServiceWorkerContainer registration are outside
+qualification. A type annotation/assertion cannot prove a primitive/native value origin.
+These refusals do not add implicit invocation or browser code-loader edges; ordinary/v1
+facts and explicitly proven primitive/native boundaries retain their stated domain.
+The profile's effective module settings are `module: "ESNext"` and
+`moduleResolution: "Bundler"`. Missing settings take those defaults. Explicit incompatible
+settings, including inherited NodeNext/Node16 or CommonJS, remain outside qualification and
+produce `SOURCE_CONFIGURATION_MODULE_UNSUPPORTED:` or
+`SOURCE_CONFIGURATION_RESOLUTION_UNSUPPORTED:`; hashing the configuration cannot
+substitute for honoring or refusing its semantics.
+Scope reconstruction also refuses CommonJS loading, including a literal `require()` or
+external `import = require()` in an excluded source. Such an importer cannot disappear
+from the dependency obligation merely because it is outside the configured selector.
+Inline TypeScript `import()` type queries are also outside the profile and suspend scope
+admission with `DEPENDENCY_SCOPE_IMPORT_TYPE_UNSUPPORTED:` until qualified static
+module links are available for that construct.
+
+Ambient Node global origins follow the known static `global` and `globalThis` self aliases,
+including quoted member names, before accessing `process`. Exporting the whole ambient
+container, passing it to a call or placing it in another container leaves the qualified
+grammar and produces `NATIVE_MODULE_MEMBER_UNSUPPORTED:ambient-global` (with the source
+path in dependency-scope diagnostics). Ordinary member access such as `globalThis.console`
+and locally shadowed names remain distinct. This refusal does not model `Reflect`, invent
+runtime dependency edges or claim complete runtime resolution.
+
+The same static boundary applies to the whole native `process` container: opaque arguments,
+exports and container escapes produce `NATIVE_MODULE_MEMBER_UNSUPPORTED:ambient-process`.
+Computed native members and the legacy `mainModule` loader boundary are explicitly unmodeled
+as `process.` and `process.mainModule`. The ordinary static member set is exactly
+`argv`, `cwd`, `env`, `execPath`, `exit`, `exitCode`, `platform`, `stderr`, `stdout` and `versions`.
+Every other used native member is explicitly unmodeled as `process.`; unused or
+type-only imports and locally shadowed objects remain distinct. This allowlist defines an
+opaque runtime boundary, not execution or dependency completeness for these members.
+No `Reflect` resolution or runtime CommonJS dependency edge is inferred from these refusals.
+Unshadowed ambient `require` and `module`, including their static global-container members,
+remain unsupported CommonJS primitives when used. Aliasing a loader cannot remove that
+refusal; locally declared names and ordinary global members remain distinct.
+
+The qualified inventory never classifies a source as generated solely because an ancestor
+is named `build`, `dist`, `coverage`, `.turbo` or `.next`. It retains tracked sources,
+repository-locally nonignored sources and sources selected by the original user configuration.
+Repository-local `.gitignore` and `.gitattributes` controls are bound to the input identity.
+Host-global/shared Git exclusions do not narrow this profile. An untracked generated output
+ignored by repository-local rules and outside the original selector is explicitly excluded
+as `IGNORED_GENERATED_OUTPUT`; broad dependency discovery preserves that original boundary.
+Named TypeScript configurations, package manifests and lockfiles remain retained even when
+Git ignores them beneath an output directory. Repository-local relative `extends` references
+are retained as configuration inputs; missing or unsupported configuration references remain
+explicit refusals. A retained untracked cache JSON is not exempt from the membership check.
+The qualification harness uses the same fresh external Turbo cache for miss, hit and
+failing-build observations, keeping those artifacts outside its repository input inventory.
+Metadata under `.git` and `.semctx`, and installed dependencies under `node_modules`, remain
+outside the qualified source inventory. These boundaries do not establish runtime completeness.
+
+## Reproduction and validation
+
+Build and pack the CLI, install that tarball into a disposable wrapper, generate the plugins
+with the repository's pinned Bun 1.4.0, then run the public harness with explicit artifact paths:
+
+```sh
+bun scripts/qualify-modelo-static.ts --source-root \
+ --artifact-manifest \
+ --cli /node_modules/semctx/dist/index.js \
+ --mcp /plugins/claude-code/dist/semctx-mcp.js \
+ --plugin-cli /plugins/claude-code/dist/semctx.js \
+ --legacy-cli /apps/cli/dist/index.js \
+ --regression-cli /apps/cli/dist/index.js \
+ --directory-regression-cli /apps/cli/dist/index.js \
+ --loader-regression-cli /apps/cli/dist/index.js \
+ --output-dir
+```
+
+The input manifest uses `QualificationInputManifestSchema` in
+`scripts/modelo-artifact-identity.ts`. Produce it independently after freezing a clean
+candidate and recording pinned-Bun generation. It binds the candidate commit/tree,
+Bun executable, complete compiler install and every consumed payload file, including
+shared chunks, workers and TypeScript libraries. Committed release payloads also match
+the complete candidate Git-object inventory. Installed candidate CLI payloads carry the
+independently recorded generation command, successful exit and complete output hashes.
+Historical payloads retain `sourceIdentity: "UNKNOWN"` and their original bytes; they
+never acquire the candidate identity. This manifest is input evidence, not a signature
+or independent proof that its recorded generation actually ran. Retain the raw generation
+logs beside it. The harness validates inputs before scenarios and again at completion.
+
+A qualified refusal must be a full schema-valid `BLOCK` report with rejected admission and
+CLI exit 3. Positive and negative MCP witnesses require the actual non-error text verification
+envelope and a full schema-valid structured report, not `isError: true` with a report-shaped object.
+Positive qualification also requires admitted analysis and a nonBLOCK result. A recorded protocol
+or transport failure invalidates the witness even if a valid-looking report was received; normal
+stdout closure after the final reply is not itself an early-close failure.
+The qualification harness's MCP exchange has a 30-second ceiling, an 8 MiB stdout-byte cap and
+64 KiB retained stderr while draining the stream. Teardown has a separate 2-second polite grace
+and 5-second bounds for forced-exit/read/cancel waits, for a declared 57-second helper worst case.
+Unobserved direct-child exit or unfinished/failed owned-stream completion refuses the witness.
+Cancellation is recorded separately from natural pipe closure. Only the direct spawned child's
+exit is claimed; this is not a process-tree sandbox or observed Unix termination efficacy.
+Crashes, protocol/process bounds, empty output and malformed reports do not count as semantic
+refusal. Named operational errors for an uninitialized root or legacy unsupported configuration
+remain separate from structured admission reports.
+
+The harness preserves actual command output and numeric exit codes. Its baseline reproduces
+the incident with `PASS` despite an unanalysed changed `.mjs` and stale index. The corrected
+profile requires effective extraction after actual rebuilding. Negative cases cover added,
+modified, renamed and deleted files after indexing; failed parsing, unsupported constructs,
+partial indexing, empty selectors, wrong roots and real process interruption. The old runtime
+must also refuse the qualified selector. No absence of detected violations substitutes for
+analyzed obligations.
+Build the regression CLI from commit `774bb72027f3ce68b69c29b1f12acf3c110a3ef0`.
+It reproduces positive admission for excluded TypeScript-only reexports/literal imports
+and incompatible inherited module settings. The candidate must refuse those same inputs.
+The directory regression artifact comes from public commit
+`52cc7232f691b948b150225118ff2dc4de7f72ad`. Its tracked importers under `build`/`dist`
+disappear even with an explicit selector. The corrected package must enumerate the importer,
+reject it when excluded, and actually analyze it when selected. Existing drift refusals in
+the historical artifact remain recorded as existing protections.
+
+The loader regression artifact comes from public commit
+`95744a3c0df2bfe4a0cac06ac5b452d649ce9317`. Its selected direct `createRequire`
+loader, excluded named alias, excluded TypeScript namespace factory and excluded
+reexport barrel can admit a changed local `.mjs` after a fresh rebuild. The public
+witnesses retain those actual outcomes. The corrected package must reject the same
+sources with `COMMONJS_UNSUPPORTED` diagnostics naming the offending source, not
+invent an import/call edge for the runtime-loaded target. This remains diagnostic
+coverage: runtime CommonJS loading is outside the admitted static profile.
+`--loader-witnesses-only` runs these before/after cases without dependency installs
+or a pipeline campaign; its receipt always has `qualified: false`. Complete
+qualification requires all historical artifacts, including `--loader-regression-cli`.
+The same matrix includes excluded JavaScript and TypeScript namespaces copied by
+assignment before accessing `createRequire`. These reproduce admission in the
+historical loader artifact and must produce the same explicit CommonJS refusal.
+Namespace escape through assignment is not a qualified static dependency path.
+Named `default` imports (including a statically quoted import name), namespace
+destructuring of `Module`, and quoted destructuring of `createRequire` have their
+own JavaScript/TypeScript before/after witnesses. These known static binding
+routes must receive the same loader refusal; this diagnostic coverage does not
+establish completeness for every Node module access or runtime loading pattern.
+The native-module member grammar admits ordinary use only for `isBuiltin` and
+`builtinModules`. Known `createRequire` and `_load` routes receive explicit
+CommonJS refusal, including default-module and named-import `_load` witnesses
+in JavaScript and TypeScript. Other used native members, such as access to
+`Module.prototype.require`, receive `NATIVE_MODULE_MEMBER_UNSUPPORTED` rather
+than an inferred dependency edge or a claim that their execution was analyzed.
+Default/Module namespace bindings remain eligible only along the understood
+immutable/static routes. This closed member policy applies to JavaScript analysis
+and broad TypeScript dependency-scope discovery.
+The global Node builtin resolver `process.getBuiltinModule` also remains outside
+the qualified static profile. Its intermediate namespace-alias witness requires
+an explicit unmodeled-member diagnostic; it does not claim that the resolver or
+the resulting loader was executed or that its runtime dependency was extracted.
+The same resolver refusal is witnessed through a statically quoted default import
+from `node:process` and a destructured `getBuiltinModule` alias in TypeScript.
+These are equivalent syntax routes to the existing unmodeled resolver, not new
+runtime capabilities.
+
+Repository gates, independently executed negative witnesses and the fresh aggregate auditor
+must all bind the complete candidate. Source, build, local checks, hosted CI and independent
+review remain separate observations. See the PR evidence for the exact commit, commands,
+artifact identities and observed results; this document alone is not a readiness receipt.
+
+## Invalidation and delivery states
+
+Any source/configuration/selector, relevant manifest or lockfile, Git endpoint, analyzer/compiler,
+index snapshot or store change invalidates the corresponding qualification. Unsupported required
+syntax, unresolved scope, failed parsing, stale input, source drift during analysis and interrupted
+rebuilding suspend admission. Rebuilding must consume sources and record a new complete result.
+
+Package qualification does not install a global CLI or plugin, reload an existing host session,
+activate a consumer gate or establish observed use in that consumer. Those states require their
+own observations. Approval gates, brokers and shared agent approval policy are outside this PR.
diff --git a/docs/reference/change-impact.md b/docs/reference/change-impact.md
index 4b098f5a..656cd02e 100644
--- a/docs/reference/change-impact.md
+++ b/docs/reference/change-impact.md
@@ -71,7 +71,8 @@ the order they are decided (an open code set):
| `reason` | meaning |
| --- | --- |
-| `LANGUAGE_UNSUPPORTED` | no semctx producer reads this language (`csharp`, `rust`, `yaml`, `toml`, `json`, `powershell`, `msbuild`, `javascript`, … or `unknown`); only TypeScript, Python, Markdown and SQL are analysed |
+| `LANGUAGE_UNSUPPORTED` | no enabled semctx producer reads this language (`csharp`, `rust`, `yaml`, `toml`, `json`, `powershell`, `msbuild`, … or `unknown`); JavaScript additionally requires configuration v2 and explicit language registration |
+| `LANGUAGE_DISABLED`, `ANALYZER_EVIDENCE_MISSING`, `ANALYSIS_FAILED`, `ANALYSIS_PARTIAL` | JavaScript was disabled, has no bound producer ledger, failed parsing, or produced explicit partial diagnostics; an indexed node alone does not establish effective analysis |
| `OUTSIDE_SELECTION` | a supported language outside the configured analysis selection |
| `INDEX_BINDING_BROKEN` | the binding is broken, so no file was joined to the index |
| `BINARY_CONTENT`, `METADATA_ONLY`, `UNRECOGNIZED_DIFF_BLOCK` | the diff carries no analysable text for the file |
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index b44561e3..44df068c 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -128,6 +128,24 @@ Create `.semctx/` (SQLite db + config) and install the non-destructive `.gitigno
authored `.semctx/semantic/` files versioned while excluding local runtime state. Never touches
application code.
+The main repository store, shared by CLI and MCP, sets a 1,000 ms SQLite
+[`busy_timeout`](https://www.sqlite.org/pragma.html#pragma_busy_timeout) on each writer before
+initialization. This bounds lock waiting for each SQLite operation, including the closing
+checkpoint; it is not a deadline for the whole command. CLI commands close their writer in
+`finally`, before emitting a success result. Context preparation also closes it when provider
+retrieval fails. If both the operation and cleanup fail, the original failure retains the cleanup
+error as suppressed evidence.
+
+Persistent WAL is disabled as soon as WAL mode is selected, so a failed writer initialization
+also closes a quiescent database without leaving sidecars that prevent an immutable read.
+
+Once competing connections release their locks within that budget, closing the last managed
+writer checkpoints the database, leaves WAL mode and removes the `-wal`/`-shm` sidecars. A
+connection that remains active can still prevent cleanup: closing reports `STORE_ERROR`, and
+immutable readers continue to refuse active sidecars. After that connection closes, a subsequent
+writer can finish cleanup. Semctx never deletes active WAL files manually. This does not change
+the store schema or make abrupt process termination equivalent to `finally` cleanup.
+
| option | description |
| --- | --- |
| `--polyglot` | explicitly create config v2 with `globs-v1` selection and TypeScript/Python/Markdown/SQL modes |
@@ -307,6 +325,8 @@ intact. Re-run `semctx index` to repair a broken binding.
Refs are resolved to object ids once, before any hunk is measured, and every subsequent `merge-base`
and `diff` uses those ids: a branch moved while the analysis runs cannot re-attribute the hunks to
another commit. A `--head` that Git cannot resolve is an error, never a silently dropped anchor.
+Qualified final input checks also reobserve the effective merge-base; a changed base coordinate
+rejects admission even when the resolved head and diff text remain unchanged.
A diff semctx did not compute carries no provenance of its own, and no flag can supply one.
`--from-file`, and diff text handed to the MCP `semctx_verify_change` / `semctx_change_verify`
@@ -337,10 +357,46 @@ survives indexing, persistence and a fresh read instead of scrolling past once.
The base must exist locally — semctx never fetches implicitly. In CI, check out with
`fetch-depth: 0`.
+Outside qualified selection, a range removing lines, files or paths also reads the merge-base's
+raw Git blobs and checks guards missing at the head. It does not run checkout filters or materialize Git links.
+Selected base symlinks or gitlinks are refused with `GIT_ERROR`, rather than interpreted as source.
+The current configuration controls this additional analysis: excluded changed base paths remain
+unobserved and produce a warning plus an explicit unknown, not recovered guards. A selected changed
+base path with failed, disabled or unsupported analysis blocks with `analysis_scope_incomplete`.
+Config v2 also discloses incomplete negative test/guard evidence from the base analysis. These
+warnings do not establish faithful recovery of excluded or incomplete base scope. Recovered
+base-guard findings identify their base provenance with file-only locations; they do not present
+an old line as an observed head declaration or fabricate deleted symbols into `changedSymbols`.
+
+For qualified refreshed working-tree removals, verification attempts old HEAD guard capture only
+when current analysis is admitted. A current rejection remains BLOCK with its own reasons and
+observed binding state, without supplemental old capture. Otherwise-admitted removals capture
+full safe immutable HEAD inventory and retained old source under the exact current Semctx policy
+and pinned current SDK/compiler, then compare guards with the refreshed head. This is not
+historical policy/SDK admission replay. Old bound analyzed obligations and capability/authority checks are required;
+unsafe, unavailable, unbound or unsupported capture returns rejected `BLOCK` and invalid binding
+with `QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED` details. Successful comparison preserves
+configured rule severities and structural current-test evidence, not runtime test execution or
+complete negative evidence. Full HEAD capture/selected analysis per otherwise-admitted removing
+diff has no qualified overall latency or memory ceiling.
+
+The opt-in `qualified-static-v1` profile cannot yet recover admitted committed-range base inputs.
+Any committed range removing base lines, files or paths therefore returns an explicit `BLOCK` report with
+`QUALIFIED_BASE_RECOVERY_UNSUPPORTED` in rejected admission, rather than attempting the scratch
+analysis or throwing an internal snapshot error. Ordinary body replacements are included. CLI
+exits 3 even with `--fail-on none`; pure additions keep their existing admission path. Refreshed
+working-tree verification remains available, but does not qualify the committed range. This is a
+compatibility restriction until immutable base eligibility and compiler inputs are captured.
+
**JSON report** (`--format json`, `schemaVersion 1`): `verdict`, `base`, `head`, `mergeBase`,
`range`, `changedFiles`, `changedSymbols`, `impactedContracts`, `impactedInvariants`,
`recommendedTests`, `contradictions`, `unknowns`, `findings` (each with `tier`, `severity`,
`locations`), `summary { blockCount, warnCount }`. Additive-only within a major `schemaVersion`.
+Qualified admission remains an additive version-1 field. An admitted report's `changedFiles`
+are a subset of `analysisAdmission.changeCoverage.expected`; static closure can add obligations.
+Complete repository coverage refuses explicitly failed/missing/unsupported outcomes or analyzed
+records with reasons, while excluded records can coexist with complete selected coverage.
+These schema checks detect represented contradictions, not paths missing from every inventory.
## `impact diff`
diff --git a/docs/reference/index-worker-benchmark.md b/docs/reference/index-worker-benchmark.md
index 2910723b..3e341312 100644
--- a/docs/reference/index-worker-benchmark.md
+++ b/docs/reference/index-worker-benchmark.md
@@ -39,6 +39,13 @@ identity/count fields; physical SQLite file bytes are excluded. All samples in a
fingerprint component; a missing or divergent component fails the benchmark and names the corpus,
sample index and exact differing component.
+Equivalence also requires each sample to take its expected execution path: one requested worker
+must report `single` with one used worker and no fallback reason; the safe corpus at two or four
+workers must report `parallel` with exactly that count and no fallback reason. The hostile corpora
+at two or four workers must report `preflight-fallback` with one used worker and the corpus-specific
+global-script or augmentation reason. A mismatch fails and names the corpus and sample even if
+all fingerprints agree. Intentional changes to explicit-worker policy must update this assertion.
+
Duration and native peak RSS are summarized per worker count (median, min, max, range), computed
only over the samples where that metric was actually measured. Native peak RSS (`maxRSS`, bytes)
and CPU time (`user`/`system`/`total`, microseconds) come from the subprocess's own lifetime
@@ -55,13 +62,21 @@ fixture identity, samples, equivalence verdict and per-worker-count summary. Ver
must select the new version 2 fields explicitly; current CI only checks the exit status, so no
threshold is applied to duration or memory.
+Under ADR 0031, selected worker/indexing CI lanes run this unchanged protocol with dimensions
+4 by 2. Each successful smoke writes the complete JSON to the ignored `.semctx/multicore-index.json`
+as well as the logs, and uploads it as `multicore-index---` using the run's
+default artifact retention. The matrix job index keeps reports from test shards distinct. The upload
+fails if the report is missing. Lanes without a selected smoke produce no benchmark artifact.
+Attempt-specific names retain earlier reports when a job is rerun; the full
+24-by-100 investigation remains available on demand.
+
## Known limits
- `crossOsComparability` is always reported as `UNKNOWN`: native resource-usage APIs are
OS-specific, so only same-host, same-runtime comparisons are meaningful. Windows-vs-other-OS
comparability is not established by this harness.
-- Real-repository (non-synthetic) and Apple Silicon baselines remain `NOT_MEASURED`; this harness
- has not been run on Apple Silicon hardware (none is available to the maintainer) and does not
- claim to represent one from a Windows or synthetic-corpus run.
+- Real-repository (non-synthetic) and calibrated Apple Silicon baselines remain `NOT_MEASURED`;
+ synthetic-corpus runs on an arm64 host do not establish real-repository memory or multicore
+ targets, and Windows results do not represent Apple Silicon.
- The benchmark proves equivalence and reports the observed cost of that equivalence; it makes no
performance-improvement claim and applies no automatic time or memory threshold.
diff --git a/docs/releases/v0.4.3.md b/docs/releases/v0.4.3.md
new file mode 100644
index 00000000..7d66025a
--- /dev/null
+++ b/docs/releases/v0.4.3.md
@@ -0,0 +1,70 @@
+# v0.4.3: qualified JavaScript analysis and stronger verification
+
+Semctx 0.4.3 keeps the npm CLI and host plugins on one release identity. Bun >=1.4.0,
+Codex 0.147.0 and Claude Code 2.1.229 remain the declared host baseline.
+
+## What changes
+
+- Setup reports expose the observed source scope, exclusions and bounded exact-path include
+ proposals. A proposal does not change configuration or establish readiness.
+- The opt-in `modelo-suite-static-v1` profile admits supported JavaScript ESM inputs with
+ `qualified-static-v1` selection. Admission binds Git changes, retained inputs, the analyzed
+ scope and fresh index evidence. Unsupported loaders, callbacks, coercions, protocol effects
+ and browser code execution refuse explicitly. This is not a universal JavaScript guarantee.
+- Outside qualified selection, committed-range verification recovers guards removed from the
+ merge-base, including replaced invariant identities. Ordinary deleted-node recovery can borrow
+ a former covering test only when its canonical test endpoint survives at the head. Existing
+ rules keep configured severities and head-side test checks. Qualified refreshed working-tree
+ removals separately recover immutable HEAD guards under current policy and SDK/compiler;
+ unavailable, unbound or unsupported capture refuses explicitly. Base findings use file-only
+ locations, not old lines presented as head declarations. This is structural comparison, not
+ historical admission or runtime test execution. Qualified committed removals still refuse;
+ pure additions retain their existing domain. Verify qualified replacements before committing.
+- Public report fields remain version 1, with stricter rejection of contradictory admitted scope
+ or complete repository outcomes.
+- SQLite stores bound writer contention and CLI commands reliably release their stores.
+- Multicore CI smoke reports are archived separately for each OS, shard and workflow attempt.
+- Documentation keeps indexing outside pre-commit hooks. Development tools, MCP SDK packages and
+ GitHub Actions receive maintenance updates with regenerated runtime bundles.
+
+The [qualification contract](../qualification/modelo-suite-static-v1.md) owns the supported
+profile and its explicit refusals. Static admission does not prove runtime or business behavior.
+
+## Install or upgrade
+
+After the unchanged release workflow publishes and attests this version:
+
+```bash
+bunx semctx@0.4.3 install
+```
+
+Installation does not reload an active session. Open a new Codex task, or run
+`/reload-plugins` in Claude Code (restart if reload fails). Roll back with the previously
+published 0.4.2 through the native host mechanism, not by editing a locked cache. This is a
+consumer rollback; it does not replace the immutable 0.4.3 npm version, move `stable` by hand or
+prove an active session reloaded. Reopen or reload the host after a rollback as after an upgrade.
+
+## Evidence and limits
+
+Source tests, CI, package publication, two-host delivery and active-session loading remain
+separate observations. The annotated release tag must pass the canonical gate; npm must expose
+that exact commit; both immutable host payloads must pass delivery before `stable` advances.
+A historical host-baseline receipt is not a receipt for this version. The declared baseline comes
+from [delivery run 34664142432](https://github.com/hoklims/semctx/actions/runs/34664142432),
+not a 0.4.3 delivery observation.
+
+Publication targets (not evidence that publication already completed):
+- [npm semctx 0.4.3](https://www.npmjs.com/package/semctx/v/0.4.3)
+- [GitHub v0.4.3 release](https://github.com/hoklims/semctx/releases/tag/v0.4.3)
+- [Public packaged demo](https://hoklims.github.io/semctx/demo/)
+
+The demo is a three-case packaged TypeScript fixture, not a JavaScript qualification or runtime
+correctness proof. Its candidate projection records the observed package version and exact runtime
+digest; older observations retain their original identities. After publication, the demo must be
+rerun from the exact downloaded 0.4.3 artifact before release-phase Pages evidence is published.
+The page cannot turn candidate bytes or an older demo into released-artifact evidence.
+
+Oh My Pi remains experimental and outside the two-host delivery proof. Unsupported semantic
+coverage, UNKNOWN runtime effects, untested hosts and active-session loading remain explicit.
+The packaged JavaScript consumer is synthetic; it does not qualify an unrelated private pipeline.
+Adoption, retention and human contribution time remain NOT_MEASURED.
diff --git a/examples/github-actions/semctx-strict.yml b/examples/github-actions/semctx-strict.yml
index 3fdc74d7..22317fe2 100644
--- a/examples/github-actions/semctx-strict.yml
+++ b/examples/github-actions/semctx-strict.yml
@@ -19,7 +19,7 @@ jobs:
fetch-depth: 0
- id: semctx
- uses: hoklims/semctx/packages/github-action@v0.4.2
+ uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
diff --git a/examples/github-actions/semctx.yml b/examples/github-actions/semctx.yml
index b9a6d20a..f33125c0 100644
--- a/examples/github-actions/semctx.yml
+++ b/examples/github-actions/semctx.yml
@@ -21,7 +21,7 @@ jobs:
# semctx never fetches implicitly.
fetch-depth: 0
- - uses: hoklims/semctx/packages/github-action@v0.4.2
+ - uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
diff --git a/examples/modelo-static-consumer/README.md b/examples/modelo-static-consumer/README.md
new file mode 100644
index 00000000..8b89f7a4
--- /dev/null
+++ b/examples/modelo-static-consumer/README.md
@@ -0,0 +1,131 @@
+# Public static consumer qualification
+
+This synthetic consumer contains no professional source, document or link. Its
+workspace patterns and declared tool versions reproduce the qualification
+consumer topology. `scripts/modelo-static-fixture.ts` generates each repository
+under a new output directory, so no live pull request or checkout is modified.
+
+Before running the harness:
+
+1. Freeze a clean candidate and record `git rev-parse HEAD` and
+ `git rev-parse HEAD^{tree}`. Use the repository's pinned Bun 1.4.0.
+2. From the repository root, build the CLI with `bun scripts/build-cli-package.ts`.
+ Run `bun pm pack` from `apps/cli`, then install that tarball in a disposable
+ wrapper. Generate the plugins with `bun scripts/build-plugin-runtime.ts` from
+ the repository root. Retain the exact commands, numeric exits and raw generation
+ logs outside the candidate checkout.
+3. Independently write an input manifest matching `QualificationInputManifestSchema`
+ in `scripts/modelo-artifact-identity.ts`. Record the candidate commit/tree, Bun
+ version and executable SHA-256, compiler install root/version and every compiler
+ file's relative path/SHA-256. Record every consumed payload's root, entrypoint
+ roles and complete file inventory/hashes; `artifactFiles(root)` computes the
+ inventory. Installed candidate CLI payloads need the recorded generation
+ commit/tree, command, exit 0 and complete output hashes. Committed plugin payloads
+ use their `releaseGitDirectory` and must match the candidate's Git objects;
+ uncommitted plugin payloads need recorded generation provenance instead.
+ Historical payloads retain their original bytes and `sourceIdentity: "UNKNOWN"`;
+ do not assign them candidate generation provenance. Keep the manifest and raw
+ generation logs together. The manifest records evidence, not a signature or proof
+ that generation ran.
+
+Run against those **built** artifacts, retaining the baseline artifacts from before
+the corrections. Supply the manifest as a mandatory input; the harness validates it
+before scenarios and again at completion:
+
+```sh
+bun scripts/qualify-modelo-static.ts \
+ --source-root /path/to/candidate \
+ --artifact-manifest /path/to/independently-generated-input-manifest.json \
+ --cli /path/to/candidate/apps/cli/dist/index.js \
+ --mcp /path/to/candidate/plugins/claude-code/dist/semctx-mcp.js \
+ --plugin-cli /path/to/candidate/plugins/claude-code/dist/semctx.js \
+ --legacy-cli /path/to/baseline/apps/cli/dist/index.js \
+ --regression-cli /path/to/pre-audit-correction/apps/cli/dist/index.js \
+ --directory-regression-cli /path/to/pre-directory-correction/apps/cli/dist/index.js \
+ --loader-regression-cli /path/to/pre-loader-correction/apps/cli/dist/index.js \
+ --output-dir /path/to/new-evidence-directory
+```
+
+The harness installs pinned pnpm 12.9.1 locally through Bun, and installs Turbo
+2.11.7, TypeScript 7.0.2 and Vitest 5.0.3 in the disposable consumer. The analyzer's
+internal TypeScript version remains a separate artifact identity. Network access
+is necessary for the initial installation. Installation errors fail qualification;
+declared versions do not count as observed runtime versions.
+
+Build the legacy baseline from `f6c0556d51662b8b763500945215f82ae75f64b9`
+and the pre-audit-correction artifact from `774bb72027f3ce68b69c29b1f12acf3c110a3ef0`.
+Both are public source revisions and use generated anonymous fixtures.
+Build the directory regression artifact from
+`52cc7232f691b948b150225118ff2dc4de7f72ad` to reproduce tracked source omissions under
+`build`/`dist`, with and without an explicit selector.
+`--audit-witnesses-only` runs only the three before/after audit cases. It records
+`scope: "audit-witnesses-only"` and `qualified: false`, even when those assertions pass.
+`--directory-witnesses-only` has the same qualification boundary for directory cases.
+Build the loader regression artifact from
+`95744a3c0df2bfe4a0cac06ac5b452d649ce9317`. `--loader-witnesses-only` runs its
+sixteen before/after cases and records `qualified: false` even when they pass.
+Complete qualification requires `--loader-regression-cli`; other focused modes
+retain their existing requirements and never qualify the complete profile.
+
+The process exits nonzero if any scenario fails or if any required actual historical
+witness is absent. `qualification.json` retains raw commands, stdout, stderr,
+exit codes, source identity and bundle SHA-256 hashes. Existing fixture paths
+are never overwritten; use a new output directory for each run.
+
+| Witness | Required observation |
+| --- | --- |
+| Historical `.mjs` omission | Baseline has no `value` symbol; health says STALE; verify nevertheless returns PASS and exit 0 |
+| Qualified configuration boundary | `qualified-static-v1` requires the named profile; the old CLI rejects indexing and verification with nonzero exits |
+| Historical case matrix | Actual old CLI health, index and verify observations for edit/add/delete/rename, syntax errors, computed import, CommonJS, partial/empty selection and wrong root; existing refusals remain explicit |
+| Mixed ESM / TypeScript | Exported `.mjs` symbol, imports and real call edges reach `.ts` bridge and `.js` consumer |
+| TypeScript-only inbound closure | An excluded static reexport or literal dynamic importer remains a required file and rejects admission; the pre-audit artifact admits the same input |
+| Inherited module semantics | Explicit inherited NodeNext settings are refused by the ESNext/Bundler profile; the pre-audit artifact silently admits the same input |
+| Authored sources in output directories | Tracked importers under `build`/`dist` remain required; excluded ones reject and selected ones are actually analyzed |
+| Refresh | Source edit first refuses; full indexing then admits the covered static change |
+| CLI / plugin / MCP | Built artifacts report the same admission; MCP is invoked through real stdio JSON-RPC |
+| Added / edited / deleted / renamed | Index taken before the mutation cannot admit the change |
+| Failed parse | Invalid `.mjs` cannot be represented as analyzed PASS |
+| Unsupported construction | Nonliteral dynamic import refuses admission |
+| CommonJS | Diagnostic-only `.cjs` is an explicit negative case |
+| `createRequire` family | Direct and aliased factories, namespace access/assignment, named default imports, static Module/factory destructuring and reexport barrels reject with explicit CommonJS diagnostics after fresh indexing; no loaded-target edge is fabricated |
+| Partial / empty selection | Disabled JavaScript or selector matching no source cannot admit obligations |
+| Wrong nested root | An initialized parent does not silently turn an uninitialized leaf into success |
+| Interrupted indexing | After observing the persisted incomplete marker, kill a real rebuild; the previous complete index cannot admit the source change |
+| Test execution | Vitest executes one passing test; a source mutation makes it fail nonzero |
+| Synthetic cache / propagation | Turbo first misses, then hits; relevant source mutation causes an actual build failure and nonzero exit |
+
+PASS is a static analysis result. It does not establish test execution, Turbo
+cache validity, failure propagation or pipeline health. Those obligations have
+separate raw observations here, limited to this synthetic consumer. A `.cjs`
+file or unresolved dynamic module expression is outside the admitted profile.
+Node `createRequire`, including factory aliases, namespace access and reexport
+barrels, also remains outside that profile. The witness changes a local `.mjs`
+with a real TypeScript consumer and captures the loader limitation separately.
+The static binding witnesses include quoted `default` import names and quoted
+`createRequire` destructuring in TypeScript. Their explicit refusals qualify
+these known routes, not every possible Node namespace or runtime loader pattern.
+Native-module ordinary members are limited to `isBuiltin` and `builtinModules`.
+The `_load` JavaScript/default and TypeScript/named-alias cases explicitly refuse
+CommonJS analysis. Used unmodeled members, represented by `Module.prototype.require`,
+instead require `NATIVE_MODULE_MEMBER_UNSUPPORTED`; no runtime target edge may be
+fabricated. The closed member policy also governs broad TypeScript discovery.
+The global builtin resolver `process.getBuiltinModule`, through an intermediate
+namespace alias, is likewise outside this profile and requires an explicit
+unmodeled-member refusal rather than a runtime dependency or execution claim.
+The same refusal is also checked through a quoted default `node:process` import
+and a TypeScript destructured getter alias; no new runtime API is qualified.
+
+The harness proves package behavior for the named static profile only when all
+its assertions pass. It does not prove installation in the real consumer, loading
+in an existing editor/agent session, or observed use on a professional pipeline.
+Those stages require their own observations. A source, config, analyzer, selected
+root, dependency or bundle change invalidates the relevant qualification receipt.
+An interrupted rebuild, stale index or new unsupported obligation requires a
+fresh analysis before admission. Independent proof review of the frozen candidate
+remains required; the modified admission code cannot approve itself.
+
+The historical matrix records old behavior without requiring every old case to
+have passed: a refusal already present is not a regression fixed by this change.
+An old process-interruption observation is not attested by this harness. The
+candidate interruption witness instead waits for its actual persisted incomplete
+marker before killing the rebuild. These two evidence scopes remain separate.
diff --git a/package.json b/package.json
index 48cf46a1..619d9dee 100644
--- a/package.json
+++ b/package.json
@@ -53,9 +53,9 @@
"devDependencies": {
"@eslint/js": "10.0.1",
"@types/bun": "^1.4.2",
- "eslint": "10.11.0",
- "globals": "17.12.0",
+ "eslint": "10.12.0",
+ "globals": "17.13.0",
"typescript": "^5.6.3",
- "typescript-eslint": "8.71.0"
+ "typescript-eslint": "8.71.1"
}
}
diff --git a/packages/app-services/package.json b/packages/app-services/package.json
index 4fdb95e7..742bd1b0 100644
--- a/packages/app-services/package.json
+++ b/packages/app-services/package.json
@@ -1,6 +1,6 @@
{
"name": "@semantic-context/app-services",
- "version": "0.4.2",
+ "version": "0.4.3",
"private": true,
"license": "Apache-2.0",
"type": "module",
diff --git a/packages/app-services/src/analyzer-identity-generated.ts b/packages/app-services/src/analyzer-identity-generated.ts
new file mode 100644
index 00000000..3b3fdf2c
--- /dev/null
+++ b/packages/app-services/src/analyzer-identity-generated.ts
@@ -0,0 +1,3 @@
+// Generated by bun scripts/analyzer-identity.ts; do not edit.
+// Covers 287 implementation/configuration files and TypeScript 5.9.3.
+export const QUALIFIED_ANALYZER_IDENTITY = "sha256:344d0821bde8fa7f3c122416c509b907f32a1af62fd545740377865fde300c2d";
diff --git a/packages/app-services/src/change-impact.ts b/packages/app-services/src/change-impact.ts
index 2063d9a2..1c7f5073 100644
--- a/packages/app-services/src/change-impact.ts
+++ b/packages/app-services/src/change-impact.ts
@@ -154,6 +154,7 @@ function outlinesFor(
diff: ParsedDiffChanges,
sideOf: (path: string) => "old" | "new",
revisions: SideRevisions,
+ eligible: (path: string) => boolean,
): { bound: Map; other: Map } {
const bound = new Map();
const other = new Map();
@@ -165,7 +166,7 @@ function outlinesFor(
{ side: "new" as const, path: file.filePath, revision: revisions.new },
];
for (const { side, path, revision } of sides) {
- if (sourceLanguage(path) !== "typescript") continue;
+ if (!eligible(path)) continue;
const text = readSide(root, revision, path);
if (text === undefined) continue;
(side === rangeSide ? bound : other).set(path, outlineTopLevel(text, path));
@@ -186,8 +187,8 @@ function untrackedPaths(root: string): string[] {
.sort(compareIds);
}
-const TS_PATHSPECS = ["*.ts", "*.tsx", "*.mts", "*.cts"];
-const RESOLUTION_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", ".d.ts", "/index.ts", "/index.tsx"];
+const TS_PATHSPECS = ["*.ts", "*.tsx", "*.mts", "*.cts", "*.js", "*.jsx", "*.mjs", "*.cjs"];
+const RESOLUTION_SUFFIXES = ["", ".ts", ".tsx", ".mts", ".cts", ".d.ts", ".js", ".jsx", ".mjs", ".cjs", "/index.ts", "/index.tsx", "/index.js", "/index.jsx", "/index.mjs"];
function escapeRegex(text: string): string {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -219,10 +220,11 @@ function scanModuleLinks(
revision: string | null,
graph: RepositoryGraph,
packages: readonly ImpactPackage[],
+ eligible: (path: string) => boolean,
): { links: UnindexedModuleLink[]; unread: string[]; whole: WholeModuleRead[] } | undefined {
const indexed = new Set(
graph.nodes
- .filter((node) => (node.kind === "module" || node.kind === "test") && node.filePath !== undefined)
+ .filter((node) => (node.kind === "module" || node.kind === "test") && node.filePath !== undefined && eligible(node.filePath))
.map((node) => node.filePath!),
);
const patterns = [
@@ -260,7 +262,7 @@ function scanModuleLinks(
const unread: string[] = [];
const gitBlind: string[] = [];
for (const path of [...indexed].sort(compareIds)) {
- if (universe.has(path) || sourceLanguage(path) !== "typescript") continue;
+ if (universe.has(path) || !eligible(path)) continue;
const text = readSide(root, null, path);
if (text === undefined) unread.push(path);
else {
@@ -278,6 +280,22 @@ function scanModuleLinks(
};
const links: UnindexedModuleLink[] = [];
const whole: WholeModuleRead[] = [];
+ const nodePaths = new Map(graph.nodes.map(node => [node.id, node.filePath]));
+ const importTargets = new Map>();
+ for (const edge of graph.edges) {
+ if (edge.kind !== "imports") continue;
+ const from = nodePaths.get(edge.from); const to = nodePaths.get(edge.to);
+ if (from === undefined || to === undefined || !indexed.has(to)) continue;
+ let specifiers: unknown = typeof edge.metadata["specifier"] === "string" ? [edge.metadata["specifier"]] : [];
+ if (typeof edge.metadata["specifiers"] === "string") {
+ try { specifiers = JSON.parse(edge.metadata["specifiers"]); } catch { continue; }
+ }
+ if (!Array.isArray(specifiers) || !specifiers.every(specifier => typeof specifier === "string")) continue;
+ for (const specifier of specifiers) {
+ const key = JSON.stringify([from, specifier]); const targets = importTargets.get(key) ?? new Set();
+ targets.add(to); importTargets.set(key, targets);
+ }
+ }
for (const path of [...candidates, ...gitBlind]) {
const text = texts.get(path);
if (text === undefined) continue;
@@ -286,9 +304,13 @@ function scanModuleLinks(
if (link.kind !== "import") links.push({ from: path, kind: link.kind, line: link.line, target: { nonLiteral: true } });
continue;
}
+ const resolved = link.kind === "import" ? importTargets.get(JSON.stringify([path, link.specifier])) : undefined;
+ const resolvedTarget = resolved?.size === 1 ? [...resolved][0] : undefined;
+ if (resolvedTarget !== undefined && link.whole === true) whole.push({ from: path, kind: link.kind, line: link.line, target: { path: resolvedTarget } });
if (link.specifier.startsWith(".")) {
- const target = resolveRelativeSpecifier(path, link.specifier, indexed);
- if (target !== undefined && link.whole === true) whole.push({ from: path, kind: link.kind, line: link.line, target: { path: target } });
+ const target = resolved === undefined ? resolveRelativeSpecifier(path, link.specifier, indexed)
+ : resolvedTarget;
+ if (target !== undefined && link.whole === true && resolved === undefined) whole.push({ from: path, kind: link.kind, line: link.line, target: { path: target } });
// A relative import already has its edge; only the edgeless kinds are needed here.
if (link.kind === "import") continue;
if (target !== undefined) links.push({ from: path, kind: link.kind, line: link.line, target: { path: target } });
@@ -297,7 +319,7 @@ function scanModuleLinks(
const identity = packageOf(link.specifier);
if (identity === undefined) continue;
links.push({ from: path, kind: link.kind, line: link.line, target: { package: identity } });
- if (link.whole === true) whole.push({ from: path, kind: link.kind, line: link.line, target: { package: identity } });
+ if (link.whole === true && resolved === undefined) whole.push({ from: path, kind: link.kind, line: link.line, target: { package: identity } });
}
}
return { links, unread, whole };
@@ -567,6 +589,29 @@ export function runChangeImpact(root: string, source: ChangeImpactRequest, optio
const claims = store.loadClaims();
const evidence = store.loadEvidence();
const facts = { graph, claims, evidence };
+ const javascriptOutcomes = new Map();
+ const typescriptOutcomes = new Map();
+ const planeSnapshot = parsePlaneAIndexSnapshot(store.getMeta(PLANE_A_INDEX_SNAPSHOT_META_KEY));
+ for (const entry of planeSnapshot?.sidecar.discoveryLedger ?? []) {
+ if (entry.selectionDecision !== "selected") continue;
+ const outcomes = entry.scope.language === "javascript" && (entry.analysisOutcome !== "analyzed" || entry.selectedProducer?.identity === "@semantic-context/ts-analyzer/javascript")
+ ? javascriptOutcomes : entry.scope.language === "typescript" && (entry.analysisOutcome !== "analyzed" || entry.selectedProducer?.identity === "@semantic-context/ts-analyzer")
+ ? typescriptOutcomes : undefined;
+ if (outcomes === undefined) continue;
+ for (const path of entry.scope.selectedPaths) outcomes.set(path, { outcome: entry.analysisOutcome, reasons: entry.analysisReasons });
+ }
+ const eligibleSource = (path: string): boolean => (sourceLanguage(path) === "typescript" && (config.version !== 2
+ || (typescriptOutcomes.get(path)?.outcome === "analyzed" && typescriptOutcomes.get(path)?.reasons.length === 0)))
+ || (sourceLanguage(path) === "javascript" && config.version === 2 && config.languages.javascript === "on"
+ && javascriptOutcomes.get(path)?.outcome === "analyzed" && javascriptOutcomes.get(path)?.reasons.length === 0);
+ const unsupportedSourceFiles = new Set(graph.nodes.flatMap((node) => node.filePath !== undefined
+ && ["javascript", "typescript"].includes(sourceLanguage(node.filePath)) && !eligibleSource(node.filePath) ? [node.filePath] : []));
+ for (const path of [...typescriptOutcomes.keys(), ...javascriptOutcomes.keys()]) {
+ if (!eligibleSource(path)) unsupportedSourceFiles.add(path);
+ }
+ const impactNodes = graph.nodes.filter((node) => node.filePath === undefined || !unsupportedSourceFiles.has(node.filePath));
+ const impactNodeIds = new Set(impactNodes.map((node) => node.id));
+ const impactGraph = { nodes: impactNodes, edges: graph.edges.filter((edge) => impactNodeIds.has(edge.from) && impactNodeIds.has(edge.to)) };
const diff = parseUnifiedDiffChanges(diffText);
const breaks: string[] = [];
@@ -601,23 +646,27 @@ export function runChangeImpact(root: string, source: ChangeImpactRequest, optio
const packages = workspacePackages(store.getMeta(PLANE_A_INDEX_SNAPSHOT_META_KEY));
// Unchanged files read the same on disk as at indexing (clean then, or dirty and untouched since).
const scanRevision = indexedDirty && source.kind === "working-tree" ? null : committedSide === "old" ? revisions.old : revisions.new;
- const scan = breaks.length > 0 ? undefined : scanModuleLinks(root, scanRevision, graph, packages);
+ const scan = breaks.length > 0 ? undefined : scanModuleLinks(root, scanRevision, impactGraph, packages, eligibleSource);
const core = breaks.length > 0
? null
: computeChangeImpact({
- index: new GraphIndex(graph),
+ index: new GraphIndex(impactGraph),
diff,
rangeSide: sideOf,
- outlines: outlinesFor(root, diff, sideOf, revisions),
+ outlines: outlinesFor(root, diff, sideOf, revisions, eligibleSource),
untrackedPaths: untracked,
packages,
surfaces: surfaceInput?.map ?? null,
isPathSelected: (path) => isPathSelected(config, path),
- hasCallEdges: (path) => sourceLanguage(path) === "typescript",
+ hasCallEdges: eligibleSource,
...(scan !== undefined ? { moduleLinks: scan.links, moduleLinksUnread: scan.unread, wholeModuleReads: scan.whole } : {}),
...(unchangedSinceIndexing !== undefined ? { unchangedSinceIndexing } : {}),
bounds,
});
+ if (core !== null) core.unresolved = sortUnresolved([...core.unresolved, ...[...unsupportedSourceFiles].map((path) => ({
+ code: sourceLanguage(path) === "javascript" ? "JAVASCRIPT_ANALYSIS_INCOMPLETE" : "TYPESCRIPT_ANALYSIS_INCOMPLETE", scope: "file" as const, file: path,
+ detail: "The indexed source producer is disabled, missing, failed, or partial; no effective impact facts are admitted for this file.", affects: "reach" as const,
+ }))]);
// Searched inside the mutable-state bracket below: a worktree or index that moves during the
// search makes its result unusable, exactly like the index-derived sets. The declarations come
@@ -681,7 +730,7 @@ export function runChangeImpact(root: string, source: ChangeImpactRequest, optio
const indexedFiles = new Set(graph.nodes.flatMap((node) => (node.filePath === undefined ? [] : [node.filePath])));
const coverage = (files: Parameters[0]) =>
- withFileCoverage(files, { config, bound: broken || core === null ? null : { sideOf, indexedFiles } });
+ withFileCoverage(files, { config, bound: broken || core === null ? null : { sideOf, indexedFiles, javascriptOutcomes, typescriptOutcomes } });
if (broken || core === null) {
// Re-indexing cannot bind a staged or range diff while the index reads uncommitted files.
diff --git a/packages/app-services/src/file-coverage.ts b/packages/app-services/src/file-coverage.ts
index e63e975c..352456eb 100644
--- a/packages/app-services/src/file-coverage.ts
+++ b/packages/app-services/src/file-coverage.ts
@@ -41,7 +41,7 @@ const NAMED_EXTENSIONS: Record = {
};
/** Languages semctx has a producer for; any other language is unsupported, file by file. */
-const ANALYZED_LANGUAGES = new Set(["typescript", "python", "markdown", "sql"]);
+const ANALYZED_LANGUAGES = new Set(["typescript", "python", "markdown", "sql", "javascript"]);
export function fileLanguage(path: string): string {
const known = sourceLanguage(path);
@@ -55,7 +55,9 @@ export function fileLanguage(path: string): string {
export interface FileCoverageInput {
config: SemctxConfig;
/** Null when the binding is broken: no file can have been joined to the index. */
- bound: { sideOf: (path: string) => "old" | "new"; indexedFiles: ReadonlySet } | null;
+ bound: { sideOf: (path: string) => "old" | "new"; indexedFiles: ReadonlySet;
+ javascriptOutcomes?: ReadonlyMap;
+ typescriptOutcomes?: ReadonlyMap } | null;
}
/**
@@ -70,6 +72,8 @@ export function coverageOf(file: ChangedFile, input: FileCoverageInput): FileCov
const language = fileLanguage(boundPath);
const notAnalyzed = (reason: string): FileCoverage => ({ status: "not_analyzed", language, reason });
if (!ANALYZED_LANGUAGES.has(language)) return notAnalyzed("LANGUAGE_UNSUPPORTED");
+ if (language === "javascript" && (input.config.version !== 2 || input.config.languages.javascript === undefined)) return notAnalyzed("LANGUAGE_UNSUPPORTED");
+ if (language === "javascript" && input.config.version === 2 && input.config.languages.javascript === "off") return notAnalyzed("LANGUAGE_DISABLED");
if (!isPathSelected(input.config, file.path) && (file.oldPath === undefined || !isPathSelected(input.config, file.oldPath))) {
return notAnalyzed("OUTSIDE_SELECTION");
}
@@ -78,6 +82,12 @@ export function coverageOf(file: ChangedFile, input: FileCoverageInput): FileCov
if (file.status === "mode_only") return notAnalyzed("METADATA_ONLY");
if (file.status === "unrecognized") return notAnalyzed("UNRECOGNIZED_DIFF_BLOCK");
if (file.status === "untracked") return notAnalyzed("UNTRACKED_NOT_DIFFED");
+ if (language === "javascript" || (language === "typescript" && input.config.version === 2)) {
+ const observed = (language === "javascript" ? input.bound.javascriptOutcomes : input.bound.typescriptOutcomes)?.get(boundPath);
+ if (observed === undefined) return notAnalyzed("ANALYZER_EVIDENCE_MISSING");
+ if (observed.outcome !== "analyzed") return notAnalyzed(`ANALYSIS_${observed.outcome.toUpperCase()}`);
+ if (observed.reasons.length > 0) return notAnalyzed("ANALYSIS_PARTIAL");
+ }
return input.bound.indexedFiles.has(boundPath)
? { status: "analyzed", language }
: notAnalyzed("NOT_INDEXED");
diff --git a/packages/app-services/src/freshness.ts b/packages/app-services/src/freshness.ts
index 68279a00..55d7b432 100644
--- a/packages/app-services/src/freshness.ts
+++ b/packages/app-services/src/freshness.ts
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
-import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
+import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
import { basename, dirname, relative, resolve } from "node:path";
import {
compareIds,
@@ -27,11 +27,15 @@ import type {
SemanticNode,
} from "@semantic-context/semantic-model/reconciliation-read";
import type { DiscoveredFile } from "@semantic-context/ts-analyzer";
+import { createQualifiedPathEligibility, isHardExcludedPath } from "@semantic-context/ts-analyzer";
import {
canonicalSourceText,
digestCanonical,
} from "@semantic-context/plane-a-internal";
import packageJson from "../package.json";
+import { QUALIFIED_ANALYZER_IDENTITY } from "./analyzer-identity-generated";
+import { analyzeWorkspaceSync } from "@semantic-context/workspace-analyzer-internal";
+import { readCurrentConfigBytes } from "@semantic-context/repository-store";
export const CONTROL_INDEX_SNAPSHOT_META_KEY = "control_index_snapshot_v1";
export const PLANE_A_INDEX_SNAPSHOT_META_KEY = "plane_a_index_snapshot_v1";
@@ -197,8 +201,65 @@ export function fingerprintSemanticModel(model: SemanticModel): Sha256Hash {
return hash("semantic-model", serializeControlReport(normalized));
}
+/** Retained raw inputs: consumers must analyze these bytes, not re-read a mutable filesystem. */
+export function captureQualifiedAnalysisInputs(config: SemctxConfig): {
+ digest: Sha256Hash; files: { path: string; bytes: Uint8Array }[]; workspaceRoots: readonly string[];
+ configurationInput: { state: "present"; bytes: Uint8Array } | { state: "absent" };
+} {
+ let configurationInput: { state: "present"; bytes: Uint8Array } | { state: "absent" };
+ try { configurationInput = { state: "present", bytes: readCurrentConfigBytes(config.repositoryRoot) }; }
+ catch (error) {
+ if (!(error instanceof SemctxError) || error.code !== "CONFIG_NOT_FOUND") throw error;
+ configurationInput = { state: "absent" };
+ }
+ const entries: { path: string; contentHash: string }[] = [];
+ const files: { path: string; bytes: Uint8Array }[] = [];
+ const eligible = createQualifiedPathEligibility(config);
+ const qualifiedRoot = canonicalRepositoryRoot(config.repositoryRoot);
+ const walk = (dir: string): void => {
+ if (dir !== config.repositoryRoot && existsSync(resolve(dir, ".git"))) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified source scope contains a nested Git repository", { path: relative(config.repositoryRoot, dir) });
+ }
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((left, right) => compareIds(left.name, right.name))) {
+ const path = resolve(dir, entry.name);
+ const relPath = relative(config.repositoryRoot, path).replaceAll("\\", "/");
+ if (isHardExcludedPath(relPath)) continue;
+ const stat = lstatSync(path);
+ if (!entry.isDirectory() && !eligible(relPath)) continue;
+ if (stat.isSymbolicLink()) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified source scope cannot read a symbolic link", { path: relative(config.repositoryRoot, path) });
+ }
+ if (entry.isDirectory()) { walk(path); continue; }
+ // Source bytes, raw manifests/configuration and lockfiles all influence qualification.
+ if (!isQualifiedRepositoryInputPath(relPath)) continue;
+ if (!stat.isFile()) throw new SemctxError("INVALID_TASK_INPUT", "qualified source scope requires a regular file", { path: relPath });
+ if (!canonicalRepositoryRoot(path).startsWith(`${qualifiedRoot}/`)) throw new SemctxError("INVALID_TASK_INPUT", "qualified source lies outside the repository");
+ const bytes = readFileSync(path);
+ entries.push({ path: relPath, contentHash: hash("qualified-input-bytes", bytes) });
+ files.push({ path: relPath, bytes });
+ }
+ };
+ walk(config.repositoryRoot);
+ // Seal only directory membership consumed by workspace projection, using these retained
+ // manifest bytes. Unrelated empty output directories do not affect the qualified identity.
+ const manifestContents = new Map(files.map(file => [resolve(config.repositoryRoot, file.path), Buffer.from(file.bytes).toString("utf8")]));
+ const workspaceLayout = analyzeWorkspaceSync({ repositoryRoot: config.repositoryRoot, manifestContents });
+ const workspaceRoots = [...new Set([...workspaceLayout.nodes, ...workspaceLayout.candidates].map(entry => entry.root))].sort(compareIds);
+ const rawConfiguration = configurationInput.state === "present" ? { state: "present", contentHash: hash("qualified-input-bytes", configurationInput.bytes) } : configurationInput;
+ return { digest: hash("qualified-analysis-input-v1", serializeControlReport({ config, rawConfiguration, analyzer: QUALIFIED_ANALYZER_IDENTITY, files: entries.sort((a, b) => compareIds(a.path, b.path)), workspaceLayout })), files, workspaceRoots, configurationInput };
+}
+
+/** Repository-owned inputs sealed by the named profile; shared with Git post-image checks. */
+export function isQualifiedRepositoryInputPath(path: string): boolean {
+ if (isHardExcludedPath(path)) return false;
+ const name = path.slice(path.lastIndexOf("/") + 1);
+ return /\.(?:[cm]?[jt]sx?|py|sql|mdx?|json|ya?ml)$/.test(name)
+ || /^(?:pyproject\.toml|\.gitignore|\.gitattributes|bun\.lockb?|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$/.test(name);
+}
+
/** Fingerprint the exact discovered Plane A contents plus the parsed analyzer configuration. */
export function fingerprintAnalysisInputs(config: SemctxConfig, files: readonly DiscoveredFile[]): Sha256Hash {
+ if (config.version === 2 && config.analysisProfile === "modelo-suite-static-v1") return captureQualifiedAnalysisInputs(config).digest;
const manifest = {
config,
files: files
diff --git a/packages/app-services/src/index-health.ts b/packages/app-services/src/index-health.ts
index 68b57e16..ece5b1bf 100644
--- a/packages/app-services/src/index-health.ts
+++ b/packages/app-services/src/index-health.ts
@@ -640,10 +640,10 @@ function pathWithinRoot(path: string, root: string): boolean {
return root === "." || path === root || path.startsWith(`${root}/`);
}
-function evaluateSnapshot(
- snapshot: PersistedPlaneAIndexSnapshotV1,
+function evaluatePlaneAArtifacts(
+ snapshot: Pick,
bindingAttestation: "valid" | "invalid",
- freshness: ControlFreshnessStatusReport,
+ currentFreshness: ControlFreshnessStatusReport["verdict"],
configVersion: 1 | 2,
producerConfigurationDigest: string,
): PlaneAEvaluationReport {
@@ -732,7 +732,7 @@ function evaluateSnapshot(
completedResults: scopedResults,
factBatches: scopedBatches,
bindingAttestation,
- currentFreshness: freshness.verdict,
+ currentFreshness,
capabilityProfiles: scopedProfiles,
requiredCapability,
taskRelativeAuthority: {
@@ -744,6 +744,21 @@ function evaluateSnapshot(
return aggregatePlaneAEvaluations(decisions);
}
+/** Private in-memory capture evaluation. Store freshness is not an old-image authority. */
+export function evaluateCapturedPlaneA(
+ sidecar: PlaneASidecarV1, workspace: WorkspaceProjection, configVersion: 1 | 2,
+ producerConfigurationDigest: string,
+): PlaneAEvaluationReport {
+ return evaluatePlaneAArtifacts({ sidecar, workspace }, "valid", "UNSEALED", configVersion, producerConfigurationDigest);
+}
+
+function evaluateSnapshot(
+ snapshot: PersistedPlaneAIndexSnapshotV1, bindingAttestation: "valid" | "invalid",
+ freshness: ControlFreshnessStatusReport, configVersion: 1 | 2, producerConfigurationDigest: string,
+): PlaneAEvaluationReport {
+ return evaluatePlaneAArtifacts(snapshot, bindingAttestation, freshness.verdict, configVersion, producerConfigurationDigest);
+}
+
function emptyWorkspace(): WorkspaceProjection {
return {
schemaVersion: 1,
diff --git a/packages/app-services/src/index.ts b/packages/app-services/src/index.ts
index db830b3c..950696dd 100644
--- a/packages/app-services/src/index.ts
+++ b/packages/app-services/src/index.ts
@@ -219,6 +219,7 @@ export {
controlRepositoryIdentity,
evaluateControlFreshness,
fingerprintAnalysisInputs,
+ isQualifiedRepositoryInputPath,
fingerprintRepositoryFacts,
fingerprintRepositoryGraph,
fingerprintSemanticModel,
diff --git a/packages/app-services/src/indexing.ts b/packages/app-services/src/indexing.ts
index 420ce7ab..080c9ce7 100644
--- a/packages/app-services/src/indexing.ts
+++ b/packages/app-services/src/indexing.ts
@@ -1,4 +1,5 @@
import { SemctxError, attachSuppressedError, type Claim, type SemctxConfig } from "@semantic-context/core";
+import { resolve } from "node:path";
import { buildClaims, GraphIndex, parseObservedDiffHunks } from "@semantic-context/context-engine";
import { loadConfig, openStore, SCHEMA_VERSION } from "@semantic-context/repository-store";
import { SealedAttestationIndexV1Schema, type ControlFreshnessSeal } from "@semantic-context/control-model";
@@ -28,6 +29,7 @@ import {
captureTrackedWorkingDiff,
controlRepositoryIdentity,
fingerprintAnalysisInputs,
+ captureQualifiedAnalysisInputs,
fingerprintRepositoryFacts,
fingerprintSemanticModel,
type IndexedControlSnapshot,
@@ -39,6 +41,7 @@ import {
import { CONTROL_ATTESTATION_INDEX_META_KEY } from "./control-queries";
import { inspectSemanticLifecycle } from "./semantic-check";
import { analyzePlaneARuntime, analyzePlaneARuntimeAsync } from "./plane-a-runtime";
+import { isQualified, QUALIFIED_BUILD_META } from "./qualified-analysis";
import {
createPlaneAIndexSnapshot,
} from "./index-health";
@@ -74,6 +77,7 @@ interface PreparedRepositoryIndex {
discoveryBefore?: DiscoveryResult;
analysisInputHash: string;
discoveryLedgerDigest: string;
+ compilerInputs?: ReadonlyMap;
lifecycleBefore: ReturnType;
semanticModelHash: string;
}
@@ -113,11 +117,21 @@ function discoveryForFiles(files: readonly DiscoveredFile[]): DiscoveryResult {
};
}
+/** Private caller-owned retained snapshot; does not persist or qualify historical policy. */
+export function analyzeRetainedRepository(
+ config: SemctxConfig,
+ discovery: DiscoveryResult,
+ compilerInputs: ReadonlyMap,
+): InternalRepositoryAnalysis {
+ return analyzeAndBuildClaimsInternal(config, discovery, compilerInputs);
+}
+
function analyzeAndBuildClaimsInternal(
config: SemctxConfig,
discovery: DiscoveryResult,
+ compilerInputs?: ReadonlyMap,
): InternalRepositoryAnalysis {
- const runtime = analyzePlaneARuntime(config, discovery);
+ const runtime = analyzePlaneARuntime(config, discovery, compilerInputs);
return {
analysis: runtime.analysis,
claims: buildClaims(new GraphIndex(runtime.analysis.graph)),
@@ -154,7 +168,7 @@ function workspaceArtifacts(analysis: AnalysisResult): WorkspaceArtifact[] {
/** Rebuild and persist Plane A. Store lifetime is owned by the application service. */
export function indexRepository(root: string, indexedAt: string): RepositoryIndex {
const prepared = prepareRepositoryIndex(root, indexedAt);
- const indexed = analyzeAndBuildClaimsInternal(prepared.configBefore, prepared.discoveryBefore!);
+ const indexed = analyzeAndBuildClaimsInternal(prepared.configBefore, prepared.discoveryBefore!, prepared.compilerInputs);
prepared.discoveryBefore = undefined;
return completeRepositoryIndex(root, indexedAt, prepared, indexed);
}
@@ -165,7 +179,7 @@ export async function indexRepositoryAsync(
workers: IndexWorkerSelection = "auto",
): Promise {
const prepared = prepareRepositoryIndex(root, indexedAt);
- const runtime = await analyzePlaneARuntimeAsync(prepared.configBefore, prepared.discoveryBefore!, workers);
+ const runtime = await analyzePlaneARuntimeAsync(prepared.configBefore, prepared.discoveryBefore!, workers, prepared.compilerInputs);
const indexed: InternalRepositoryAnalysis = {
analysis: runtime.analysis,
claims: buildClaims(new GraphIndex(runtime.analysis.graph)),
@@ -185,6 +199,10 @@ function prepareRepositoryIndex(root: string, indexedAt: string): PreparedReposi
}
const repositoryRoot = canonicalRepositoryRoot(root);
const configBefore = loadConfig(root);
+ if (isQualified(configBefore)) {
+ const buildStore = openStore(root);
+ try { buildStore.setMeta(QUALIFIED_BUILD_META, "incomplete"); } finally { buildStore.close(); }
+ }
const gitBefore = captureGitState(root);
const repositoryIdentity = controlRepositoryIdentity(root);
const trackedDiffBefore = captureTrackedWorkingDiff(root);
@@ -193,7 +211,12 @@ function prepareRepositoryIndex(root: string, indexedAt: string): PreparedReposi
diffBytes: trackedDiffBefore,
});
const discoveryBefore: DiscoveryResult = discoverRepository(configBefore);
- const analysisInputHash = fingerprintAnalysisInputs(configBefore, discoveryBefore.files);
+ const qualifiedInputs = isQualified(configBefore) ? captureQualifiedAnalysisInputs(configBefore) : undefined;
+ const compilerInputs = qualifiedInputs === undefined ? undefined : new Map(qualifiedInputs.files.map((file) => [resolve(repositoryRoot, file.path), Buffer.from(file.bytes).toString("utf8")]));
+ if (compilerInputs !== undefined && discoveryBefore.files.some((file) => compilerInputs.get(resolve(file.absPath)) !== file.content)) {
+ throw new SemctxError("INVALID_TASK_INPUT", "source discovery changed before the qualified compiler input capture");
+ }
+ const analysisInputHash = qualifiedInputs?.digest ?? fingerprintAnalysisInputs(configBefore, discoveryBefore.files);
const discoveryLedgerDigest = digestCanonical(discoveryBefore.candidates);
const semanticBefore = loadSemanticModel(root);
const lifecycleBefore = inspectSemanticLifecycle(root, semanticBefore.model.changes);
@@ -221,6 +244,7 @@ function prepareRepositoryIndex(root: string, indexedAt: string): PreparedReposi
discoveryBefore,
analysisInputHash,
discoveryLedgerDigest,
+ compilerInputs,
lifecycleBefore,
semanticModelHash,
};
@@ -356,6 +380,7 @@ function completeRepositoryIndex(
[UNRESOLVED_REFERENCE_INDEX_META_KEY]: JSON.stringify(unresolvedReferenceIndex),
[CONTROL_OBSERVED_HUNK_INDEX_META_KEY]: JSON.stringify(observedIndex),
[CONTROL_INDEX_SNAPSHOT_META_KEY]: JSON.stringify(snapshot),
+ ...(isQualified(configAfter) ? { [QUALIFIED_BUILD_META]: "complete" } : {}),
},
});
const result = {
diff --git a/packages/app-services/src/plane-a-authority-policy.ts b/packages/app-services/src/plane-a-authority-policy.ts
index 9400b855..2efb1ccd 100644
--- a/packages/app-services/src/plane-a-authority-policy.ts
+++ b/packages/app-services/src/plane-a-authority-policy.ts
@@ -79,6 +79,13 @@ const AUTHORITY_REGISTRATIONS: readonly AuthorityRegistration[] = [
dialectVersion: "5.9.3",
factKinds: TYPESCRIPT_FACT_KINDS,
},
+ {
+ // Static ESM fact use only. Capability/completeness gates separately reject partial sources.
+ configVersions: [2],
+ language: "javascript",
+ dialectVersion: "5.9.3",
+ factKinds: TYPESCRIPT_FACT_KINDS,
+ },
{
configVersions: [2],
language: "python",
diff --git a/packages/app-services/src/plane-a-capability-requirements.ts b/packages/app-services/src/plane-a-capability-requirements.ts
index 89074d8b..93d629f2 100644
--- a/packages/app-services/src/plane-a-capability-requirements.ts
+++ b/packages/app-services/src/plane-a-capability-requirements.ts
@@ -102,6 +102,15 @@ interface CapabilityRegistration {
}
const REGISTRATIONS: readonly CapabilityRegistration[] = [
+ {
+ configVersions: [2],
+ language: "javascript",
+ dialectVersion: "5.9.3",
+ producer: { identity: "@semantic-context/ts-analyzer/javascript", version: "0.1.0" },
+ factKinds: TYPESCRIPT_FACT_KINDS,
+ resolutionSemantics: "javascript-static-esm-v1",
+ completenessClaims: ["producer-declared"],
+ },
{
configVersions: [1, 2],
language: "typescript",
diff --git a/packages/app-services/src/plane-a-runtime.ts b/packages/app-services/src/plane-a-runtime.ts
index 6b6766e7..46fcd766 100644
--- a/packages/app-services/src/plane-a-runtime.ts
+++ b/packages/app-services/src/plane-a-runtime.ts
@@ -43,6 +43,7 @@ import {
import {
analyzeRepository,
analyzeRepositoryAsync,
+ getQualifiedCallIntegrityReasons,
degradeDivergentPlaneAFacts,
detectMarkerDivergence,
type AnalysisResult,
@@ -50,9 +51,13 @@ import {
type DiscoveryCandidate,
type DiscoveryResult,
TYPESCRIPT_DIALECT_VERSION,
+ inspectJavaScriptSource,
+ inspectSourceParsing,
+ inspectModuleConfiguration,
type IndexWorkerSelection,
type MarkerDeclaration,
type TypeScriptParallelism,
+ type CompilerInputSnapshot,
} from "@semantic-context/ts-analyzer";
import {
analyzeWorkspaceSync,
@@ -70,6 +75,11 @@ const PYTHON_PRODUCER: ProducerIdentity = {
version: "0.1.0",
};
+const JAVASCRIPT_PRODUCER: ProducerIdentity = {
+ identity: "@semantic-context/ts-analyzer/javascript",
+ version: "0.1.0",
+};
+
const RUNTIME_FACT_SCHEMA = {
schemaVersion: 1,
facts: ["node", "edge"],
@@ -110,19 +120,27 @@ interface PythonFacts {
export function analyzePlaneARuntime(
config: SemctxConfig,
discovery: DiscoveryResult,
+ compilerInputs?: CompilerInputSnapshot,
): PlaneARuntimeResult {
- const legacyAnalysis = analyzeRepository(config, discovery.files);
- return composePlaneARuntime(config, discovery, legacyAnalysis);
+ if (String(config.version === 2 ? config.selectionMode : "") === "qualified-static-v1" && compilerInputs === undefined) {
+ throw new Error("SOURCE_SNAPSHOT_REQUIRED: qualified analysis requires retained source and configuration inputs");
+ }
+ const legacyAnalysis = analyzeRepository(config, discovery.files, compilerInputs);
+ return composePlaneARuntime(config, discovery, legacyAnalysis, compilerInputs);
}
export async function analyzePlaneARuntimeAsync(
config: SemctxConfig,
discovery: DiscoveryResult,
workers: IndexWorkerSelection,
+ compilerInputs?: CompilerInputSnapshot,
): Promise {
- const result = await analyzeRepositoryAsync(config, discovery.files, workers);
+ if (String(config.version === 2 ? config.selectionMode : "") === "qualified-static-v1" && compilerInputs === undefined) {
+ throw new Error("SOURCE_SNAPSHOT_REQUIRED: qualified analysis requires retained source and configuration inputs");
+ }
+ const result = await analyzeRepositoryAsync(config, discovery.files, workers, compilerInputs);
return {
- ...composePlaneARuntime(config, discovery, result.analysis),
+ ...composePlaneARuntime(config, discovery, result.analysis, compilerInputs),
parallelism: result.parallelism,
};
}
@@ -131,6 +149,7 @@ function composePlaneARuntime(
config: SemctxConfig,
discovery: DiscoveryResult,
legacyAnalysis: AnalysisResult,
+ compilerInputs?: CompilerInputSnapshot,
): PlaneARuntimeResult {
const legacySidecar = getPlaneASidecar(legacyAnalysis);
if (legacySidecar === undefined) {
@@ -187,9 +206,46 @@ function composePlaneARuntime(
}
const perPath: PerPathAnalysis[] = [];
+ const localCallReasons = getQualifiedCallIntegrityReasons(legacyAnalysis);
for (const candidate of selectedAnalyzable) {
const file = filesByPath.get(candidate.relPath);
if (file === undefined) continue;
+ if (config.version === 2 && candidate.language === "typescript") {
+ if (config.selectionMode === "qualified-static-v1" && config.analysisProfile === "modelo-suite-static-v1" && file.role === "migration") {
+ forcedOutcomes.set(candidate.relPath, "unsupported");
+ forcedAnalysisReasons.set(candidate.relPath, ["SOURCE_MIGRATION_SOURCE_UNSUPPORTED"]);
+ continue;
+ }
+ if (/\.d\.(?:ts|mts|cts)$/.test(file.relPath)) {
+ forcedOutcomes.set(candidate.relPath, "unsupported");
+ forcedAnalysisReasons.set(candidate.relPath, ["SOURCE_DECLARATION_FILE_UNSUPPORTED"]);
+ continue;
+ }
+ const reasons = [...inspectSourceParsing(file.absPath, file.content), ...inspectModuleConfiguration(file.absPath, config.repositoryRoot, compilerInputs)];
+ if (reasons.length > 0) {
+ forcedOutcomes.set(candidate.relPath, "failed");
+ forcedAnalysisReasons.set(candidate.relPath, ["PRODUCER_FAILED", ...reasons]);
+ continue;
+ }
+ }
+ if (candidate.language === "javascript") {
+ const diagnostics = inspectJavaScriptSource(file.absPath, file.content, config.repositoryRoot, compilerInputs);
+ const reasons = [...diagnostics.reasons, ...(localCallReasons.get(candidate.relPath) ?? [])];
+ if (diagnostics.parseFailed) {
+ forcedOutcomes.set(candidate.relPath, "failed");
+ forcedAnalysisReasons.set(candidate.relPath, ["PRODUCER_FAILED", ...diagnostics.reasons]);
+ continue;
+ }
+ perPath.push({
+ candidate, file, producer: JAVASCRIPT_PRODUCER,
+ facts: legacyFactsByPath.get(candidate.relPath) ?? [],
+ analysisReasons: reasons,
+ completenessClaim: reasons.length === 0 ? "producer-declared" : "partial",
+ negativeEvidenceEligible: false,
+ resolutionSemantics: "javascript-static-esm-v1",
+ });
+ continue;
+ }
if (candidate.language === "python") {
if (config.version === 1) continue;
if (forcedOutcomes.has(candidate.relPath)) {
@@ -221,8 +277,8 @@ function composePlaneARuntime(
file,
producer: TYPESCRIPT_PRODUCER,
facts: legacyFactsByPath.get(candidate.relPath) ?? [],
- analysisReasons: [],
- completenessClaim: "producer-declared",
+ analysisReasons: localCallReasons.get(candidate.relPath) ?? [],
+ completenessClaim: (localCallReasons.get(candidate.relPath)?.length ?? 0) === 0 ? "producer-declared" : "partial",
negativeEvidenceEligible: false,
resolutionSemantics: candidate.language === "typescript"
? "typescript-static-v1"
@@ -241,6 +297,7 @@ function composePlaneARuntime(
repositoryRoot: config.repositoryRoot,
repositoryId: repositoryIdentity,
artifacts: provisionalArtifacts,
+ ...(compilerInputs === undefined ? {} : { manifestContents: compilerInputs }),
});
const workspaceUnitByPath = workspaceUnitsByPath(
workspaceProjection,
@@ -270,7 +327,8 @@ function composePlaneARuntime(
const failedProducerByPath = new Map();
for (const candidate of discovery.candidates) {
if (candidate.selectionDecision !== "selected" || candidate.analysisOutcome !== "failed") continue;
- const producer = candidate.language === "python" ? PYTHON_PRODUCER : TYPESCRIPT_PRODUCER;
+ const producer = candidate.language === "python" ? PYTHON_PRODUCER
+ : candidate.language === "javascript" ? JAVASCRIPT_PRODUCER : TYPESCRIPT_PRODUCER;
const scope = scopeForCandidate(
repositoryIdentity,
candidate,
@@ -377,9 +435,8 @@ function composePlaneARuntime(
?? (completed === undefined ? "failed" : "analyzed");
const analysisReasons = outcome === "analyzed"
? perPath.find((item) => item.candidate.relPath === candidate.relPath)?.analysisReasons ?? []
- : outcome === "failed"
- ? forcedAnalysisReasons.get(candidate.relPath) ?? ["PRODUCER_FAILED", candidate.reason]
- : [candidate.reason];
+ : forcedAnalysisReasons.get(candidate.relPath) ?? (outcome === "failed"
+ ? ["PRODUCER_FAILED", candidate.reason] : [candidate.reason]);
const selectedProducer = completed?.producer ?? failedProducerByPath.get(candidate.relPath);
return normalizeDiscoveryLedgerEntry({
candidateIdentity: `${candidate.language}:${candidate.relPath}`,
@@ -729,7 +786,7 @@ function scopeForCandidate(
selectedPaths,
...(workspaceUnitId === undefined ? {} : { workspaceUnitId }),
language: candidate.language,
- ...(candidate.language === "typescript"
+ ...(candidate.language === "typescript" || candidate.language === "javascript"
? { dialectVersion: TYPESCRIPT_DIALECT_VERSION }
: {}),
...(candidate.language === "python" ? { dialectVersion: "<=3.12" } : {}),
diff --git a/packages/app-services/src/qualified-analysis.ts b/packages/app-services/src/qualified-analysis.ts
new file mode 100644
index 00000000..ab82709d
--- /dev/null
+++ b/packages/app-services/src/qualified-analysis.ts
@@ -0,0 +1,195 @@
+import type { AnalysisAdmission, RepositoryGraph, SemctxConfig } from "@semantic-context/core";
+import { digestCanonical } from "@semantic-context/plane-a-internal";
+import { discoverRepository, extractTypeScript, inspectSourceParsing, inspectModuleConfiguration, inspectNativeModuleBindings, registerOpaqueCallbackOwnership } from "@semantic-context/ts-analyzer";
+import type { IndexHealthReportV1 } from "./index-health";
+import { QUALIFIED_ANALYZER_IDENTITY } from "./analyzer-identity-generated";
+import { isBuiltin } from "node:module";
+import ts from "typescript";
+import { evaluateAnalysisAdmission } from "@semantic-context/context-engine";
+import type { captureQualifiedAnalysisInputs } from "./freshness";
+import { dirname, resolve } from "node:path";
+import type { ControlFreshnessStatusReport } from "@semantic-context/control-model/reconciliation";
+
+interface OpaqueGraphBinding { graphDigest: string; inputHash: string }
+const ownershipBindings = new WeakSet();
+/** Private identity proof only; it neither authorizes controls nor changes their gates. */
+export function bindOpaqueCallbackGraph(input: {
+ graph: RepositoryGraph; actualFactsHash: string; freshness: ControlFreshnessStatusReport | null;
+ consumedHash: string; expectedHash: string; bindingValid: boolean; buildComplete: boolean; checkChanged: boolean;
+}): OpaqueGraphBinding | undefined {
+ const observed = input.freshness; const seal = observed?.freshnessSeal;
+ if (seal === undefined || seal === null || !input.bindingValid || !input.buildComplete || input.checkChanged
+ || !(observed?.verdict === "FRESH" || (observed?.verdict === "DIRTY_KNOWN" && observed.reasons.every(reason => reason === "WORKING_TREE_DIRTY")))
+ || seal.repositoryGraphHash !== input.actualFactsHash || seal.indexedRepositoryGraphHash !== input.actualFactsHash
+ || seal.analysisInputHash !== input.consumedHash || seal.indexedAnalysisInputHash !== input.consumedHash || input.expectedHash !== input.consumedHash) return undefined;
+ const token = Object.freeze({ graphDigest: digestCanonical(input.graph), inputHash: input.consumedHash });
+ ownershipBindings.add(token); return token;
+}
+
+export const QUALIFIED_BUILD_META = "qualified_analysis_build_v1";
+export function isQualified(config: SemctxConfig): boolean {
+ return config.version === 2 && config.selectionMode === "qualified-static-v1" && config.analysisProfile === "modelo-suite-static-v1";
+}
+export function qualifiedAdmission(input: {
+ config: SemctxConfig; graph: RepositoryGraph; changedPaths: readonly string[];
+ health: IndexHealthReportV1; sourceHash: string; diff: string; indexSnapshot: string;
+ bindingReasons: string[]; checkChanged: boolean; buildState: string | undefined;
+ sourceCommits: string[]; baseCommit: string | null;
+ expectedInputHash: string;
+ consumedInputs: ReturnType;
+ opaqueGraphBinding?: OpaqueGraphBinding;
+}): AnalysisAdmission {
+ const { config, health } = input;
+ const evaluationsByCandidate = new Map();
+ for (const decision of health.evaluations.decisions) {
+ const group = evaluationsByCandidate.get(decision.candidateIdentity) ?? [];
+ evaluationsByCandidate.set(decision.candidateIdentity, [...group, decision]);
+ }
+ const { links, reasons, unresolvedImports, externalModuleSpecifiers } = inspectRetainedQualifiedScope(input);
+ const admission = evaluateAnalysisAdmission({
+ changedPaths: input.changedPaths, links, candidates: health.candidates.map((candidate) => {
+ const evaluations = evaluationsByCandidate.get(candidate.candidateIdentity) ?? [];
+ return { ...candidate, admissible: evaluations.length > 0 && evaluations.every((decision) => decision.decisionKind !== "pre_subject"
+ && [decision.gates.discoveryAndScope, decision.gates.bindingAndIntegrity, decision.gates.currentFreshness, decision.gates.capabilityMatch, decision.gates.taskRelativeAuthority].every((gate) => gate === "passed")) };
+ }),
+ current: discoverRepository(config).candidates.map((candidate) => ({ path: candidate.relPath, selectionDecision: candidate.selectionDecision, reason: candidate.reason })),
+ unresolvedImports,
+ scopeReasons: reasons, bindingReasons: input.bindingReasons, bindingStatus: health.binding.status,
+ canRunHighRiskControl: health.freshness.canRunHighRiskControl, freshnessReasons: health.freshness.reasons,
+ buildComplete: input.buildState === "complete", checkChanged: input.checkChanged, repositoryCoverageStatus: health.coverage.status,
+ });
+ return {
+ schemaVersion: 1, profile: "modelo-suite-static-v1", ...admission,
+ identity: { source: input.sourceHash, sourceCommits: input.sourceCommits, baseCommit: input.baseCommit, diff: digestCanonical(input.diff), config: digestCanonical(config), analyzer: QUALIFIED_ANALYZER_IDENTITY, indexSnapshot: input.indexSnapshot },
+ binding: { status: input.bindingReasons.length > 0 ? "invalid" : health.binding.status, reasons: input.bindingReasons },
+ indexFreshness: { verdict: health.freshness.verdict, reasons: health.freshness.reasons },
+ controlFreshness: { verdict: health.freshness.verdict, reasons: health.freshness.reasons },
+ checkFreshness: { status: input.checkChanged ? "changed" : "current", reasons: input.checkChanged ? ["CHECK_INPUT_CHANGED"] : [] },
+ limitations: ["Bounded static ESM/TypeScript analysis; no runtime dependency completeness or pipeline guarantee.", "Deleted source post-images and unknown configurations require separate qualification.", "PASS does not observe tests, Turbo cache behavior, failure propagation, or pipeline execution.",
+ `Opaque external static module boundaries (built-ins or manifest-declared non-workspace packages): ${externalModuleSpecifiers.join(", ") || "none"}. Their runtime behavior and APIs are not qualified.`,
+ ],
+ proofObligations: [
+ { id: "tests-execution", status: "not_observed", observation: "Run required tests and retain exact candidate exit status." },
+ { id: "turbo-cache", status: "not_observed", observation: "Observe cache miss/hit and invalidation with representative tasks." },
+ { id: "failure-propagation", status: "not_observed", observation: "Inject a failing task and observe nonzero pipeline exit." },
+ { id: "pipeline-execution", status: "not_observed", observation: "Execute the actual required pipeline on the exact candidate." },
+ ],
+ };
+}
+
+/** Private retained-input scope projection, shared by current admission and immutable old capture. */
+export function inspectRetainedQualifiedScope(input: {
+ config: SemctxConfig; graph: RepositoryGraph; expectedInputHash: string;
+ consumedInputs: ReturnType;
+ opaqueGraphBinding?: OpaqueGraphBinding; checkChanged: boolean;
+ retainedCompilerInputs?: ReadonlyMap;
+}): { links: [string, string][]; reasons: string[];
+ unresolvedImports: { path: string; specifier: string }[]; externalModuleSpecifiers: string[] } {
+ const { config } = input;
+ // Re-discover outside configured selectors: an excluded importer must not vanish from closure.
+ const broad = discoverRepository({ ...config, version: 2, selectionMode: "globs-v1", include: ["**/*"], exclude: [], languages: {
+ typescript: "on", javascript: "on", python: "on", markdown: "on", sql: "on",
+ } }, config);
+ const links: [string, string][] = [];
+ const consumed = input.consumedInputs;
+ const sourceFiles = consumed.files.filter((file) => /\.[cm]?[jt]sx?$/.test(file.path)).map((file) => ({ relPath: file.path, absPath: resolve(config.repositoryRoot, file.path), content: Buffer.from(file.bytes).toString("utf8") }));
+ // Retain the Map identity consumed and owned by immutable capture analysis.
+ const compilerInputs = input.retainedCompilerInputs ?? new Map(consumed.files.map((file) => [resolve(config.repositoryRoot, file.path), Buffer.from(file.bytes).toString("utf8")]));
+ const token = input.opaqueGraphBinding;
+ if (token !== undefined && ownershipBindings.has(token) && token.graphDigest === digestCanonical(input.graph)
+ && token.inputHash === consumed.digest && token.inputHash === input.expectedInputHash && !input.checkChanged) {
+ registerOpaqueCallbackOwnership(compilerInputs, input.graph, config.repositoryRoot);
+ }
+ const workspaceNames = new Set();
+ const externalPackages = new Map>();
+ const aliases: string[] = [];
+ for (const file of consumed.files.filter((entry) => /(?:^|\/)package\.json$/.test(entry.path) || /(?:^|\/)tsconfig[^/]*\.json$/.test(entry.path))) {
+ const parsed = ts.parseConfigFileTextToJson(file.path, Buffer.from(file.bytes).toString("utf8"));
+ if (parsed.error !== undefined) continue; // The retained configuration inspection reports errors.
+ const manifest = parsed.config as { name?: unknown; dependencies?: Record; devDependencies?: Record; peerDependencies?: Record; optionalDependencies?: Record; compilerOptions?: { paths?: Record } };
+ if (manifest === null || typeof manifest !== "object") continue;
+ if (file.path.endsWith("package.json") && typeof manifest.name === "string") workspaceNames.add(manifest.name);
+ const declared = new Set();
+ for (const [name, version] of Object.entries({ ...manifest.dependencies, ...manifest.devDependencies, ...manifest.peerDependencies, ...manifest.optionalDependencies })) {
+ if (typeof version === "string" && !/^(?:workspace:|file:|link:)/.test(version)) declared.add(name);
+ }
+ if (file.path.endsWith("package.json")) externalPackages.set(dirname(resolve(config.repositoryRoot, file.path)), declared);
+ aliases.push(...Object.keys(manifest.compilerOptions?.paths ?? {}));
+ }
+ const isExternal = (specifier: string, from: string): boolean => {
+ if (isBuiltin(specifier)) return true;
+ if (specifier.startsWith(".") || specifier.startsWith("/") || aliases.some((alias) => new Bun.Glob(alias).match(specifier))) return false;
+ const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
+ if (workspaceNames.has(name)) return false;
+ let directory = dirname(resolve(config.repositoryRoot, from));
+ for (;;) {
+ if (externalPackages.get(directory)?.has(name)) return true;
+ if (directory === resolve(config.repositoryRoot) || dirname(directory) === directory) return false;
+ directory = dirname(directory);
+ }
+ };
+ const reasons: string[] = [];
+ let extraction: ReturnType = { modules: [], symbols: [], imports: [], calls: [] };
+ try {
+ extraction = extractTypeScript(sourceFiles.map((file) => file.absPath), config.repositoryRoot, compilerInputs);
+ } catch (error) {
+ if (!(error instanceof Error) || !/^(?:IMPORT|REFERENCE|SOURCE_LINK)_OUTSIDE_REPOSITORY:/.test(error.message)) throw error;
+ // The confinement guard must remain fail-closed while qualification returns its rejection.
+ reasons.push("DEPENDENCY_SCOPE_OUTSIDE_REPOSITORY");
+ }
+ for (const entry of extraction.imports) if (entry.resolvedRelPath !== undefined) links.push([entry.fromRelPath, entry.resolvedRelPath]);
+ const byId = new Map(input.graph.nodes.map((node) => [node.id, node.filePath]));
+ for (const edge of input.graph.edges) {
+ if (!["imports", "exports", "calls", "depends_on"].includes(edge.kind)) continue;
+ const from = byId.get(edge.from); const to = byId.get(edge.to);
+ if (from !== undefined && to !== undefined) links.push([from, to]);
+ }
+ if (consumed.digest !== input.expectedInputHash) reasons.push("DEPENDENCY_SCOPE_INPUT_CHANGED");
+ for (const entry of extraction.imports) {
+ if (entry.resolvedRelPath === undefined && !isExternal(entry.moduleSpecifier, entry.fromRelPath)) {
+ reasons.push(`DEPENDENCY_SCOPE_UNRESOLVED_IMPORT:${entry.fromRelPath}:${entry.moduleSpecifier}`);
+ }
+ }
+ // An excluded or failed importer can hide a required inbound edge. Scope discovery itself
+ // therefore needs syntactically inspectable files and literal dependency specifiers.
+ for (const candidate of broad.candidates) {
+ if (candidate.reason === "READ_FAILED" || candidate.reason.endsWith("OUTSIDE_REPOSITORY")) reasons.push(`DEPENDENCY_SCOPE_UNREADABLE:${candidate.relPath}`);
+ }
+ for (const file of broad.files) if (file.role === "migration" && file.language === "typescript") reasons.push(`DEPENDENCY_SCOPE_MIGRATION_SOURCE_UNSUPPORTED:${file.relPath}`);
+ for (const source of sourceFiles) {
+ if (/\.d\.(?:ts|mts|cts)$/.test(source.relPath)) reasons.push(`DEPENDENCY_SCOPE_DECLARATION_UNSUPPORTED:${source.relPath}`);
+ const ast = ts.createSourceFile(source.relPath, source.content, ts.ScriptTarget.Latest, true);
+ const native = inspectNativeModuleBindings(ast);
+ if (native.commonJsUnsupported) reasons.push(`DEPENDENCY_SCOPE_COMMONJS_UNSUPPORTED:${source.relPath}`);
+ if (native.runtimeEvaluationUnsupported) reasons.push(`DEPENDENCY_SCOPE_RUNTIME_CODE:${source.relPath}`);
+ for (const reference of ts.preProcessFile(source.content, true, true).referencedFiles) {
+ if (!compilerInputs.has(resolve(dirname(source.absPath), reference.fileName))) {
+ reasons.push(`DEPENDENCY_SCOPE_UNRETAINED_REFERENCE:${source.relPath}:${reference.fileName}`);
+ }
+ }
+ for (const member of native.unmodeledMembers) reasons.push(`DEPENDENCY_SCOPE_NATIVE_MODULE_MEMBER_UNSUPPORTED:${source.relPath}:${member}`);
+ for (const diagnostic of inspectSourceParsing(source.relPath, source.content)) reasons.push(`DEPENDENCY_SCOPE_PARSE_FAILED:${source.relPath}:${diagnostic}`);
+ for (const diagnostic of inspectModuleConfiguration(source.absPath, config.repositoryRoot, compilerInputs, ast)) reasons.push(`DEPENDENCY_SCOPE_CONFIGURATION:${source.relPath}:${diagnostic}`);
+ const visit = (node: ts.Node): void => {
+ if (ts.isCallExpression(node)) {
+ const dynamic = node.expression.kind === ts.SyntaxKind.ImportKeyword;
+ if (dynamic && (node.arguments.length !== 1 || !ts.isStringLiteral(node.arguments[0]!))) reasons.push(`DEPENDENCY_SCOPE_COMPUTED_IMPORT:${source.relPath}`);
+ }
+ if ((ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference))
+ || (ts.isExportAssignment(node) && node.isExportEquals)) {
+ reasons.push(`DEPENDENCY_SCOPE_COMMONJS_UNSUPPORTED:${source.relPath}`);
+ }
+ if (ts.isImportTypeNode(node)) reasons.push(`DEPENDENCY_SCOPE_IMPORT_TYPE_UNSUPPORTED:${source.relPath}`);
+ if (ts.isWithStatement(node)) {
+ reasons.push(`DEPENDENCY_SCOPE_RUNTIME_CODE:${source.relPath}`);
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(ast);
+ }
+ return { links, reasons,
+ externalModuleSpecifiers: [...new Set(extraction.imports.filter(entry => isExternal(entry.moduleSpecifier, entry.fromRelPath)).map(entry => entry.moduleSpecifier))].sort(),
+ unresolvedImports: extraction.imports
+ .filter((entry) => entry.resolvedRelPath === undefined && !isExternal(entry.moduleSpecifier, entry.fromRelPath))
+ .map((entry) => ({ path: entry.fromRelPath, specifier: entry.moduleSpecifier })) };
+}
diff --git a/packages/app-services/src/setup.ts b/packages/app-services/src/setup.ts
index 26d9c0ef..7921df47 100644
--- a/packages/app-services/src/setup.ts
+++ b/packages/app-services/src/setup.ts
@@ -320,7 +320,7 @@ export function planSetupRepository(
}
const discovery = discoverRepository(config);
const selectedByLanguage = Object.fromEntries(
- ["typescript", "python", "markdown", "sql"].map((language) => [
+ ["typescript", "javascript", "python", "markdown", "sql"].map((language) => [
language,
discovery.files.filter((file) => (file.language ?? sourceLanguage(file.relPath)) === language).length,
]),
@@ -441,7 +441,7 @@ function prepareSetupRepository(
const fileCount = countTypeScriptFiles(config);
const selectedCount = discovery.files.length;
const selectedByLanguage = Object.fromEntries(
- ["typescript", "python", "markdown", "sql"].map((language) => [
+ ["typescript", "javascript", "python", "markdown", "sql"].map((language) => [
language,
discovery.files.filter(
(file) => (file.language ?? sourceLanguage(file.relPath)) === language,
diff --git a/packages/app-services/src/verification-state.ts b/packages/app-services/src/verification-state.ts
index 3f60ef0b..6d7ec02b 100644
--- a/packages/app-services/src/verification-state.ts
+++ b/packages/app-services/src/verification-state.ts
@@ -123,9 +123,9 @@ function objectPayload(root: string, objectId: string): Uint8Array {
return git(root, ["cat-file", "blob", objectId]);
}
-function hashObject(root: string, path: string, payload: Uint8Array): string {
+function hashObject(root: string, path: string, payload: Uint8Array, attributeSource?: string): string {
const objectId = new TextDecoder().decode(
- git(root, ["hash-object", `--path=${path}`, "--stdin"], payload),
+ git(root, [...(attributeSource === undefined ? [] : [`--attr-source=${attributeSource}`]), "hash-object", `--path=${path}`, "--stdin"], payload),
).trim();
if (!/^[0-9a-f]{40,64}$/.test(objectId)) {
throw new SemctxError("GIT_ERROR", "cannot capture verification source state: invalid object id", {
@@ -160,6 +160,8 @@ interface AttributeSnapshot {
ident: string;
workingTreeEncoding: string;
explicitTransformer: boolean;
+ /** Full selected conversion context, present only in the qualified retained helper. */
+ conversionMetadata?: string;
}
function attributeSnapshotIsSafe(snapshot: AttributeSnapshot): boolean {
@@ -167,14 +169,47 @@ function attributeSnapshotIsSafe(snapshot: AttributeSnapshot): boolean {
&& snapshot.ident === "unspecified" && snapshot.workingTreeEncoding === "unspecified";
}
+function retainedPostImageOperation(reason: string, observe: () => T): T {
+ try { return observe(); }
+ catch (error) {
+ if (!(error instanceof SemctxError) || error.code !== "GIT_ERROR") throw error;
+ throw new SemctxError(error.code, error.message, { ...error.details, qualifiedPostImageReason: reason });
+ }
+}
+
+/** Internal qualified post-image seam: convert retained bytes, never re-read their sources. */
+export function retainedGitBlobObjectIds(root: string, headCommit: string, files: readonly { path: string; bytes: Uint8Array }[]): Map {
+ const paths = files.map(file => file.path);
+ const before = retainedPostImageOperation("QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND", () => checkAttrBatch(root, paths, headCommit));
+ const format = gitObjectFormat(headCommit);
+ const identities = new Map();
+ for (const file of files) {
+ identities.set(file.path, !file.bytes.includes(0x0d) && attributeSnapshotIsSafe(before.get(file.path)!)
+ ? localBlobObjectId(format, file.bytes) : retainedPostImageOperation("QUALIFIED_POST_IMAGE_CONVERSION_FAILED", () => hashObject(root, file.path, file.bytes, headCommit)));
+ }
+ const barrier = verificationAttributeBarrierForTesting;
+ verificationAttributeBarrierForTesting = undefined;
+ barrier?.();
+ const after = retainedPostImageOperation("QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND", () => checkAttrBatch(root, paths, headCommit));
+ for (const path of paths) {
+ const initial = before.get(path)!; const final = after.get(path)!;
+ if (initial.filter !== final.filter || initial.ident !== final.ident
+ || initial.workingTreeEncoding !== final.workingTreeEncoding || initial.explicitTransformer !== final.explicitTransformer
+ || initial.conversionMetadata !== final.conversionMetadata) {
+ throw new SemctxError("GIT_ERROR", "cannot compare retained post-image: attribute metadata changed during capture", { path, qualifiedPostImageReason: "QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND" });
+ }
+ }
+ return identities;
+}
+
/** Batch-resolve `filter`/`ident`/`working-tree-encoding` for candidate paths in one `git
* check-attr --stdin` call. Every requested path must yield a complete triple; anything else
* (malformed output, duplicates, missing records) refuses rather than admitting raw hashing. */
-function checkAttrBatch(root: string, paths: readonly string[]): Map {
+function checkAttrBatch(root: string, paths: readonly string[], attributeSource?: string, presentFilters?: Map): Map {
if (paths.length === 0) return new Map();
const stdin = new TextEncoder().encode(paths.map((path) => `${path}\0`).join(""));
const raw = new TextDecoder().decode(
- git(root, ["check-attr", "--stdin", "-z", ...VERIFICATION_ATTRIBUTES], stdin),
+ git(root, [...(attributeSource === undefined ? [] : [`--attr-source=${attributeSource}`]), "check-attr", "--stdin", "-z", ...VERIFICATION_ATTRIBUTES], stdin),
);
if (!raw.endsWith("\0")) {
throw new SemctxError("GIT_ERROR", "cannot capture verification source state: unterminated check-attr output");
@@ -210,7 +245,7 @@ function checkAttrBatch(root: string, paths: readonly string[]): Map();
// Named check-attr output conflates sentinel states with literal values such as filter=unset.
// --all omits truly unspecified attributes, so explicit transformer values stay on Git's path.
- const allRaw = new TextDecoder().decode(git(root, ["check-attr", "--stdin", "-z", "--all"], stdin));
+ const allRaw = new TextDecoder().decode(git(root, [...(attributeSource === undefined ? [] : [`--attr-source=${attributeSource}`]), "check-attr", "--stdin", "-z", "--all"], stdin));
if (allRaw.length > 0 && !allRaw.endsWith("\0")) {
throw new SemctxError("GIT_ERROR", "cannot capture verification source state: unterminated check-attr output");
}
@@ -220,6 +255,7 @@ function checkAttrBatch(root: string, paths: readonly string[]): Map();
const allKeys = new Set();
+ const conversionMetadata = new Map();
for (let index = 0; index < allFields.length; index += 3) {
const path = allFields[index]!;
const attribute = allFields[index + 1]!;
@@ -229,6 +265,12 @@ function checkAttrBatch(root: string, paths: readonly string[]): Map left < right ? -1 : left > right ? 1 : 0)) }),
+ });
}
return result;
}
+/** Private qualified working-tree boundary: inspect conversion authority, never run a driver. */
+export function retainedWorkingTreeFilterPaths(root: string, paths: readonly string[]): string[] {
+ const filters = new Map();
+ checkAttrBatch(root, paths, undefined, filters);
+ const configured = new Map();
+ for (const name of new Set(filters.values())) {
+ let active = false;
+ for (const kind of ["clean", "process"]) {
+ const result = Bun.spawnSync(["git", "config", "--null", "--get-all", `filter.${name}.${kind}`], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0 && result.exitCode !== 1) throw new SemctxError("GIT_ERROR", "cannot establish working-tree filter configuration");
+ if (result.exitCode === 0 && new TextDecoder().decode(result.stdout).split("\0").some(value => value.length > 0)) active = true;
+ }
+ configured.set(name, active);
+ }
+ // Configured reserved names can be indistinguishable from Git's set/unset sentinels.
+ return [...filters].filter(([, name]) => configured.get(name)).map(([path]) => path).sort();
+}
+
type VerificationAttributeBarrier = () => void;
let verificationAttributeBarrierForTesting: VerificationAttributeBarrier | undefined;
diff --git a/packages/app-services/src/verify.ts b/packages/app-services/src/verify.ts
index 4ba54345..3be7c7c6 100644
--- a/packages/app-services/src/verify.ts
+++ b/packages/app-services/src/verify.ts
@@ -1,11 +1,17 @@
-import { readFileSync } from "node:fs";
-import { digestCanonical } from "@semantic-context/plane-a-internal";
-import { SemctxError, type VerifyReport } from "@semantic-context/core";
+import { createHash } from "node:crypto";
+import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, isAbsolute, join, posix, resolve } from "node:path";
+import { canonicalSourceText, digestCanonical, getPlaneASidecar, type PlaneASidecarV1 } from "@semantic-context/plane-a-internal";
+import { SemctxError, type SemctxConfig, type VerifyReport } from "@semantic-context/core";
import { loadConfig } from "@semantic-context/repository-store";
import { discoverRepository, isPathSelected } from "@semantic-context/ts-analyzer";
import {
GraphIndex,
analyzeDiff,
+ analyzeLostGuards,
+ diffRemovesBaseLines,
+ parseUnifiedDiffChanges,
buildVerifyReport,
computeCoChanges,
parseUnifiedDiffScopePaths,
@@ -14,16 +20,19 @@ import {
type VerifyReportGitMeta,
type VerifyResult,
} from "@semantic-context/context-engine";
+import { analyzeAndBuildClaims, analyzeRetainedRepository } from "./indexing";
import { openReadyRepository } from "./readiness";
-import { indexHealth, type IndexHealthReportV1 } from "./index-health";
+import { evaluateCapturedPlaneA, indexHealth, type IndexHealthReportV1 } from "./index-health";
import { controlStatusWithSemanticInputs } from "./control";
import {
readUnresolvedReferenceBinding,
type UnresolvedReferenceBindingReason,
} from "./unresolved-references";
import { CONTROL_INDEX_SNAPSHOT_META_KEY, fingerprintRepositoryFacts, parseIndexedControlSnapshot } from "./freshness";
-import type { ControlFreshnessReason, ControlFreshnessStatusReport } from "@semantic-context/control-model";
-import { fingerprintVerificationSource } from "./verification-state";
+import { CLEAN_CONTROL_WORKING_DIFF_HASH, type ControlFreshnessReason, type ControlFreshnessStatusReport } from "@semantic-context/control-model";
+import { fingerprintVerificationSource, retainedGitBlobObjectIds, retainedWorkingTreeFilterPaths } from "./verification-state";
+import { isQualified, qualifiedAdmission, QUALIFIED_BUILD_META, bindOpaqueCallbackGraph, inspectRetainedQualifiedScope } from "./qualified-analysis";
+import { canonicalRepositoryRoot, captureQualifiedAnalysisInputs, isQualifiedRepositoryInputPath } from "./freshness";
/**
* `head` names the commit the analysed post-image belongs to. It is optional everywhere and means
@@ -571,25 +580,407 @@ function discloseUnresolvedReferences(
);
}
+/** Compare retained repository inputs with the actual selected Git post-image. */
+function qualifiedPostImageInputs(root: string, retained: ReturnType, source: VerifySource, identity: SourceIdentity): string[] {
+ if (identity.kind !== "commits" || identity.commits.length !== 1) return ["QUALIFIED_POST_IMAGE_UNBOUND"];
+ const format = git(root, ["rev-parse", "--show-object-format"]);
+ const algorithm = format.out.trim();
+ if (format.code !== 0 || (algorithm !== "sha1" && algorithm !== "sha256")) return ["QUALIFIED_POST_IMAGE_UNBOUND"];
+ const inventory = git(root, source.kind === "staged"
+ ? ["ls-files", "--stage", "-z"] : ["ls-tree", "-r", "-z", identity.commits[0]!]);
+ if (inventory.code !== 0) return ["QUALIFIED_POST_IMAGE_UNBOUND"];
+ const blobs = new Map();
+ const directories = new Set(["."]);
+ for (const item of inventory.out.split("\0").filter(Boolean)) {
+ const separator = item.indexOf("\t");
+ const fields = item.slice(0, separator).split(" ");
+ if (separator < 0 || fields.length !== 3) return ["QUALIFIED_POST_IMAGE_UNBOUND"];
+ if (source.kind === "staged" ? fields[2] !== "0" : fields[1] !== "blob") continue;
+ const path = item.slice(separator + 1);
+ blobs.set(path, source.kind === "staged" ? fields[1]! : fields[2]!);
+ for (let directory = posix.dirname(path); directory !== "."; directory = posix.dirname(directory)) directories.add(directory);
+ }
+ const version = git(root, ["--version"]);
+ const components = /^git version (\d+)\.(\d+)(?:\.|$)/.exec(version.out.trim());
+ if (version.code !== 0 || components === null) return ["QUALIFIED_POST_IMAGE_UNBOUND"];
+ if (Number(components[1]) < 2 || (Number(components[1]) === 2 && Number(components[2]) < 41)) {
+ throw new SemctxError("UNSUPPORTED", "qualified staged/range verification requires Git >= 2.41 (--attr-source)", { version: version.out.trim() });
+ }
+ let objectIds: Map;
+ try { objectIds = retainedGitBlobObjectIds(root, identity.commits[0]!, retained.files); }
+ catch (error) {
+ if (!(error instanceof SemctxError) || error.code !== "GIT_ERROR") throw error;
+ const reason = error.details["qualifiedPostImageReason"];
+ if (reason !== "QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND" && reason !== "QUALIFIED_POST_IMAGE_CONVERSION_FAILED") throw error;
+ return [reason];
+ }
+ const reasons: string[] = [];
+ const retainedPaths = new Set(retained.files.map(file => file.path));
+ for (const file of retained.files) {
+ const expected = blobs.get(file.path);
+ if (expected === undefined) reasons.push(`QUALIFIED_POST_IMAGE_INPUT_ABSENT:${file.path}`);
+ else {
+ const actual = objectIds.get(file.path);
+ if (actual !== expected) reasons.push(`QUALIFIED_POST_IMAGE_INPUT_MISMATCH:${file.path}`);
+ }
+ }
+ for (const path of blobs.keys()) {
+ if (isQualifiedRepositoryInputPath(path) && !retainedPaths.has(path)) reasons.push(`QUALIFIED_POST_IMAGE_INPUT_UNRETAINED:${path}`);
+ }
+ for (const workspace of retained.workspaceRoots) {
+ if (!directories.has(workspace)) reasons.push(`QUALIFIED_POST_IMAGE_WORKSPACE_ABSENT:${workspace}`);
+ }
+ return reasons;
+}
+
+/** The qualified closure requires materialized relevant tracked inputs; ordinary analysis is unchanged. */
+function qualifiedCheckoutReasons(root: string): string[] {
+ const reasons: string[] = [];
+ const sparse = git(root, ["config", "--bool", "core.sparseCheckout"]);
+ if (sparse.code === 0 && sparse.out.trim() === "true") reasons.push("QUALIFIED_SPARSE_CHECKOUT_UNSUPPORTED");
+ else if (sparse.code !== 0 && sparse.code !== 1) reasons.push("QUALIFIED_CHECKOUT_INVENTORY_UNAVAILABLE");
+ const flags = git(root, ["ls-files", "-v", "-z"]);
+ if (flags.code !== 0) return [...reasons, "QUALIFIED_CHECKOUT_INVENTORY_UNAVAILABLE"];
+ for (const record of flags.out.split("\0").filter(Boolean)) {
+ if (!/^[A-Za-z] /.test(record)) { reasons.push("QUALIFIED_CHECKOUT_INVENTORY_UNAVAILABLE"); continue; }
+ const path = record.slice(2);
+ if (/^[Ss] /.test(record) && isQualifiedRepositoryInputPath(path)) reasons.push(`QUALIFIED_SKIP_WORKTREE_UNSUPPORTED:${path}`);
+ if (/^[a-z] /.test(record) && isQualifiedRepositoryInputPath(path)) reasons.push(`QUALIFIED_ASSUME_UNCHANGED_UNSUPPORTED:${path}`);
+ }
+ return reasons;
+}
+
+interface CommitTreeAnalysis {
+ index: GraphIndex;
+ sidecar: PlaneASidecarV1;
+}
+
+/** Read committed blob bytes without Git checkout filters, links or changes to the live index. */
+function analyzeCommitTree(root: string, commit: string, config: SemctxConfig,
+ retained?: { diffText: string; currentObligations: readonly string[] },
+): CommitTreeAnalysis {
+ const scratch = canonicalRepositoryRoot(mkdtempSync(join(tmpdir(), "semctx-base-")));
+ try {
+ const listed = Bun.spawnSync(["git", "ls-tree", "-r", "-z", commit], {
+ cwd: root, stdout: "pipe", stderr: "pipe",
+ });
+ if (listed.exitCode !== 0) {
+ throw new SemctxError("GIT_ERROR", `cannot read the base tree ${commit.slice(0, 12)}`, {
+ stderr: new TextDecoder().decode(listed.stderr).trim(),
+ });
+ }
+ const entries: Array<{ path: string; oid: string; mode: string }> = [];
+ const blobId = (bytes: Uint8Array, oid: string): string => createHash(oid.length === 64 ? "sha256" : "sha1")
+ .update(`blob ${bytes.byteLength}\0`).update(bytes).digest("hex");
+ const destinations = new Set();
+ for (const record of new TextDecoder("utf-8", { fatal: true }).decode(listed.stdout).split("\0")) {
+ if (record === "") continue;
+ const match = /^(\d{6}) (blob|commit) ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record);
+ if (match === null) throw new SemctxError("GIT_ERROR", "invalid base tree entry");
+ const [, mode, , oid, path] = match;
+ if (path === undefined || isAbsolute(path) || path.includes(":") || path.includes("\\")
+ || path.split("/").some((part) => part === "" || part === "." || part === ".." || part.toLowerCase() === ".git")) {
+ throw new SemctxError("GIT_ERROR", "unsafe base tree path", { path });
+ }
+ if (mode !== "100644" && mode !== "100755") {
+ if (retained || isPathSelected(config, path)) {
+ throw new SemctxError("GIT_ERROR", "selected base tree links and gitlinks are unsupported", { path, mode });
+ }
+ continue;
+ }
+ const destinationKey = process.platform === "win32" ? path.toLowerCase() : path;
+ if (destinations.has(destinationKey)
+ || (process.platform === "win32" && path.split("/").some((part) => /[. ]$/.test(part)))) {
+ throw new SemctxError("GIT_ERROR", "base tree paths alias on this filesystem", { path });
+ }
+ destinations.add(destinationKey);
+ entries.push({ path, oid: oid!, mode: mode! });
+ }
+ const blobs = Bun.spawnSync(["git", "cat-file", "--batch"], {
+ cwd: root,
+ stdin: new TextEncoder().encode(entries.map((entry) => `${entry.oid}\n`).join("")),
+ stdout: "pipe", stderr: "pipe",
+ });
+ if (blobs.exitCode !== 0) throw new SemctxError("GIT_ERROR", "cannot read base tree blobs");
+ let offset = 0;
+ for (const entry of entries) {
+ const newline = blobs.stdout.indexOf(10, offset);
+ const header = newline < 0 ? "" : new TextDecoder().decode(blobs.stdout.subarray(offset, newline));
+ const match = /^([0-9a-f]{40,64}) blob (\d+)$/.exec(header);
+ const size = match === null ? NaN : Number(match[2]);
+ offset = newline + 1;
+ if (match?.[1] !== entry.oid || !Number.isSafeInteger(size) || size < 0
+ || offset + size >= blobs.stdout.byteLength || blobs.stdout[offset + size] !== 10) {
+ throw new SemctxError("GIT_ERROR", "invalid base blob response", { path: entry.path });
+ }
+ const bytes = blobs.stdout.subarray(offset, offset + size);
+ if (blobId(bytes, entry.oid) !== entry.oid) throw new SemctxError("GIT_ERROR", "base blob bytes do not match immutable object", { path: entry.path });
+ const destination = join(scratch, entry.path);
+ mkdirSync(dirname(destination), { recursive: true });
+ writeFileSync(destination, bytes);
+ offset += size + 1;
+ }
+ if (offset !== blobs.stdout.byteLength) throw new SemctxError("GIT_ERROR", "unexpected base blob response");
+ const scratchConfig = { ...config, repositoryRoot: scratch };
+ if (!retained) {
+ const analysis = analyzeAndBuildClaims(scratchConfig).analysis;
+ const sidecar = getPlaneASidecar(analysis);
+ if (sidecar === undefined) throw new SemctxError("GIT_ERROR", "base analysis has no discovery ledger");
+ return { index: new GraphIndex(analysis.graph), sidecar };
+ }
+ // The private index marks immutable HEAD inventory as tracked, including ignored output paths.
+ // No checkout/filter runs, and neither the live repository nor its index is modified.
+ if (Object.keys(process.env).some((name) => /^(?:GIT_DIR|GIT_WORK_TREE|GIT_INDEX_FILE|GIT_COMMON_DIR|GIT_OBJECT_DIRECTORY|GIT_ALTERNATE_OBJECT_DIRECTORIES|GIT_CONFIG|GIT_CONFIG_SYSTEM|GIT_CONFIG_GLOBAL|GIT_CONFIG_COUNT|GIT_CONFIG_PARAMETERS|GIT_CONFIG_KEY_\d+|GIT_CONFIG_VALUE_\d+)$/i.test(name))) {
+ throw new SemctxError("GIT_ERROR", "qualified base capture has overriding Git environment");
+ }
+ const scratchGit = (args: string[], stdin?: Uint8Array): void => {
+ const result = Bun.spawnSync(["git", "-c", "core.autocrlf=false", "-c", "core.fsmonitor=false", ...args], {
+ cwd: scratch, stdin, stdout: "pipe", stderr: "pipe",
+ env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null" },
+ });
+ if (result.exitCode !== 0) throw new SemctxError("GIT_ERROR", "qualified base private index failed");
+ };
+ scratchGit(["init", "-q", "--template=", `--object-format=${entries[0]?.oid.length === 64 ? "sha256" : "sha1"}`]);
+ scratchGit(["config", "core.fsmonitor", "false"]);
+ scratchGit(["config", "core.worktree", scratch]);
+ scratchGit(["config", "core.bare", "false"]);
+ scratchGit(["update-index", "--info-only", "-z", "--index-info"], new TextEncoder().encode(
+ entries.map((entry) => `${entry.mode} ${entry.oid}\t${entry.path}\0`).join(""),
+ ));
+ const current = captureQualifiedAnalysisInputs(config);
+ // An ignored current policy is still the explicit paired authority. Do not keep HEAD policy.
+ rmSync(join(scratch, ".semctx/config.json"), { force: true });
+ if (current.configurationInput.state === "present") {
+ mkdirSync(join(scratch, ".semctx"), { recursive: true });
+ writeFileSync(join(scratch, ".semctx/config.json"), current.configurationInput.bytes);
+ }
+ const before = captureQualifiedAnalysisInputs(scratchConfig);
+ if (digestCanonical(before.configurationInput) !== digestCanonical(current.configurationInput)) {
+ throw new SemctxError("GIT_ERROR", "qualified base did not capture the exact current raw policy");
+ }
+ const assertManifestBinding = (): void => {
+ for (const entry of entries) {
+ if (entry.path === ".semctx/config.json") continue;
+ if (blobId(readFileSync(join(scratch, entry.path)), entry.oid) !== entry.oid) {
+ throw new SemctxError("GIT_ERROR", "qualified base materialization differs from immutable HEAD", { path: entry.path });
+ }
+ }
+ };
+ assertManifestBinding();
+ const entryByPath = new Map(entries.map(entry => [entry.path, entry]));
+ for (const file of before.files) {
+ const entry = entryByPath.get(file.path);
+ if (entry === undefined || blobId(file.bytes, entry.oid) !== entry.oid) {
+ throw new SemctxError("GIT_ERROR", `qualified base consumed an unbound HEAD input: ${file.path}`);
+ }
+ }
+ const compilerInputs = new Map(before.files.map((file) => [resolve(scratch, file.path), Buffer.from(file.bytes).toString("utf8")]));
+ const discovery = discoverRepository(scratchConfig);
+ if (discovery.files.some((file) => compilerInputs.get(resolve(file.absPath)) !== file.content)) {
+ throw new SemctxError("GIT_ERROR", "qualified base discovery is not bound to retained inputs");
+ }
+ const captured = analyzeRetainedRepository(scratchConfig, discovery, compilerInputs);
+ assertManifestBinding();
+ if (before.digest !== captureQualifiedAnalysisInputs(scratchConfig).digest
+ || current.digest !== captureQualifiedAnalysisInputs(config).digest) {
+ throw new SemctxError("GIT_ERROR", "qualified base retained inputs or current authority changed");
+ }
+ const graph = captured.analysis.graph;
+ const scope = inspectRetainedQualifiedScope({ config: scratchConfig, graph, consumedInputs: before,
+ expectedInputHash: before.digest, checkChanged: false, retainedCompilerInputs: compilerInputs });
+ const inventory = new Set(entries.map(entry => entry.path));
+ const parsed = parseUnifiedDiffChanges(retained.diffText);
+ const obligated = new Set(parsed.files.filter(file => file.status !== "added").map(file => file.oldPath ?? file.filePath));
+ for (const block of parsed.unscoped) for (const path of block.paths) obligated.add(path);
+ // Add current dependency obligations that existed in HEAD. Head-only additions have no old image.
+ for (const path of retained.currentObligations) if (inventory.has(path)) obligated.add(path);
+ const links: [string, string][] = [...scope.links];
+ const byId = new Map(graph.nodes.map(node => [node.id, node]));
+ const provenancePaths = (nodeId: string): string[] => {
+ const node = byId.get(nodeId);
+ return node === undefined ? [] : [...new Set([
+ ...(node.filePath === undefined ? [] : [node.filePath]), ...node.evidence.map(evidence => evidence.filePath),
+ ])];
+ };
+ for (const edge of graph.edges) {
+ if (!["constrained_by", "tested_by", "covers", "verifies", "documents", "declares", "related_to"].includes(edge.kind)) continue;
+ const paths = [...new Set([...provenancePaths(edge.from), ...provenancePaths(edge.to), ...edge.evidence.map(evidence => evidence.filePath)])];
+ for (const from of paths) for (const to of paths) if (from !== to) links.push([from, to]);
+ }
+ let expanded = true;
+ while (expanded) {
+ expanded = false;
+ for (const [from, to] of links) {
+ if (!obligated.has(from) && !obligated.has(to)) continue;
+ for (const path of [from, to]) if (!obligated.has(path)) { obligated.add(path); expanded = true; }
+ }
+ }
+ // Missing old links cannot prove an unsupported broad importer is irrelevant.
+ // Preserve the existing qualified broad scope guard; only factual ledger use is scoped below.
+ if (scope.reasons.length > 0) throw new SemctxError("GIT_ERROR", scope.reasons.join(", "));
+ const evaluations = evaluateCapturedPlaneA(captured.sidecar, captured.workspaceProjection,
+ config.version, digestCanonical(scratchConfig));
+ for (const path of [...obligated].sort()) {
+ const candidate = discovery.candidates.filter(candidate => candidate.relPath === path);
+ const ledger = captured.sidecar.discoveryLedger.filter(entry => entry.scope.selectedPaths.length === 1 && entry.scope.selectedPaths[0] === path);
+ if (candidate.length !== 1 || ledger.length !== 1 || candidate[0]!.selectionDecision !== "selected"
+ || ledger[0]!.selectionDecision !== "selected" || ledger[0]!.analysisOutcome !== "analyzed"
+ || ledger[0]!.analysisReasons.length > 0) {
+ throw new SemctxError("GIT_ERROR", `qualified base obligation is not analyzed: ${path}; ${ledger.flatMap(entry => [...entry.selectionReasons, ...entry.analysisReasons]).join(", ")}`);
+ }
+ const file = discovery.files.find(file => file.relPath === path);
+ const entry = ledger[0]!;
+ const expectedSource = file === undefined ? null : digestCanonical({ relPath: file.relPath, role: file.role,
+ language: file.language ?? null, content: canonicalSourceText(file.content) });
+ if (entry.scope.sourceStateDigest !== expectedSource || captured.sidecar.producerConfigurationDigest !== digestCanonical(scratchConfig)) {
+ throw new SemctxError("GIT_ERROR", `qualified base obligation has an unbound source/policy: ${path}`);
+ }
+ const decisions = evaluations.decisions.filter(decision => decision.candidateIdentity === entry.candidateIdentity);
+ // UNSEALED is honest for this ephemeral repository. Immutable raw-manifest/capture checks above
+ // supply temporal authority, independently of current-store seals and negative completeness.
+ if (decisions.length === 0 || decisions.some(decision => decision.decisionKind === "pre_subject"
+ || [decision.gates.discoveryAndScope, decision.gates.bindingAndIntegrity,
+ decision.gates.capabilityMatch, decision.gates.taskRelativeAuthority].some(gate => gate !== "passed"))) {
+ throw new SemctxError("GIT_ERROR", `qualified base obligation has no admissible factual producer: ${path}; ${decisions.flatMap(decision => decision.reasons.map(reason => reason.code)).join(", ")}`);
+ }
+ }
+ assertManifestBinding();
+ if (before.digest !== captureQualifiedAnalysisInputs(scratchConfig).digest
+ || current.digest !== captureQualifiedAnalysisInputs(config).digest) {
+ throw new SemctxError("GIT_ERROR", "qualified base inputs changed during obligated-scope evaluation");
+ }
+ return { index: new GraphIndex(captured.analysis.graph), sidecar: captured.sidecar };
+ } finally {
+ rmSync(scratch, { recursive: true, force: true });
+ }
+}
+
+/**
+ * A committed range is indexed at its head, so a guard the change deletes, strips or moves is not in
+ * the graph the head-side analysis reads. Recover those from the merge-base and add them as findings.
+ */
+function applyLostGuards(
+ root: string,
+ config: SemctxConfig,
+ result: VerifyResult,
+ headIndex: GraphIndex,
+ mergeBase: string,
+ diffText: string,
+): VerifyResult {
+ if (!diffRemovesBaseLines(diffText)) return result;
+ if (isQualified(config)) {
+ const paths = parseUnifiedDiffScopePaths(diffText).join(", ");
+ return {
+ ...result,
+ verdict: "BLOCK",
+ findings: [...result.findings, {
+ rule: "analysis_scope_incomplete", severity: "block", nodeIds: [],
+ message: `Qualified committed ranges removing base lines are unsupported: ${paths}`,
+ }],
+ unknowns: [...new Set([...result.unknowns,
+ `Qualified base guards were not recovered for: ${paths}. Immutable base source eligibility and compiler inputs are not captured; use refreshed working-tree verification before committing.`,
+ ])],
+ };
+ }
+ const base = analyzeCommitTree(root, mergeBase, config);
+ const lost = analyzeLostGuards({ baseIndex: base.index, headIndex, config, diffText });
+ const changed = new Set(parseUnifiedDiffScopePaths(diffText));
+ const failed: string[] = [];
+ const excluded: string[] = [];
+ const partial: string[] = [];
+ for (const entry of base.sidecar.discoveryLedger) {
+ const paths = entry.scope.selectedPaths.filter((path) => changed.has(path));
+ if (paths.length === 0) continue;
+ if (entry.selectionDecision === "excluded") {
+ if (!entry.selectionReasons.includes("LEGACY_UNSUPPORTED_EXTENSION")) excluded.push(...paths);
+ } else if (entry.analysisOutcome !== "analyzed") {
+ failed.push(...paths);
+ } else if (config.version === 2) {
+ const profiles = base.sidecar.capabilityProfiles.filter((profile) =>
+ digestCanonical(profile.scope) === digestCanonical(entry.scope));
+ if (entry.analysisReasons.length > 0 || profiles.length === 0
+ || profiles.some((profile) => !profile.negativeEvidenceEligible)) partial.push(...paths);
+ }
+ }
+ const findings = [...result.findings, ...lost.findings];
+ const unknowns = [...result.unknowns, ...lost.unknowns];
+ for (const [paths, severity, detail] of [
+ [failed, "block", "selected base scope lacks an admissible analyzer"],
+ [excluded, "warn", "base scope was excluded by the current configuration; removed guards were not checked"],
+ [partial, "warn", "base analysis has no complete negative test/guard evidence"],
+ ] as const) {
+ if (paths.length === 0) continue;
+ const named = [...new Set(paths)].sort().join(", ");
+ findings.push({ rule: "analysis_scope_incomplete", severity, message: `${detail}: ${named}`, nodeIds: [] });
+ unknowns.push(`${detail}: ${named}.`);
+ }
+ return {
+ ...result,
+ findings,
+ unknowns: [...new Set(unknowns)],
+ verdict: findings.some((f) => f.severity === "block") ? "BLOCK"
+ : findings.some((f) => f.severity === "warn") ? "WARN" : "PASS",
+ };
+}
+
+/** Paired structural comparison under current policy/compiler, not historical policy admission. */
+function applyQualifiedWorkingTreeGuards(
+ root: string, config: SemctxConfig, result: VerifyResult, headIndex: GraphIndex,
+ source: ResolvedVerifySource, captureReasons: string[], currentObligations: readonly string[],
+): VerifyResult {
+ const paths = parseUnifiedDiffScopePaths(source.diffText ?? "");
+ try {
+ if (source.identity.kind !== "commits" || source.identity.commits.length !== 1) {
+ throw new SemctxError("GIT_ERROR", "qualified base capture requires one resolved HEAD");
+ }
+ const base = analyzeCommitTree(root, source.identity.commits[0]!, config, { diffText: source.diffText ?? "", currentObligations });
+ const lost = analyzeLostGuards({ baseIndex: base.index, headIndex, config, diffText: source.diffText ?? "" });
+ const findings = [...result.findings, ...lost.findings];
+ return { ...result, findings, unknowns: [...new Set([...result.unknowns, ...lost.unknowns])],
+ verdict: findings.some((finding) => finding.severity === "block") ? "BLOCK"
+ : findings.some((finding) => finding.severity === "warn") ? "WARN" : "PASS" };
+ } catch (error) {
+ const detail = error instanceof Error ? error.message : String(error);
+ captureReasons.push(`QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED:${paths.join(", ")}:${detail}`);
+ return { ...result, verdict: "BLOCK", findings: [...result.findings, {
+ rule: "analysis_scope_incomplete", severity: "block", nodeIds: [],
+ locations: paths.map((file) => ({ file })),
+ message: `Qualified immutable HEAD guards could not be recovered: ${detail}; ${paths.join(", ")}`,
+ }], unknowns: [...result.unknowns, "Immutable HEAD structural capture was unavailable; no historical policy or runtime execution is claimed."] };
+ }
+}
+
/** Shared CLI/MCP verification use case. Always returns the ADR-0008 report. */
export function runVerify(root: string, source: VerifySource): VerifyComputation {
const store = openReadyRepository(root);
try {
const config = loadConfig(root);
+ if (isQualified(config)) {
+ const worktree = git(root, ["rev-parse", "--show-toplevel"]);
+ if (worktree.code !== 0 || canonicalRepositoryRoot(worktree.out.trim()) !== canonicalRepositoryRoot(root)) {
+ throw new SemctxError("INVALID_TASK_INPUT", "qualified analysis root must be the exact Git worktree root");
+ }
+ }
+ const inputBefore = isQualified(config) ? captureQualifiedAnalysisInputs(config).digest : null;
const resolved = resolveSource(root, source, false);
const graph = store.loadGraph();
const claims = store.loadClaims();
const analyzedRepositoryFactsHash = fingerprintRepositoryFacts({ graph, claims, evidence: store.loadEvidence() });
const analyzedIndexSnapshotHash = digestCanonical(store.getMeta(CONTROL_INDEX_SNAPSHOT_META_KEY) ?? null);
- const baseResult = analyzeDiff({
- index: new GraphIndex(graph),
+ const headIndex = new GraphIndex(graph);
+ const headResult = analyzeDiff({
+ index: headIndex,
claims,
config,
diffText: resolved.diffText ?? "",
- // Working-tree and staged indexes describe the old HEAD snapshot. A commit range is
- // analysed against its indexed head, so its graph ranges use the diff's new coordinates.
- nodeRangeSide: source.kind === "range" ? "new" : "old",
+ // Qualified staged/range admission below requires a clean index; dirty snapshots support
+ // working-tree sources only. Legacy working-tree and staged
+ // analysis retains old HEAD coordinates; ranges always use the indexed new side.
+ nodeRangeSide: source.kind === "range" || isQualified(config) ? "new" : "old",
});
+ const baseResult = source.kind === "range" && resolved.git.mergeBase !== null
+ ? applyLostGuards(root, config, headResult, headIndex, resolved.git.mergeBase, resolved.diffText ?? "")
+ : headResult;
const afterAnalysis = verifyAnalysisBarrierForTesting;
verifyAnalysisBarrierForTesting = undefined;
afterAnalysis?.();
@@ -601,7 +992,7 @@ export function runVerify(root: string, source: VerifySource): VerifyComputation
store,
applyIndexBindingGate(root, store, baseResult, resolved.identity, (hash) => analyzedSemanticInputHashes.push(hash)),
);
- const result = config.version === 2
+ let result = config.version === 2
? applyAnalysisHealthPreflight(
gated,
changedScopePaths,
@@ -610,12 +1001,92 @@ export function runVerify(root: string, source: VerifySource): VerifyComputation
(path) => isPathSelected(config, path),
)
: gated;
+ const inputAfter = isQualified(config) ? captureQualifiedAnalysisInputs(loadConfig(root)) : null;
+ const qualifiedPostImageReasons: string[] = isQualified(config) ? qualifiedCheckoutReasons(root) : [];
+ if (isQualified(config) && source.kind === "range" && diffRemovesBaseLines(resolved.diffText ?? "")) {
+ qualifiedPostImageReasons.push("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ }
+ if (isQualified(config) && source.kind === "working-tree") {
+ // Git diff omits these inputs. Do not claim whole-change qualification from
+ // expected paths alone without an actual diff/effect model for their addition.
+ const tracked = git(root, ["ls-files", "--cached", "-z"]);
+ if (tracked.code !== 0) qualifiedPostImageReasons.push("QUALIFIED_WORKING_TREE_UNTRACKED_INPUT_UNBOUND");
+ else {
+ const trackedPaths = new Set(tracked.out.split("\0"));
+ for (const path of inputAfter!.files.map(file => file.path).filter(path => !trackedPaths.has(path)).sort()) {
+ qualifiedPostImageReasons.push(`QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:${path}`);
+ }
+ }
+ try {
+ for (const path of retainedWorkingTreeFilterPaths(root, inputAfter!.files.map(file => file.path))) {
+ qualifiedPostImageReasons.push(`QUALIFIED_WORKING_TREE_FILTER_UNSUPPORTED:${path}`);
+ }
+ } catch {
+ qualifiedPostImageReasons.push("QUALIFIED_WORKING_TREE_FILTER_UNBOUND");
+ }
+ }
+ if (isQualified(config) && (source.kind === "staged" || source.kind === "range")) {
+ let indexed: ReturnType = null;
+ try {
+ indexed = parseIndexedControlSnapshot(store.getMeta(CONTROL_INDEX_SNAPSHOT_META_KEY));
+ } catch {
+ // The binding probe separately reports malformed metadata; never infer a clean tree.
+ }
+ if (indexed?.workingDiffHash === null || indexed === null) qualifiedPostImageReasons.push("QUALIFIED_POST_IMAGE_UNBOUND");
+ else if (indexed.workingDiffHash !== CLEAN_CONTROL_WORKING_DIFF_HASH) qualifiedPostImageReasons.push("QUALIFIED_POST_IMAGE_DIRTY_INDEX");
+ else if (!qualifiedPostImageReasons.includes("QUALIFIED_BASE_RECOVERY_UNSUPPORTED")) {
+ qualifiedPostImageReasons.push(...qualifiedPostImageInputs(root, inputAfter!, source, resolved.identity));
+ }
+ }
+ const qualifiedBinding = isQualified(config) ? observeIndexBinding(root, store, resolved.identity, (hash) => analyzedSemanticInputHashes.push(hash)) : undefined;
+ const qualifiedHealth = isQualified(config) ? indexHealth(root) : undefined;
+ const analysisAdmission = isQualified(config) ? qualifiedAdmission({
+ config, graph, changedPaths: changedScopePaths, health: qualifiedHealth!,
+ sourceHash: digestCanonical({ inputs: inputBefore, sourceIdentity: resolved.identity }), diff: resolved.diffText ?? "", indexSnapshot: analyzedIndexSnapshotHash,
+ bindingReasons: [...qualifiedBinding!.breaks, ...qualifiedPostImageReasons],
+ checkChanged: inputBefore !== inputAfter!.digest,
+ buildState: store.getMeta(QUALIFIED_BUILD_META),
+ sourceCommits: resolved.identity.kind === "absent" ? [] : [...resolved.identity.commits], baseCommit: resolved.git.mergeBase,
+ expectedInputHash: inputBefore!,
+ consumedInputs: inputAfter!,
+ opaqueGraphBinding: bindOpaqueCallbackGraph({ graph, actualFactsHash: analyzedRepositoryFactsHash, freshness: qualifiedBinding!.freshness,
+ consumedHash: inputAfter!.digest, expectedHash: inputBefore!, bindingValid: qualifiedHealth!.binding.status === "valid",
+ buildComplete: store.getMeta(QUALIFIED_BUILD_META) === "complete", checkChanged: inputBefore !== inputAfter!.digest }),
+ }) : undefined;
+ if (analysisAdmission?.status === "admitted" && source.kind === "working-tree" && diffRemovesBaseLines(resolved.diffText ?? "")) {
+ const oldCaptureReasons: string[] = [];
+ result = applyQualifiedWorkingTreeGuards(root, config, result, headIndex, resolved, oldCaptureReasons, analysisAdmission.changeCoverage.expected);
+ if (oldCaptureReasons.length > 0) {
+ analysisAdmission.status = "rejected";
+ analysisAdmission.reasons.push(...oldCaptureReasons);
+ analysisAdmission.binding.status = "invalid";
+ analysisAdmission.binding.reasons.push(...oldCaptureReasons);
+ }
+ }
const coChanges = resolved.includeCoChanges && resolved.coChangeHead !== null
? historicalCoChanges(root, result.changedFiles, resolved.coChangeHead)
: [];
+ if (analysisAdmission !== undefined) {
+ const finalSource = resolveSource(root, source, false);
+ if (inputBefore !== captureQualifiedAnalysisInputs(loadConfig(root)).digest
+ || finalSource.diffText !== resolved.diffText
+ || finalSource.git.mergeBase !== resolved.git.mergeBase
+ || digestCanonical(finalSource.identity) !== digestCanonical(resolved.identity)
+ || digestCanonical(store.getMeta(CONTROL_INDEX_SNAPSHOT_META_KEY) ?? null) !== analyzedIndexSnapshotHash
+ || store.getMeta(QUALIFIED_BUILD_META) !== "complete") {
+ analysisAdmission.status = "rejected";
+ analysisAdmission.checkFreshness = { status: "changed", reasons: ["CHECK_INPUT_CHANGED"] };
+ analysisAdmission.reasons = [...new Set([...analysisAdmission.reasons, "CHECK_INPUT_CHANGED"])];
+ }
+ }
+ if (analysisAdmission?.status === "rejected") {
+ result = { ...result, verdict: "BLOCK", findings: [...result.findings, {
+ rule: "analysis_scope_incomplete", severity: "block", message: `Qualified analysis rejected: ${analysisAdmission.reasons.join(", ")}`, nodeIds: [],
+ }] };
+ }
return {
result,
- report: buildVerifyReport(result, resolved.git, config.blockingRules, coChanges),
+ report: { ...buildVerifyReport(result, resolved.git, config.blockingRules, coChanges), ...(analysisAdmission === undefined ? {} : { analysisAdmission }) },
git: resolved.git,
coChanges,
analyzedSourceHash: resolved.analyzedSourceHash ?? null,
diff --git a/packages/app-services/test/change-impact-javascript-coverage.test.ts b/packages/app-services/test/change-impact-javascript-coverage.test.ts
new file mode 100644
index 00000000..8e72cedf
--- /dev/null
+++ b/packages/app-services/test/change-impact-javascript-coverage.test.ts
@@ -0,0 +1,135 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runChangeImpact } from "../src";
+import { inspectJavaScriptSource } from "@semantic-context/ts-analyzer";
+for (const mode of ["directory", "sibling", "substitution"]) test(`unknown namespace key remains an explicit unsupported source: ${mode}`, () => {
+ const original = `import * as ns from './widget${mode === "substitution" ? ".js" : ""}'; ${mode === "substitution" ? "import './widget.ts';" : ""} export function read(key) { return ns[key]; }\n`;
+ expect(inspectJavaScriptSource("/fixture/consumer.mjs", original).reasons).toContain("JAVASCRIPT_PROPERTY_KEY_UNSUPPORTED");
+});
+for (const mode of ["directory", "sibling", "substitution"]) test(`a namespace consumer observes newly exported values with resolution: ${mode}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-impact-jsx-"));
+ const git = (...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src/widget"), { recursive: true });
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/widget/index.jsx"), "export const existing = 1;\n");
+ if (mode !== "directory") {
+ writeFileSync(join(root, "src/widget/index.ts"), "export const decoy = 1;\n");
+ writeFileSync(join(root, "src/widget.js"), "export const existing = 1;\n");
+ }
+ if (mode === "substitution") writeFileSync(join(root, "src/widget.ts"), "export const existing = 1;\n");
+ writeFileSync(join(root, "src/consumer.mjs"), `import * as ns from './widget${mode === "substitution" ? ".js" : ""}'; ${mode === "substitution" ? "import './widget.ts';" : ""} export function read() { const key = 'added'; return ns[key]; }\n`);
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: ["src/**/*"], languages: { javascript: "on", typescript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, mode === "substitution" ? "src/widget.ts" : mode === "sibling" ? "src/widget.js" : "src/widget/index.jsx"), "export const existing = 1;\nexport const added = 2;\n");
+ const report = runChangeImpact(root, { kind: "working-tree" });
+ expect(report.analysis.binding.status).toBe("bound");
+ expect(report.changes.units?.some(unit => unit.behavioral)).toBe(true);
+ expect(report.possiblyAffected?.some(target => target.file === "src/consumer.mjs")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+for (const language of ["typescript", "javascript"] as const) for (const selected of [true, false]) test(`round3 ledger-only ${language} escaped importer contributes an impact gap only when selected: ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-impact-ledger-"));
+ const git = (...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const consumerPath = `src/consumer.${language === "javascript" ? "js" : "ts"}`;
+ writeFileSync(join(root, consumerPath), "import { main } from './main'; import { outside } from '../../outside'; export function consumer() { return main() + outside; }\n");
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: selected ? ["src/**/*.ts", "src/**/*.js"] : ["src/main.ts"], languages: { typescript: "on", javascript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runChangeImpact(root, { kind: "working-tree" });
+ expect(report.analysis.binding.status).toBe("bound");
+ if (selected) {
+ expect(report.unresolved).toContainEqual(expect.objectContaining({ code: `${language.toUpperCase()}_ANALYSIS_INCOMPLETE`, file: consumerPath, affects: "reach" }));
+ expect(report.analysis.confidence.level).toBe("low");
+ } else expect(report.unresolved.some(gap => gap.code === "TYPESCRIPT_ANALYSIS_INCOMPLETE")).toBe(false);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("failed v2 TypeScript facts cannot produce analyzed coverage or change units", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-impact-ts-failed-"));
+ const git = (...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/value.ts"), "export function broken( {\n");
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: ["src/**/*.ts"], languages: { typescript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/value.ts"), "export function broken( { // changed\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runChangeImpact(root, { kind: "working-tree" });
+ expect(report.analysis.binding.status).toBe("bound");
+ expect(report.changes.files[0]?.coverage).toMatchObject({ status: "not_analyzed", reason: "ANALYSIS_FAILED" });
+ expect(report.changes.units).toEqual([]);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+for (const [name, before, after, analyzed] of [
+ ["esm", "export function value() { return 1; }", "export function value() { return 2; }", true],
+ ["commonjs", "export function value() { return 1; } module.exports = value;", "export function value() { return 2; } module.exports = value;", false],
+ ["parse-failed", "export function broken( {", "export function broken( { // changed", false],
+] as const) test(`per-file impact coverage reflects actual indexed JavaScript ${name} outcome`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-impact-js-coverage-"));
+ const git = (...args: string[]): void => {
+ const process = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (process.exitCode !== 0) throw new Error(new TextDecoder().decode(process.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/value.mjs"), before);
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: ["src/**/*.mjs"], languages: { javascript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/value.mjs"), after);
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runChangeImpact(root, { kind: "working-tree" });
+ expect(report.analysis.binding.status).toBe("bound");
+ expect(report.changes.files.find((file) => file.path === "src/value.mjs")?.coverage?.status).toBe(analyzed ? "analyzed" : "not_analyzed");
+ expect(report.analysis.fileCoverage?.analyzed).toBe(analyzed ? 1 : 0);
+ if (!analyzed) expect(report.changes.units).toEqual([]);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+for (const changed of ["src/base.ts", "src/bridge.mjs"]) test(`primitive-safe mixed-language impact follows actual call edges from ${changed}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-mixed-impact-"));
+ const git = (...args: string[]): void => {
+ const process = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (process.exitCode !== 0) throw new Error(new TextDecoder().decode(process.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const files = {
+ "src/base.ts": "export function base() { return 1; }\n",
+ "src/bridge.mjs": "import { base } from './base.ts'; export function bridge() { return base(1); }\n",
+ "src/entry.ts": "import { bridge } from './bridge.mjs'; export function entry() { return bridge(); }\n",
+ };
+ for (const [path, content] of Object.entries(files)) writeFileSync(join(root, path), content);
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: ["src/**/*.ts", "src/**/*.mjs"], languages: { javascript: "on", typescript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, changed), files[changed as keyof typeof files].replace("1", "2"));
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runChangeImpact(root, { kind: "working-tree" });
+ expect(report.analysis.binding.status).toBe("bound");
+ expect(report.changes.files.find((file) => file.path === changed)?.coverage?.status).toBe("analyzed");
+ expect([...(report.directlyAffected ?? []), ...(report.transitivelyAffected ?? [])].some((target) => target.name === "entry")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/javascript-runtime.test.ts b/packages/app-services/test/javascript-runtime.test.ts
new file mode 100644
index 00000000..0ab40505
--- /dev/null
+++ b/packages/app-services/test/javascript-runtime.test.ts
@@ -0,0 +1,92 @@
+import { afterEach, expect, it } from "bun:test";
+import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { discoverRepository } from "@semantic-context/ts-analyzer";
+import { analyzePlaneARuntime } from "../src/plane-a-runtime";
+import { resolvePlaneACapabilityRequirement } from "../src/plane-a-capability-requirements";
+import { GraphIndex } from "@semantic-context/context-engine";
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+function analyze(path: string, content: string) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-runtime-")); roots.push(root);
+ writeFileSync(join(root, path), content);
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*"], exclude: [], languages: { javascript: "on" as const } };
+ return analyzePlaneARuntime(config, discoverRepository(config));
+}
+it("binds genuine ESM facts to an explicit JS producer and compiler dialect", () => {
+ const result = analyze("script.mjs", "export function run() { return 1; }");
+ expect(result.discoveryLedger[0]).toMatchObject({ analysisOutcome: "analyzed", analysisReasons: [], scope: { language: "javascript", dialectVersion: "5.9.3" }, selectedProducer: { identity: "@semantic-context/ts-analyzer/javascript", version: "0.1.0" } });
+ expect(result.sidecar.capabilityProfiles.find(p => p.factKind === "function")).toMatchObject({ completenessClaim: "producer-declared", resolutionSemantics: "javascript-static-esm-v1", negativeEvidenceEligible: false });
+});
+it("records parsing failure instead of completing a recovered AST", () => {
+ const result = analyze("script.mjs", "export function broken( {");
+ expect(result.discoveryLedger[0]!.analysisOutcome).toBe("failed");
+ expect(result.discoveryLedger[0]!.analysisReasons.some(r => r.startsWith("JAVASCRIPT_PARSE_ERROR:"))).toBe(true);
+ expect(result.sidecar.factBatches.some(b => b.scope.language === "javascript")).toBe(false);
+});
+it("records TypeScript parsing failure in the opt-in runtime", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ts-runtime-")); roots.push(root);
+ writeFileSync(join(root, "bad.ts"), "export function broken( {");
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*.ts"], exclude: [] };
+ const result = analyzePlaneARuntime(config, discoverRepository(config));
+ expect(result.discoveryLedger[0]!.analysisOutcome).toBe("failed");
+ expect(result.discoveryLedger[0]!.analysisReasons.some(r => r.startsWith("SOURCE_PARSE_ERROR:"))).toBe(true);
+});
+it("refuses TypeScript-only annotations disguised by an mjs extension", () => {
+ const result = analyze("script.mjs", "export function run(value: number) { return value; }");
+ expect(result.discoveryLedger[0]!.analysisOutcome).toBe("failed");
+ expect(result.discoveryLedger[0]!.analysisReasons.some(r => r.startsWith("JAVASCRIPT_PARSE_ERROR:8010"))).toBe(true);
+});
+it("keeps inherited analyzer configuration outside the repository diagnostic-only", () => {
+ const parent = mkdtempSync(join(tmpdir(), "semctx-config-boundary-")); roots.push(parent);
+ const root = join(parent, "repository"); mkdirSync(root);
+ writeFileSync(join(parent, "base.json"), JSON.stringify({ compilerOptions: { baseUrl: "." } }));
+ writeFileSync(join(root, "tsconfig.json"), JSON.stringify({ extends: "../base.json" }));
+ writeFileSync(join(root, "script.mjs"), "export function run() { return 1; }");
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*.mjs"], exclude: [], languages: { javascript: "on" as const } };
+ const result = analyzePlaneARuntime(config, discoverRepository(config));
+ expect(result.discoveryLedger.find(e => e.scope.language === "javascript")!.analysisReasons).toContain("SOURCE_CONFIGURATION_OUTSIDE_REPOSITORY");
+ expect(result.sidecar.capabilityProfiles.find(p => p.scope.language === "javascript")!.completenessClaim).toBe("partial");
+});
+it("resolves aliases, inherited tsconfig, declarations and TS/JS transitive calls under a nested monorepo", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-mixed-")); roots.push(root);
+ mkdirSync(join(root, "suite", "packages", "lib", "src"), { recursive: true });
+ const files = {
+ "tsconfig.json": JSON.stringify({ compilerOptions: { baseUrl: ".", paths: { "@fixture/lib/*": ["suite/packages/lib/src/*"] } } }),
+ "suite/tsconfig.json": JSON.stringify({ extends: "../tsconfig.json" }),
+ "suite/packages/lib/src/base.ts": "export function base() { return 1; }",
+ "suite/packages/lib/src/leaf.mjs": "import { base } from './base.ts'; function leaf() { return base(); } export { leaf }; export const amount = 1;",
+ "suite/packages/lib/src/leaf.d.mts": "export declare function leaf(): number;",
+ "suite/packages/lib/src/main.ts": "import { leaf } from '@fixture/lib/leaf.mjs'; export function main() { return leaf(); }",
+ "suite/packages/lib/src/entry.mjs": "import { main } from './main.ts'; export function entry() { return main(); } export async function load() { return import('./leaf.mjs'); }",
+ };
+ for (const [path, content] of Object.entries(files)) writeFileSync(join(root, path), content);
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*.{ts,mjs,mts}"], exclude: [], languages: { typescript: "on" as const, javascript: "on" as const } };
+ const result = analyzePlaneARuntime(config, discoverRepository(config));
+ const graph = result.analysis.graph;
+ const index = new GraphIndex(graph);
+ const base = graph.nodes.find(n => n.kind === "function" && n.name === "base")!;
+ const entry = graph.nodes.find(n => n.kind === "function" && n.name === "entry")!;
+ expect(index.distancesFrom([base.id], ["calls"], "in").get(entry.id)).toBe(3);
+ expect(graph.nodes.find(n => n.name === "leaf" && n.kind === "function")!.exported).toBe(true);
+ const leafModule = graph.nodes.find(n => n.kind === "module" && n.filePath?.endsWith("leaf.mjs"))!;
+ expect(JSON.parse(String(leafModule.metadata.staticExports))).toContainEqual({ name: "amount", declarationKind: "variable" });
+ expect(result.discoveryLedger.filter(e => e.scope.language === "javascript").every(e => e.analysisReasons.length === 0)).toBe(true);
+ expect(graph.edges.some(e => e.kind === "imports" && e.from.includes("entry.mjs") && e.to === leafModule.id)).toBe(true);
+});
+for (const [path, source, reason] of [
+ ["script.cjs", "module.exports = require('./missing.js');", "JAVASCRIPT_COMMONJS_UNSUPPORTED"],
+ ["script.mjs", "export function run(path) { return import(path); }", "JAVASCRIPT_DYNAMIC_IMPORT_UNSUPPORTED"],
+ ["script.js", "export { missing } from './missing.mjs';", "JAVASCRIPT_IMPORT_UNRESOLVED:./missing.mjs"],
+ ["script.js", "import { missing } from '@fixture/missing';", "JAVASCRIPT_IMPORT_UNRESOLVED:@fixture/missing"],
+]) {
+ it(`keeps ${reason} explicit and excludes partial capability admission`, () => {
+ const result = analyze(path!, source!);
+ expect(result.discoveryLedger[0]!.analysisReasons).toContain(reason!);
+ const profile = result.sidecar.capabilityProfiles.find(p => p.scope.language === "javascript")!;
+ expect(profile.completenessClaim).toBe("partial");
+ expect(resolvePlaneACapabilityRequirement({ configVersion: 2, producerConfigurationDigest: result.sidecar.producerConfigurationDigest, task: "verify", operation: "change", language: "javascript", factKind: profile.factKind, completenessClaim: profile.completenessClaim })).toBeNull();
+ });
+}
diff --git a/packages/app-services/test/lost-guard-report-locations.test.ts b/packages/app-services/test/lost-guard-report-locations.test.ts
new file mode 100644
index 00000000..c281c283
--- /dev/null
+++ b/packages/app-services/test/lost-guard-report-locations.test.ts
@@ -0,0 +1,42 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createDefaultConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const ADAPTER = join(import.meta.dir, "../../github-action/src/adapter.mjs");
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+
+test("deleted invariant keeps a base file anchor without inventing a head line or symbol", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-lost-guard-location-"));
+ roots.push(root); mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/money.ts"), "/** @invariant nonnegative: stays nonnegative */\nexport function clamp() { return 1; }\n");
+ writeFileSync(join(root, "src/helper.ts"), "export function helper() { return 1; }\n");
+ git(root, "init", "-q", "-b", "main"); git(root, "add", "."); git(root, "commit", "-q", "-m", "guarded base");
+ initWorkspace(root, createDefaultConfig(root));
+ git(root, "checkout", "-q", "-b", "remove"); git(root, "rm", "-q", "src/money.ts"); git(root, "commit", "-q", "-m", "removed guard");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const { result, report } = runVerify(root, { kind: "range", base: "main" });
+ expect(result.verdict).toBe("BLOCK");
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ const finding = report.findings.find((entry) => entry.rule === "invariant_touched_without_test")!;
+ expect(finding).toBeDefined();
+ expect(finding.locations).toEqual([{ file: "src/money.ts" }]);
+ expect(finding.message).toContain("base");
+ expect(report.changedSymbols).not.toEqual(expect.arrayContaining([expect.objectContaining({ file: "src/money.ts" })]));
+ const reportPath = join(root, "report.json"); writeFileSync(reportPath, JSON.stringify(report));
+ const adapter = Bun.spawnSync([process.execPath, ADAPTER, reportPath], { stdout: "pipe", stderr: "pipe", env: { ...process.env, INPUT_FAIL_ON: "block", INPUT_REQUIRE_QUALIFIED: "false" } });
+ expect(adapter.exitCode).toBe(1);
+ const output = new TextDecoder().decode(adapter.stdout);
+ expect(output).toContain("file=src/money.ts");
+ expect(output).not.toContain(",line=");
+}, 60_000);
diff --git a/packages/app-services/test/plane-a-authority-policy.test.ts b/packages/app-services/test/plane-a-authority-policy.test.ts
index a5fae8ea..1780f332 100644
--- a/packages/app-services/test/plane-a-authority-policy.test.ts
+++ b/packages/app-services/test/plane-a-authority-policy.test.ts
@@ -1,4 +1,10 @@
import { describe, expect, it } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
import {
REGISTERED_PLANE_A_AUTHORITY_COUNT,
isPlaneAOperationAdmitted,
@@ -28,7 +34,7 @@ function admitted(
describe("independent Plane A task-relative authority policy", () => {
it("admits only the explicit current policy surface", () => {
- expect(REGISTERED_PLANE_A_AUTHORITY_COUNT).toBe(85);
+ expect(REGISTERED_PLANE_A_AUTHORITY_COUNT).toBe(107);
expect(admitted()).toBe(true);
expect(admitted({
scope: {
@@ -40,6 +46,44 @@ describe("independent Plane A task-relative authority policy", () => {
})).toBe(true);
});
+ it("registers only the qualified JavaScript verify/change fact tuples", () => {
+ const javascript = { ...scope, language: "javascript", selectedPaths: ["scripts/value.mjs"] };
+ expect(admitted({ scope: javascript })).toBe(true);
+ for (const overrides of [
+ { configVersion: 1 as const }, { task: "execute" }, { operation: "approve" },
+ { factKind: "runtime_execution" }, { scope: { ...javascript, dialectVersion: "5.8.0" } },
+ ]) expect(admitted({ scope: javascript, ...overrides })).toBe(false);
+ });
+
+ it("admits genuinely indexed mixed ESM facts only after refreshing the changed sources", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-operation-admission-"));
+ const git = (...args: string[]): void => {
+ const process = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (process.exitCode !== 0) throw new Error(new TextDecoder().decode(process.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/value.mjs"), "export function value() { return 1; }\n");
+ writeFileSync(join(root, "src/consumer.ts"), "import { value } from './value.mjs'; export function consumer() { return value(); }\n");
+ git("init", "-q"); git("add", ".");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*.ts", "src/**/*.mjs"], languages: { typescript: "on", javascript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/value.mjs"), "export function value() { return 2; }\n");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("rejected");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toEqual(["src/consumer.ts", "src/value.mjs"]);
+ const health = indexHealth(root);
+ const javascriptCandidate = health.candidates.find((candidate) => candidate.path === "src/value.mjs")!;
+ const evaluations = health.evaluations.decisions.filter((decision) => decision.candidateIdentity === javascriptCandidate.candidateIdentity);
+ expect(evaluations.length).toBeGreaterThan(0);
+ expect(evaluations.every((decision) => decision.gates.taskRelativeAuthority === "passed")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+ }, 60_000);
+
it("denies unknown task, operation, language, dialect, fact kind, and config tuples", () => {
expect(admitted({ task: "index-health", operation: "inspect" })).toBe(false);
expect(admitted({ operation: "delete" })).toBe(false);
diff --git a/packages/app-services/test/plane-a-capability-requirements.test.ts b/packages/app-services/test/plane-a-capability-requirements.test.ts
index 21cfa195..f5edefe4 100644
--- a/packages/app-services/test/plane-a-capability-requirements.test.ts
+++ b/packages/app-services/test/plane-a-capability-requirements.test.ts
@@ -25,8 +25,8 @@ function requirement(input: {
}
describe("consumer-owned Plane A capability requirements", () => {
- it("registers the exact closed TS, Python, Markdown, and SQL policy surface", () => {
- expect(REGISTERED_PLANE_A_CAPABILITY_COUNT).toBe(100);
+ it("registers the exact closed TS, JavaScript ESM, Python, Markdown, and SQL policy surface", () => {
+ expect(REGISTERED_PLANE_A_CAPABILITY_COUNT).toBe(122);
expect(TYPESCRIPT_DIALECT_VERSION).toBe("5.9.3");
expect(requirement({
configVersion: 2,
diff --git a/packages/app-services/test/qualified-accessors.test.ts b/packages/app-services/test/qualified-accessors.test.ts
new file mode 100644
index 00000000..5d4094ac
--- /dev/null
+++ b/packages/app-services/test/qualified-accessors.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "export const settings = { get value() { return 1; } }; export function consume() { return settings.value; }", "ACCESSOR_UNSUPPORTED"],
+ ["js", "export const settings = { get value() { return 1; } }; export function consume() { return settings.value; }", "ACCESSOR_UNSUPPORTED"],
+ ["ts", "let value = 1; export const settings = { set value(next: number) { value = next; } }; export function consume() { settings.value = 2; return value; }", "ACCESSOR_UNSUPPORTED"],
+ ["js", "let value = 1; export const settings = { set value(next) { value = next; } }; export function consume() { settings.value = 2; return value; }", "ACCESSOR_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-analysis.test.ts b/packages/app-services/test/qualified-analysis.test.ts
new file mode 100644
index 00000000..692c586e
--- /dev/null
+++ b/packages/app-services/test/qualified-analysis.test.ts
@@ -0,0 +1,409 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, createDefaultConfig, SemctxConfigSchema, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+import { __setIndexRepositoryCaptureBarrierForTesting } from "../src/indexing";
+import { __setVerifyAnalysisBarrierForTesting } from "../src/verify";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+
+const roots: string[] = [];
+test("qualified staged attributes and CRLF remain explicitly refused before post-image conversion", () => {
+ const root = selectedRepository();
+ git(root, "config", "core.autocrlf", "false");
+ writeFileSync(join(root, ".gitattributes"), "src/*.ts text eol=lf\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\r\n");
+ git(root, "add", ".gitattributes", "src/main.ts");
+ indexRepository(root, "2026-10-10T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "staged" }).report.analysisAdmission;
+ console.info(JSON.stringify({ stagedAttributes: admission?.status, reasons: admission?.reasons }));
+ expect(admission?.reasons.some(reason => reason.includes("QUALIFIED_POST_IMAGE_INPUT_MISMATCH"))).toBe(false);
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("QUALIFIED_POST_IMAGE_DIRTY_INDEX");
+ expect(admission?.reasons).toContain("UNSUPPORTED_CHANGED_OBLIGATION:.gitattributes");
+});
+test("qualified CRLF body replacement remains refused without captured base inputs", () => {
+ const root = selectedRepository();
+ git(root, "config", "core.autocrlf", "false");
+ writeFileSync(join(root, ".gitattributes"), "src/*.ts text eol=crlf\n");
+ git(root, "add", ".gitattributes");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "CRLF configuration");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\r\n");
+ writeFileSync(join(root, "src/consumer.ts"), "import { main } from './main'; export function consume() { return main(); }\r\n");
+ git(root, "add", ".");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "CRLF candidate");
+ indexRepository(root, "2026-10-10T10:01:00.000Z");
+ const result = runVerify(root, { kind: "range", base: "HEAD^" });
+ expect(result.report.analysisAdmission?.status).toBe("rejected");
+ expect(result.report.analysisAdmission?.reasons).toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(result.result.verdict).toBe("BLOCK");
+});
+test("qualified clean range compares CRLF retained inputs after Git conversion", () => {
+ const root = selectedRepository();
+ git(root, "config", "core.autocrlf", "false");
+ writeFileSync(join(root, ".gitattributes"), "src/*.ts text eol=crlf\n");
+ git(root, "add", ".gitattributes");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "CRLF configuration");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\r\nexport function added() { return 2; }\r\n");
+ writeFileSync(join(root, "src/consumer.ts"), "import { main } from './main'; export function consume() { return main(); }\r\n");
+ git(root, "add", ".");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "CRLF candidate");
+ indexRepository(root, "2026-10-10T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "range", base: "HEAD^" }).report.analysisAdmission;
+ console.info(JSON.stringify({ observed: admission?.status, reasons: admission?.binding.reasons }));
+ expect(admission?.status).toBe("admitted");
+});
+for (const extension of ["d.ts", "d.mts", "d.cts"]) test(`round4 v2 declaration ${extension} is unsupported and never supplies complete facts`, () => {
+ const root = selectedRepository();
+ const path = `src/api.${extension}`;
+ writeFileSync(join(root, ".semctx/config.json"), JSON.stringify({ ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } }));
+ writeFileSync(join(root, path), "export interface Api { value: number }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ expect(indexHealth(root).candidates.find(candidate => candidate.path === path)).toMatchObject({ analysisOutcome: "unsupported", analysisReasons: ["SOURCE_DECLARATION_FILE_UNSUPPORTED"] });
+});
+test("round4 ignored retained sources cannot bind a clean committed post-image", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nsrc/hidden.ts\n");
+ git(root, "add", ".gitignore");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "ignore configuration");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\nexport function added() { return 2; }\n");
+ git(root, "add", "src/main.ts");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "tracked candidate");
+ writeFileSync(join(root, "src/hidden.ts"), "import { main } from './main'; export function hidden() { return main(); }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const range = runVerify(root, { kind: "range", base: "HEAD^" }).report.analysisAdmission;
+ expect(range?.status).toBe("rejected");
+ expect(range?.reasons).not.toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(range?.reasons).toContain("QUALIFIED_POST_IMAGE_INPUT_ABSENT:src/hidden.ts");
+ const staged = runVerify(root, { kind: "staged" }).report.analysisAdmission;
+ expect(staged?.binding.reasons).toContain("QUALIFIED_POST_IMAGE_INPUT_ABSENT:src/hidden.ts");
+});
+test("round4 ignored empty workspace membership cannot bind a committed post-image", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "package.json"), '{"name":"root","workspaces":["packages/*"]}');
+ writeFileSync(join(root, ".gitignore"), ".semctx/\npackages/\n");
+ git(root, "add", "package.json", ".gitignore");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "workspace configuration");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\nexport function added() { return 2; }\n");
+ git(root, "add", "src/main.ts");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "tracked candidate");
+ mkdirSync(join(root, "packages/ghost"), { recursive: true });
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "range", base: "HEAD^" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).not.toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(admission?.binding.reasons).toContain("QUALIFIED_POST_IMAGE_WORKSPACE_ABSENT:packages/ghost");
+});
+for (const path of ["src/module.cts", "src/module.d.cts"]) test(`round4 CommonJS TypeScript extension stays outside ESM qualification: ${path}`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, path), "export interface Marker { value: number }\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(reason => reason.includes("SOURCE_COMMONJS_EXTENSION_UNSUPPORTED"))).toBe(true);
+});
+for (const kind of ["staged", "range"] as const) test(`round3 dirty qualified index rejects ${kind} post-image and admits its actual working tree`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ git(root, "add", "src/main.ts");
+ if (kind === "range") git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "second");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 3; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, kind === "staged" ? { kind } : { kind, base: "HEAD^" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("QUALIFIED_POST_IMAGE_DIRTY_INDEX");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+});
+test("qualified indexed HEAD body replacements remain refused without captured base inputs", () => {
+ const root = selectedRepository();
+ for (const value of [2, 3]) {
+ writeFileSync(join(root, "src/main.ts"), `export function main() { return ${value}; }\n`);
+ git(root, "add", "src/main.ts");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", `version ${value}`);
+ }
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const result = runVerify(root, { kind: "range", base: "HEAD^" });
+ expect(result.report.analysisAdmission?.status).toBe("rejected");
+ expect(result.report.analysisAdmission?.reasons).toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(result.result.verdict).toBe("BLOCK");
+});
+test("round3 clean qualified range admits its indexed HEAD and rejects an older destination", () => {
+ const root = selectedRepository();
+ for (const value of [2, 3]) {
+ writeFileSync(join(root, `src/added${value}.ts`), `export function added${value}() { return ${value}; }\n`);
+ git(root, "add", `src/added${value}.ts`);
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", `version ${value}`);
+ }
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ expect(runVerify(root, { kind: "range", base: "HEAD^" }).report.analysisAdmission?.status).toBe("admitted");
+ const older = runVerify(root, { kind: "range", base: "HEAD^^", head: "HEAD^" }).report.analysisAdmission;
+ expect(older?.status).toBe("rejected");
+ expect(older?.binding.reasons).toContain("ANALYZED_COMMIT_MISMATCH");
+});
+for (const operation of ["add", "remove"] as const) test(`round3 empty workspace directory ${operation} invalidates qualified admission`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "package.json"), '{"name":"root","workspaces":["packages/*"]}');
+ mkdirSync(join(root, "packages"));
+ if (operation === "remove") mkdirSync(join(root, "packages/ghost"));
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ if (operation === "add") mkdirSync(join(root, "packages/ghost"));
+ else rmSync(join(root, "packages/ghost"), { recursive: true });
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.indexFreshness.reasons).toContain("ANALYSIS_INPUT_MISMATCH");
+});
+test("round3 unrelated empty output directories do not change qualified inputs", () => {
+ const root = selectedRepository();
+ const config = createGlobSelectionConfig(root);
+ const before = captureQualifiedAnalysisInputs(config).digest;
+ mkdirSync(join(root, "build/random"), { recursive: true });
+ expect(captureQualifiedAnalysisInputs(config).digest).toBe(before);
+});
+test("round3 local require calls remain admitted ordinary TypeScript", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "const require = (value: number) => value; export function main() { return require(2); }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+});
+for (const ignored of [false, true]) test(`round3 ${ignored ? "ignored output" : "untracked"} pyproject drift invalidates qualified admission`, () => {
+ const root = selectedRepository();
+ const directory = ignored ? "build" : "python";
+ mkdirSync(join(root, directory));
+ if (ignored) writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/\n");
+ writeFileSync(join(root, directory, "pyproject.toml"), '[project]\nname = "before"\n');
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ writeFileSync(join(root, directory, "pyproject.toml"), '[project]\nname = "after"\n');
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.indexFreshness.reasons).toContain("ANALYSIS_INPUT_MISMATCH");
+});
+for (const [path, content] of [
+ ["src/globals.ts", "const FLAG = 2;"], ["src/globals.d.ts", "declare const FLAG: number;"],
+ ["src/augment.ts", "export {}; declare global { const FLAG: number; }"],
+]) test(`round3 global source mode refuses unmodeled cross-file reads: ${path}`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, path!), content!);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return FLAG; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(reason => /SOURCE_(?:GLOBAL_SCRIPT|GLOBAL_AUGMENTATION)_UNSUPPORTED/.test(reason))).toBe(true);
+});
+for (const mutation of ["exports.legacy = main;", "Object.assign(exports, { main });"]) test(`round2 ambient TypeScript exports rejects: ${mutation}`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), `export function main() { return 2; } ${mutation}\n`);
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("DEPENDENCY_SCOPE_COMMONJS_UNSUPPORTED:src/main.ts");
+});
+test("round2 local exports and evaluator shadows remain admitted ordinary functions", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "const exports = { legacy: 0 }; function eval(value: number) { return value; } function Function(value: number) { return value; } export function main() { exports.legacy = 2; return eval(Function(exports.legacy)); }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+});
+for (const content of [
+ "globalThis.eval(\"import('./src/main.ts')\");",
+ "window.eval(\"import('./src/main.ts')\");",
+ "const load = self.Function(\"return import('./src/main.ts')\"); load();",
+ "const name = 'eval'; window[name](\"import('./src/main.ts')\");",
+ "const run = eval; run(\"import('./src/main.ts')\");",
+ "const Build = globalThis.Function; new Build(\"return import('./src/main.ts')\");",
+]) test(`round2 excluded intrinsic evaluation rejects: ${content}`, () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "hidden.ts"), content);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("DEPENDENCY_SCOPE_RUNTIME_CODE:hidden.ts");
+});
+test("round2 TypeScript export equals rejects the qualified ESM profile", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "function main() { return 2; } export = main;\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("DEPENDENCY_SCOPE_COMMONJS_UNSUPPORTED:src/main.ts");
+});
+test("round2 unretained generated reference returns structured rejected admission", () => {
+ const root = selectedRepository();
+ mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/\n");
+ writeFileSync(join(root, "build/generated.ts"), "export const generated = 1;\n");
+ writeFileSync(join(root, "src/main.ts"), '/// \nexport function main() { return 2; }\n');
+ expect(() => indexRepository(root, "2026-10-09T10:01:00.000Z")).not.toThrow();
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain("DEPENDENCY_SCOPE_UNRETAINED_REFERENCE:src/main.ts:../build/generated.ts");
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+});
+test("manifest optional dependencies are admitted as opaque external boundaries", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "package.json"), JSON.stringify({ name: "fixture", optionalDependencies: { optional: "1.0.0" } }));
+ git(root, "add", "package.json");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "track optional dependency fixture");
+ writeFileSync(join(root, "src/main.ts"), "import { value } from 'optional'; export function main() { return value(); }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("admitted");
+ expect(admission?.limitations.some(reason => reason.includes("optional"))).toBe(true);
+});
+test("an excluded escaped importer returns structured dependency-scope rejection", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "hidden.ts"), "import { privateValue } from '../outside.ts'; export const hidden = privateValue;\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain("DEPENDENCY_SCOPE_UNREADABLE:hidden.ts");
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+});
+test("reserved profile markers cannot silently downgrade through legacy config stripping", () => {
+ const legacy = createDefaultConfig(".");
+ expect(SemctxConfigSchema.safeParse(legacy).success).toBe(true);
+ expect(SemctxConfigSchema.safeParse({ ...legacy, futureInformationalKey: "ignored" }).success).toBe(true);
+ expect(SemctxConfigSchema.safeParse({ ...legacy, analysisProfile: "modelo-suite-static-v1" }).success).toBe(false);
+ expect(SemctxConfigSchema.safeParse({ ...legacy, selectionMode: "qualified-static-v1" }).success).toBe(false);
+ expect(SemctxConfigSchema.safeParse({ ...createGlobSelectionConfig("."), analysisProfile: "modelo-suite-static-v1" }).success).toBe(false);
+ expect(SemctxConfigSchema.safeParse({ ...createGlobSelectionConfig("."), selectionMode: "qualified-static-v1" }).success).toBe(false);
+});
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+function git(root: string, ...args: string[]): void {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+}
+test("qualified profile refuses an excluded changed mjs even with an otherwise healthy index", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-")); roots.push(root);
+ mkdirSync(join(root, "scripts")); mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export const main = 1;\n");
+ writeFileSync(join(root, "scripts/check.mjs"), "export const check = 1;\n");
+ git(root, "init", "-q"); git(root, "add", ".");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", include: ["src/**/*.ts"], analysisProfile: "modelo-suite-static-v1" });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "scripts/check.mjs"), "export const check = 2;\n");
+ const computation = runVerify(root, { kind: "working-tree" });
+ expect(computation.result.verdict).toBe("BLOCK");
+ expect(computation.report.analysisAdmission?.changeCoverage.files).toEqual(expect.arrayContaining([
+ expect.objectContaining({ path: "scripts/check.mjs", status: "excluded" }),
+ ]));
+});
+
+function selectedRepository(): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-selected-")); roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, "src/consumer.ts"), "import { main } from './main'; export function consume() { return main(); }\n");
+ git(root, "init", "-q"); git(root, "add", ".");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", include: ["src/**/*.ts"], analysisProfile: "modelo-suite-static-v1" });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ return root;
+}
+test("stale inputs reject then actual rebuild admits current sources with dependent closure", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ const stale = runVerify(root, { kind: "working-tree" });
+ expect(stale.report.analysisAdmission?.status).toBe("rejected");
+ expect(stale.report.analysisAdmission?.indexFreshness.verdict).toBe("STALE");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const refreshed = runVerify(root, { kind: "working-tree" });
+ expect(refreshed.report.analysisAdmission?.status).toBe("admitted");
+ expect(refreshed.report.analysisAdmission?.changeCoverage.analyzed).toEqual(["src/consumer.ts", "src/main.ts"]);
+ expect(refreshed.report.changedSymbols.map((symbol) => symbol.name)).toContain("main");
+});
+test("raw whitespace, excluded untracked sources and manifests invalidate analysis identity", () => {
+ for (const [path, content] of [["src/main.ts", "export function main() { return 1; } \n"], ["untracked.mjs", "export const hidden = 1;\n"], ["package.json", '{"name":"fixture","type":"module"}']]) {
+ const root = selectedRepository(); writeFileSync(join(root, path!), content!);
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.indexFreshness.reasons).toContain("ANALYSIS_INPUT_MISMATCH");
+ }
+});
+test("zero obligations never produce qualified admission", () => {
+ const root = selectedRepository();
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.reasons).toContain("ZERO_ANALYZED_OBLIGATIONS");
+});
+test("an interrupted rebuild rejects the preserved old index even when inputs match", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ __setIndexRepositoryCaptureBarrierForTesting(() => { throw new Error("interrupted fixture"); });
+ expect(() => indexRepository(root, "2026-10-09T10:02:00.000Z")).toThrow("interrupted fixture");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.reasons).toContain("INDEX_BUILD_INCOMPLETE");
+});
+test("a configured nested root cannot hide worktree obligations via git --relative", () => {
+ const root = selectedRepository(); const nested = join(root, "src");
+ initWorkspace(nested, { ...createGlobSelectionConfig(nested), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1" });
+ indexRepository(nested, "2026-10-09T10:00:00.000Z");
+ expect(() => runVerify(nested, { kind: "working-tree" })).toThrow("exact Git worktree root");
+});
+test("excluded parse-failed or computed importers cannot silently disappear from dependency scope", () => {
+ for (const content of ["import { main } from './src/main'; export function broken( {", "const path = './src/main'; import(path);", "import { main } from '@hidden/inbound'; export const value = main();"]) {
+ const root = selectedRepository();
+ writeFileSync(join(root, "hidden.ts"), content);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some((reason) => reason.startsWith("DEPENDENCY_SCOPE_"))).toBe(true);
+ }
+});
+test("post-capture mutation is refused rather than describing the new bytes as analyzed", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ __setVerifyAnalysisBarrierForTesting(() => writeFileSync(join(root, "hidden.ts"), "export const hidden = 1;\n"));
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("changed");
+ expect(report.verdict).toBe("BLOCK");
+});
+test("structured validation rejects forged stale admission and a rejected PASS report", () => {
+ const root = selectedRepository();
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ const forged = { ...report, analysisAdmission: { ...report.analysisAdmission!, indexFreshness: { verdict: "STALE", reasons: ["ANALYSIS_INPUT_MISMATCH"] } } };
+ expect(VerifyReportSchema.safeParse(forged).success).toBe(false);
+ expect(VerifyReportSchema.safeParse({ ...report, verdict: "PASS", analysisAdmission: { ...report.analysisAdmission!, status: "rejected" } }).success).toBe(false);
+});
+test("CLI none policy cannot turn rejected qualified analysis into process success", () => {
+ const root = selectedRepository();
+ const process = Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ expect(process.exitCode).toBe(3);
+ const report = JSON.parse(new TextDecoder().decode(process.stdout));
+ expect(report.analysisAdmission.status).toBe("rejected");
+ expect(report.verdict).toBe("BLOCK");
+});
+test("qualified raw input inventory refuses an external directory link without following it", () => {
+ const root = selectedRepository();
+ const outside = mkdtempSync(join(tmpdir(), "semctx-qualified-outside-")); roots.push(outside);
+ writeFileSync(join(outside, "hidden.ts"), "export const privateContent = 1;\n");
+ symlinkSync(outside, join(root, "linked"), process.platform === "win32" ? "junction" : "dir");
+ expect(() => runVerify(root, { kind: "working-tree" })).toThrow("cannot read a symbolic link");
+});
+
+test("excluded TypeScript dynamic construction cannot hide an inbound scope", () => {
+ for (const content of [
+ 'const load = new Function("return import(\'./src/main.ts\')"); export const result = load();\n',
+ 'with (globalThis) { const load = packageLoader; load("./src/main.ts"); }\n',
+ ]) {
+ const root = selectedRepository();
+ writeFileSync(join(root, "hidden.ts"), content);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain("DEPENDENCY_SCOPE_RUNTIME_CODE:hidden.ts");
+ }
+});
diff --git a/packages/app-services/test/qualified-array-coercion-mutation.test.ts b/packages/app-services/test/qualified-array-coercion-mutation.test.ts
new file mode 100644
index 00000000..782ff95a
--- /dev/null
+++ b/packages/app-services/test/qualified-array-coercion-mutation.test.ts
@@ -0,0 +1,47 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const method of ["join", "toLocaleString"]) for (const extension of ["ts", "mjs"]) for (const selected of [true, false]) {
+ test(`qualified mutated array ${method} ${extension} selected=${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-array-coercion-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, `src/hidden.${extension}`);
+ writeFileSync(hidden, "export function hidden() { return 1; }");
+ const hook = method === "join" ? "toString" : "toLocaleString";
+ const assertion = extension === "ts" ? " as any" : "";
+ writeFileSync(join(root, `src/consumer.${extension}`), `import { hidden } from './hidden.${extension}'; const values = [1]; values[0] = { ${hook}: hidden }${assertion}; export function run() { return values.${method}(); }`);
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import { run } from './consumer.${extension}'; export function check() { return run(); }`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, {
+ ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1",
+ include: selected ? ["src/**/*"] : [`src/hidden.${extension}`], languages: { typescript: "on", javascript: "on" },
+ });
+ writeFileSync(hidden, "export function hidden() { return 2; }");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ method, extension, selected, admission: report.analysisAdmission, cliExit: cli.exitCode, cliStdout: new TextDecoder().decode(cli.stdout), cliStderr: new TextDecoder().decode(cli.stderr) }));
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:src/consumer.${extension}:SOURCE_COERCION_UNSUPPORTED`);
+ if (selected) expect(report.analysisAdmission?.changeCoverage.files.find(file => file.path === `src/consumer.${extension}`)?.reasons).toContain("SOURCE_COERCION_UNSUPPORTED");
+ expect(cli.exitCode).toBe(3);
+ const cliReport = JSON.parse(new TextDecoder().decode(cli.stdout)) as { verdict: string; analysisAdmission: { status: string; reasons: string[] } };
+ expect(cliReport.verdict).toBe("BLOCK");
+ expect(cliReport.analysisAdmission.status).toBe("rejected");
+ expect(cliReport.analysisAdmission.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:src/consumer.${extension}:SOURCE_COERCION_UNSUPPORTED`);
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ }, 180_000);
+}
diff --git a/packages/app-services/test/qualified-await-global-mutation.test.ts b/packages/app-services/test/qualified-await-global-mutation.test.ts
new file mode 100644
index 00000000..ef1f1e10
--- /dev/null
+++ b/packages/app-services/test/qualified-await-global-mutation.test.ts
@@ -0,0 +1,87 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const cases = [
+ [
+ "reassigned async",
+ "SOURCE_AWAIT_UNSUPPORTED",
+ "async function load(){return 1;}load=()=>({then:hidden});export async function run(){await load();return 1;}"
+ ],
+ [
+ "for await thenable",
+ "SOURCE_AWAIT_UNSUPPORTED",
+ "export async function run(){for await(const item of [{then:hidden}]){}return 1;}"
+ ],
+ [
+ "plural global descriptor",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Object.defineProperties(globalThis,{Math:{value:{random:hidden}}});export function run(){return Math.random();}"
+ ],
+ [
+ "reflect global set",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Reflect.set(globalThis,'Math',{random:hidden});export function run(){return Math.random();}"
+ ],
+ [
+ "assign global namespace",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Object.assign(globalThis,{Math:{random:hidden}});export function run(){return Math.random();}"
+ ]
+] as const;
+const controls = [
+ ["unaltered async", "async function load(){return 1;}export async function run(){await load();return 1;}"],
+ ["primitive for-await", "export async function run(){for await(const item of [1,2]){}return 1;}"],
+ ["local/data mutations", "const globalThis={Math:{random:()=>1}};Object.defineProperties(globalThis,{Math:{value:{random:()=>2}}});Reflect.set(globalThis,'Math',{random:()=>3});Object.assign(globalThis,{Math:{random:()=>4}});export function run(){return 1;}"],
+] as const;
+
+for (const extension of ["ts", "mjs"]) for (const [name, reason, body] of [...cases, ...controls.map(([name, body]) => [name, undefined, body] as const)]) {
+ test(`qualified ${name} ${extension} binds hidden-only edit and its intended reason`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-await-global-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, `src/hidden.${extension}`);
+ writeFileSync(hidden, "export function hidden(){return 1;}\n");
+ writeFileSync(join(root, `src/main.${extension}`), `import {hidden} from './hidden.${extension}';${body}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (child.exitCode !== 0) throw new Error(new TextDecoder().decode(child.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function hidden(){return 2;}\n");
+ indexRepository(root, "2026-10-12T08:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, extension, report }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ if (reason === undefined) {
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ } else {
+ expect(report.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:src/main.${extension}:${reason}`);
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.verdict).toBe("BLOCK");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ const stdout = new TextDecoder().decode(cli.stdout); const stderr = new TextDecoder().decode(cli.stderr);
+ console.info(JSON.stringify({ name, extension, cli: { exitCode: cli.exitCode, stdout, stderr } }));
+ expect(cli.exitCode).toBe(3);
+ const parsed = VerifyReportSchema.safeParse(JSON.parse(stdout));
+ expect(parsed.success).toBe(true);
+ if (!parsed.success) throw new Error("invalid CLI report");
+ expect(parsed.data.verdict).toBe("BLOCK");
+ expect(parsed.data.analysisAdmission?.status).toBe("rejected");
+ expect(parsed.data.analysisAdmission?.binding.status).toBe("valid");
+ expect(parsed.data.analysisAdmission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ expect(parsed.data.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(parsed.data.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ expect(parsed.data.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:src/main.${extension}:${reason}`);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+ }, 180_000);
+}
diff --git a/packages/app-services/test/qualified-build-directory-probe.test.ts b/packages/app-services/test/qualified-build-directory-probe.test.ts
new file mode 100644
index 00000000..7495de4a
--- /dev/null
+++ b/packages/app-services/test/qualified-build-directory-probe.test.ts
@@ -0,0 +1,235 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { createDefaultConfig, createGlobSelectionConfig } from "@semantic-context/core";
+import { discoverRepository, isPathSelected } from "@semantic-context/ts-analyzer";
+import { initWorkspace, loadConfig, openStore } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { captureQualifiedAnalysisInputs, CONTROL_INDEX_SNAPSHOT_META_KEY } from "../src/freshness";
+
+for (const [hidden, content] of [
+ ["tooling/build/hidden.ts", "export { main } from '../../src/main';"],
+ ["dist/hidden.mjs", "export { main } from '../src/main.ts';"],
+]) for (const explicitlyIncluded of [false, true]) {
+ test(`tracked ${hidden}, explicit include ${explicitlyIncluded}, cannot disappear from scope`, () => {
+ const physicalRoot = mkdtempSync(join(tmpdir(), "semctx-authored-build-scope-"));
+ const aliasParent = mkdtempSync(join(tmpdir(), "semctx-build-scope-alias-"));
+ const root = join(aliasParent, "checkout");
+ symlinkSync(physicalRoot, root, process.platform === "win32" ? "junction" : "dir");
+ const git = (...args: string[]): string => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+ };
+ try {
+ mkdirSync(join(root, "src")); mkdirSync(dirname(join(root, hidden!)), { recursive: true });
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, hidden!), content!);
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["src/**/*.ts", ...(explicitlyIncluded ? [hidden!] : [])], languages: { typescript: "on" as const, javascript: "on" as const } };
+ initWorkspace(root, config);
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ const cli = Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ const discovery = discoverRepository(config);
+ expect(git("ls-files", "--", hidden!).length > 0).toBe(true);
+ expect(isPathSelected(config, hidden!)).toBe(explicitlyIncluded);
+ expect(discovery.candidates).toContainEqual(expect.objectContaining({ relPath: hidden, selectionDecision: explicitlyIncluded ? "selected" : "excluded" }));
+ // The index binds the canonical configuration loaded by its workspace, including
+ // physical roots such as macOS /private/var and Windows directory junctions.
+ const inputs = captureQualifiedAnalysisInputs(loadConfig(root));
+ expect(inputs.files).toContainEqual(expect.objectContaining({ path: hidden }));
+ const store = openStore(root);
+ try { expect(JSON.parse(store.getMeta(CONTROL_INDEX_SNAPSHOT_META_KEY)!).analysisInputHash).toBe(inputs.digest); }
+ finally { store.close(); }
+ expect(report.analysisAdmission?.changeCoverage.expected).toContain(hidden!);
+ expect(report.analysisAdmission?.status).toBe(explicitlyIncluded ? "admitted" : "rejected");
+ expect(cli.exitCode).toBe(explicitlyIncluded ? 0 : 3);
+ expect(JSON.parse(new TextDecoder().decode(cli.stdout)).analysisAdmission?.status).toBe(explicitlyIncluded ? "admitted" : "rejected");
+ if (explicitlyIncluded) {
+ expect(discovery.files.some((file) => file.relPath === hidden)).toBe(true);
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toEqual([hidden!, "src/main.ts"].sort());
+ }
+ else expect(report.analysisAdmission?.changeCoverage.files).toContainEqual(expect.objectContaining({ path: hidden, status: "excluded" }));
+ writeFileSync(join(root, hidden!), `${content}\n// drift after indexing\n`);
+ const stale = runVerify(root, { kind: "working-tree" }).report;
+ expect(stale.analysisAdmission?.status).toBe("rejected");
+ expect(stale.analysisAdmission?.indexFreshness.verdict).toBe("STALE");
+ } finally {
+ rmSync(aliasParent, { recursive: true, force: true });
+ rmSync(physicalRoot, { recursive: true, force: true });
+ }
+ }, 60_000);
+}
+
+for (const selected of [false, true]) {
+ test(`ignored untracked generated source respects the actual selector: ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ignored-build-scope-"));
+ try {
+ mkdirSync(join(root, "src")); mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const git = (...args: string[]) => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ // Unsupported runtime code must not pollute qualification when outside the real selector.
+ writeFileSync(join(root, "build/generated.ts"), "export function generated() { return eval('1'); }\n");
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["src/**/*.ts", ...(selected ? ["build/**/*.ts"] : [])] };
+ initWorkspace(root, config);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ const discovery = discoverRepository(config);
+ expect(discovery.candidates).toContainEqual(expect.objectContaining({ relPath: "build/generated.ts", selectionDecision: selected ? "selected" : "excluded", reason: selected ? "SELECTED" : "IGNORED_GENERATED_OUTPUT" }));
+ expect(discovery.files.some(file => file.relPath === "build/generated.ts")).toBe(selected);
+ const before = captureQualifiedAnalysisInputs(config);
+ expect(before.files.some(file => file.path === "build/generated.ts")).toBe(selected);
+ expect(before.files.some(file => file.path === ".gitignore")).toBe(true);
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe(selected ? "rejected" : "admitted");
+ if (!selected) expect(admission?.changeCoverage.expected).not.toContain("build/generated.ts");
+ writeFileSync(join(root, "build/generated.ts"), "export function generated() { return eval('2'); }\n");
+ expect(captureQualifiedAnalysisInputs(config).digest === before.digest).toBe(!selected);
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ expect(captureQualifiedAnalysisInputs(config).digest).not.toBe(before.digest);
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("rejected");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+ }, 60_000);
+}
+
+test("untracked authored outputs remain obligations and legacy profiles retain directory exclusions", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-untracked-build-scope-"));
+ try {
+ mkdirSync(join(root, "src")); mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const git = (...args: string[]) => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ git("init", "-q"); git("add", ".");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ writeFileSync(join(root, "build/consumer.ts"), "export { main } from '../src/main';\n");
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["src/**/*.ts"] };
+ initWorkspace(root, config);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.changeCoverage.files).toContainEqual(expect.objectContaining({ path: "build/consumer.ts", status: "excluded" }));
+ for (const legacy of [createDefaultConfig(root), { ...createGlobSelectionConfig(root), include: ["**/*.ts"] }]) {
+ expect(discoverRepository(legacy).candidates.some(candidate => candidate.relPath === "build/consumer.ts")).toBe(false);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("ignored compiler configuration symlinks are explicitly refused", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-build-config-link-"));
+ try {
+ mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/**\n");
+ writeFileSync(join(root, "build/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, "compiler-base.json"), '{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext"}}');
+ const result = Bun.spawnSync(["git", "init", "-q"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ symlinkSync(join(root, "compiler-base.json"), join(root, "build/tsconfig.json"), "file");
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["build/**/*.ts"] };
+ expect(() => captureQualifiedAnalysisInputs(config)).toThrow(expect.objectContaining({ code: "INVALID_TASK_INPUT" }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+for (const inherited of [false, true]) test(`ignored build configuration cannot fall back to qualified defaults: inherited ${inherited}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-build-configuration-"));
+ try {
+ mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/**\n");
+ writeFileSync(join(root, "build/main.ts"), "export function main() { return 1; }\n");
+ const git = (...args: string[]) => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ git("init", "-q"); git("add", ".gitignore"); git("add", "-f", "build/main.ts");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ const unsupported = '{"compilerOptions":{"module":"NodeNext","moduleResolution":"NodeNext"}}';
+ writeFileSync(join(root, "build/tsconfig.json"), inherited ? '{"extends":"./config-base.json"}' : unsupported);
+ if (inherited) writeFileSync(join(root, "build/config-base.json"), unsupported);
+ writeFileSync(join(root, "build/package.json"), '{"type":"module"}');
+ writeFileSync(join(root, "build/cache.json"), '{"generated":true}');
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["build/**/*.ts"] };
+ initWorkspace(root, config);
+ writeFileSync(join(root, "build/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ const inputs = captureQualifiedAnalysisInputs(config);
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ const cli = Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ inherited, captured: inputs.files.map(file => file.path), admission: report.analysisAdmission?.status, cliExit: cli.exitCode }));
+ expect(inputs.files.some(file => file.path === "build/tsconfig.json")).toBe(true);
+ expect(inputs.files.some(file => file.path === "build/package.json")).toBe(true);
+ expect(inputs.files.some(file => file.path === "build/cache.json")).toBe(false);
+ if (inherited) expect(inputs.files.some(file => file.path === "build/config-base.json")).toBe(true);
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(cli.exitCode).toBe(3);
+ expect(report.analysisAdmission?.reasons.some(reason => reason.endsWith("SOURCE_CONFIGURATION_MODULE_UNSUPPORTED:NodeNext"))).toBe(true);
+ if (inherited) {
+ rmSync(join(root, "build/config-base.json"));
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const missing = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(missing?.status).toBe("rejected");
+ expect(missing?.reasons.some(reason => reason.includes("SOURCE_CONFIGURATION_INVALID"))).toBe(true);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+for (const tracked of [false, true]) test(tracked ? "ignore and attribute controls inside ignored outputs stay sealed" : "untracked ignore and attribute controls inside ignored outputs refuse qualification", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-build-ignore-controls-"));
+ try {
+ mkdirSync(join(root, "src")); mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\nbuild/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const git = (...args: string[]) => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ git("init", "-q"); git("add", ".");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ writeFileSync(join(root, "build/.gitignore"), "*.ts\n");
+ writeFileSync(join(root, "build/.gitattributes"), "*.ts text\n");
+ if (tracked) {
+ git("add", "-f", "build/.gitignore", "build/.gitattributes");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "tracked ignored controls");
+ }
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["src/**/*.ts"] };
+ initWorkspace(root, config);
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ if (!tracked) {
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.binding).toEqual({ status: "invalid", reasons: [
+ "QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:build/.gitattributes",
+ "QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:build/.gitignore",
+ ] });
+ expect(admission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ const inputs = captureQualifiedAnalysisInputs(config);
+ for (const path of ["build/.gitignore", "build/.gitattributes"]) expect(inputs.files.some(file => file.path === path)).toBe(true);
+ return;
+ }
+ expect(admission?.status).toBe("admitted");
+ expect(admission?.binding.status).toBe("valid");
+ for (const path of ["build/.gitignore", "build/.gitattributes"]) {
+ const before = captureQualifiedAnalysisInputs(config);
+ expect(before.files.some(file => file.path === path)).toBe(true);
+ writeFileSync(join(root, path), "# changed control\n");
+ expect(captureQualifiedAnalysisInputs(config).digest).not.toBe(before.digest);
+ const stale = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(stale?.status).toBe("rejected");
+ expect(stale?.indexFreshness.verdict).toBe("STALE");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-builtin-execution-reexport.test.ts b/packages/app-services/test/qualified-builtin-execution-reexport.test.ts
new file mode 100644
index 00000000..f0f89f3c
--- /dev/null
+++ b/packages/app-services/test/qualified-builtin-execution-reexport.test.ts
@@ -0,0 +1,72 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`child change through launcher barrel ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-launcher-barrel-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, `${selected ? "src/" : ""}launcher.${extension}`), "export { fork } from 'node:child_process';\n");
+ writeFileSync(join(root, `src/main.${extension}`), `import { fork } from '${selected ? "./" : "../"}launcher'; export function run() { return fork('./src/child.js'); }\n`);
+ const child = join(root, "src/child.js"); writeFileSync(child, "export function child() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(child, "export function child() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const result = runVerify(root, { kind: "working-tree" }); const admission = result.report.analysisAdmission;
+ console.info(JSON.stringify({ extension, selected, admission, recommendedTests: result.report.recommendedTests }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(value => value.includes("PROCESS_LAUNCH_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) for (const [name, barrel, consumer] of [
+ ["fork", "export { fork } from 'node:cluster';", "import { fork } from 'LAUNCHER'; export function run() { return fork(); }"],
+ ["setupPrimary alias", "export { setupPrimary as launch } from 'node:cluster';", "import { launch } from 'LAUNCHER'; export function run() { return launch(); }"],
+] as const) test(`cluster barrel ${name} ${extension}, selected ${selected} refuses hidden child change`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-cluster-barrel-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, `${selected ? "src/" : ""}launcher.${extension}`), `${barrel}\n`);
+ writeFileSync(join(root, `src/main.${extension}`), `${consumer.replace("LAUNCHER", `${selected ? "./" : "../"}launcher`)}\n`);
+ const child = join(root, "src/child.js"); writeFileSync(child, "export function child() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(child, "export function child() { return 2; }\n");
+ const indexed = indexRepository(root, "2026-10-11T10:00:00.000Z");
+ expect(indexed.analysis.graph.edges.filter(edge => edge.kind === "calls")).toEqual([]);
+ const report = runVerify(root, { kind: "working-tree" }).report; const admission = report.analysisAdmission;
+ console.info(JSON.stringify({ name, extension, selected, admission, graph: indexed.analysis.graph }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.verdict).toBe("BLOCK");
+ expect(admission?.binding.status).toBe("valid");
+ expect(admission?.status).toBe("rejected");
+ expect([...admission?.reasons ?? [], ...admission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(value => value.includes("PROCESS_LAUNCH_UNSUPPORTED"))).toBe(true);
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ const cliReport = VerifyReportSchema.parse(JSON.parse(new TextDecoder().decode(cli.stdout)));
+ expect(cliReport.verdict).toBe("BLOCK");
+ expect(cliReport.analysisAdmission?.binding.status).toBe("valid");
+ expect([...cliReport.analysisAdmission?.reasons ?? [], ...cliReport.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(value => value.includes("PROCESS_LAUNCH_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("native launcher barrel executes the owned child", async () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-native-barrel-"));
+ try {
+ writeFileSync(join(root, "launcher.mjs"), "export { fork } from 'node:child_process';\n");
+ writeFileSync(join(root, "child.mjs"), "console.log('OWNED_BARREL_CHILD');\n");
+ writeFileSync(join(root, "main.mjs"), "import {fork} from './launcher.mjs'; const child=fork(new URL('./child.mjs', import.meta.url),[],{execPath:process.execPath,silent:true});child.stdout.pipe(process.stdout);child.on('close',code=>{process.exitCode=code;});\n");
+ const child = Bun.spawn([process.execPath, join(root, "main.mjs")], { stdout: "pipe", stderr: "pipe" });
+ const output = await new Response(child.stdout).text(); const code = await child.exited;
+ expect(code).toBe(0); expect(output).toContain("OWNED_BARREL_CHILD");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 30_000);
diff --git a/packages/app-services/test/qualified-call-coordinates.test.ts b/packages/app-services/test/qualified-call-coordinates.test.ts
new file mode 100644
index 00000000..e1d3764a
--- /dev/null
+++ b/packages/app-services/test/qualified-call-coordinates.test.ts
@@ -0,0 +1,40 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const content of [
+ "export interface Repository { save(): void } export function confirm(repo: Repository) { repo.save(); }",
+ "export function confirm(callback: () => void) { callback(); }",
+ "export function save() { return 1; } export interface Repository { save(): number } export function confirm(repo: Repository) { return repo.save(); }",
+ "export function save() { return 1; } const repository = { save() { return 2; } }; export function confirm() { return repository.save(); }",
+ "export function save() { return 1; } const repository = { save: () => 2 }; export function confirm() { return repository.save(); }",
+ "export function save() { return 1; } const repository = { save }; export function confirm() { return repository.save(); }",
+ "export function save() { return 1; } export function helper() { return 2; } const repository = { save() { return helper(); } }; export {};",
+ "export function helper() { return 1; } const repository = { save: (() => helper()) }; export {};",
+ "export function helper() { return 1; } const repository = { save: ((() => helper()) as () => number) }; export {};",
+]) test(`qualified extracted local call without a modeled coordinate cannot admit: ${content}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-call-coordinate-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), `${content}\n`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), `${content}\n\n`);
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const candidate = indexHealth(root).candidates.find(candidate => candidate.path === "src/main.ts")!;
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ case: content, observedAdmission: report.analysisAdmission?.status, reasons: candidate.analysisReasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ expect(candidate.analysisReasons.some(reason => reason.startsWith("QUALIFIED_CALL_COORDINATE_CALLEE_MISSING:") || reason.startsWith("QUALIFIED_CALL_COORDINATE_CALLEE_UNMODELED:") || reason.startsWith("QUALIFIED_CALL_COORDINATE_CALLER_UNMODELED:"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-call-shapes.test.ts b/packages/app-services/test/qualified-call-shapes.test.ts
new file mode 100644
index 00000000..4d7b1d91
--- /dev/null
+++ b/packages/app-services/test/qualified-call-shapes.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [expression, selected] of [["(flag ? helper : fallback)()", true], ["(flag ? helper : fallback)()", false], ["(() => helper())()", true], ["(0, helper)()", true]] as const) test(`qualified TS refuses unmodeled call ${expression}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-call-shape-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = selected ? "src/caller.ts" : "hidden.ts";
+ writeFileSync(join(root, path), `export function helper() { return 1; } function fallback() { return 2; } export function caller(flag: boolean) { return ${expression}; }\n`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, observed: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("SOURCE_DYNAMIC_CALL_UNSUPPORTED"))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisOutcome).not.toBe("analyzed");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-callable-runtime.test.ts b/packages/app-services/test/qualified-callable-runtime.test.ts
new file mode 100644
index 00000000..668a43fc
--- /dev/null
+++ b/packages/app-services/test/qualified-callable-runtime.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "export const C = class {};", "CALLABLE_EXPORT_UNSUPPORTED"],
+ ["js", "export const helper = true ? (() => 1) : (() => 2);", "CALLABLE_EXPORT_UNSUPPORTED"],
+ ["ts", "export function read() { return (() => {}).constructor.call(null, \"return import('./hidden.js')\").call(null); }", "DYNAMIC_EVALUATION_UNSUPPORTED"],
+ ["js", "export function read() { return (() => {}).constructor.call(null, \"return import('./hidden.js')\").call(null); }", "DYNAMIC_EVALUATION_UNSUPPORTED"],
+ ["ts", "export function read() { return import.meta.require('./hidden.js'); }", "COMMONJS_UNSUPPORTED"],
+ ["js", "export function read() { return import.meta.require('./hidden.js'); }", "COMMONJS_UNSUPPORTED"],
+ ["ts", "const key = 'require'; export function read() { return import.meta[key].call(null, './hidden.js'); }", "COMMONJS_UNSUPPORTED"],
+ ["js", "export function read() { return ({ meta: import.meta }).meta.require('./hidden.js'); }", "COMMONJS_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-callback-descriptor.test.ts b/packages/app-services/test/qualified-callback-descriptor.test.ts
new file mode 100644
index 00000000..8766a864
--- /dev/null
+++ b/packages/app-services/test/qualified-callback-descriptor.test.ts
@@ -0,0 +1,33 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason, label] of [
+ ["js", "function helper() { return 1; } export function start() { return setTimeout(helper, 0); }", "TIMER_CALLBACK_UNSUPPORTED", "native callback"],
+ ["ts", "function helper() { return 1; } const box = {}; Object.defineProperty(box, 'value', { get: helper }); export function read() { return box.value; }", "DESCRIPTOR_ACCESSOR_UNSUPPORTED", "literal descriptor"],
+ ["ts", "declare function eval(source: string): unknown; export function run() { return eval(\"import('./hidden.js')\"); }", "AMBIENT_FUNCTION_UNSUPPORTED", "ambient declaration"],
+ ["js", "const { defineProperty } = Object; function helper() { return 1; } const box = {}; export function start() { defineProperty.call(Object, box, 'value', { get: helper }); return box.value; }", "DESCRIPTOR_ACCESSOR_UNSUPPORTED", "shorthand follow-up"],
+ ["ts", "function helper() { return 1; } const box = {}; Object.defineProperty(box, 'value', { __proto__: { get: helper } }); export function read() { return box.value; }", "DESCRIPTOR_ACCESSOR_UNSUPPORTED", "prototype follow-up"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${label}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-callback-descriptor-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-checkout-boundaries.test.ts b/packages/app-services/test/qualified-checkout-boundaries.test.ts
new file mode 100644
index 00000000..4834977e
--- /dev/null
+++ b/packages/app-services/test/qualified-checkout-boundaries.test.ts
@@ -0,0 +1,45 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+function git(root: string, ...args: string[]): void {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+}
+test("selected changed Python remains outside qualified TS/JS closure despite an excluded Python importer", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-python-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/core.py"), "def core():\n return 1\n");
+ writeFileSync(join(root, "hidden.py"), "from src.core import core\ndef hidden():\n return core()\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { python: "on" } });
+ writeFileSync(join(root, "src/core.py"), "def core():\n return 2\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ case: "Python", status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons, coverage: report.analysisAdmission?.changeCoverage.files }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.changeCoverage.files).toContainEqual(expect.objectContaining({ path: "src/core.py", status: "unsupported", reasons: ["OUTSIDE_BOUNDED_ESM_TYPESCRIPT_PROFILE"] }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+for (const mode of ["skip", "skip-assume", "assume", "sparse"]) test(`qualified working-tree refuses incomplete checkout mode ${mode}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-checkout-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ mkdirSync(join(root, "hidden"));
+ writeFileSync(join(root, "hidden/consumer.ts"), "import { main } from '../src/main'; export function hidden() { return main(); }\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ if (mode === "sparse") { git(root, "sparse-checkout", "init", "--cone"); git(root, "sparse-checkout", "set", "src"); }
+ else { if (mode !== "assume") git(root, "update-index", "--skip-worktree", "hidden/consumer.ts"); if (mode === "skip-assume" || mode === "assume") git(root, "update-index", "--assume-unchanged", "hidden/consumer.ts"); }
+ rmSync(join(root, "hidden/consumer.ts"), { force: true });
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ case: mode, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("SKIP_WORKTREE_UNSUPPORTED") || reason.includes("ASSUME_UNCHANGED_UNSUPPORTED") || reason === "QUALIFIED_SPARSE_CHECKOUT_UNSUPPORTED")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-classic-jsx-factory.test.ts b/packages/app-services/test/qualified-classic-jsx-factory.test.ts
new file mode 100644
index 00000000..00604381
--- /dev/null
+++ b/packages/app-services/test/qualified-classic-jsx-factory.test.ts
@@ -0,0 +1,30 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+test("classic factory body change cannot silently omit the test importing render", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-classic-impact-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "tsconfig.json"), '{"compilerOptions":{"jsx":"react","jsxFactory":"h","module":"ESNext","moduleResolution":"Bundler"}}');
+ const factory = join(root, "src/factory.ts"); writeFileSync(factory, "export function h(){return 1;}");
+ writeFileSync(join(root, "src/render.tsx"), "import {h} from './factory';export function render(){return ;}");
+ writeFileSync(join(root, "src/render.test.ts"), "import {render} from './render';export function check(){render();return 1;}");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(factory, "export function h(){return 2;}"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("CLASSIC_JSX_RUNTIME_UNSUPPORTED"))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-coercion-assimilation.test.ts b/packages/app-services/test/qualified-coercion-assimilation.test.ts
new file mode 100644
index 00000000..dd7c48e8
--- /dev/null
+++ b/packages/app-services/test/qualified-coercion-assimilation.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "const value = { [Symbol.toPrimitive]() { return 1; } }; export function start() { return '' + value; }", "COERCION_UNSUPPORTED"],
+ ["js", "const value = { then() { return 1; } }; export async function start() { return value; }", "ASYNC_RETURN_UNSUPPORTED"],
+ ["ts", "const trap = () => 1; const value = new Proxy({}, { get: trap }); export function start() { return value.key; }", "PROXY_UNSUPPORTED"],
+ ["js", "export function start() { return navigator.serviceWorker.register('./sw.js', { type: 'module' }); }", "SERVICE_WORKER_LOADER_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-coercion-worklet-helpers.test.ts b/packages/app-services/test/qualified-coercion-worklet-helpers.test.ts
new file mode 100644
index 00000000..982470ce
--- /dev/null
+++ b/packages/app-services/test/qualified-coercion-worklet-helpers.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [content, label] of [
+ ["const input = { valueOf() { return 1; } }; export function run() { return +input; }", "COERCION_UNSUPPORTED"],
+ ["const audio = new AudioContext(); export function run() { return audio.audioWorklet.addModule('./processor.js'); }", "WORKLET_LOADER_UNSUPPORTED"],
+ ["export async function run() { return 1; }", "CONFIGURATION_HELPERS_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified SDK ${label} callback ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-sdk-callback-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ if (label === "CONFIGURATION_HELPERS_UNSUPPORTED") writeFileSync(join(root, "tsconfig.json"), '{"compilerOptions":{"target":"ES5","module":"ESNext","importHelpers":true}}');
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes(label))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(reason => reason.includes(label))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-commonjs-scope.test.ts b/packages/app-services/test/qualified-commonjs-scope.test.ts
new file mode 100644
index 00000000..22a65396
--- /dev/null
+++ b/packages/app-services/test/qualified-commonjs-scope.test.ts
@@ -0,0 +1,39 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [path, content, reason] of [
+ ["hidden.ts", "const { main } = require('./src/main.ts'); export const value = main();", "COMMONJS_UNSUPPORTED"],
+ ["hidden.mjs", "module.exports = require('./src/main.ts');", "COMMONJS_UNSUPPORTED"],
+ ["hidden-import-equals.ts", "import value = require('./src/main.ts'); export const hidden = value.main();", "COMMONJS_UNSUPPORTED"],
+ ["hidden-import-type.ts", "export type Hidden = typeof import('./src/main').main;", "IMPORT_TYPE_UNSUPPORTED"],
+]) test(`excluded ${path} unsupported dependency cannot silently escape scope`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-hidden-commonjs-"));
+ const git = (...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, path!), content!);
+ git("init", "-q"); git("add", ".");
+ git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*.ts"], languages: { typescript: "on", javascript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ const cli = Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ case: path, status: report.analysisAdmission?.status, cliExit: cli.exitCode }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_${reason}:${path}`);
+ expect(cli.exitCode).toBe(3);
+ expect(JSON.parse(new TextDecoder().decode(cli.stdout)).analysisAdmission.status).toBe("rejected");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-construction-reference.test.ts b/packages/app-services/test/qualified-construction-reference.test.ts
new file mode 100644
index 00000000..31d56dbe
--- /dev/null
+++ b/packages/app-services/test/qualified-construction-reference.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "function touch() { return 1; } export class Helper { constructor() { touch(); } } export function make() { return new Helper(); }", "CONSTRUCTION_UNSUPPORTED"],
+ ["js", "function touch() { return 1; } export class Helper { constructor() { touch(); } } export function make() { return new Helper(); }", "CONSTRUCTION_UNSUPPORTED"],
+ ["ts", "/// \nexport {};", "REFERENCE_PATH_UNSUPPORTED"],
+ ["js", "/// \nexport {};", "REFERENCE_PATH_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), selected ? content.replace("'./src/main.ts'", "'./main.ts'") : content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-create-require.test.ts b/packages/app-services/test/qualified-create-require.test.ts
new file mode 100644
index 00000000..c10c87e7
--- /dev/null
+++ b/packages/app-services/test/qualified-create-require.test.ts
@@ -0,0 +1,67 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const globalSelfAliasSource = ["globalThis.globalThis", "globalThis.global", "global.globalThis", "global.global"].map((receiver, index) =>
+ `export function entry${index}() { const native = ${receiver}.process.getBuiltinModule('module'); const load = native.createRequire(import.meta.url); return load('./src/main.mjs'); }`).join("\n");
+const cases: { path: string; source: string; selected: boolean; barrel?: string; mainPath?: string; unmodeled?: string }[] = [
+ { path: "hidden-ambient-require.mjs", source: "const load = globalThis.require; export const hidden = load('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-process-opaque.ts", source: "export const native = process.binding('contextify');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.binding" },
+ { path: "hidden-process-escape.mjs", source: "const get = Reflect.get(process, 'getBuiltinModule'); const native = get('module'); const load = native.createRequire(import.meta.url); export const hidden = load('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "ambient-process" },
+ { path: "hidden-process-computed.ts", source: "const key = 'getBuiltinModule'; const native = process[key]('module'); const load = native.createRequire(import.meta.url); export const hidden = load('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process." },
+ { path: "hidden-process-mainmodule.mjs", source: "export const hidden = process.mainModule.require('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.mainModule" },
+ { path: "hidden-global-escape.ts", source: "const p = Reflect.get(globalThis, 'process'); const M = p.getBuiltinModule('module'); const load = M.createRequire(import.meta.url); export const hidden = load('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "ambient-global" },
+ { path: "hidden-global-self-alias.mjs", source: globalSelfAliasSource, selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-global-self-alias.ts", source: globalSelfAliasSource, selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-global-process.mjs", source: "const p = global.process; const get = p.getBuiltinModule; const M = get('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-globalthis-process.mjs", source: "const p = globalThis.process; const get = p.getBuiltinModule; const M = get('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-globalthis-process.ts", source: "const { getBuiltinModule: get } = globalThis['process']; const M = get('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-process-default.mjs", source: "import { default as p } from 'node:process'; const M = p.getBuiltinModule('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-process-destructure.ts", source: "const { getBuiltinModule: get } = process; const M = get('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden.mjs", source: "import { createRequire } from 'node:module'; const load = createRequire(import.meta.url); export function hidden() { return load('./src/main.ts').main(); }", selected: true },
+ { path: "hidden-alias.mjs", source: "import { createRequire as makeLoader } from 'module'; const load = makeLoader(import.meta.url); export const hidden = load('./src/main.ts');", selected: false },
+ { path: "hidden-namespace.ts", source: "import * as nodeModule from 'node:module'; const load = nodeModule.createRequire(import.meta.url); export const hidden = load('./src/main.ts');", selected: false },
+ { path: "hidden-barrel.ts", source: "import { make } from './barrel.mjs'; const load = make(import.meta.url); export const hidden = load('./src/main.ts');", selected: false, barrel: "export { createRequire as make } from 'node:module';" },
+ { path: "hidden-assignment.mjs", source: "import * as m from 'node:module'; let n; n = m; const r = n.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-assignment.ts", source: "import * as m from 'module'; let n; n = m; const r = n.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-export-default.mjs", source: "import { default as M } from 'node:module'; const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-export-string.ts", source: "import { 'default' as M } from 'module'; const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-export-destructure.mjs", source: "import * as m from 'node:module'; const { default: M } = m; const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-export-module.ts", source: "import * as m from 'node:module'; const { Module: M } = m; const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-export-quoted.mjs", source: "import * as m from 'node:module'; const { 'createRequire': make } = m; const r = make(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-native-load.mjs", source: "import M from 'node:module'; export const hidden = M._load('./src/main.mjs', undefined, false);", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-native-load.ts", source: "import { _load as nativeLoad } from 'module'; export const hidden = nativeLoad('./src/main.mjs', undefined, false);", selected: false, mainPath: "src/main.mjs" },
+ { path: "hidden-native-prototype.mjs", source: "import M from 'node:module'; const loader = M.prototype.require; export const hidden = loader.call({filename: import.meta.filename}, './src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "prototype" },
+ { path: "hidden-native-member.ts", source: "import { _resolveFilename as resolveName } from 'node:module'; export const hidden = resolveName('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "_resolveFilename" },
+ { path: "hidden-global-getter.mjs", source: "const get = process.getBuiltinModule; const M = get('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+ { path: "hidden-global-getter.ts", source: "const M = process.getBuiltinModule('module'); const r = M.createRequire(import.meta.url); export const hidden = r('./src/main.mjs');", selected: false, mainPath: "src/main.mjs", unmodeled: "process.getBuiltinModule" },
+];
+for (const { path, source, selected, barrel, mainPath = "src/main.ts", unmodeled } of cases) test(`qualified ${path} createRequire source cannot hide its CommonJS dependency`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-node-loader-"));
+ const git = (...args: string[]): void => {
+ const command = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (command.exitCode !== 0) throw new Error(new TextDecoder().decode(command.stderr));
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, mainPath), "export function main() { return 1; }\n");
+ writeFileSync(join(root, path!), source!);
+ if (barrel !== undefined) writeFileSync(join(root, "barrel.mjs"), barrel);
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*.ts", "src/**/*.mjs", ...(selected ? [path!] : [])], languages: { typescript: "on", javascript: "on" } });
+ indexRepository(root, "2026-10-09T10:00:00.000Z");
+ writeFileSync(join(root, mainPath), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-09T10:01:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ const cli = Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ path, selected, admission: report.analysisAdmission?.status, cliExit: cli.exitCode }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons).toContain(unmodeled === undefined
+ ? `DEPENDENCY_SCOPE_COMMONJS_UNSUPPORTED:${barrel === undefined ? path : "barrel.mjs"}`
+ : `DEPENDENCY_SCOPE_NATIVE_MODULE_MEMBER_UNSUPPORTED:${path}:${unmodeled}`);
+ expect(cli.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-current-admission-guards.test.ts b/packages/app-services/test/qualified-current-admission-guards.test.ts
new file mode 100644
index 00000000..95e539bf
--- /dev/null
+++ b/packages/app-services/test/qualified-current-admission-guards.test.ts
@@ -0,0 +1,88 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const unsupported = "function helper() { return true; } Object.defineProperty(Array, Symbol.hasInstance, { value: helper }); export function run(value) { return value instanceof Array; }\n";
+const supported = unsupported.replace("Symbol.hasInstance", "'metadata'");
+function git(root: string, ...args: string[]): void {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+}
+function repository(files: Record): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-current-admission-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ mkdirSync(join(root, "test"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ for (const [path, source] of Object.entries(files)) writeFileSync(join(root, path), source);
+ git(root, "init", "-q", "-b", "main");
+ git(root, "config", "core.autocrlf", "false");
+ git(root, "add", ".");
+ git(root, "commit", "-q", "-m", "base");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*", "test/**/*"] });
+ return root;
+}
+function refreshed(root: string, expected: string[]) {
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const observed = runVerify(root, { kind: "working-tree" });
+ console.info(JSON.stringify({ expected, report: observed.report }));
+ expect(VerifyReportSchema.safeParse(observed.report).success).toBe(true);
+ const admission = observed.report.analysisAdmission!;
+ expect(admission.indexFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission.controlFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission.checkFreshness).toEqual({ status: "current", reasons: [] });
+ expect(admission.changeCoverage.expected).toEqual(expected);
+ expect(admission.changeCoverage.analyzed).toEqual(expected);
+ return observed;
+}
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+
+test("current protocol refusal preserves its precise diagnostic and current binding", () => {
+ const root = repository({ "src/main.ts": "export function main() { return 1; }\n", "src/subject.ts": unsupported });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ const observed = refreshed(root, ["src/main.ts"]);
+ expect(observed.report.verdict).toBe("BLOCK");
+ expect(observed.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed.report.analysisAdmission?.reasons).toEqual(["DEPENDENCY_SCOPE_CONFIGURATION:src/subject.ts:SOURCE_PROTOCOL_DESCRIPTOR_UNSUPPORTED"]);
+ expect(observed.report.analysisAdmission?.binding).toEqual({ status: "valid", reasons: [] });
+}, 60_000);
+
+test("otherwise admitted current source cannot erase an unsupported immutable HEAD subject", () => {
+ const root = repository({ "src/main.ts": "export function main() { return 1; }\n", "src/subject.ts": unsupported });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ writeFileSync(join(root, "src/subject.ts"), supported);
+ const observed = refreshed(root, ["src/main.ts", "src/subject.ts"]);
+ const reason = "QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED:src/main.ts, src/subject.ts:DEPENDENCY_SCOPE_CONFIGURATION:src/subject.ts:SOURCE_PROTOCOL_DESCRIPTOR_UNSUPPORTED";
+ expect(observed.report.verdict).toBe("BLOCK");
+ expect(observed.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed.report.analysisAdmission?.reasons).toEqual([reason]);
+ expect(observed.report.analysisAdmission?.binding).toEqual({ status: "invalid", reasons: [reason] });
+}, 60_000);
+
+test("admitted current source still recovers removed invariant and security guards", () => {
+ const root = repository({ "src/main.ts": "/** @invariant fixed: stays fixed\n * @tag security */\nexport function main() { return 1; }\n" });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const observed = refreshed(root, ["src/main.ts"]);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.report.analysisAdmission?.reasons).toEqual([]);
+ expect(observed.report.analysisAdmission?.binding).toEqual({ status: "valid", reasons: [] });
+ expect(observed.report.verdict).toBe("BLOCK");
+ expect(observed.result.findings.map(finding => finding.rule)).toContain("invariant_touched_without_test");
+ expect(observed.result.findings.map(finding => finding.rule)).toContain("security_surface_without_verification");
+}, 60_000);
+
+test("admitted body replacement retains unchanged guards and its covering test", () => {
+ const guard = "/** @invariant fixed: stays fixed */\n";
+ const root = repository({ "src/main.ts": `${guard}export function main() { return 1; }\n`, "test/main.test.ts": 'import { expect, test } from "bun:test";\nimport { main } from "../src/main";\ntest("main", () => expect(main()).toBe(1));\n' });
+ writeFileSync(join(root, "src/main.ts"), `${guard}export function main() { return 2; }\n`);
+ const observed = refreshed(root, ["src/main.ts", "test/main.test.ts"]);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.report.analysisAdmission?.reasons).toEqual([]);
+ expect(observed.report.analysisAdmission?.binding).toEqual({ status: "valid", reasons: [] });
+ expect(observed.report.verdict).not.toBe("BLOCK");
+}, 60_000);
diff --git a/packages/app-services/test/qualified-date-cluster-document-write.test.ts b/packages/app-services/test/qualified-date-cluster-document-write.test.ts
new file mode 100644
index 00000000..ea3e7854
--- /dev/null
+++ b/packages/app-services/test/qualified-date-cluster-document-write.test.ts
@@ -0,0 +1,39 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, body, changedName, reason] of [
+ ["Date valueOf", "import {helper} from './helper.EXT';const value={valueOf:helper};export function run(){return new Date(value);}", "helper", "SOURCE_COERCION_UNSUPPORTED"],
+ ["cluster executable", "import cluster from 'node:cluster';export function run(){cluster.setupPrimary({exec:'./src/hidden.EXT'});return cluster.fork();}", "hidden", "SOURCE_PROCESS_LAUNCH_UNSUPPORTED"],
+ ["document.write", "export function run(){document.write('');}", "hidden", "SOURCE_DOCUMENT_WRITE_UNSUPPORTED"],
+ ["document.writeln", "export function run(){document.writeln('');}", "hidden", "SOURCE_DOCUMENT_WRITE_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "mjs"]) for (const selection of ["selected", "excluded"]) test(`qualified ${name} ${extension} ${selection} rejects a hidden source change`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-date-cluster-write-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const changed = join(root, `src/${changedName}.${extension}`);
+ writeFileSync(changed, "export function helper(){return 1;}");
+ writeFileSync(join(root, `src/consumer.${extension}`), body.replaceAll("EXT", extension));
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selection === "selected" ? ["src/**/*"] : [`src/${changedName}.${extension}`], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(changed, "export function helper(){return 2;}");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, extension, selection, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect([...report.analysisAdmission?.reasons ?? [], ...report.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(item => item.includes(reason))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-decorated-destructured.test.ts b/packages/app-services/test/qualified-decorated-destructured.test.ts
new file mode 100644
index 00000000..472c43bc
--- /dev/null
+++ b/packages/app-services/test/qualified-decorated-destructured.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "function logged(value: unknown) { return value; } @logged export class View {}", "DECORATOR_UNSUPPORTED"],
+ ["js", "function logged(value) { return value; } @logged export class View {}", "DECORATOR_UNSUPPORTED"],
+ ["ts", "const [helper] = [() => 1]; export { helper };", "DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED"],
+ ["js", "const [helper] = [() => 1]; export { helper };", "DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-default-expression.test.ts b/packages/app-services/test/qualified-default-expression.test.ts
new file mode 100644
index 00000000..afc98916
--- /dev/null
+++ b/packages/app-services/test/qualified-default-expression.test.ts
@@ -0,0 +1,34 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+for (const extension of ["mjs", "ts"]) for (const expression of ["((value) => value + 1)", "(function (value) { return value + 1; })", "(true ? (() => 1) : (() => 2))"]) {
+ test(`unmodeled default function expression refuses ${extension}: ${expression}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-default-expression-")); roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const leaf = `src/module.${extension}`;
+ writeFileSync(join(root, leaf), `export default ${expression};\n`);
+ writeFileSync(join(root, "src/entry.ts"), `import run from './module.${extension}'; export function entry() { return run(1); }\n`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, leaf), `export default ${expression.replace("+ 1", "+ 2")};\n`);
+ const indexed = indexRepository(root, "2026-10-10T10:00:00.000Z");
+ expect(indexed.analysis.graph.nodes.some(node => node.filePath === leaf && node.name === "default")).toBe(false);
+ expect(indexed.analysis.graph.edges.filter(edge => edge.kind === "calls")).toEqual([]);
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("DEFAULT_EXPRESSION_UNSUPPORTED"))).toBe(true);
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ }, 60_000);
+}
diff --git a/packages/app-services/test/qualified-exclude-authority.test.ts b/packages/app-services/test/qualified-exclude-authority.test.ts
new file mode 100644
index 00000000..02f11caa
--- /dev/null
+++ b/packages/app-services/test/qualified-exclude-authority.test.ts
@@ -0,0 +1,34 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, loadConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+
+for (const authority of ["info/exclude", "core.excludesFile"]) test(`retained untracked source hidden by ${authority}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-exclude-authority-"));
+ const git = (...args: string[]): string => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout);
+ };
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ if (authority === "info/exclude") writeFileSync(join(root, ".git/info/exclude"), "src/new.ts\n");
+ else { const path = join(root, ".git/owned-excludes"); writeFileSync(path, "src/new.ts\n"); git("config", "--local", "core.excludesFile", path); }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ writeFileSync(join(root, "src/new.ts"), "export function added() { return 1; }\n");
+ expect(git("ls-files", "--others", "--exclude-standard", "-z")).not.toContain("src/new.ts");
+ expect(captureQualifiedAnalysisInputs(loadConfig(root)).files.some(file => file.path === "src/new.ts")).toBe(true);
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ authority, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toContain("QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:src/new.ts");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-ignored-retained-input.test.ts b/packages/app-services/test/qualified-ignored-retained-input.test.ts
new file mode 100644
index 00000000..7d719acc
--- /dev/null
+++ b/packages/app-services/test/qualified-ignored-retained-input.test.ts
@@ -0,0 +1,41 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, loadConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+
+for (const tracked of [false,true]) test(`ignored retained input tracked=${tracked}`, () => {
+ const root=mkdtempSync(join(tmpdir(),"semctx-ignored-retained-"));
+ const git=(...args:string[]):string=>{
+ const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});
+ if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout);
+ };
+ try {
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\nsrc/hidden.ts\n");
+ writeFileSync(join(root,"src/main.ts"),"export function main(){return 1;}\n");
+ git("init","-q");git("add",".");
+ if(tracked){writeFileSync(join(root,"src/hidden.ts"),"export function hidden(){return 1;}\n");git("add","-f","src/hidden.ts");}
+ git("-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture");
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:["src/**/*"]});
+ if(!tracked)writeFileSync(join(root,"src/hidden.ts"),"export function hidden(){return 1;}\n");
+ writeFileSync(join(root,"src/main.ts"),"export function main(){return 2;}\n");
+ expect(captureQualifiedAnalysisInputs(loadConfig(root)).files.some(file=>file.path==="src/hidden.ts")).toBe(true);
+ expect(git("ls-files","--others","--exclude-per-directory=.gitignore","-z")).not.toContain("src/hidden.ts");
+ expect(git("ls-files","--others","-z").includes("src/hidden.ts")).toBe(!tracked);
+ indexRepository(root,"2026-10-12T01:00:00.000Z");
+ const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({tracked,admission:report.analysisAdmission}));
+ expect(report.analysisAdmission?.status).toBe(tracked?"admitted":"rejected");
+ expect(report.analysisAdmission?.binding.status).toBe(tracked?"valid":"invalid");
+ if(!tracked){
+ expect(report.analysisAdmission?.binding.reasons).toEqual(["QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:src/hidden.ts"]);
+ const cli=join(import.meta.dir,"../../../apps/cli/src/index.ts");
+ const result=Bun.spawnSync([process.execPath,cli,"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});
+ expect(result.exitCode).toBe(3);
+ }
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-implicit-protocols.test.ts b/packages/app-services/test/qualified-implicit-protocols.test.ts
new file mode 100644
index 00000000..32842740
--- /dev/null
+++ b/packages/app-services/test/qualified-implicit-protocols.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { const [value] = values; return value; }", "ITERATION_UNSUPPORTED"],
+ ["js", "const values = { *[Symbol.iterator]() { yield 1; } }; export function* start() { yield* values; }", "ITERATION_UNSUPPORTED"],
+ ["ts", "const value = { then(resolve) { resolve(1); } }; export async function start() { return await value; }", "AWAIT_UNSUPPORTED"],
+ ["js", "const matcher = { [Symbol.hasInstance](value) { return !!value; } }; export function start(value) { return value instanceof matcher; }", "INSTANCEOF_UNSUPPORTED"],
+ ["tsx", "export function view() { return ; }", "NAMED_CONFIGURATION_UNSUPPORTED"],
+ ["ts", "const thenable = { then() { return 1; } }; function load() { return thenable; } async function native() { return 1; } export async function start() { return await (load as typeof native)(); }", "AWAIT_UNSUPPORTED"],
+ ["ts", "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { const [[value]] = [values]; return value; }", "ITERATION_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ if (reason === "NAMED_CONFIGURATION_UNSUPPORTED") writeFileSync(join(root, "tsconfig.app.json"), '{"compilerOptions":{"jsx":"react-jsx"}}');
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-in-global-descriptor.test.ts b/packages/app-services/test/qualified-in-global-descriptor.test.ts
new file mode 100644
index 00000000..cc7dd37d
--- /dev/null
+++ b/packages/app-services/test/qualified-in-global-descriptor.test.ts
@@ -0,0 +1,33 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, body, reason] of [
+ ["in key", "const key={toString:helper};const target={x:1};export function run(){return key in target;}", "SOURCE_COERCION_UNSUPPORTED"],
+ ["global descriptor", "Object.defineProperty(globalThis,'Math',{value:{random:helper}});export function run(){return Math.random();}", "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED"],
+] as const) for (const extension of ["ts","mjs"]) for (const selection of extension === "mjs" ? ["selected","excluded"] : ["selected"]) test(`qualified helper change ${name} ${extension} ${selection}`, () => {
+ const root=mkdtempSync(join(tmpdir(),"semctx-in-descriptor-"));
+ try {
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ const helper=join(root,`src/helper.${extension}`);
+ writeFileSync(helper,"export function helper(){return 1;}");
+ writeFileSync(join(root,`src/consumer.${extension}`),`import {helper} from './helper.${extension}';${body}`);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]) {
+ const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selection === "selected" ? ["src/**/*"] : [`src/helper.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");
+ const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({name,extension,selection,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect([...report.analysisAdmission?.reasons ?? [],...report.analysisAdmission?.changeCoverage.files.flatMap(file=>file.reasons) ?? []].some(item=>item.includes(reason))).toBe(true);
+ const cli=join(import.meta.dir,"../../../apps/cli/src/index.ts");
+ const result=Bun.spawnSync([process.execPath,cli,"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});
+ expect(result.exitCode).toBe(3);
+ } finally {rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-inert-location-navigation.test.ts b/packages/app-services/test/qualified-inert-location-navigation.test.ts
new file mode 100644
index 00000000..df6daa9e
--- /dev/null
+++ b/packages/app-services/test/qualified-inert-location-navigation.test.ts
@@ -0,0 +1,33 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const selected of [true, false]) test(`qualified data navigation ${extension} selected ${selected} refuses hidden execution`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inert-navigation-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, `src/hidden.${extension}`), "export function hidden(){return 1;}");
+ writeFileSync(join(root, `src/consumer.${extension}`),
+ `export function run(){location.href="data:text/html,";}`);
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selected ? ["src/**/*"] : [`src/hidden.${extension}`], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, `src/hidden.${extension}`), "export function hidden(){return 2;}");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect([...report.analysisAdmission?.reasons ?? [], ...report.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(reason => reason.includes("SCRIPT_NAVIGATION_UNSUPPORTED"))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-inline-graph-binding.test.ts b/packages/app-services/test/qualified-inline-graph-binding.test.ts
new file mode 100644
index 00000000..ddf32966
--- /dev/null
+++ b/packages/app-services/test/qualified-inline-graph-binding.test.ts
@@ -0,0 +1,62 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { createPrimitiveSafeConsumer } from "../../../scripts/modelo-static-fixture";
+
+const git = (root: string, ...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+};
+test("same indexed dirty source owns its inline edge; stale same-coordinate graph cannot prove a new value escape", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inline-seal-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}');
+ const main = join(root, "src/main.ts");
+ writeFileSync(main, "import { register } from 'opaque'; import { helper } from './helper'; export function main() { return register(() => helper()); }\n");
+ writeFileSync(join(root, "src/helper.ts"), "export function helper() { return 1; }\n");
+ writeFileSync(join(root, "src/main.test.ts"), "import { main } from './main'; import { helper } from './helper'; export function check() { helper(); return main(); }\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(join(root, "src/helper.ts"), "export function helper() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const positive = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ inlineDirtyPositive: positive.analysisAdmission }));
+ expect(positive.analysisAdmission?.status).toBe("admitted");
+ expect(positive.recommendedTests.some(test => test.file === "src/main.test.ts")).toBe(true);
+ // Same caller/callee coordinates and line: only the sealed source bytes distinguish
+ // this graph from the new callback body. A fresh registry must not borrow old edges.
+ writeFileSync(main, "import { register } from 'opaque'; import { helper } from './helper'; export function main() { return register(() => { return helper(); }); }\n");
+ const staleCalls = runVerify(root, { kind: "working-tree" }).report;
+ expect(staleCalls.analysisAdmission?.status).toBe("rejected");
+ expect(staleCalls.analysisAdmission?.reasons.some(reason => reason.includes("OPAQUE_CALLBACK_UNSUPPORTED"))).toBe(true);
+ indexRepository(root, "2026-10-11T10:00:30.000Z");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+ writeFileSync(main, "import { register } from 'opaque'; import { helper } from './helper'; export function main() { return register(() => helper); }\n");
+ const stale = runVerify(root, { kind: "working-tree" }).report;
+ expect(stale.analysisAdmission?.status).toBe("rejected");
+ expect(stale.analysisAdmission?.reasons.some(reason => reason.includes("OPAQUE_CALLBACK_UNSUPPORTED"))).toBe(true);
+ indexRepository(root, "2026-10-11T10:01:00.000Z");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("rejected");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("public primitive-safe consumer source bytes remain an explicit opaque-data refusal", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inline-public-"));
+ try {
+ createPrimitiveSafeConsumer(root);
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["suite/**/*"], languages: { typescript: "on", javascript: "on" } });
+ const path = join(root, "suite/domains/sample/core/value.mjs");
+ writeFileSync(path, "export function value(input) { return 2 + 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ publicConsumerAdmission: report.analysisAdmission }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-input-order.test.ts b/packages/app-services/test/qualified-input-order.test.ts
new file mode 100644
index 00000000..254a7420
--- /dev/null
+++ b/packages/app-services/test/qualified-input-order.test.ts
@@ -0,0 +1,18 @@
+import { expect, spyOn, test } from "bun:test";
+import * as fs from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+test("qualified retained traversal orders source bytes independently of filesystem enumeration", () => {
+ const root = fs.mkdtempSync(join(tmpdir(), "semctx-input-order-"));
+ expect(Bun.spawnSync(["git", "init", "-q"], { cwd: root }).exitCode).toBe(0);
+ fs.writeFileSync(join(root, "a.ts"), "export {}; "); fs.writeFileSync(join(root, "z.ts"), "export {}; ");
+ const original = fs.readdirSync;
+ const enumeration = spyOn(fs, "readdirSync").mockImplementation(((...args: Parameters) => {
+ const result = original(...args);
+ return [...result].reverse();
+ }) as typeof fs.readdirSync);
+ try { expect(captureQualifiedAnalysisInputs(createGlobSelectionConfig(root)).files.map(file => file.path)).toEqual(["a.ts", "z.ts"]); }
+ finally { enumeration.mockRestore(); fs.rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/app-services/test/qualified-internal-member-target.test.ts b/packages/app-services/test/qualified-internal-member-target.test.ts
new file mode 100644
index 00000000..5c270045
--- /dev/null
+++ b/packages/app-services/test/qualified-internal-member-target.test.ts
@@ -0,0 +1,24 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+for(const extension of ["ts","mjs"])for(const selection of ["selected","excluded"])test(`qualified erased internal member ${extension} ${selection}`,()=>{
+ const root=mkdtempSync(join(tmpdir(),"semctx-internal-member-"));
+ try{
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ const helper=join(root,`src/helper.${extension}`);writeFileSync(helper,"export function helper(){return 1;}");
+ writeFileSync(join(root,`src/consumer.${extension}`),`import {helper} from './helper.${extension}';const holder${extension==="ts"?":any":""}={};holder.invoke=helper;export function run(){return holder.invoke();}`);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for(const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]){
+ const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selection==="selected"?["src/**/*"]:[`src/helper.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");
+ const report=runVerify(root,{kind:"working-tree"}).report;console.info(JSON.stringify({extension,selection,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const result=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(result.exitCode).toBe(3);
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-iterator-mutation.test.ts b/packages/app-services/test/qualified-iterator-mutation.test.ts
new file mode 100644
index 00000000..1eb5721f
--- /dev/null
+++ b/packages/app-services/test/qualified-iterator-mutation.test.ts
@@ -0,0 +1,28 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified iterator mutation ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-iterator-mutation-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`;
+ writeFileSync(join(root, path), "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; const key = Symbol.iterator; values[key] = helper; export function start() { return [...values]; }");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("ITERATOR_MUTATION_UNSUPPORTED"))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(reason => reason.includes("ITERATOR_MUTATION_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-jsdoc-scope.test.ts b/packages/app-services/test/qualified-jsdoc-scope.test.ts
new file mode 100644
index 00000000..4ab1c5d6
--- /dev/null
+++ b/packages/app-services/test/qualified-jsdoc-scope.test.ts
@@ -0,0 +1,27 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const doc of ["/** @type {import('./src/main.ts').Value} */", "/** @import { Value } from './src/main.ts' */"]) test(`excluded semantic JSDoc dependency rejects qualified closure: ${doc}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-jsdoc-scope-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export class Value { value = 1; }\n");
+ writeFileSync(join(root, "hidden.mjs"), `${doc}\nexport const hidden = 1;\n`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export class Value { value = 2; }\n");
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("JSDOC_IMPORT_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-location-navigation.test.ts b/packages/app-services/test/qualified-location-navigation.test.ts
new file mode 100644
index 00000000..ecd8cad5
--- /dev/null
+++ b/packages/app-services/test/qualified-location-navigation.test.ts
@@ -0,0 +1,29 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const selected of [true, false]) test(`qualified script navigation ${extension} ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-location-navigation-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, "src/hidden.js"); writeFileSync(hidden, "export function helper(){return 1;}\n");
+ writeFileSync(join(root, `src/consumer.${extension}`), "export function run(){location.href=\"javascript:import('./hidden.js')\";}\n");
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}\n`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selected ? ["src/**/*"] : ["src/hidden.js"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function helper(){return 2;}\n"); indexRepository(root, "2026-10-12T04:40:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ extension, selected, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-math-coercion.test.ts b/packages/app-services/test/qualified-math-coercion.test.ts
new file mode 100644
index 00000000..c774e066
--- /dev/null
+++ b/packages/app-services/test/qualified-math-coercion.test.ts
@@ -0,0 +1,22 @@
+import {expect,test} from "bun:test";
+import {mkdtempSync,mkdirSync,rmSync,writeFileSync} from "node:fs";
+import {tmpdir} from "node:os";
+import {join} from "node:path";
+import {createGlobSelectionConfig} from "@semantic-context/core";
+import {initWorkspace} from "@semantic-context/repository-store";
+import {indexRepository,runVerify} from "../src";
+for(const extension of ["ts","mjs"])for(const selected of [true,false])test(`qualified Math.max ${extension} ${selected}`,()=>{
+ const root=mkdtempSync(join(tmpdir(),"semctx-math-coercion-"));
+ try{
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ const helper=join(root,`src/helper.${extension}`);writeFileSync(helper,"export function helper(){return 1;}");
+ writeFileSync(join(root,`src/consumer.${extension}`),`import {helper} from './helper.${extension}';const value={valueOf:helper};export function run(){return Math.max(value${extension==="ts"?" as any":""},0);}`);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for(const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]){const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));}
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selected?["src/**/*"]:[`src/helper.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({extension,selected,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(cli.exitCode).toBe(3);
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-migration-jsx.test.ts b/packages/app-services/test/qualified-migration-jsx.test.ts
new file mode 100644
index 00000000..3d426f9e
--- /dev/null
+++ b/packages/app-services/test/qualified-migration-jsx.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [path, content, include, expected] of [
+ ["migrations/step.ts", "export function step() { return 1; }", ["**/*"], "MIGRATION_SOURCE_UNSUPPORTED"],
+ ["migrations/step.tsx", "export function step() { return 1; }", ["**/*"], "MIGRATION_SOURCE_UNSUPPORTED"],
+ ["migrations/step.ts", "export function step() { return 1; }", ["src/**/*"], "MIGRATION_SOURCE_UNSUPPORTED"],
+ ["src/view.tsx", "export const view = ;", ["**/*"], "AUTOMATIC_JSX_RUNTIME_UNSUPPORTED"],
+ ["hidden.jsx", "export const view = ;", ["src/**/*"], "AUTOMATIC_JSX_RUNTIME_UNSUPPORTED"],
+] as const) test(`qualified scope refuses unmodeled ${path} with ${include.join(",")}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-mode-"));
+ try {
+ mkdirSync(join(root, "src")); mkdirSync(dirname(join(root, path)), { recursive: true });
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, path), content);
+ writeFileSync(join(root, "tsconfig.json"), JSON.stringify({ compilerOptions: { jsx: "react-jsx", jsxImportSource: "@fixture/runtime" } }));
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: [...include], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, observed: report.analysisAdmission?.status }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes(expected))).toBe(true);
+ if (include[0] === "**/*" && path.startsWith("migrations")) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons).toContain("SOURCE_MIGRATION_SOURCE_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-nonregular-inputs.test.ts b/packages/app-services/test/qualified-nonregular-inputs.test.ts
new file mode 100644
index 00000000..6633a727
--- /dev/null
+++ b/packages/app-services/test/qualified-nonregular-inputs.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, statSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createServer } from "node:net";
+import { createGlobSelectionConfig, SemctxError } from "@semantic-context/core";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+
+for (const kind of ["socket", "fifo"] as const) test.skipIf(process.platform === "win32")(`qualified input refuses real Unix ${kind} before reading`, async () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-nonregular-")); const path = join(root, "events.json");
+ const server = createServer(); let listening = false;
+ try {
+ expect(Bun.spawnSync(["git", "init", "-q"], { cwd: root }).exitCode).toBe(0);
+ if (kind === "socket") {
+ await new Promise((resolve, reject) => { server.once("error", reject); server.listen(path, () => { listening = true; resolve(); }); });
+ expect(statSync(path).isSocket()).toBe(true);
+ } else {
+ expect(Bun.spawnSync(["mkfifo", path]).exitCode).toBe(0); expect(statSync(path).isFIFO()).toBe(true);
+ }
+ expect(statSync(path).isFile()).toBe(false);
+ const config = { ...createGlobSelectionConfig(root), analysisProfile: "modelo-suite-static-v1" as const, selectionMode: "qualified-static-v1" as const };
+ let observed: unknown;
+ try { captureQualifiedAnalysisInputs(config); } catch (error) { observed = error; }
+ expect(observed).toBeInstanceOf(SemctxError);
+ expect((observed as SemctxError).code).toBe("INVALID_TASK_INPUT");
+ expect((observed as Error).message).toContain("regular file");
+ } finally {
+ if (listening) await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
+ rmSync(root, { recursive: true, force: true });
+ }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-number-locale-coercion.test.ts b/packages/app-services/test/qualified-number-locale-coercion.test.ts
new file mode 100644
index 00000000..9bd21be9
--- /dev/null
+++ b/packages/app-services/test/qualified-number-locale-coercion.test.ts
@@ -0,0 +1,22 @@
+import {expect,test} from "bun:test";
+import {mkdtempSync,mkdirSync,rmSync,writeFileSync} from "node:fs";
+import {tmpdir} from "node:os";
+import {join} from "node:path";
+import {createGlobSelectionConfig} from "@semantic-context/core";
+import {initWorkspace} from "@semantic-context/repository-store";
+import {indexRepository,runVerify} from "../src";
+for(const kind of ["Number","locale"])for(const extension of ["ts","mjs"])for(const selected of [true,false])test(`qualified ${kind} ${extension} ${selected}`,()=>{
+ const root=mkdtempSync(join(tmpdir(),"semctx-number-locale-"));
+ try{
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ const helper=join(root,`src/helper.${extension}`);writeFileSync(helper,"export function helper(){return 1;}");
+ writeFileSync(join(root,`src/consumer.${extension}`),`import {helper} from './helper.${extension}';const value={${kind==="Number"?"valueOf":"toLocaleString"}:helper};export function run(){return ${kind==="Number"?"Number(value)":"[value].toLocaleString()"};}`);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for(const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]){const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));}
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selected?["src/**/*"]:[`src/helper.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({kind,extension,selected,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(cli.exitCode).toBe(3);
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-opaque-data-serialization.test.ts b/packages/app-services/test/qualified-opaque-data-serialization.test.ts
new file mode 100644
index 00000000..4d4209ff
--- /dev/null
+++ b/packages/app-services/test/qualified-opaque-data-serialization.test.ts
@@ -0,0 +1,56 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const git = (root: string, ...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+};
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) for (const [name, content] of [
+ ["nested callback", "import { register } from 'opaque'; function helper() { return 1; } export function run() { return register({ handler: helper }); }"],
+ ["toJSON", "import { value } from './value'; export function run() { return JSON.stringify(value); }"],
+] as const) test(`qualified ${name} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-data-serialization-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}');
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const prefix = selected ? "src/" : "";
+ writeFileSync(join(root, `${prefix}subject.${extension}`), content);
+ writeFileSync(join(root, `${prefix}value.${extension}`), "export const value = { toJSON() { return 1; } };\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, extension, selected, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(reason => reason.includes("UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
+
+for (const [name, qualified, extra, expected] of [["qualified untracked", true, true, "rejected"], ["qualified tracked-only with ignored output", true, false, "admitted"], ["qualified ignored authored source", true, false, "rejected"], ["ordinary untracked", false, true, undefined]] as const) test(name, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-untracked-obligation-"));
+ try {
+ const ignoredOutput = name === "qualified tracked-only with ignored output";
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ignoredOutput ? ".semctx/\nnode_modules/\nbuild/\n" : ".semctx/\nnode_modules/\nignored.ts\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), ...(qualified ? { selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const } : {}), include: ["src/**/*"] });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ if (extra) writeFileSync(join(root, "src/new.ts"), "export function extra() { return 1; }\n");
+ if (ignoredOutput) mkdirSync(join(root, "build"));
+ writeFileSync(join(root, ignoredOutput ? "build/ignored.ts" : "ignored.ts"), "export function ignored() { return 1; }\n");
+ mkdirSync(join(root, ".semctx/reports")); writeFileSync(join(root, ".semctx/reports/ignored.json"), "{}");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, admission }));
+ expect(admission?.status).toBe(expected);
+ if (qualified && extra) expect(admission?.reasons).toContain("QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:src/new.ts");
+ if (name === "qualified ignored authored source") expect(admission?.binding).toEqual({ status: "invalid", reasons: ["QUALIFIED_WORKING_TREE_UNTRACKED_INPUT:ignored.ts"] });
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-plural-descriptor-regexp.test.ts b/packages/app-services/test/qualified-plural-descriptor-regexp.test.ts
new file mode 100644
index 00000000..e6fdb201
--- /dev/null
+++ b/packages/app-services/test/qualified-plural-descriptor-regexp.test.ts
@@ -0,0 +1,30 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "js"]) for (const [name, content] of [
+ ["plural iterator descriptor", "function* helper() { yield 7; } const values = [1]; Object.defineProperties(values, { [Symbol.iterator]: { value: helper } }); export function run() { return [...values]; }"],
+ ["create getter descriptor", "function helper() { return 7; } const box = Object.create({}, { value: { get: helper } }); export function run() { return box.value; }"],
+ ["RegExp hook", "function helper() { return 7; } const matcher = {}; matcher[Symbol.match] = helper; export function run() { return 'x'.match(matcher); }"],
+] as const) test(`qualified ${name} ${extension}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-descriptor-hook-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, `src/subject.${extension}`), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, extension, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(reason => reason.includes("DESCRIPTOR_UNSUPPORTED") || reason.includes("PROTOCOL_MUTATION_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-post-image-failure.test.ts b/packages/app-services/test/qualified-post-image-failure.test.ts
new file mode 100644
index 00000000..22f3f2da
--- /dev/null
+++ b/packages/app-services/test/qualified-post-image-failure.test.ts
@@ -0,0 +1,191 @@
+import { afterEach, expect, spyOn, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { __setVerificationAttributeBarrierForTesting } from "../src/verification-state";
+import { __setVerifyControlBarrierForTesting } from "../src/verify";
+
+const roots: string[] = [];
+
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "-c", "commit.gpgsign=false", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+
+function repository(removal = false): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-post-image-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main(){return 1;}\n");
+ writeFileSync(join(root, ".gitattributes"), "src/*.ts filter=broken\n");
+ git(root, "init", "-q"); git(root, "config", "core.autocrlf", "false");
+ git(root, "add", "."); git(root, "commit", "-qm", "base");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ if (removal) writeFileSync(join(root, "src/main.ts"), "export function main(){return 2;}\n");
+ else writeFileSync(join(root, "src/added.ts"), "export function added(){return 2;}\n");
+ git(root, "add", "."); git(root, "commit", "-qm", "head");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ return root;
+}
+
+function failSelectedConversion(root: string): { attempts: () => number; failure: () => { exitCode: number; stderr: string } | undefined; restore: () => void } {
+ const previous = Bun.spawnSync;
+ const native = previous.bind(Bun);
+ const head = native(["git", "rev-parse", "HEAD"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (head.exitCode !== 0) throw Error(new TextDecoder().decode(head.stderr));
+ const attributeSource = `--attr-source=${new TextDecoder().decode(head.stdout).trim()}`;
+ let attempts = 0;
+ let failure: { exitCode: number; stderr: string } | undefined;
+ Bun.spawnSync = ((command: string[], options: Parameters[1]) => {
+ if (command[0] === "git" && command[1] === attributeSource && command[2] === "hash-object") {
+ attempts++;
+ const result = native(["git", "-c", "filter.broken.clean=false", "-c", "filter.broken.required=true", ...command.slice(1)], options);
+ failure = { exitCode: result.exitCode, stderr: new TextDecoder().decode(result.stderr) };
+ return result;
+ }
+ return native(command, options);
+ }) as typeof Bun.spawnSync;
+ return { attempts: () => attempts, failure: () => failure, restore: () => { Bun.spawnSync = previous; } };
+}
+
+function invalidateAddedStatCache(root: string): void {
+ const path = join(root, "src/added.ts");
+ const stat = statSync(path);
+ utimesSync(path, stat.atime, new Date(stat.mtimeMs + 2_000));
+}
+
+function observed(root: string): { report: ReturnType["report"] | undefined; failure: unknown } {
+ let report: ReturnType["report"] | undefined;
+ let failure: unknown;
+ try { report = runVerify(root, { kind: "range", base: "HEAD^" }).report; } catch (error) { failure = error; }
+ return { report, failure };
+}
+
+afterEach(() => {
+ __setVerificationAttributeBarrierForTesting(undefined);
+ __setVerifyControlBarrierForTesting(undefined);
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+test("clean pure addition retains qualified admission", () => {
+ const { report, failure } = observed(repository());
+ expect(failure).toBeUndefined();
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report?.analysisAdmission?.status).toBe("admitted");
+ expect(report?.analysisAdmission?.binding.status).toBe("valid");
+ expect(report?.analysisAdmission?.indexFreshness.verdict).toBe("FRESH");
+ expect(report?.analysisAdmission?.changeCoverage.analyzed).toEqual(["src/added.ts"]);
+});
+
+test("attribute drift during retained comparison returns structured invalid binding", () => {
+ const root = repository();
+ __setVerifyControlBarrierForTesting(() => __setVerificationAttributeBarrierForTesting(() => writeFileSync(join(root, ".git/info/attributes"), "src/*.ts ident\n")));
+ const { report, failure } = observed(root);
+ console.info(JSON.stringify({ failure, report }));
+ expect(failure).toBeUndefined();
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report?.verdict).toBe("BLOCK");
+ expect(report?.analysisAdmission?.status).toBe("rejected");
+ expect(report?.analysisAdmission?.binding.status).toBe("invalid");
+ expect(report?.analysisAdmission?.binding.reasons).toContain("QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND");
+ expect(report?.analysisAdmission?.indexFreshness.verdict).toBe("FRESH");
+ expect(report?.analysisAdmission?.changeCoverage.analyzed).toEqual(["src/added.ts"]);
+ rmSync(join(root, ".git/info/attributes"));
+ const entry = join(root, ".git/attribute-cli.ts");
+ const module = (path: string): string => JSON.stringify(join(import.meta.dir, path).replaceAll("\\", "/"));
+ writeFileSync(entry, `import {writeFileSync} from "node:fs";
+import {join} from "node:path";
+import {__setVerifyControlBarrierForTesting} from ${module("../src/verify.ts")};
+import {__setVerificationAttributeBarrierForTesting} from ${module("../src/verification-state.ts")};
+import {runVerifyDiff} from ${module("../../../apps/cli/src/commands/verify.ts")};
+import {parseArgs} from ${module("../../../apps/cli/src/args.ts")};
+const root=process.argv[2]!;
+__setVerifyControlBarrierForTesting(()=>__setVerificationAttributeBarrierForTesting(()=>writeFileSync(join(root,".git/info/attributes"),"src/*.ts ident\\n")));
+process.exit(runVerifyDiff(root,parseArgs(["verify","diff","--base","HEAD^","--format","json","--fail-on","none"])));
+`);
+ const cli = Bun.spawnSync([process.execPath, entry, root], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ const cliReport: unknown = JSON.parse(new TextDecoder().decode(cli.stdout));
+ expect(VerifyReportSchema.safeParse(cliReport).success).toBe(true);
+ expect(cliReport).toMatchObject({ verdict: "BLOCK", analysisAdmission: { status: "rejected", binding: { status: "invalid", reasons: ["QUALIFIED_POST_IMAGE_ATTRIBUTES_UNBOUND"] } } });
+}, 60_000);
+
+test("native conversion fault injection returns rejected BLOCK and CLI exit 3 under advisory none", () => {
+ const root = repository();
+ invalidateAddedStatCache(root);
+ let conversion: ReturnType | undefined;
+ __setVerifyControlBarrierForTesting(() => { conversion = failSelectedConversion(root); });
+ let observation: ReturnType;
+ try { observation = observed(root); } finally { conversion?.restore(); }
+ const { report, failure } = observation;
+ expect(conversion?.attempts()).toBe(1);
+ expect(conversion?.failure()?.exitCode).toBeGreaterThan(0);
+ expect(conversion?.failure()?.stderr).toContain("clean filter 'broken' failed");
+ console.info(JSON.stringify({ failure, report }));
+ expect(failure).toBeUndefined();
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report?.verdict).toBe("BLOCK");
+ expect(report?.analysisAdmission?.status).toBe("rejected");
+ expect(report?.analysisAdmission?.binding.status).toBe("invalid");
+ expect(report?.analysisAdmission?.binding.reasons).toContain("QUALIFIED_POST_IMAGE_CONVERSION_FAILED");
+ expect(report?.analysisAdmission?.indexFreshness.verdict).toBe("FRESH");
+ expect(report?.analysisAdmission?.changeCoverage.analyzed).toEqual(["src/added.ts"]);
+ const entry = join(root, ".git/conversion-cli.ts");
+ const module = (path: string): string => JSON.stringify(join(import.meta.dir, path).replaceAll("\\", "/"));
+ writeFileSync(entry, `import {__setVerifyControlBarrierForTesting} from ${module("../src/verify.ts")};
+import {runVerifyDiff} from ${module("../../../apps/cli/src/commands/verify.ts")};
+import {parseArgs} from ${module("../../../apps/cli/src/args.ts")};
+${failSelectedConversion.toString()}
+const root=process.argv[2]!;
+let conversion: ReturnType | undefined;
+__setVerifyControlBarrierForTesting(()=>{conversion=failSelectedConversion(root);});
+let code: number;
+try { code=runVerifyDiff(root,parseArgs(["verify","diff","--base","HEAD^","--format","json","--fail-on","none"])); }
+finally { conversion?.restore(); }
+if(conversion?.attempts()!==1) throw Error("selected conversion was not exercised exactly once");
+if(!(conversion.failure()!.exitCode>0) || !conversion.failure()?.stderr.includes("clean filter 'broken' failed")) throw Error("native selected clean conversion did not fail");
+process.exit(code);
+`);
+ invalidateAddedStatCache(root);
+ const cli = Bun.spawnSync([process.execPath, entry, root], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ const cliReport: unknown = JSON.parse(new TextDecoder().decode(cli.stdout));
+ expect(VerifyReportSchema.safeParse(cliReport).success).toBe(true);
+ expect(cliReport).toMatchObject({ verdict: "BLOCK", analysisAdmission: { status: "rejected", binding: { status: "invalid", reasons: ["QUALIFIED_POST_IMAGE_CONVERSION_FAILED"] } } });
+}, 60_000);
+
+test("explicit unsupported base recovery is not preempted by selected conversion", () => {
+ const root = repository(true);
+ let conversion: ReturnType | undefined;
+ __setVerifyControlBarrierForTesting(() => { conversion = failSelectedConversion(root); });
+ let observation: ReturnType;
+ try { observation = observed(root); } finally { conversion?.restore(); }
+ const { report, failure } = observation;
+ expect(conversion?.attempts()).toBe(0);
+ expect(failure).toBeUndefined();
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report?.verdict).toBe("BLOCK");
+ expect(report?.analysisAdmission?.status).toBe("rejected");
+ expect(report?.analysisAdmission?.binding.reasons).toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(report?.analysisAdmission?.binding.reasons).not.toContain("QUALIFIED_POST_IMAGE_CONVERSION_FAILED");
+});
+
+
+test("Git below the selected attribute prerequisite stays an operational diagnostic", () => {
+ const root = repository();
+ const original = Bun.spawnSync.bind(Bun);
+ const spawn = spyOn(Bun, "spawnSync").mockImplementation(((command: string[], options: Parameters[1]) => {
+ if (command[0] === "git" && command[1] === "--version") return { exitCode: 0, stdout: Buffer.from("git version 2.40.0\n"), stderr: Buffer.alloc(0) };
+ return original(command, options);
+ }) as typeof Bun.spawnSync);
+ try {
+ const { report, failure } = observed(root);
+ expect(report).toBeUndefined();
+ expect(failure).toMatchObject({ code: "UNSUPPORTED", message: "qualified staged/range verification requires Git >= 2.41 (--attr-source)" });
+ } finally { spawn.mockRestore(); }
+});
diff --git a/packages/app-services/test/qualified-process-launcher.test.ts b/packages/app-services/test/qualified-process-launcher.test.ts
new file mode 100644
index 00000000..dbd6b689
--- /dev/null
+++ b/packages/app-services/test/qualified-process-launcher.test.ts
@@ -0,0 +1,69 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const git = (root: string, ...args: string[]): void => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+};
+for (const [name, content, reason] of [
+ ["builtin fork", "import { fork } from 'node:child_process'; export function run() { return fork('./hidden.js'); }", "PROCESS_LAUNCH_UNSUPPORTED"],
+ ["internal higher-order", "function invoke(cb) { return cb(); } function helper() { return 1; } export function run() { return invoke(helper); }", undefined],
+] as const) test(name, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-process-launch-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, "src/subject.ts"), content);
+ writeFileSync(join(root, "src/hidden.js"), "export function hidden() { return 1; }\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ if (name === "internal higher-order") writeFileSync(join(root, "src/subject.ts"), content.replace("function helper() { return 1; }", "function helper() { return 2; }"));
+ else writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, admission }));
+ expect(admission?.status).toBe("rejected");
+ if (reason !== undefined) expect(admission?.reasons.some(value => value.includes(reason))).toBe(true);
+ else {
+ expect(admission?.changeCoverage.expected).toContain("src/subject.ts");
+ expect(admission?.changeCoverage.files.some(file => file.path === "src/subject.ts" && file.reasons.some(value => value.includes("QUALIFIED_CALL_COORDINATE_CALLEE_MISSING")))).toBe(true);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
+
+test("native fork executes the owned repository target", async () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-native-fork-"));
+ try {
+ writeFileSync(join(root, "hidden.mjs"), "console.log('OWNED_CHILD_EXECUTED');\n");
+ const { fork } = await import("node:child_process");
+ const child = fork(join(root, "hidden.mjs"), [], { execPath: process.execPath, silent: true });
+ let output = "";
+ child.stdout?.on("data", chunk => { output += String(chunk); });
+ const code = await new Promise((resolve, reject) => { child.on("error", reject); child.on("close", resolve); });
+ expect(code).toBe(0); expect(output).toContain("OWNED_CHILD_EXECUTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 30_000);
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`fork closure ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-fork-closure-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const prefix = selected ? "src/" : "";
+ writeFileSync(join(root, `${prefix}subject.${extension}`), "import { fork } from 'node:child_process'; export function run() { return fork('./hidden.js'); }\n");
+ writeFileSync(join(root, `${prefix}hidden.js`), "export function hidden() { return 1; }\n");
+ git(root, "init", "-q"); git(root, "add", "."); git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ extension, selected, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(value => value.includes("PROCESS_LAUNCH_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-property-disposal.test.ts b/packages/app-services/test/qualified-property-disposal.test.ts
new file mode 100644
index 00000000..ef9c131e
--- /dev/null
+++ b/packages/app-services/test/qualified-property-disposal.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "function helper() {} const value: any = []; value.then = helper; export async function start() { await value; }", "AWAIT_UNSUPPORTED"],
+ ["js", "const key = { [Symbol.toPrimitive]() { return 'value'; } }; const target = { value: 1 }; export function start() { return target[key]; }", "PROPERTY_KEY_UNSUPPORTED"],
+ ["ts", "const disposable = { [Symbol.dispose]() {} }; export function start() { using resource = disposable; return 1; }", "DISPOSAL_UNSUPPORTED"],
+ ["cjs", "export function start() { return 1; }", "COMMONJS"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-protocol-definition.test.ts b/packages/app-services/test/qualified-protocol-definition.test.ts
new file mode 100644
index 00000000..9322c314
--- /dev/null
+++ b/packages/app-services/test/qualified-protocol-definition.test.ts
@@ -0,0 +1,29 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`helper change in direct protocol ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-defined-protocol-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const file = join(root, `${selected ? "src/" : ""}subject.${extension}`);
+ const content = "function helper() { return 1; } const matcher = { [Symbol.match]: helper }; export function run() { return 'x'.match(matcher); }\n";
+ writeFileSync(file, content);
+ if (!selected) writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ if (selected) writeFileSync(file, content.replace("return 1;", "return 2;"));
+ else writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ extension, selected, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(value => value.includes("PROTOCOL_DEFINITION_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-protocol-promise-opaque.test.ts b/packages/app-services/test/qualified-protocol-promise-opaque.test.ts
new file mode 100644
index 00000000..c17c051e
--- /dev/null
+++ b/packages/app-services/test/qualified-protocol-promise-opaque.test.ts
@@ -0,0 +1,98 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema, type VerifyReport } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+const cases = [
+ {
+ label: "PROTOCOL_DESCRIPTOR_UNSUPPORTED", role: "Object.defineProperty",
+ content: "function helper() { return true; } Object.defineProperty(Array, Symbol.hasInstance, { value: helper }); export function run(value) { return value instanceof Array; }",
+ control: "function helper() { return true; } Object.defineProperty(Array, 'metadata', { value: helper }); export function run(value) { return value instanceof Array; }",
+ },
+ {
+ label: "PROTOCOL_MUTATION_UNSUPPORTED", role: "Reflect.set",
+ content: "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Reflect.set(values, Symbol.iterator, helper); export function run() { return [...values]; }",
+ control: "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Reflect.set(values, 'metadata', helper); export function run() { return [...values]; }",
+ },
+ {
+ label: "PROTOCOL_DESCRIPTOR_UNSUPPORTED", role: "Reflect.defineProperty",
+ content: "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Reflect.defineProperty(values, Symbol.iterator, { value: helper }); export function run() { return [...values]; }",
+ control: "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Reflect.defineProperty(values, 'metadata', { value: helper }); export function run() { return [...values]; }",
+ },
+ {
+ label: "PROMISE_ASSIMILATION_UNSUPPORTED", role: "Promise.resolve",
+ content: "const value = { then() { return 1; } }; export function start() { return Promise.resolve(value); }",
+ control: "const value = 1; export function start() { return Promise.resolve(value); }",
+ },
+ {
+ label: "OPAQUE_CALLBACK_UNSUPPORTED", role: "opaque callback",
+ content: "import { register } from 'opaque'; function helper() { return 1; } export function start() { return register(helper); }",
+ control: "import { register } from 'opaque'; function helper() { return 1; } export function start() { return register(helper()); }",
+ },
+] as const;
+
+function withFixture(content: string, extension: string, selected: boolean, check: (root: string, path: string, report: VerifyReport) => void): void {
+ const root = mkdtempSync(join(tmpdir(), "semctx-sdk-callback-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}');
+ const path = `${selected ? "src/" : ""}subject.${extension}`;
+ writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["config", "core.autocrlf", "false"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ const admission = report.analysisAdmission;
+ expect(admission?.binding).toEqual({ status: "valid", reasons: [] });
+ expect(admission?.indexFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission?.checkFreshness).toEqual({ status: "current", reasons: [] });
+ expect(admission?.controlFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission?.changeCoverage.expected).toEqual(["src/main.ts"]);
+ expect(admission?.changeCoverage.analyzed).toEqual(["src/main.ts"]);
+ expect(admission?.changeCoverage.files).toEqual([{ path: "src/main.ts", status: "analyzed", reasons: [] }]);
+ check(root, path, report);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}
+
+for (const { content, label, role } of cases) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified SDK ${role} ${label} callback ${extension}, selected ${selected}`, () => {
+ withFixture(content, extension, selected, (root, path, report) => {
+ const reason = `SOURCE_${label}`;
+ expect(report.analysisAdmission?.reasons).toEqual([`DEPENDENCY_SCOPE_CONFIGURATION:${path}:${reason}`]);
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.verdict).toBe("BLOCK");
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons).toContain(reason);
+ });
+}, 60_000);
+
+for (const { control, role } of cases) test(`qualified SDK ${role} paired data control is admitted`, () => {
+ withFixture(control, "ts", true, (root, path, report) => {
+ expect(report.verdict).not.toBe("BLOCK");
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ expect(report.analysisAdmission?.reasons).toEqual([]);
+ expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons).toEqual([]);
+ });
+}, 60_000);
+
+test("reassigned retained callee cannot publish primitive-data admission", () => {
+ const original = "import { expect } from 'opaque'; function helper() {} function value() { return 1; } export function run() { return expect(value()); }\n";
+ withFixture(original.replace("export function run", "value = () => helper; export function run"), "js", true, (_root, path, report) => {
+ expect(report.analysisAdmission?.reasons).toEqual([`DEPENDENCY_SCOPE_CONFIGURATION:${path}:SOURCE_OPAQUE_CALLBACK_UNSUPPORTED`]);
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.verdict).toBe("BLOCK");
+ });
+ withFixture(original, "js", true, (_root, _path, report) => {
+ expect(report.verdict).not.toBe("BLOCK");
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ expect(report.analysisAdmission?.reasons).toEqual([]);
+ });
+}, 180_000);
diff --git a/packages/app-services/test/qualified-prototype-mutation.test.ts b/packages/app-services/test/qualified-prototype-mutation.test.ts
new file mode 100644
index 00000000..e3ac83b8
--- /dev/null
+++ b/packages/app-services/test/qualified-prototype-mutation.test.ts
@@ -0,0 +1,26 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified prototype mutation ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-prototype-mutation-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, `${selected ? 'src/' : ''}subject.${extension}`), "function* helper() { yield 7; } const values = [1]; Object.setPrototypeOf(values, { [Symbol.iterator]: helper }); export function run() { return [...values]; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ extension, selected, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(reason => reason.includes("PROTOTYPE_MUTATION_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-range-base.test.ts b/packages/app-services/test/qualified-range-base.test.ts
new file mode 100644
index 00000000..8b5b2883
--- /dev/null
+++ b/packages/app-services/test/qualified-range-base.test.ts
@@ -0,0 +1,104 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const GUARDED = "/** @invariant fixed-value: the value stays positive */\nexport function value() { return 1; }\n";
+const CLI = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], {
+ cwd: root, stdout: "pipe", stderr: "pipe",
+ });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+
+function range(extension: "ts" | "mjs", edit: (root: string, path: string) => void) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-range-base-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const path = `src/value.${extension}`;
+ writeFileSync(join(root, path), GUARDED);
+ writeFileSync(join(root, `src/helper.${extension}`), "export function helper() { return 1; }\n");
+ git(root, "init", "-q", "-b", "main");
+ git(root, "add", ".");
+ git(root, "commit", "-q", "-m", "base");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ git(root, "checkout", "-q", "-b", "change");
+ edit(root, path);
+ git(root, "add", "-A");
+ git(root, "commit", "-q", "-m", "head");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ let observed: ReturnType | undefined;
+ let failure: unknown;
+ try { observed = runVerify(root, { kind: "range", base: "main" }); }
+ catch (error) { failure = error; }
+ return { root, path, observed, failure };
+}
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+for (const extension of ["ts", "mjs"] as const) describe(`qualified ${extension} committed range base boundary`, () => {
+ for (const kind of ["delete", "strip-marker", "body-only"] as const) it(`returns explicit BLOCK, not an internal exception, for ${kind}`, () => {
+ const { root, path, observed, failure } = range(extension, (root, path) => {
+ if (kind === "delete") git(root, "rm", "-q", path);
+ else writeFileSync(join(root, path), kind === "strip-marker" ? GUARDED.split("\n").slice(1).join("\n") : GUARDED.replace("return 1", "return 2"));
+ });
+ expect(failure).toBeUndefined();
+ expect(observed?.result.verdict).toBe("BLOCK");
+ expect(observed?.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed?.result.findings).toEqual(expect.arrayContaining([expect.objectContaining({ rule: "analysis_scope_incomplete", severity: "block" })]));
+ expect(observed?.result.unknowns.join("\n")).toContain(path);
+ const processResult = Bun.spawnSync([process.execPath, CLI, "verify", "diff", "--root", root, "--base", "main", "--format", "json", "--fail-on", "none"], {
+ stdout: "pipe", stderr: "pipe",
+ });
+ expect(processResult.exitCode).toBe(3);
+ const report = JSON.parse(new TextDecoder().decode(processResult.stdout));
+ expect(report.verdict).toBe("BLOCK");
+ expect(report.analysisAdmission.status).toBe("rejected");
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ }, 60_000);
+
+ it("keeps invariant identity replacement explicitly outside qualified base recovery", () => {
+ const { path, observed, failure } = range(extension, (root, path) => writeFileSync(
+ join(root, path),
+ GUARDED.replace("fixed-value: the value stays positive", "finite-value: the value stays finite"),
+ ));
+ expect(failure).toBeUndefined();
+ expect(observed?.result.verdict).toBe("BLOCK");
+ expect(observed?.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed?.report.analysisAdmission?.reasons).toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+ expect(observed?.result.findings).toEqual(expect.arrayContaining([expect.objectContaining({ rule: "analysis_scope_incomplete", severity: "block" })]));
+ expect(observed?.result.unknowns.join("\n")).toContain(path);
+ }, 60_000);
+
+ it("keeps a clean pure addition eligible without base recovery", () => {
+ const { observed, failure } = range(extension, (root) => writeFileSync(join(root, `src/added.${extension}`), "export function added() { return 3; }\n"));
+ expect(failure).toBeUndefined();
+ expect(observed?.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed?.result.verdict).not.toBe("BLOCK");
+ }, 60_000);
+});
+
+for (const [control, content] of [
+ ["package.json", '{"name":"fixture","type":"module"}\n'],
+ ["tsconfig.json", '{"compilerOptions":{"module":"ESNext","moduleResolution":"Bundler"}}\n'],
+ [".gitignore", ".semctx/\n# head ignore policy\n"],
+] as const) it(`head ${control} changes cannot substitute for qualified base capture`, () => {
+ const { observed, failure } = range("ts", (root, path) => {
+ writeFileSync(join(root, path), GUARDED.replace("return 1", "return 2"));
+ writeFileSync(join(root, control), content);
+ });
+ expect(failure).toBeUndefined();
+ expect(observed?.result.verdict).toBe("BLOCK");
+ expect(observed?.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed?.report.analysisAdmission?.reasons).toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+}, 60_000);
diff --git a/packages/app-services/test/qualified-range-coordinate-drift.test.ts b/packages/app-services/test/qualified-range-coordinate-drift.test.ts
new file mode 100644
index 00000000..b6df8ca3
--- /dev/null
+++ b/packages/app-services/test/qualified-range-coordinate-drift.test.ts
@@ -0,0 +1,91 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { __setVerifyAnalysisBarrierForTesting, resolveSource } from "../src/verify";
+
+const roots: string[] = [];
+const SOURCE = { kind: "range", base: "refs/heads/verification-base" } as const;
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "-c", "commit.gpgsign=false", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+function repository() {
+ const root = mkdtempSync(join(tmpdir(), "semctx-range-coordinate-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ git(root, "init", "-q", "-b", "main");
+ git(root, "add", "."); git(root, "commit", "-q", "-m", "first ancestor");
+ const first = git(root, "rev-parse", "HEAD");
+ git(root, "commit", "-q", "--allow-empty", "-m", "same-tree successor");
+ const second = git(root, "rev-parse", "HEAD");
+ expect(first).not.toBe(second);
+ expect(git(root, "rev-parse", `${first}^{tree}`)).toBe(git(root, "rev-parse", `${second}^{tree}`));
+ git(root, "update-ref", SOURCE.base, first);
+ git(root, "update-ref", "refs/heads/unrelated", first);
+ writeFileSync(join(root, "src/added.ts"), "export function added() { return 2; }\n");
+ git(root, "add", "src/added.ts"); git(root, "commit", "-q", "-m", "pure addition head");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ return { root, first, second };
+}
+function assertCurrentHead(report: ReturnType["report"]) {
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.indexFreshness.verdict).toBe("FRESH");
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toContain("src/added.ts");
+ expect(report.analysisAdmission?.reasons).not.toContain("QUALIFIED_BASE_RECOVERY_UNSUPPORTED");
+}
+afterEach(() => {
+ __setVerifyAnalysisBarrierForTesting(undefined);
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+test("qualified range rejects merge-base coordinate drift despite identical diff bytes", () => {
+ const { root, first, second } = repository();
+ const before = resolveSource(root, SOURCE, false);
+ __setVerifyAnalysisBarrierForTesting(() => git(root, "update-ref", SOURCE.base, second));
+ const observed = runVerify(root, SOURCE);
+ const after = resolveSource(root, SOURCE, false);
+ assertCurrentHead(observed.report);
+ expect(before.diffText).toBe(after.diffText);
+ expect(before.identity).toEqual(after.identity);
+ expect(before.git.mergeBase).toBe(first);
+ expect(after.git.mergeBase).toBe(second);
+ expect(observed.report.mergeBase).toBe(first);
+ expect(observed.report.analysisAdmission?.identity.baseCommit).toBe(first);
+ console.info(JSON.stringify({ before: before.git, after: after.git, report: observed.report }));
+ expect(observed.report.analysisAdmission?.reasons).toContain("CHECK_INPUT_CHANGED");
+ expect(observed.report.analysisAdmission?.checkFreshness.status).toBe("changed");
+ expect(observed.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed.result.verdict).toBe("BLOCK");
+}, 60_000);
+
+test("qualified range keeps unchanged merge-base coordinates admitted", () => {
+ const { root, first } = repository();
+ const observed = runVerify(root, SOURCE);
+ assertCurrentHead(observed.report);
+ expect(observed.report.mergeBase).toBe(first);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.report.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(observed.report.analysisAdmission?.reasons).not.toContain("CHECK_INPUT_CHANGED");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+}, 60_000);
+
+test("moving an unrelated ref does not refuse a stable qualified range", () => {
+ const { root, first, second } = repository();
+ __setVerifyAnalysisBarrierForTesting(() => git(root, "update-ref", "refs/heads/unrelated", second));
+ const observed = runVerify(root, SOURCE);
+ assertCurrentHead(observed.report);
+ expect(observed.report.mergeBase).toBe(first);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.report.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(observed.report.analysisAdmission?.reasons).not.toContain("CHECK_INPUT_CHANGED");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+}, 60_000);
diff --git a/packages/app-services/test/qualified-raw-control-config.test.ts b/packages/app-services/test/qualified-raw-control-config.test.ts
new file mode 100644
index 00000000..8a660c7b
--- /dev/null
+++ b/packages/app-services/test/qualified-raw-control-config.test.ts
@@ -0,0 +1,68 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, loadConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+import { __setVerifyAnalysisBarrierForTesting } from "../src/verify";
+
+function fixture(): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-raw-control-"));
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ return root;
+}
+
+test("equivalent raw control configuration bytes change the digest without joining source/Git inventory", () => {
+ const root = fixture();
+ try {
+ const config = loadConfig(root); const before = captureQualifiedAnalysisInputs(config);
+ const path = join(root, ".semctx/config.json"); const bytes = readFileSync(path);
+ writeFileSync(path, ` \n${bytes.toString()}\n`);
+ expect(loadConfig(root)).toEqual(config);
+ expect(captureQualifiedAnalysisInputs(loadConfig(root)).digest).not.toBe(before.digest);
+ expect(captureQualifiedAnalysisInputs(config).files.some(file => file.path.startsWith(".semctx/"))).toBe(false);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+for (const boundary of ["indexed", "after-analysis"]) test(`raw control byte drift refuses at ${boundary}`, () => {
+ const root = fixture();
+ try {
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const path = join(root, ".semctx/config.json"); const bytes = readFileSync(path);
+ const change = (): void => { writeFileSync(path, ` \n${bytes.toString()}\n`); };
+ if (boundary === "indexed") change(); else __setVerifyAnalysisBarrierForTesting(change);
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ boundary, admission: report.analysisAdmission }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ if (boundary === "after-analysis") expect(report.analysisAdmission?.checkFreshness.status).toBe("changed");
+ } finally { __setVerifyAnalysisBarrierForTesting(undefined); rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("missing and malformed raw configuration remain distinguishable and other controls stay excluded", () => {
+ const root = fixture();
+ try {
+ const config = loadConfig(root); const path = join(root, ".semctx/config.json");
+ const present = captureQualifiedAnalysisInputs(config);
+ writeFileSync(join(root, ".semctx/report.json"), "changed report bytes");
+ expect(captureQualifiedAnalysisInputs(config).digest).toBe(present.digest);
+ writeFileSync(path, "malformed raw configuration");
+ const malformed = captureQualifiedAnalysisInputs(config);
+ expect(malformed.digest).not.toBe(present.digest);
+ expect(malformed.configurationInput.state).toBe("present");
+ expect(() => loadConfig(root)).toThrow();
+ rmSync(path);
+ const absent = captureQualifiedAnalysisInputs(config);
+ expect(absent.configurationInput.state).toBe("absent");
+ expect(absent.digest).not.toBe(malformed.digest);
+ expect(() => loadConfig(root)).toThrow();
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/app-services/test/qualified-reflect-iteration.test.ts b/packages/app-services/test/qualified-reflect-iteration.test.ts
new file mode 100644
index 00000000..d20fe30a
--- /dev/null
+++ b/packages/app-services/test/qualified-reflect-iteration.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "function helper() {} export function start() { return Reflect.apply(helper, null, []); }", "REFLECT_INVOCATION_UNSUPPORTED"],
+ ["js", "function Helper() {} export function start() { return Reflect.construct(Helper, []); }", "REFLECT_INVOCATION_UNSUPPORTED"],
+ ["ts", "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { for (const value of values) return value; }", "ITERATION_UNSUPPORTED"],
+ ["js", "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { return [...values]; }", "ITERATION_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-resumption-bindings.test.ts b/packages/app-services/test/qualified-resumption-bindings.test.ts
new file mode 100644
index 00000000..3aced359
--- /dev/null
+++ b/packages/app-services/test/qualified-resumption-bindings.test.ts
@@ -0,0 +1,41 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const cases = [
+ ["cached generator", "export function* values(){yield 1;}", "export function* values(){yield 2;}", "import {values} from './dep';const iterator=values();export function run(){return iterator.next().value;}", "import {run} from './consumer';export const observed=run();"],
+ ["borrowed RegExp", "export const matcher={exec(){return null;}};", "export const matcher={exec(){return undefined;}};", "import {matcher} from './dep';export function run(){return RegExp.prototype[Symbol.match].call(matcher as RegExp,'x');}", "import {run} from './consumer';export const observed=run();"],
+ ["constant test binding", "export const SECURITY_MODE=true;", "export const SECURITY_MODE=false;", "export function run(){return 1;}", "import {SECURITY_MODE} from './dep';export const observed=SECURITY_MODE;"],
+ ["excluded cached generator", "export function* values(){yield 1;}", "export function* values(){yield 1;}", "import {values} from '../dep';const iterator=values();export function run(){return iterator.next().value;}", "import {run} from './consumer';export const observed=run();"],
+] as const;
+for (const [name, before, after, consumer, testContent] of cases) test(`qualified causal change ${name}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-resumption-binding-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const dep = join(root, name === "excluded cached generator" ? "dep.ts" : "src/dep.ts"); writeFileSync(dep, before);
+ if (name === "excluded cached generator") writeFileSync(join(root, "src/changed.ts"), "export function changed(){return 1;}");
+ writeFileSync(join(root, "src/consumer.ts"), consumer); writeFileSync(join(root, "src/consumer.test.ts"), testContent);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ if (name === "excluded cached generator") writeFileSync(join(root, "src/changed.ts"), "export function changed(){return 2;}");
+ else writeFileSync(dep, after);
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ if (name.includes("cached generator")) expect(report.analysisAdmission?.reasons.some(reason => reason.includes("ITERATION_UNSUPPORTED"))).toBe(true);
+ if (name === "borrowed RegExp") expect(report.analysisAdmission?.reasons.some(reason => reason.includes("consumer.ts:SOURCE_OPAQUE_CALLBACK_UNSUPPORTED"))).toBe(true);
+ if (name === "constant test binding") expect(report.analysisAdmission?.changeCoverage.files.some(file => file.reasons.some(reason => reason.includes("QUALIFIED_TEST_BINDING_MISSING:src/dep.ts:SECURITY_MODE")))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const outcome = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ name, cliExitCode: outcome.exitCode }));
+ expect(outcome.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-retained-temp-root.test.ts b/packages/app-services/test/qualified-retained-temp-root.test.ts
new file mode 100644
index 00000000..45116cca
--- /dev/null
+++ b/packages/app-services/test/qualified-retained-temp-root.test.ts
@@ -0,0 +1,97 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+const CHILD_TIMEOUT_MS = 60_000;
+const repositoryRoot = join(import.meta.dir, "../../..");
+
+function childSource(): string {
+ return `import assert from "node:assert/strict";
+import {createHash} from "node:crypto";
+import {lstatSync,mkdirSync,readFileSync,realpathSync,writeFileSync} from "node:fs";
+import {tmpdir} from "node:os";
+import {join,resolve} from "node:path";
+import {createGlobSelectionConfig,VerifyReportSchema} from "@semantic-context/core";
+import {initWorkspace,loadConfig} from "@semantic-context/repository-store";
+import {indexRepository,runVerify} from ${JSON.stringify(join(repositoryRoot, "packages/app-services/src/index.ts"))};
+const scenario=process.env.SEMCTX_TEMP_ROOT_SCENARIO;
+const temporary=process.env.SEMCTX_TEMP_ROOT_PATH;
+const physical=process.env.SEMCTX_TEMP_ROOT_PHYSICAL;
+const root=process.env.SEMCTX_TEMP_ROOT_REPOSITORY;
+assert.ok(scenario&&temporary&&physical&&root,"child scenario paths must be supplied");
+assert.equal(resolve(tmpdir()),resolve(temporary),"the real child os.tmpdir must consume its isolated environment");
+assert.equal(realpathSync.native(tmpdir()),physical,"temp alias must resolve to the actual physical directory");
+assert.equal(resolve(tmpdir())!==physical,scenario!=="canonical-body","alias scenarios must traverse a real directory link");
+assert.equal(lstatSync(tmpdir()).isSymbolicLink(),scenario!=="canonical-body","the aliased temporary directory must actually be a symlink or junction");
+mkdirSync(root);
+assert.equal(realpathSync.native(root),root,"the live fixture root must already be canonical");
+const guarded="/** @invariant fixed: stays fixed */\\nexport function value() { return 1; }\\n";
+mkdirSync(join(root,"src"));
+writeFileSync(join(root,".gitignore"),".semctx/\\n");
+writeFileSync(join(root,"src/value.ts"),guarded);
+if(scenario!=="alias-marker"){
+ mkdirSync(join(root,"test"));
+ writeFileSync(join(root,"test/value.test.ts"),'import { expect, test } from "bun:test";\\nimport { value } from "../src/value";\\ntest("value", () => expect(value()).toBe(1));\\n');
+}
+const git=(...args)=>{
+ const result=Bun.spawnSync(["git","-c","user.name=Semctx Test","-c","user.email=semctx@example.test","-c","commit.gpgsign=false","-c","core.autocrlf=false",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});
+ assert.equal(result.exitCode,0,new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout);
+};
+git("init","-q","-b","main");git("add",".");git("commit","-q","-m","guarded base");
+assert.equal(git("show","HEAD:src/value.ts"),guarded,"immutable HEAD must retain the original guard bytes");
+initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:["src/**/*","test/**/*"]});
+assert.equal(loadConfig(root).repositoryRoot,root);
+writeFileSync(join(root,"src/value.ts"),scenario==="alias-marker"?"export function value() { return 1; }\\n":guarded.replace("return 1","return 2"));
+indexRepository(root,"2026-10-12T00:00:00.000Z");
+const hash=bytes=>createHash("sha256").update(bytes).digest("hex");
+const before={head:git("rev-parse","HEAD").trim(),inventory:git("ls-tree","-r","HEAD"),diff:git("diff","--no-ext-diff","--no-textconv","HEAD","--"),index:hash(readFileSync(join(root,".git/index"))),source:hash(readFileSync(join(root,"src/value.ts"))),headSource:hash(guarded)};
+assert.ok(/^-[^-]/m.test(before.diff),"the control must actually remove base lines");
+const report=runVerify(root,{kind:"working-tree"}).report;
+console.info(JSON.stringify({scenario,temporary:tmpdir(),physical,root,before,report}));
+assert.equal(hash(readFileSync(join(root,".git/index"))),before.index,"old capture must not mutate the live Git index");
+assert.equal(git("rev-parse","HEAD").trim(),before.head);
+assert.equal(git("ls-tree","-r","HEAD"),before.inventory);
+assert.equal(git("diff","--no-ext-diff","--no-textconv","HEAD","--"),before.diff);
+assert.equal(hash(readFileSync(join(root,"src/value.ts"))),before.source);
+assert.equal(VerifyReportSchema.safeParse(report).success,true,"the actual public report must validate");
+assert.equal(report.analysisAdmission?.indexFreshness.verdict,"DIRTY_KNOWN");
+assert.equal(report.analysisAdmission?.checkFreshness.status,"current");
+assert.ok(report.analysisAdmission?.changeCoverage.analyzed.includes("src/value.ts"));
+if(scenario==="alias-marker"){
+ assert.ok(report.findings.some(finding=>finding.rule==="invariant_touched_without_test"&&finding.severity==="block"),"linked-temp capture must recover the original strict invariant rule, not only a layout refusal");
+ assert.equal(report.verdict,"BLOCK");
+}else{
+ assert.equal(report.analysisAdmission?.status,"admitted","covered body replacement must remain admitted when private temp root has an alias ancestor");
+ assert.notEqual(report.verdict,"BLOCK");
+ assert.ok(report.recommendedTests.some(item=>item.file==="test/value.test.ts"));
+}
+assert.equal(report.analysisAdmission?.status,"admitted");
+assert.equal(report.analysisAdmission?.binding.status,"valid");
+`;
+}
+
+for (const scenario of ["canonical-body", "alias-body", "alias-marker"] as const) {
+ test(`qualified immutable HEAD capture uses a safe physical temp root: ${scenario}`, () => {
+ const base = realpathSync.native(mkdtempSync(join(tmpdir(), "semctx-retained-temp-root-")));
+ try {
+ const physical = join(base, "physical-temp");
+ mkdirSync(physical);
+ const alias = join(base, "alias-temp");
+ if (scenario !== "canonical-body") symlinkSync(physical, alias, process.platform === "win32" ? "junction" : "dir");
+ const temporary = scenario === "canonical-body" ? physical : alias;
+ const child = Bun.spawnSync([process.execPath, "-e", childSource()], {
+ cwd: repositoryRoot, stdout: "pipe", stderr: "pipe", timeout: CHILD_TIMEOUT_MS,
+ env: { ...process.env, TMPDIR: temporary, TMP: temporary, TEMP: temporary,
+ SEMCTX_TEMP_ROOT_SCENARIO: scenario, SEMCTX_TEMP_ROOT_PATH: temporary,
+ SEMCTX_TEMP_ROOT_PHYSICAL: physical, SEMCTX_TEMP_ROOT_REPOSITORY: join(base, "repository") },
+ });
+ console.info(new TextDecoder().decode(child.stdout));
+ console.info(new TextDecoder().decode(child.stderr));
+ expect(child.exitCode).toBe(0);
+ } finally {
+ rmSync(base, { recursive: true, force: true });
+ }
+ }, CHILD_TIMEOUT_MS);
+}
diff --git a/packages/app-services/test/qualified-runtime-code-boundaries.test.ts b/packages/app-services/test/qualified-runtime-code-boundaries.test.ts
new file mode 100644
index 00000000..2df6865f
--- /dev/null
+++ b/packages/app-services/test/qualified-runtime-code-boundaries.test.ts
@@ -0,0 +1,48 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, body, changedName, reason] of [
+ ["wasm start", "import {helper} from './helper.EXT';const bytes=new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,12,1,3,101,110,118,4,104,111,111,107,0,0,8,1,0]);export function run(){return WebAssembly.instantiate(bytes,{env:{hook:helper}});}", "helper", "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["script insertion", "export function run(){const script=document.createElement('script');script.src='./hidden.EXT';document.head.append(script);}", "hidden", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+ ["uppercase HTML script insertion", "export function run(){const script=document.createElement('SCRIPT');script.src='./hidden.EXT';document.head.append(script);}", "hidden", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "mjs"]) for (const selection of ["selected", "excluded"]) test(`qualified ${name} ${extension} ${selection} rejects a hidden source change`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-runtime-code-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const changed = join(root, `src/${changedName}.${extension}`);
+ writeFileSync(changed, "export function helper(){return 1;}");
+ writeFileSync(join(root, `src/consumer.${extension}`), body.replaceAll("EXT", extension));
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selection === "selected" ? ["src/**/*"] : [`src/${changedName}.${extension}`], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(changed, "export function helper(){return 2;}");
+ const indexed = indexRepository(root, "2026-10-12T00:00:00.000Z");
+ if (changedName === "hidden") {
+ const hiddenIds = new Set(indexed.analysis.graph.nodes.filter(node => node.filePath === `src/hidden.${extension}`).map(node => node.id));
+ expect(indexed.analysis.graph.edges.filter(edge => ["calls", "imports"].includes(edge.kind) && hiddenIds.has(edge.to))).toEqual([]);
+ }
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, extension, selection, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.verdict).toBe("BLOCK");
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect([...report.analysisAdmission?.reasons ?? [], ...report.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(item => item.includes(reason))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ const cliReport = VerifyReportSchema.parse(JSON.parse(new TextDecoder().decode(result.stdout)));
+ expect(cliReport.verdict).toBe("BLOCK");
+ expect(cliReport.analysisAdmission?.binding.status).toBe("valid");
+ expect([...cliReport.analysisAdmission?.reasons ?? [], ...cliReport.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, name === "uppercase HTML script insertion" ? 60_000 : 180_000);
diff --git a/packages/app-services/test/qualified-runtime-code-family.test.ts b/packages/app-services/test/qualified-runtime-code-family.test.ts
new file mode 100644
index 00000000..5ec7cd42
--- /dev/null
+++ b/packages/app-services/test/qualified-runtime-code-family.test.ts
@@ -0,0 +1,38 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, body, changedName, reason] of [
+ ["streaming wasm start", "import {helper} from './helper.EXT';export function run(){return WebAssembly.instantiateStreaming(fetch('module.wasm'),{env:{hook:helper}});}", "helper", "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["appendChild script insertion", "export function run(){const script=document.createElement('script');script.src='./hidden.EXT';document.head.appendChild(script);}", "hidden", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+ ["nullable SDK callback", "import {helper} from './helper.EXT';const button=document.createElement('button');button.onclick=helper;export function run(){button.click();}", "helper", "SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "mjs"]) for (const selection of ["selected", "excluded"]) test(`qualified ${name} ${extension} ${selection} rejects a hidden source change`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-runtime-family-"));
+ try {
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const changed = join(root, `src/${changedName}.${extension}`);
+ writeFileSync(changed, "export function helper(){return 1;}");
+ writeFileSync(join(root, `src/consumer.${extension}`), body.replaceAll("EXT", extension));
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selection === "selected" ? ["src/**/*"] : [`src/${changedName}.${extension}`], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(changed, "export function helper(){return 2;}");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, extension, selection, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect([...report.analysisAdmission?.reasons ?? [], ...report.analysisAdmission?.changeCoverage.files.flatMap(file => file.reasons) ?? []].some(item => item.includes(reason))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-runtime-value-imports.test.ts b/packages/app-services/test/qualified-runtime-value-imports.test.ts
new file mode 100644
index 00000000..85989865
--- /dev/null
+++ b/packages/app-services/test/qualified-runtime-value-imports.test.ts
@@ -0,0 +1,56 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, consumer, testContent] of [
+ ["namespace test value", "export function run(){return 1;}", "import * as dep from './dep';export const observed=dep.FLAG;"],
+ ["production imported value", "import {FLAG} from './dep';export function run(){return FLAG;}", "import {run} from './consumer';export function check(){return run();}"],
+] as const) test(`causal constant change ${name}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-runtime-value-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const dep = join(root, "src/dep.ts"); writeFileSync(dep, "export const FLAG=true;");
+ writeFileSync(join(root, "src/consumer.ts"), consumer); writeFileSync(join(root, "src/consumer.test.ts"), testContent);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(dep, "export const FLAG=false;"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.changeCoverage.files.some(file => file.reasons.some(reason => /QUALIFIED_(TEST|IMPORT)_BINDING_MISSING:src\/dep.ts:FLAG/.test(reason)))).toBe(true);
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const outcome = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ name, cliExitCode: outcome.exitCode })); expect(outcome.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
+
+for (const [name, depContent, consumer] of [
+ ["unused", "export const FLAG=true;", "import {FLAG} from './dep';export function run(){return 1;}"],
+ ["type-only", "export const FLAG=true;", "import type {FLAG} from './dep';export type Mode=typeof FLAG;export function run(){return 1;}"],
+ ["modeled namespace", "export function FLAG(){return 1;}", "import * as dep from './dep';export function run(){return dep.FLAG();}"],
+] as const) test(`qualified runtime import positive ${name}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-import-positive-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const dep = join(root, "src/dep.ts"); writeFileSync(dep, depContent);
+ writeFileSync(join(root, "src/consumer.ts"), consumer);
+ writeFileSync(join(root, "src/consumer.test.ts"), name === "modeled namespace"
+ ? "import * as dep from './dep';export function check(){return dep.FLAG();}"
+ : "import {run} from './consumer';export function check(){return run();}");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(dep, depContent.replace("true", "false").replace("return 1", "return 2")); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ if (name === "modeled namespace") expect(report.recommendedTests.some(test => test.file === "src/consumer.test.ts")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-sdk-callback-dictionary.test.ts b/packages/app-services/test/qualified-sdk-callback-dictionary.test.ts
new file mode 100644
index 00000000..896c3ee9
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-callback-dictionary.test.ts
@@ -0,0 +1,22 @@
+import {expect,test} from "bun:test";
+import {mkdtempSync,mkdirSync,rmSync,writeFileSync} from "node:fs";
+import {tmpdir} from "node:os";
+import {join} from "node:path";
+import {createGlobSelectionConfig} from "@semantic-context/core";
+import {initWorkspace} from "@semantic-context/repository-store";
+import {indexRepository,runVerify} from "../src";
+for(const extension of ["ts","mjs"])for(const selected of [true,false])test(`qualified callback dictionary ${extension} ${selected}`,()=>{
+ const root=mkdtempSync(join(tmpdir(),"semctx-callback-dictionary-"));
+ try{
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ const helper=join(root,`src/helper.${extension}`);writeFileSync(helper,"export function helper(){return 1;}");
+ writeFileSync(join(root,`src/consumer.${extension}`),`import {helper} from './helper.${extension}';export function run(){return new ReadableStream({start:helper});}`);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for(const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]){const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));}
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selected?["src/**/*"]:[`src/helper.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({extension,selected,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(cli.exitCode).toBe(3);
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-sdk-callback.test.ts b/packages/app-services/test/qualified-sdk-callback.test.ts
new file mode 100644
index 00000000..c9e8375e
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-callback.test.ts
@@ -0,0 +1,57 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+import { discoverRepository, extractTypeScript } from "@semantic-context/ts-analyzer";
+
+for (const [content, label] of [
+ ["function helper(value) { return value; } export function run() { return [1].map(helper); }", "method"],
+ ["function executor() {} export async function run() { await new Promise(executor); }", "constructor"],
+] as const) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified SDK ${label} callback ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-sdk-callback-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("SDK_CALLBACK_UNSUPPORTED"))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(reason => reason.includes("SDK_CALLBACK_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("JavaScript migration-directory source retains exported facts, test association and recommendation", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-migration-source-"));
+ try {
+ mkdirSync(join(root, "migrations")); mkdirSync(join(root, "test"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const step = join(root, "migrations/step.js"); const check = join(root, "test/step.test.js");
+ writeFileSync(step, "export function step() { return 1; }\n");
+ writeFileSync(check, "import { step } from '../migrations/step.js'; export function check() { return step(); }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, include: ["**/*"], languages: { typescript: "on" as const, javascript: "on" as const } };
+ initWorkspace(root, config);
+ expect(discoverRepository(config).files.find(file => file.relPath === "migrations/step.js")?.role).toBe("source");
+ const extraction = extractTypeScript([step, check], root);
+ expect(extraction.symbols).toContainEqual(expect.objectContaining({ relPath: "migrations/step.js", name: "step", exported: true }));
+ expect(extraction.calls).toContainEqual(expect.objectContaining({ callerRelPath: "test/step.test.js", calleeRelPath: "migrations/step.js", calleeSymbolPath: "step" }));
+ writeFileSync(step, "export function step() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ expect(report.recommendedTests.some(test => test.file === "test/step.test.js")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-sdk-global-combinator.test.ts b/packages/app-services/test/qualified-sdk-global-combinator.test.ts
new file mode 100644
index 00000000..27e4e551
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-global-combinator.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, content] of [
+ ["destructured SDK global", "function helper() { return 1; } [Math] = [{ random: helper }]; export function run() { return Math.random(); }"],
+ ["asserted undefined comparator", "function helper() { return 1; } const values = [{toString:helper},{toString:helper}]; export function run() { return values.sort(undefined as any); }"],
+ ["global replacement", "function helper() { return 1; } Math = { ...Math, random: helper }; export function run() { return Math.random(); }"],
+ ["explicit primitive hook", "function helper() { return 'value'; } const value = {}; value[Symbol.toPrimitive] = helper; export function run() { return String(value); }"],
+ ["combinator thenable", "function helper() {} export function run() { return Promise.all([{ then: helper }]); }"],
+] as const) test(`causal helper change ${name}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-global-assimilation-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const file = join(root, "src/main.ts"); writeFileSync(file, content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"] });
+ writeFileSync(file, content.replace("return 1;", "return 2;").replace("return 'value';", "return 'changed';").replace("function helper() {}", "function helper() { return 1; }"));
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.length).toBeGreaterThan(0);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-sdk-helper-coercion.test.ts b/packages/app-services/test/qualified-sdk-helper-coercion.test.ts
new file mode 100644
index 00000000..a02155b1
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-helper-coercion.test.ts
@@ -0,0 +1,87 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const cases = [
+ [
+ "Number.call",
+ "const value={valueOf:hidden};",
+ "Number.call(null,value)"
+ ],
+ [
+ "String.call",
+ "const value={toString:hidden};",
+ "String.call(null,value)"
+ ],
+ [
+ "Math.max.call",
+ "const value={valueOf:hidden};",
+ "Math.max.call(null,value,0)"
+ ],
+ [
+ "Array.join.call",
+ "const value={toString:hidden};",
+ "Array.prototype.join.call([value])"
+ ]
+] as const;
+for (const extension of ["ts", "mjs"]) for (const selected of [true, false]) test(`qualified SDK helper coercion ${extension} selected ${selected} binds the imported helper-only change`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-helper-coercion-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, `src/hidden.${extension}`); writeFileSync(hidden, "export function hidden(){return 1;}\n");
+ const subjects = cases.map(([name, setup, call], index) => {
+ const path = `${selected ? "src/" : ""}subject-${index}.${extension}`;
+ writeFileSync(join(root, path), `import {hidden} from '${selected ? "./" : "./src/"}hidden.${extension}';${setup}export function run(){return ${call};}`);
+ return { name, path };
+ });
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (child.exitCode !== 0) throw new Error(new TextDecoder().decode(child.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function hidden(){return 2;}\n"); indexRepository(root, "2026-10-12T09:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ extension, selected, subjects, report }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ for (const { path } of subjects) expect(report.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:${path}:SOURCE_COERCION_UNSUPPORTED`);
+ expect(report.analysisAdmission?.status).toBe("rejected"); expect(report.verdict).toBe("BLOCK");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ const stdout = new TextDecoder().decode(cli.stdout); const stderr = new TextDecoder().decode(cli.stderr);
+ console.info(JSON.stringify({ extension, selected, cli: { exitCode: cli.exitCode, stdout, stderr } }));
+ expect(cli.exitCode).toBe(3);
+ const parsed = VerifyReportSchema.safeParse(JSON.parse(stdout)); expect(parsed.success).toBe(true);
+ if (!parsed.success) throw new Error("invalid CLI report");
+ expect(parsed.data.verdict).toBe("BLOCK"); expect(parsed.data.analysisAdmission?.status).toBe("rejected");
+ expect(parsed.data.analysisAdmission?.binding.status).toBe("valid");
+ expect(parsed.data.analysisAdmission?.checkFreshness.status).toBe("current");
+ for (const { path } of subjects) expect(parsed.data.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:${path}:SOURCE_COERCION_UNSUPPORTED`);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
+
+for (const extension of ["ts", "mjs"]) test(`qualified pristine SDK primitive helpers ${extension} remain admitted`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-helper-primitives-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, `src/hidden.${extension}`); writeFileSync(hidden, "export function hidden(){return 1;}\n");
+ const body = "export function number(){return Number.call(null,1);}export function max(){return Math.max.call(null,1,0);}export function text(){return String.call(null,'value');}export function joined(){return Array.prototype.join.call([1,2]);}";
+ writeFileSync(join(root, `src/main.${extension}`), `import {hidden} from './hidden.${extension}';${body}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (child.exitCode !== 0) throw new Error(new TextDecoder().decode(child.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function hidden(){return 2;}\n"); indexRepository(root, "2026-10-12T09:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report; console.info(JSON.stringify({ extension, control: true, report }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true); expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("current"); expect(report.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-sdk-iteration-mutation.test.ts b/packages/app-services/test/qualified-sdk-iteration-mutation.test.ts
new file mode 100644
index 00000000..655b72ae
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-iteration-mutation.test.ts
@@ -0,0 +1,30 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [content, label] of [
+ ["function* iterator() { yield 1; } const value = { [Symbol.iterator]: iterator }; export function run() { return Array.from(value); }", "iteration"],
+ ["function helper() { return 1; } Object.assign(Math, { random: helper }); export function run() { return Math.random(); }", "mutation"],
+] as const) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified SDK ${label} callback ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-sdk-callback-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes(`SDK_${label.toUpperCase()}_UNSUPPORTED`))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(reason => reason.includes(`SDK_${label.toUpperCase()}_UNSUPPORTED`))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-sdk-protocol-loader.test.ts b/packages/app-services/test/qualified-sdk-protocol-loader.test.ts
new file mode 100644
index 00000000..229236eb
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-protocol-loader.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [content, reason] of [
+ ["function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Object.defineProperty(values, Symbol.iterator, { value: helper }); export function start() { return [...values]; }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["export function load() { importScripts('./hidden.js'); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["function helper() { return 1; } Math.random = helper; export function run() { return Math.random(); }", "SDK_CALLABLE_MUTATION_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-sdk-protocol-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-sdk-receiver-loop-bindings.test.ts b/packages/app-services/test/qualified-sdk-receiver-loop-bindings.test.ts
new file mode 100644
index 00000000..43fcd259
--- /dev/null
+++ b/packages/app-services/test/qualified-sdk-receiver-loop-bindings.test.ts
@@ -0,0 +1,71 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const selected of [true, false]) test(`qualified callee binding origins ${extension} selected ${selected} reject a hidden-only edit for the intended reasons`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-callee-bindings-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}');
+ const hidden = join(root, `src/hidden.${extension}`); writeFileSync(hidden, "export function hidden(){return 1;}\n");
+ const bodies: [string, string, string][] = [
+ ["async identifier loop", "async function load(){return 1;}export async function run(){for(load of [()=>({then:hidden})]){}await load();return 1;}", "SOURCE_AWAIT_UNSUPPORTED"],
+ ["async destructuring loop", "async function load(){return 1;}export async function run(){for([load] of [[()=>({then:hidden})]]){}await load();return 1;}", "SOURCE_AWAIT_UNSUPPORTED"],
+ ["opaque primitive-return loop", "import {write} from 'opaque';function value(){return 1;}export function run(){for(value of [()=>hidden]){}return write(value());}", "SOURCE_OPAQUE_CALLBACK_UNSUPPORTED"],
+ ];
+ if (extension === "ts") bodies.push(["SDK-shaped local member", "const env:typeof globalThis={parseInt:hidden} as unknown as typeof globalThis;export function run(){return env.parseInt('1');}", "SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ const subjects = bodies.map(([name, body, reason], index) => {
+ const path = `${selected ? "src/" : ""}subject-${index}.${extension}`;
+ writeFileSync(join(root, path), `import {hidden} from '${selected ? "./" : "./src/"}hidden.${extension}';${body}`);
+ return { name, path, reason };
+ });
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (child.exitCode !== 0) throw new Error(new TextDecoder().decode(child.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function hidden(){return 2;}\n"); indexRepository(root, "2026-10-12T09:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ extension, selected, subjects, report }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("current");
+ expect(report.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ for (const { path, reason } of subjects) expect(report.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:${path}:${reason}`);
+ expect(report.analysisAdmission?.status).toBe("rejected"); expect(report.verdict).toBe("BLOCK");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ const stdout = new TextDecoder().decode(cli.stdout); const stderr = new TextDecoder().decode(cli.stderr);
+ console.info(JSON.stringify({ extension, selected, cli: { exitCode: cli.exitCode, stdout, stderr } }));
+ expect(cli.exitCode).toBe(3);
+ const parsed = VerifyReportSchema.safeParse(JSON.parse(stdout)); expect(parsed.success).toBe(true);
+ if (!parsed.success) throw new Error("invalid CLI report");
+ expect(parsed.data.verdict).toBe("BLOCK"); expect(parsed.data.analysisAdmission?.status).toBe("rejected");
+ expect(parsed.data.analysisAdmission?.binding.status).toBe("valid");
+ expect(parsed.data.analysisAdmission?.checkFreshness.status).toBe("current");
+ for (const { path, reason } of subjects) expect(parsed.data.analysisAdmission?.reasons).toContain(`DEPENDENCY_SCOPE_CONFIGURATION:${path}:${reason}`);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+for (const extension of ["ts", "mjs"]) test(`qualified pristine and retained own member plus unwritten loop controls ${extension} remain admitted`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-callee-binding-controls-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const hidden = join(root, `src/hidden.${extension}`); writeFileSync(hidden, "export function hidden(){return 1;}\n");
+ writeFileSync(join(root, `src/main.${extension}`), `import * as env from './hidden.${extension}';export function own(){return env.hidden('1');}export function sdk(){return globalThis.parseInt('1');}async function load(){return 1;}export async function run(){for(const load of [1]){}await load();return 1;}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (child.exitCode !== 0) throw new Error(new TextDecoder().decode(child.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(hidden, "export function hidden(){return 2;}\n"); indexRepository(root, "2026-10-12T09:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report; console.info(JSON.stringify({ extension, control: true, report }));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true); expect(report.analysisAdmission?.binding.status).toBe("valid");
+ expect(report.analysisAdmission?.checkFreshness.status).toBe("current"); expect(report.analysisAdmission?.changeCoverage.analyzed).toContain(`src/hidden.${extension}`);
+ expect(report.analysisAdmission?.status).toBe("admitted");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/app-services/test/qualified-species-sort-vm.test.ts b/packages/app-services/test/qualified-species-sort-vm.test.ts
new file mode 100644
index 00000000..20669862
--- /dev/null
+++ b/packages/app-services/test/qualified-species-sort-vm.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const cases = [
+ ["species", "function helper() {} Object.defineProperty(Array, Symbol.species, { value: helper }); export function run() { return [1, 2].slice(); }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["sort", "const values = [{ toString() { return 'b'; } }, { toString() { return 'a'; } }]; export function run() { return values.sort(); }", "SORT_COERCION_UNSUPPORTED"],
+ ["vm", "import { runInThisContext } from 'node:vm'; export function run() { return runInThisContext('7'); }", "RUNTIME_EVALUATION_UNSUPPORTED"],
+] as const;
+for (const [name, content, reason] of cases) for (const extension of ["ts", "js"]) for (const selected of [true, false]) test(`qualified ${name} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-implicit-hooks-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, `${selected ? 'src/' : ''}subject.${extension}`), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" }); if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ console.info(JSON.stringify({ name, extension, selected, admission }));
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons.some(value => value.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-string-join-dynamic-test.test.ts b/packages/app-services/test/qualified-string-join-dynamic-test.test.ts
new file mode 100644
index 00000000..459d4bba
--- /dev/null
+++ b/packages/app-services/test/qualified-string-join-dynamic-test.test.ts
@@ -0,0 +1,23 @@
+import {expect,test} from "bun:test";
+import {mkdtempSync,mkdirSync,rmSync,writeFileSync} from "node:fs";
+import {tmpdir} from "node:os";
+import {join} from "node:path";
+import {createGlobSelectionConfig} from "@semantic-context/core";
+import {initWorkspace} from "@semantic-context/repository-store";
+import {indexRepository,runVerify} from "../src";
+for(const kind of ["String","join","dynamic-test"])for(const extension of ["ts","mjs"])for(const selected of [true,false])test(`qualified ${kind} ${extension} ${selected}`,()=>{
+ const root=mkdtempSync(join(tmpdir(),"semctx-coerce-dynamic-"));
+ try{
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");writeFileSync(join(root,"package.json"),'{"devDependencies":{"vitest":"4.0.0"}}');
+ const helper=join(root,`src/dep.${extension}`);writeFileSync(helper,"export function helper(){return 1;}");
+ const consumer=`import {helper} from './dep.${extension}';const value={toString:helper};export function run(){return ${kind==="String"?"String(value)":"[value].join()"};}`;
+ if(kind!=="dynamic-test")writeFileSync(join(root,`src/consumer.${extension}`),consumer);
+ writeFileSync(join(root,`src/consumer.test.${extension}`),kind==="dynamic-test"?`import {test,expect} from 'vitest';test('helper',async()=>{const dep=await import('./dep.${extension}');expect(dep.helper()).toBe(1);});`:`import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for(const args of [["init","-q"],["add","."],["-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture"]]){const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)throw Error(new TextDecoder().decode(result.stderr));}
+ initWorkspace(root,{...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:selected?["src/**/*"]:[`src/dep.${extension}`],languages:{typescript:"on",javascript:"on"}});
+ writeFileSync(helper,"export function helper(){return 2;}");indexRepository(root,"2026-10-12T00:00:00.000Z");const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({kind,extension,selected,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe("rejected");expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(cli.exitCode).toBe(3);
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
diff --git a/packages/app-services/test/qualified-template-capture.test.ts b/packages/app-services/test/qualified-template-capture.test.ts
new file mode 100644
index 00000000..fb01e262
--- /dev/null
+++ b/packages/app-services/test/qualified-template-capture.test.ts
@@ -0,0 +1,90 @@
+import { expect, spyOn, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+import { __setVerifyAnalysisBarrierForTesting } from "../src/verify";
+
+function fixture(): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-template-capture-"));
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ return root;
+}
+for (const language of ["ts", "js"]) for (const selected of [true, false]) test(`qualified tagged template ${language}, selected ${selected}, cannot claim complete invocation analysis`, () => {
+ const root = fixture();
+ try {
+ const path = `${selected ? "src/" : ""}render.${language}`;
+ writeFileSync(join(root, path), "export function tag(parts) { return parts[0]; } export function render() { return tag`value`; }\n");
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(reason => reason.includes("TAGGED_TEMPLATE_UNSUPPORTED"))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(reason => reason.includes("TAGGED_TEMPLATE_UNSUPPORTED"))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("qualified verification keeps three fresh boundaries and only its useful selected discovery", () => {
+ const root = fixture(); const spawn = Bun.spawnSync;
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ let directDiscoveries = 0; let directCaptures = 0;
+ const processSpy = spyOn(Bun, "spawnSync").mockImplementation(((...args: Parameters) => {
+ const command = args[0];
+ if (Array.isArray(command) && command.includes("--exclude-per-directory=.gitignore")) {
+ const frames = new Error().stack?.split("\n") ?? [];
+ const discoverFrame = frames.findIndex(frame => frame.includes("at discoverRepository "));
+ const captureFrame = frames.findIndex(frame => frame.includes("at captureQualifiedAnalysisInputs "));
+ if (discoverFrame >= 0 && frames[discoverFrame + 1]?.includes("at runVerify ")) directDiscoveries++;
+ if (captureFrame >= 0 && frames[captureFrame + 1]?.includes("at runVerify ")) directCaptures++;
+ }
+ return spawn(...args);
+ }) as typeof spawn);
+ try {
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+ console.info(JSON.stringify({ directDiscoveries, directCaptures }));
+ expect(directDiscoveries).toBe(1);
+ expect(directCaptures).toBe(3);
+ } finally { processSpy.mockRestore(); rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+for (const mutation of ["source", "config", "inventory"]) test(`fresh qualified after-analysis capture refuses ${mutation} drift`, () => {
+ const root = fixture();
+ try {
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ __setVerifyAnalysisBarrierForTesting(() => {
+ if (mutation === "source") writeFileSync(join(root, "src/main.ts"), "export function main() { return 3; }\n");
+ else if (mutation === "inventory") writeFileSync(join(root, "hidden.ts"), "export function hidden() { return 1; }\n");
+ else writeFileSync(join(root, ".semctx/config.json"), JSON.stringify({ ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], exclude: ["src/unused.ts"], languages: { typescript: "on", javascript: "on" } }));
+ });
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(admission?.status).toBe("rejected"); expect(admission?.checkFreshness.reasons).toContain("CHECK_INPUT_CHANGED");
+ } finally { __setVerifyAnalysisBarrierForTesting(undefined); rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+test("final qualified capture detects drift after admission instead of reusing an earlier snapshot", () => {
+ const root = fixture(); const spawn = Bun.spawnSync;
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ let directCaptures = 0; let mutated = false;
+ const processSpy = spyOn(Bun, "spawnSync").mockImplementation(((...args: Parameters) => {
+ if (Array.isArray(args[0]) && args[0].includes("--exclude-per-directory=.gitignore")) {
+ const frames = new Error().stack?.split("\n") ?? [];
+ const captureFrame = frames.findIndex(frame => frame.includes("at captureQualifiedAnalysisInputs "));
+ if (captureFrame >= 0 && frames[captureFrame + 1]?.includes("at runVerify ") && ++directCaptures === 3) {
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 3; }\n"); mutated = true;
+ }
+ }
+ return spawn(...args);
+ }) as typeof spawn);
+ try {
+ const admission = runVerify(root, { kind: "working-tree" }).report.analysisAdmission;
+ expect(mutated).toBe(true); expect(admission?.status).toBe("rejected");
+ expect(admission?.checkFreshness.reasons).toContain("CHECK_INPUT_CHANGED");
+ } finally { processSpy.mockRestore(); rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-typescript-closure.test.ts b/packages/app-services/test/qualified-typescript-closure.test.ts
new file mode 100644
index 00000000..fdf026c2
--- /dev/null
+++ b/packages/app-services/test/qualified-typescript-closure.test.ts
@@ -0,0 +1,58 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+function git(root: string, ...args: string[]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode) throw new Error(new TextDecoder().decode(result.stderr));
+}
+function fixture(hidden?: string, inheritedModule?: string, inheritedResolution = inheritedModule) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ts-qualified-")); roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ if (hidden) writeFileSync(join(root, "hidden.ts"), hidden);
+ if (inheritedModule) {
+ writeFileSync(join(root, "base.json"), JSON.stringify({ compilerOptions: { module: inheritedModule, moduleResolution: inheritedResolution } }));
+ writeFileSync(join(root, "tsconfig.json"), JSON.stringify({ extends: "./base.json" }));
+ }
+ git(root, "init", "-q"); git(root, "add", ".");
+ git(root, "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", "fixture");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", include: ["src/**/*.ts"], analysisProfile: "modelo-suite-static-v1" });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-09T10:01:00.000Z");
+ return root;
+}
+test("qualified profile retains its explicitly inherited ESNext/Bundler configuration", () => {
+ const root = fixture(undefined, "ESNext", "Bundler");
+ expect(runVerify(root, { kind: "working-tree" }).report.analysisAdmission?.status).toBe("admitted");
+ expect(verifyExit(root)).toBe(0);
+});
+function verifyExit(root: string) {
+ return Bun.spawnSync(["bun", join(import.meta.dir, "../../../apps/cli/src/index.ts"), "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" }).exitCode;
+}
+for (const hidden of ["export { main } from './src/main';\n", "export async function load() { return import('./src/main'); }\n"]) {
+ test(`qualified TS-only scope refuses excluded static dependent: ${hidden.trim()}`, () => {
+ const root = fixture(hidden);
+ const computation = runVerify(root, { kind: "working-tree" });
+ const actualExit = verifyExit(root);
+ expect({ admission: computation.report.analysisAdmission?.status, verdict: computation.result.verdict, exitCode: actualExit }).toEqual({ admission: "rejected", verdict: "BLOCK", exitCode: 3 });
+ expect(computation.report.analysisAdmission?.changeCoverage.files).toContainEqual(expect.objectContaining({ path: "hidden.ts", status: "excluded" }));
+ });
+}
+for (const module of ["NodeNext", "Node16"]) {
+ test(`qualified profile refuses inherited ${module} rather than reinterpreting it`, () => {
+ const root = fixture(undefined, module);
+ const computation = runVerify(root, { kind: "working-tree" });
+ const actualExit = verifyExit(root);
+ expect({ admission: computation.report.analysisAdmission?.status, verdict: computation.result.verdict, exitCode: actualExit }).toEqual({ admission: "rejected", verdict: "BLOCK", exitCode: 3 });
+ for (const code of ["SOURCE_CONFIGURATION_MODULE_UNSUPPORTED", "SOURCE_CONFIGURATION_RESOLUTION_UNSUPPORTED"]) {
+ expect(computation.report.analysisAdmission?.reasons.some(reason => reason.endsWith(`${code}:${module}`))).toBe(true);
+ }
+ });
+}
diff --git a/packages/app-services/test/qualified-unmodeled-dependencies.test.ts b/packages/app-services/test/qualified-unmodeled-dependencies.test.ts
new file mode 100644
index 00000000..9c93d6e9
--- /dev/null
+++ b/packages/app-services/test/qualified-unmodeled-dependencies.test.ts
@@ -0,0 +1,34 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["tsx", "export function Widget() { return null; } export function View() { return ; }", "JSX_COMPONENT_UNSUPPORTED"],
+ ["jsx", "export function Widget() { return null; } export function View() { return ; }", "JSX_COMPONENT_UNSUPPORTED"],
+ ["ts", "export function helper() { return 1; } export function caller() { return helper.call(null); }", "FUNCTION_HELPER_UNSUPPORTED"],
+ ["js", "export function helper() { return 1; } export function caller() { return helper.apply(null, []); }", "FUNCTION_HELPER_UNSUPPORTED"],
+ ["ts", "export class Base {} export class Derived extends Base {}", "INTERNAL_HERITAGE_UNSUPPORTED"],
+ ["js", "export class Base {} export class Derived extends Base {}", "INTERNAL_HERITAGE_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-unmodeled-dependency-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-unresolved-meta-prototype.test.ts b/packages/app-services/test/qualified-unresolved-meta-prototype.test.ts
new file mode 100644
index 00000000..65a4cf59
--- /dev/null
+++ b/packages/app-services/test/qualified-unresolved-meta-prototype.test.ts
@@ -0,0 +1,37 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+for (const [name, body, changedName] of [
+ ["unresolved host launcher", "export function run(){return Bun.spawnSync(['bun','./src/hidden.EXT']);}", "hidden"],
+ ["prototype setter", "import {helper} from './helper.EXT';const value={__proto__:{toString:helper}};export function run(){return value.toString();}", "helper"],
+ ["import meta callable static hole", "const pages=import.meta.glob('./pages/*.EXT',{eager:true});export function run(){return pages['./pages/hidden.EXT'];}", "pages/hidden"],
+] as const) for (const extension of ["ts", "mjs"]) for (const selection of ["selected", "excluded"]) test(`qualified ${name} ${extension} ${selection} rejects an unproven runtime boundary`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-unresolved-meta-proto-"));
+ try {
+ mkdirSync(join(root, "src/pages"), { recursive: true });
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ const changed = join(root, `src/${changedName}.${extension}`);
+ writeFileSync(changed, "export function helper(){return 1;}");
+ writeFileSync(join(root, `src/consumer.${extension}`), body.replaceAll("EXT", extension));
+ writeFileSync(join(root, `src/consumer.test.${extension}`), `import {run} from './consumer.${extension}';export function check(){return run();}`);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: selection === "selected" ? ["src/**/*"] : [`src/${changedName}.${extension}`], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(changed, "export function helper(){return 2;}");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ name, extension, selection, admission: report.analysisAdmission, recommendedTests: report.recommendedTests }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.binding.status).toBe("valid");
+ const cli = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+ const result = Bun.spawnSync([process.execPath, cli, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(3);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 180_000);
diff --git a/packages/app-services/test/qualified-worker-timer.test.ts b/packages/app-services/test/qualified-worker-timer.test.ts
new file mode 100644
index 00000000..187be934
--- /dev/null
+++ b/packages/app-services/test/qualified-worker-timer.test.ts
@@ -0,0 +1,33 @@
+import { expect, test } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexHealth, indexRepository, runVerify } from "../src";
+
+for (const [extension, content, reason] of [
+ ["ts", "export function start() { return new Worker('./worker.js', { type: 'module' }); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["js", "export function start() { return new SharedWorker(new URL('./worker.js', import.meta.url)); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["ts", "export function start() { return globalThis.setTimeout(\"import('./hidden.js')\", 0); }", "TIMER_EVALUATION_UNSUPPORTED"],
+ ["js", "export function start() { return self.setInterval(\"import('./hidden.js')\", 0); }", "TIMER_EVALUATION_UNSUPPORTED"],
+ ["ts", "const { setTimeout: schedule } = globalThis; const later = schedule; export function start() { return later.call(null, 'hidden()', 0); }", "TIMER_EVALUATION_UNSUPPORTED"],
+] as const) for (const selected of [true, false]) test(`qualified ${reason} ${extension}, selected ${selected}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-decorated-destructured-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ const path = `${selected ? "src/" : ""}subject.${extension}`; writeFileSync(join(root, path), content);
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n"); indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ console.info(JSON.stringify({ path, status: report.analysisAdmission?.status, reasons: report.analysisAdmission?.reasons }));
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ expect(report.analysisAdmission?.reasons.some(item => item.includes(reason))).toBe(true);
+ if (selected) expect(indexHealth(root).candidates.find(candidate => candidate.path === path)?.analysisReasons.some(item => item.includes(reason))).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
diff --git a/packages/app-services/test/qualified-working-tree-filter.test.ts b/packages/app-services/test/qualified-working-tree-filter.test.ts
new file mode 100644
index 00000000..0103986e
--- /dev/null
+++ b/packages/app-services/test/qualified-working-tree-filter.test.ts
@@ -0,0 +1,69 @@
+import { expect, test } from "bun:test";
+import { createHash } from "node:crypto";
+import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, loadConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+import { captureQualifiedAnalysisInputs } from "../src/freshness";
+import { retainedWorkingTreeFilterPaths } from "../src/verification-state";
+
+for (const [name, attributes, driver, qualified] of [
+ ["active qualified", "*.ts filter=drop\n", true, true],
+ ["active ordinary", "*.ts filter=drop\n", true, false],
+ ["named unconfigured", "*.ts filter=drop\n", false, true],
+ ["text normalization", "*.ts text eol=lf\n", false, true],
+ ["reserved configured", "*.ts filter=unset\n", true, true],
+] as const) test(`working-tree filter ${name}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-filter-scope-"));
+ const git = (...args: string[]): string => {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout);
+ };
+ try {
+ mkdirSync(join(root,"src")); writeFileSync(join(root,".gitignore"),".semctx/\n");
+ writeFileSync(join(root,".gitattributes"),attributes);
+ writeFileSync(join(root,"src/main.ts"),"export function main(){return 3;}\n");
+ const hidden = join(root,"src/hidden.ts"); const before="export function hidden(){return 1;}\n";
+ writeFileSync(hidden,before);
+ writeFileSync(join(root,"src/hidden.test.ts"),"import {hidden} from './hidden';export function check(){return hidden();}\n");
+ git("init","-q");
+ if(driver){
+ const script=join(root,".git/drop.cjs");
+ writeFileSync(script,"let text='';process.stdin.on('data',chunk=>text+=chunk);process.stdin.on('end',()=>process.stdout.write(text.replaceAll('return 2','return 1')));\n");
+ git("config","--local",`filter.${name === "reserved configured" ? "unset" : "drop"}.clean`,`"${process.execPath.replaceAll("\\","/")}" "${script.replaceAll("\\","/")}"`);
+ }
+ git("add",".");git("-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","fixture");
+ initWorkspace(root,{...createGlobSelectionConfig(root),...(qualified?{selectionMode:"qualified-static-v1" as const,analysisProfile:"modelo-suite-static-v1" as const}:{}),include:["src/**/*"]});
+ writeFileSync(hidden,"export function hidden(){return 2;}\n");writeFileSync(join(root,"src/main.ts"),"export function main(){return 4;}\n");
+ const paths=git("diff","--name-only","HEAD").trim().split("\n");
+ expect(paths).toContain("src/main.ts");expect(paths.includes("src/hidden.ts")).toBe(!driver);
+ expect(createHash("sha256").update(readFileSync(hidden)).digest("hex")).not.toBe(createHash("sha256").update(before).digest("hex"));
+ if(qualified)expect(captureQualifiedAnalysisInputs(loadConfig(root)).files.some(file=>file.path==="src/hidden.ts")).toBe(true);
+ indexRepository(root,"2026-10-12T03:00:00.000Z");
+ const report=runVerify(root,{kind:"working-tree"}).report;
+ console.info(JSON.stringify({name,git:git("--version").trim(),attributes:git("check-attr","filter","--","src/hidden.ts").trim(),rawBefore:createHash("sha256").update(before).digest("hex"),rawAfter:createHash("sha256").update(readFileSync(hidden)).digest("hex"),diffPaths:paths,admission:report.analysisAdmission,recommendedTests:report.recommendedTests}));
+ expect(report.analysisAdmission?.status).toBe(qualified?(driver?"rejected":"admitted"):undefined);
+ if(qualified&&driver){
+ expect(report.analysisAdmission?.binding.reasons).toContain("QUALIFIED_WORKING_TREE_FILTER_UNSUPPORTED:src/hidden.ts");
+ const result=Bun.spawnSync([process.execPath,join(import.meta.dir,"../../../apps/cli/src/index.ts"),"verify","diff","--root",root,"--format","json","--fail-on","none"],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(result.exitCode).toBe(3);
+ }
+ }finally{rmSync(root,{recursive:true,force:true});}
+},180_000);
+
+test("process-only filter authority is inspected without executing its command", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-process-filter-"));
+ try {
+ mkdirSync(join(root,"src")); writeFileSync(join(root,"src/main.ts"),"export function main(){return 1;}\n");
+ writeFileSync(join(root,".gitattributes"),"*.ts filter=pipe\n");
+ expect(Bun.spawnSync(["git","init","-q"],{cwd:root,stdout:"pipe",stderr:"pipe"}).exitCode).toBe(0);
+ const marker=join(root,".git/executed");const script=join(root,".git/process.cjs");
+ writeFileSync(script,`require('node:fs').writeFileSync(${JSON.stringify(marker)},'executed');\n`);
+ const command=`"${process.execPath.replaceAll("\\","/")}" "${script.replaceAll("\\","/")}"`;
+ expect(Bun.spawnSync(["git","config","--local","filter.pipe.process",command],{cwd:root,stdout:"pipe",stderr:"pipe"}).exitCode).toBe(0);
+ expect(retainedWorkingTreeFilterPaths(root,["src/main.ts"])).toEqual(["src/main.ts"]);
+ expect(existsSync(marker)).toBe(false);
+ } finally {rmSync(root,{recursive:true,force:true});}
+});
diff --git a/packages/app-services/test/qualified-working-tree-guard-loss.test.ts b/packages/app-services/test/qualified-working-tree-guard-loss.test.ts
new file mode 100644
index 00000000..bfc3ef42
--- /dev/null
+++ b/packages/app-services/test/qualified-working-tree-guard-loss.test.ts
@@ -0,0 +1,274 @@
+import { afterEach, expect, spyOn, test } from "bun:test";
+import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig, VerifyReportSchema } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const CLI = join(import.meta.dir, "../../../apps/cli/src/index.ts");
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+function repository(source: string) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-qualified-worktree-guard-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/value.ts"), source);
+ git(root, "init", "-q", "-b", "main"); git(root, "add", "."); git(root, "commit", "-q", "-m", "guarded base");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*", "test/**/*"] });
+ return root;
+}
+function refreshed(root: string) {
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const observed = runVerify(root, { kind: "working-tree" });
+ expect(VerifyReportSchema.safeParse(observed.report).success).toBe(true);
+ expect(observed.report.analysisAdmission?.changeCoverage.analyzed).toContain("src/value.ts");
+ expect(observed.report.analysisAdmission?.indexFreshness.verdict).toBe("DIRTY_KNOWN");
+ expect(observed.report.analysisAdmission?.checkFreshness.status).toBe("current");
+ return observed;
+}
+function expectCurrentScopeRejection(observed: ReturnType, excludedPath: string, reasons: string[]) {
+ const admission = observed.report.analysisAdmission;
+ expect(VerifyReportSchema.safeParse(observed.report).success).toBe(true);
+ expect(observed.result.verdict).toBe("BLOCK");
+ expect(observed.report.verdict).toBe("BLOCK");
+ expect(admission?.status).toBe("rejected");
+ expect(admission?.reasons).toEqual(reasons);
+ expect(admission?.binding).toEqual({ status: "valid", reasons: [] });
+ expect(admission?.changeCoverage).toEqual({
+ expected: [excludedPath, "src/value.ts"],
+ analyzed: ["src/value.ts"],
+ files: [
+ { path: excludedPath, status: "excluded", reasons: ["INCLUDE_MISS", "INCLUDE_MISS"] },
+ { path: "src/value.ts", status: "analyzed", reasons: [] },
+ ],
+ });
+ expect(admission?.indexFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission?.controlFreshness).toEqual({ verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] });
+ expect(admission?.checkFreshness).toEqual({ status: "current", reasons: [] });
+}
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+
+for (const [kind, rule, before, after] of [
+ ["invariant", "invariant_touched_without_test", "/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n", "export function value() { return 1; }\n"],
+ ["security", "security_surface_without_verification", "/** @tag security */\nexport function value() { return 1; }\n", "export function value() { return 1; }\n"],
+ ["exported contract", "contract_changed_without_test", "export interface Value { value: number }\n", "interface Value { value: number }\nexport function value() { return 1; }\n"],
+] as const) test(`refreshed qualified working tree cannot erase ${kind} guard`, () => {
+ const root = repository(before);
+ writeFileSync(join(root, "src/value.ts"), after);
+ const observed = refreshed(root);
+ console.info(JSON.stringify({ kind, report: observed.report }));
+ expect(observed.result.findings.map((finding) => finding.rule)).toContain(rule);
+ expect(observed.result.verdict).not.toBe("PASS");
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ const cli = Bun.spawnSync([process.execPath, CLI, "verify", "diff", "--root", root, "--format", "json", "--fail-on", "none"], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(0);
+ const report = JSON.parse(new TextDecoder().decode(cli.stdout));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission.status).toBe("admitted");
+ expect(report.findings.map((finding: { rule: string }) => finding.rule)).toContain(rule);
+}, 60_000);
+
+test("pure addition preserves an unchanged guard and an actual covering test", () => {
+ const root = repository("/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n");
+ mkdirSync(join(root, "test"));
+ writeFileSync(join(root, "test/value.test.ts"), 'import { expect, test } from "bun:test";\nimport { value } from "../src/value";\ntest("value", () => expect(value()).toBe(1));\n');
+ git(root, "add", "test/value.test.ts"); git(root, "commit", "-q", "-m", "covering base test");
+ writeFileSync(join(root, "src/value.ts"), "/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\nexport function extra() { return 2; }\n");
+ const observed = refreshed(root);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+}, 60_000);
+
+test("body replacement with unchanged guard and covering test preserves existing admission", () => {
+ const root = repository("/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n");
+ mkdirSync(join(root, "test"));
+ writeFileSync(join(root, "test/value.test.ts"), 'import { expect, test } from "bun:test";\nimport { value } from "../src/value";\ntest("value", () => expect(value()).toBe(1));\n');
+ git(root, "add", "test/value.test.ts"); git(root, "commit", "-q", "-m", "covering base test");
+ writeFileSync(join(root, "src/value.ts"), "/** @invariant fixed: stays fixed */\nexport function value() { return 2; }\n");
+ const observed = refreshed(root);
+ console.info(JSON.stringify({ bodyReplacement: observed.report }));
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+}, 60_000);
+
+test("retained authored output imports a force-tracked covering test without touching the live index", () => {
+ const root = repository('/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n');
+ mkdirSync(join(root, "dist"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\ndist/\n");
+ writeFileSync(join(root, "dist/cover.test.ts"), 'import { expect, test } from "bun:test";\nimport { value } from "../src/value";\ntest("value", () => expect(value()).toBe(1));\n');
+ const configPath = join(root, ".semctx/config.json");
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
+ config.include.push("dist/**/*");
+ writeFileSync(configPath, JSON.stringify(config));
+ git(root, "add", ".gitignore"); git(root, "add", "-f", "dist/cover.test.ts"); git(root, "commit", "-q", "-m", "ignored authored test");
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 1; }\n');
+ const beforeIndex = readFileSync(join(root, ".git/index"));
+ const observed = refreshed(root);
+ expect(readFileSync(join(root, ".git/index"))).toEqual(beforeIndex);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+ expect(observed.result.findings.map(finding => finding.rule)).not.toContain("invariant_touched_without_test");
+}, 60_000);
+
+test("marker removal covered by a new current covering test remains admitted", () => {
+ const root = repository('/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n');
+ mkdirSync(join(root, "test"));
+ writeFileSync(join(root, "test/value.test.ts"), 'import { expect, test } from "bun:test";\nimport { value } from "../src/value";\ntest("value", () => expect(value()).toBe(1));\n');
+ git(root, "add", "test");
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 1; }\n');
+ const observed = refreshed(root);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+ expect(observed.result.findings.map(finding => finding.rule)).not.toContain("invariant_touched_without_test");
+}, 60_000);
+
+for (const [kind, source, configFiles] of [
+ ["parse", 'export function value( { return 1; }\n', []],
+ ["module", 'const value = require("node:fs");\nexport { value };\n', []],
+ ["inherited compiler policy", 'export function value() { return 1; }\n', [["tsconfig.json", '{"extends":"./base-config.json"}'], ["base-config.json", '{"compilerOptions":{"module":"CommonJS"}}']]],
+] as const) test(kind === "inherited compiler policy" ? "changed inherited compiler policy rejects current scope before old capture" : `unsupported old ${kind} cannot authorize a clean replacement`, () => {
+ const root = repository(source);
+ for (const [path, content] of configFiles) writeFileSync(join(root, path), content);
+ if (configFiles.length > 0) { git(root, "add", "."); git(root, "commit", "-q", "-m", "old compiler policy"); }
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 2; }\n');
+ if (configFiles.length > 0) writeFileSync(join(root, "base-config.json"), '{"compilerOptions":{"module":"ESNext"}}');
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const observed = runVerify(root, { kind: "working-tree" });
+ if (kind === "inherited compiler policy") {
+ expectCurrentScopeRejection(observed, "base-config.json", [
+ "ANALYSIS_SCOPE_INCOMPLETE:base-config.json:excluded",
+ "UNSUPPORTED_CHANGED_OBLIGATION:base-config.json",
+ ]);
+ } else {
+ expect(VerifyReportSchema.safeParse(observed.report).success).toBe(true);
+ expect(observed.result.verdict).toBe("BLOCK");
+ expect(observed.report.analysisAdmission?.reasons.some(reason => reason.startsWith("QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED:"))).toBe(true);
+ expect(observed.report.findings.some(finding => finding.severity === "block" && finding.locations.some(location => location.file === "src/value.ts"))).toBe(true);
+ }
+}, 60_000);
+
+test("changed excluded static importer rejects current scope before old capture", () => {
+ const root = repository('export function value() { return 1; }\n');
+ mkdirSync(join(root, "other"));
+ writeFileSync(join(root, "other/importer.ts"), 'import { value } from "../src/value";\nexport const copy = value();\n');
+ git(root, "add", "other"); git(root, "commit", "-q", "-m", "old excluded importer");
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 2; }\n');
+ writeFileSync(join(root, "other/importer.ts"), 'export const copy = 0;\n');
+ const observed = refreshed(root);
+ expectCurrentScopeRejection(observed, "other/importer.ts", ["ANALYSIS_SCOPE_INCOMPLETE:other/importer.ts:excluded"]);
+}, 60_000);
+
+for (const fault of ["missing input", "raw byte mismatch", "gitlink", "unsafe path", "scratch drift"] as const) {
+ test(`immutable old capture refuses ${fault} as a structured result`, () => {
+ const root = repository('/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n');
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 1; }\n');
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const original = Bun.spawnSync;
+ let injected = false;
+ const spy = spyOn(Bun, "spawnSync").mockImplementation((...args) => {
+ const command = args[0];
+ const result = Reflect.apply(original, Bun, args) as ReturnType;
+ if (!Array.isArray(command)) return result;
+ if (!injected && command[1] === "ls-tree" && fault === "gitlink") {
+ injected = true;
+ return { ...result, stdout: Buffer.concat([result.stdout!, Buffer.from(`160000 commit ${git(root, "rev-parse", "HEAD")}\tsrc/vendor\0`)]) };
+ }
+ if (!injected && command[1] === "ls-tree" && fault === "unsafe path") {
+ injected = true;
+ return { ...result, stdout: Buffer.from(new TextDecoder().decode(result.stdout).replace("src/value.ts", "src/../value.ts")) };
+ }
+ if (!injected && command[1] === "cat-file" && fault === "missing input") {
+ injected = true;
+ return { ...result, stdout: Buffer.from("missing\n") };
+ }
+ if (!injected && command[1] === "cat-file" && fault === "raw byte mismatch") {
+ injected = true;
+ return { ...result, stdout: Buffer.from(new TextDecoder().decode(result.stdout).replace("return 1", "return 9")) };
+ }
+ const options = args[1] as { cwd?: string } | undefined;
+ if (!injected && fault === "scratch drift" && command.includes("--index-info") && options?.cwd !== root) {
+ injected = true;
+ const scratch = options!.cwd!;
+ writeFileSync(join(scratch, "src/value.ts"), 'export function tampered() { return 9; }\n');
+ }
+ return result;
+ });
+ let observed: ReturnType;
+ try { observed = runVerify(root, { kind: "working-tree" }); }
+ finally { spy.mockRestore(); }
+ expect(injected).toBe(true);
+ expect(VerifyReportSchema.safeParse(observed.report).success).toBe(true);
+ expect(observed.result.verdict).toBe("BLOCK");
+ expect(observed.report.analysisAdmission?.status).toBe("rejected");
+ expect(observed.report.analysisAdmission?.reasons.some(reason => reason.startsWith("QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED:"))).toBe(true);
+ }, 60_000);
+}
+
+test("unrelated old unsupported source outside dependency closure does not refuse a supported body edit", () => {
+ const root = repository('export function value() { return 1; }\n');
+ writeFileSync(join(root, "src/unrelated.py"), 'def unrelated(:\n');
+ git(root, "add", "src/unrelated.py"); git(root, "commit", "-q", "-m", "old unrelated disabled Python source");
+ const configPath = join(root, ".semctx/config.json");
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
+ config.languages.python = "off";
+ writeFileSync(configPath, JSON.stringify(config));
+ // Selected disabled Python is repository partial, outside the bounded TS/JS changed closure.
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 2; }\n');
+ const observed = refreshed(root);
+ console.info(JSON.stringify({ unrelatedPartial: observed.report }));
+ expect(observed.report.analysisAdmission?.changeCoverage.expected).toEqual(["src/value.ts"]);
+ expect(observed.report.analysisAdmission?.status).toBe("admitted");
+ expect(observed.result.verdict).not.toBe("BLOCK");
+}, 60_000);
+
+test("unreadable immutable HEAD capture is rejected by CLI even under advisory none", () => {
+ const root = repository('/** @invariant fixed: stays fixed */\nexport function value() { return 1; }\n');
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 1; }\n');
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+ const entry = join(root, ".git/base-capture-cli.ts");
+ const module = (path: string): string => JSON.stringify(join(import.meta.dir, path).replaceAll("\\", "/"));
+ writeFileSync(entry, `import {runVerifyDiff} from ${module("../../../apps/cli/src/commands/verify.ts")};
+import {parseArgs} from ${module("../../../apps/cli/src/args.ts")};
+const original=Bun.spawnSync;
+Bun.spawnSync=((...args)=>{
+ const command=args[0];const result=Reflect.apply(original,Bun,args);
+ return Array.isArray(command)&&command[1]==="cat-file"?{...result,exitCode:1}:result;
+}) as typeof Bun.spawnSync;
+process.exit(runVerifyDiff(process.argv[2]!,parseArgs(["verify","diff","--format","json","--fail-on","none"])));
+`);
+ const cli = Bun.spawnSync([process.execPath, entry, root], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode).toBe(3);
+ const report = JSON.parse(new TextDecoder().decode(cli.stdout));
+ expect(VerifyReportSchema.safeParse(report).success).toBe(true);
+ expect(report.analysisAdmission.status).toBe("rejected");
+ expect(report.analysisAdmission.reasons.some((reason: string) => reason.startsWith("QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED:"))).toBe(true);
+}, 60_000);
+
+test("changed excluded computed loader rejects current scope before old capture", () => {
+ const root = repository('export function value() { return 1; }\n');
+ mkdirSync(join(root, "other"));
+ writeFileSync(join(root, "other/importer.ts"), 'const path = "../src/value";\nexport async function run() { return import(path); }\n');
+ git(root, "add", "other"); git(root, "commit", "-q", "-m", "old computed loader");
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 2; }\n');
+ writeFileSync(join(root, "other/importer.ts"), 'export const safe = 0;\n');
+ const observed = refreshed(root);
+ expectCurrentScopeRejection(observed, "other/importer.ts", ["ANALYSIS_SCOPE_INCOMPLETE:other/importer.ts:excluded"]);
+}, 60_000);
+
+test("ignored force-tracked old output dependency has an explicit eligibility decision", () => {
+ const root = repository('import { legacy } from "../dist/legacy";\nexport function value() { return legacy; }\n');
+ mkdirSync(join(root, "dist"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\ndist/\n");
+ writeFileSync(join(root, "dist/legacy.ts"), 'export const legacy = 1;\n');
+ git(root, "add", ".gitignore"); git(root, "add", "-f", "dist/legacy.ts"); git(root, "commit", "-q", "-m", "old ignored authored dependency");
+ writeFileSync(join(root, "src/value.ts"), 'export function value() { return 2; }\n');
+ const observed = refreshed(root);
+ expect(observed.result.verdict).toBe("BLOCK");
+ expect(observed.report.analysisAdmission?.reasons.some(reason => reason.includes("QUALIFIED_WORKING_TREE_BASE_CAPTURE_UNSUPPORTED") && reason.includes("dist/legacy.ts"))).toBe(true);
+}, 60_000);
diff --git a/packages/app-services/test/setup.test.ts b/packages/app-services/test/setup.test.ts
index 2404bf3b..cee28532 100644
--- a/packages/app-services/test/setup.test.ts
+++ b/packages/app-services/test/setup.test.ts
@@ -36,6 +36,21 @@ const GIT_ENV = {
GIT_COMMITTER_NAME: "semctx-test",
GIT_COMMITTER_EMAIL: "semctx-test@example.com",
};
+it("counts selected JavaScript in setup planning and index progress", () => {
+ const root = freshSample();
+ try {
+ writeFileSync(join(root, "extra.mjs"), "export function extra() { return 1; }");
+ initWorkspace(root, { ...createGlobSelectionConfig(root), include: ["**/*.ts", "**/*.mjs"], languages: { typescript: "on", javascript: "on" } });
+ const plan = planSetupRepository(root);
+ expect(plan.kind).toBe("setup_plan");
+ if (plan.kind !== "setup_plan") throw new Error("unexpected plan");
+ expect(plan.selection.selectedByLanguage.javascript).toBe(1);
+ const phases: SetupPhaseEvent[] = [];
+ const report = asSetup(setupRepository(root, { onPhase: phase => phases.push(phase) }));
+ expect(report.selection.selectedByLanguage.javascript).toBe(1);
+ expect(phases.find(phase => phase.phase === "index" && phase.stage === "start")).toMatchObject({ selectedByLanguage: { javascript: 1 } });
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
function git(cwd: string, ...args: string[]): void {
const result = Bun.spawnSync(["git", ...args], {
diff --git a/packages/app-services/test/verification-state.test.ts b/packages/app-services/test/verification-state.test.ts
index 8877ee09..e6970501 100644
--- a/packages/app-services/test/verification-state.test.ts
+++ b/packages/app-services/test/verification-state.test.ts
@@ -8,6 +8,7 @@ import {
__setVerificationAttributeBarrierForTesting,
captureRecordableVerificationGitState,
captureVerificationGitState,
+ retainedGitBlobObjectIds,
} from "../src/verification-state";
const roots: string[] = [];
@@ -99,6 +100,31 @@ afterEach(() => {
});
describe("local blob object id derivation", () => {
+ for (const conversion of ["crlf", "encoding", "filter", "identity"] as const) it(`retained post-image ${conversion} uses selected attributes and retained payload, never live content`, () => {
+ const root = repository(); const name = "input.dat";
+ const bytes = conversion === "encoding" ? Buffer.from("retained content\n", "utf16le") : Buffer.from(conversion === "crlf" ? "retained content\r\n" : "retained content\n");
+ const attributes = conversion === "encoding" ? `${name} working-tree-encoding=UTF-16LE\n` : conversion === "crlf" ? `${name} text eol=crlf\n` : conversion === "filter" ? `${name} filter=upper\n` : "";
+ if (conversion === "filter") git(root, "config", "filter.upper.clean", configureCleanFilter(root, "retained-upper", "let chunks=[];process.stdin.on('data',c=>chunks.push(c));process.stdin.on('end',()=>process.stdout.write(Buffer.concat(chunks).toString('utf8').toUpperCase()));"));
+ writeFileSync(join(root, ".gitattributes"), attributes); writeFileSync(join(root, name), bytes);
+ git(root, "add", "."); git(root, "commit", "-qm", "selected input");
+ const head = git(root, "rev-parse", "HEAD"); const expected = git(root, "rev-parse", `HEAD:${name}`);
+ writeFileSync(join(root, name), "different live source\n"); writeFileSync(join(root, ".gitattributes"), `${name} -text -filter -working-tree-encoding\n`);
+ expect(retainedGitBlobObjectIds(root, head, [{ path: name, bytes }]).get(name)).toBe(expected);
+ });
+ it("retained post-image refuses attribute context drift between observations", () => {
+ const root = repository(); const name = "input.dat";
+ const head = git(root, "rev-parse", "HEAD");
+ __setVerificationAttributeBarrierForTesting(() => writeFileSync(join(root, ".git/info/attributes"), `${name} ident\n`));
+ expect(() => retainedGitBlobObjectIds(root, head, [{ path: name, bytes: Buffer.from("retained\n") }])).toThrow("attribute metadata changed");
+ });
+ for (const override of ["-text", "eol=crlf"]) it(`retained post-image refuses text/eol conversion drift: ${override}`, () => {
+ const root = repository(); const name = "input.dat";
+ writeFileSync(join(root, ".gitattributes"), `${name} text eol=lf\n`);
+ git(root, "add", ".gitattributes"); git(root, "commit", "-qm", "selected attributes");
+ const head = git(root, "rev-parse", "HEAD");
+ __setVerificationAttributeBarrierForTesting(() => writeFileSync(join(root, ".git/info/attributes"), `${name} ${override}\n`));
+ expect(() => retainedGitBlobObjectIds(root, head, [{ path: name, bytes: Buffer.from("retained\r\n") }])).toThrow("attribute metadata changed");
+ });
it("matches the real Git oracle in a SHA-256 repository", () => {
const root = repository("sha256");
const name = "espace été.txt";
diff --git a/packages/app-services/test/verify-base-tree.test.ts b/packages/app-services/test/verify-base-tree.test.ts
new file mode 100644
index 00000000..1f733732
--- /dev/null
+++ b/packages/app-services/test/verify-base-tree.test.ts
@@ -0,0 +1,144 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { SemctxError, createDefaultConfig, createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, saveConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const MONEY = "/** @invariant money: nonnegative */\nexport function clamp(x: number) { return x < 0 ? 0 : x; }\n";
+
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], {
+ cwd: root, stdout: "pipe", stderr: "pipe",
+ });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+
+function repository(body = MONEY, v2 = false) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-base-tree-safety-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src", "money.ts"), body);
+ git(root, "init", "-q", "-b", "main");
+ git(root, "add", ".");
+ git(root, "commit", "-q", "-m", "base");
+ const config = v2 ? { ...createGlobSelectionConfig(root), include: ["**/*.ts"] } : createDefaultConfig(root);
+ initWorkspace(root, config);
+ return { root, config };
+}
+
+function commitHead(root: string): void {
+ git(root, "add", "-A");
+ git(root, "commit", "-q", "-m", "head");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+}
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+describe("range base-tree source safety", () => {
+ it("reads raw base blobs without executing checkout filters", () => {
+ const { root } = repository();
+ writeFileSync(join(root, ".gitattributes"), "*.ts filter=semctx-review\n");
+ git(root, "add", ".gitattributes");
+ git(root, "commit", "-q", "-m", "base attributes");
+ const base = git(root, "rev-parse", "HEAD");
+ git(root, "rm", "-q", "src/money.ts");
+ commitHead(root);
+ const marker = join(root, ".git", "filter-executed");
+ const filter = join(root, ".git", "filter.cjs");
+ writeFileSync(filter, `require("node:fs").writeFileSync(${JSON.stringify(marker)}, "executed");process.stdout.write("export function clamp(x: number) { return x; }\\n");`);
+ git(root, "config", "filter.semctx-review.smudge", `"${process.execPath.replaceAll("\\", "/")}" "${filter.replaceAll("\\", "/")}"`);
+ git(root, "config", "filter.semctx-review.required", "true");
+
+ const result = runVerify(root, { kind: "range", base }).result;
+ expect(existsSync(marker)).toBe(false);
+ expect(result.findings.map((finding) => finding.rule)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("blocks a selected failed base scope even when the head scope analyzes", () => {
+ const { root } = repository(`import { outside } from "../../outside";\n${MONEY}`, true);
+ const base = git(root, "rev-parse", "HEAD");
+ writeFileSync(join(root, "src", "money.ts"), "export function clamp(x: number) { return x < 0 ? 0 : x; }\n");
+ commitHead(root);
+
+ const result = runVerify(root, { kind: "range", base }).result;
+ expect(result.findings).toEqual(expect.arrayContaining([expect.objectContaining({
+ rule: "analysis_scope_incomplete", severity: "block", message: expect.stringContaining("src/money.ts"),
+ })]));
+ expect(result.unknowns.join("\n")).toContain("src/money.ts");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("discloses an excluded changed base path instead of an unqualified PASS", () => {
+ const { root, config } = repository(MONEY, true);
+ const base = git(root, "rev-parse", "HEAD");
+ git(root, "rm", "-q", "src/money.ts");
+ git(root, "commit", "-q", "-m", "delete");
+ saveConfig(root, { ...config, exclude: [...config.exclude, "src/money.ts"] });
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+
+ const result = runVerify(root, { kind: "range", base }).result;
+ expect(result.unknowns.join("\n")).toContain("src/money.ts");
+ expect(result.verdict).not.toBe("PASS");
+ expect(result.findings.map((finding) => finding.rule)).not.toContain("invariant_touched_without_test");
+ });
+
+ it("never treats a Git symlink blob as regular TypeScript when core.symlinks is false", () => {
+ const { root } = repository("export const helper = 1;\n", true);
+ git(root, "config", "core.symlinks", "false");
+ const payload = join(root, ".git", "link-payload");
+ writeFileSync(payload, MONEY);
+ const object = git(root, "hash-object", "-w", payload);
+ git(root, "update-index", "--add", "--cacheinfo", `120000,${object},src/link.ts`);
+ git(root, "commit", "-q", "-m", "base symlink");
+ const base = git(root, "rev-parse", "HEAD");
+ git(root, "update-index", "--force-remove", "src/link.ts");
+ git(root, "commit", "-q", "-m", "remove symlink");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+
+ try {
+ const result = runVerify(root, { kind: "range", base }).result;
+ expect(result.findings).toEqual(expect.arrayContaining([expect.objectContaining({
+ rule: "analysis_scope_incomplete", severity: "block", message: expect.stringContaining("src/link.ts"),
+ })]));
+ expect(result.findings.map((finding) => finding.rule)).not.toContain("invariant_touched_without_test");
+ } catch (error) {
+ expect(error).toBeInstanceOf(SemctxError);
+ expect((error as SemctxError).code).toBe("GIT_ERROR");
+ expect((error as SemctxError).details).toEqual(expect.objectContaining({ path: "src/link.ts", mode: "120000" }));
+ }
+ });
+
+ (process.platform === "win32" ? it : it.skip)("refuses case-aliased base paths before materialization on Windows", () => {
+ const { root } = repository("export const helper = 1;\n");
+ git(root, "config", "core.ignorecase", "false");
+ const upperPayload = join(root, ".git", "upper-payload");
+ const lowerPayload = join(root, ".git", "lower-payload");
+ writeFileSync(upperPayload, MONEY);
+ writeFileSync(lowerPayload, "export function clamp(x: number) { return x; }\n");
+ const upper = git(root, "hash-object", "-w", upperPayload);
+ const lower = git(root, "hash-object", "-w", lowerPayload);
+ git(root, "update-index", "--add", "--cacheinfo", `100644,${upper},src/ALIAS.ts`);
+ git(root, "update-index", "--add", "--cacheinfo", `100644,${lower},src/alias.ts`);
+ git(root, "commit", "-q", "-m", "base aliases");
+ const base = git(root, "rev-parse", "HEAD");
+ expect(git(root, "ls-tree", "--name-only", "-r", base)).toContain("src/ALIAS.ts");
+ expect(git(root, "ls-tree", "--name-only", "-r", base)).toContain("src/alias.ts");
+ git(root, "update-index", "--force-remove", "src/ALIAS.ts", "src/alias.ts");
+ git(root, "commit", "-q", "-m", "remove aliases");
+ indexRepository(root, "2026-10-12T00:00:00.000Z");
+
+ let refusal: unknown;
+ try { runVerify(root, { kind: "range", base }); }
+ catch (error) { refusal = error; }
+ expect(refusal).toBeInstanceOf(SemctxError);
+ expect((refusal as SemctxError).code).toBe("GIT_ERROR");
+ });
+});
diff --git a/packages/app-services/test/verify-computation-characterization.test.ts b/packages/app-services/test/verify-computation-characterization.test.ts
index 8fd1abab..30ccd9b4 100644
--- a/packages/app-services/test/verify-computation-characterization.test.ts
+++ b/packages/app-services/test/verify-computation-characterization.test.ts
@@ -25,6 +25,9 @@ import { CONTROL_INDEX_SNAPSHOT_META_KEY, captureGitState, captureGitStateEntrie
const FIXTURE = join(REPO_ROOT, "examples", "change-impact-replay");
const GIT_ENV = {
...process.env,
+ // Commit ids are pinned below, so the developer's own git config (e.g. commit.gpgsign) must not leak in.
+ GIT_CONFIG_GLOBAL: "/dev/null",
+ GIT_CONFIG_NOSYSTEM: "1",
GIT_AUTHOR_NAME: "Semctx Test",
GIT_AUTHOR_EMAIL: "semctx@example.test",
GIT_COMMITTER_NAME: "Semctx Test",
diff --git a/packages/app-services/test/verify-deleted-test-evidence.test.ts b/packages/app-services/test/verify-deleted-test-evidence.test.ts
new file mode 100644
index 00000000..00bb54d3
--- /dev/null
+++ b/packages/app-services/test/verify-deleted-test-evidence.test.ts
@@ -0,0 +1,64 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createDefaultConfig } from "@semantic-context/core";
+import { GraphIndex } from "@semantic-context/context-engine";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+const roots: string[] = [];
+const SOURCE = "/** @invariant nonnegative: amounts never become negative */\nexport function clamp(x: number) { return x < 0 ? 0 : x; }\n";
+const TEST = 'import { expect, test } from "bun:test";\nimport { clamp } from "../src/money";\ntest("clamp", () => expect(clamp(-1)).toBe(0));\n';
+
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args], {
+ cwd: root, stdout: "pipe", stderr: "pipe",
+ });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+
+function range(deleteTest: boolean) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-deleted-test-evidence-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ mkdirSync(join(root, "test"));
+ writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/money.ts"), SOURCE);
+ writeFileSync(join(root, "src/helper.ts"), "export function helper() { return 1; }\n");
+ writeFileSync(join(root, "test/money.test.ts"), TEST);
+ git(root, "init", "-q", "-b", "main");
+ git(root, "add", ".");
+ git(root, "commit", "-q", "-m", "guard and covering test");
+ initWorkspace(root, createDefaultConfig(root));
+ const base = new GraphIndex(indexRepository(root, "2026-10-12T00:00:00.000Z").analysis.graph);
+ const guarded = base.nodesOfKind("function").find((node) => node.name === "clamp")!;
+ expect(base.outEdges(guarded.id, ["constrained_by"]).length).toBe(1);
+ expect(base.outEdges(guarded.id, ["tested_by"]).map((edge) => base.node(edge.to)?.filePath)).toEqual(["test/money.test.ts"]);
+ git(root, "checkout", "-q", "-b", "remove");
+ git(root, "rm", "-q", "src/money.ts", ...(deleteTest ? ["test/money.test.ts"] : []));
+ git(root, "commit", "-q", "-m", "remove guarded source");
+ const head = new GraphIndex(indexRepository(root, "2026-10-12T00:01:00.000Z").analysis.graph);
+ expect(head.node(guarded.id)).toBeUndefined();
+ expect(head.nodesOfKind("test").map((node) => node.filePath)).toEqual(deleteTest ? [] : ["test/money.test.ts"]);
+ return runVerify(root, { kind: "range", base: "main" }).result;
+}
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+describe("deleted ordinary guard evidence", () => {
+ it("blocks deleting a guarded source together with its only covering test", () => {
+ const result = range(true);
+ expect(result.findings.map((finding) => finding.rule)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("keeps the existing exemption when the former covering test survives at the head", () => {
+ const result = range(false);
+ expect(result.findings.map((finding) => finding.rule)).not.toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("PASS");
+ });
+});
diff --git a/packages/app-services/test/verify-removed-guards.test.ts b/packages/app-services/test/verify-removed-guards.test.ts
new file mode 100644
index 00000000..66f09b97
--- /dev/null
+++ b/packages/app-services/test/verify-removed-guards.test.ts
@@ -0,0 +1,235 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createDefaultConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "../src";
+
+/**
+ * A PR is checked as a committed range against its base, with the index built at the PR head. What
+ * the PR removes is therefore absent from the indexed graph, so every guard it deletes, strips or
+ * moves has to be recovered from the base commit instead. Each case below is the same change a
+ * working-tree check already refuses; a range check must not be the easier path.
+ */
+
+const roots: string[] = [];
+
+const MONEY = [
+ "/**",
+ " * @invariant money-never-negative: amounts must never be negative",
+ " */",
+ "export function clamp(x: number): number {",
+ " return x < 0 ? 0 : x;",
+ "}",
+ "",
+].join("\n");
+
+const CART = [
+ "export interface CartTotal {",
+ " subtotalCents: number;",
+ " taxCents: number;",
+ "}",
+ "",
+].join("\n");
+
+const HELPER = "export function helper(x: number): number {\n return x + 1;\n}\n";
+
+function git(root: string, ...args: string[]): string {
+ const result = Bun.spawnSync(
+ ["git", "-c", "user.name=Semctx Test", "-c", "user.email=semctx@example.test", "-c", "commit.gpgsign=false", ...args],
+ { cwd: root, stdout: "pipe", stderr: "pipe" },
+ );
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ return new TextDecoder().decode(result.stdout).trim();
+}
+
+function write(root: string, path: string, content: string): void {
+ mkdirSync(join(root, path, ".."), { recursive: true });
+ writeFileSync(join(root, path), content);
+}
+
+/** `main` holds a guarded function, an exported interface and an unguarded helper, all untested. */
+function repository(money = MONEY): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-removed-guards-"));
+ roots.push(root);
+ write(root, ".gitignore", ".semctx/\n");
+ write(root, "src/money.ts", money);
+ write(root, "src/cart.ts", CART);
+ write(root, "src/helper.ts", HELPER);
+ git(root, "init", "-q", "-b", "main");
+ git(root, "add", ".");
+ git(root, "commit", "-q", "-m", "base");
+ initWorkspace(root, createDefaultConfig(root));
+ return root;
+}
+
+/** Commit `edit` on a PR branch, index its head, and verify the range against `main`. */
+function verifyPullRequest(edit: (root: string) => void, money = MONEY) {
+ const root = repository(money);
+ git(root, "checkout", "-q", "-b", "pr");
+ edit(root);
+ git(root, "add", "-A");
+ git(root, "commit", "-q", "-m", "pr");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ return runVerify(root, { kind: "range", base: "main" }).result;
+}
+
+const rulesOf = (result: ReturnType): string[] => result.findings.map((finding) => finding.rule);
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+describe("a diff with no content hunks", () => {
+ it("names the renamed paths as not compared instead of claiming nothing changed", () => {
+ const result = verifyPullRequest((root) => git(root, "mv", "src/cart.ts", "src/cart2.ts"));
+ const unknowns = result.unknowns.join("\n");
+ expect(unknowns).not.toContain("No changes detected");
+ expect(unknowns).toContain("no content hunks");
+ expect(unknowns).toContain("src/cart.ts");
+ });
+});
+
+describe("a pull request that removes a guard", () => {
+ it("blocks deleting the file that holds an untested @invariant", () => {
+ const result = verifyPullRequest((root) => git(root, "rm", "-q", "src/money.ts"));
+ expect(rulesOf(result)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("warns on deleting an exported interface that no test covers", () => {
+ const result = verifyPullRequest((root) => write(root, "src/cart.ts", ""));
+ expect(rulesOf(result)).toContain("contract_changed_without_test");
+ expect(result.verdict).toBe("WARN");
+ });
+
+ it("warns on a pure rename of a file that exports an uncovered interface, and does not claim nothing changed", () => {
+ const result = verifyPullRequest((root) => git(root, "mv", "src/cart.ts", "src/cart2.ts"));
+ expect(rulesOf(result)).toContain("contract_changed_without_test");
+ expect(result.verdict).toBe("WARN");
+ expect(result.unknowns.join("\n")).not.toContain("No changes detected");
+ });
+
+ it("blocks stripping only the invariant marker without changing the declaration", () => {
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", MONEY.replace(" * @invariant money-never-negative: amounts must never be negative\n", "")));
+ expect(rulesOf(result)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("blocks replacing only the invariant identity without changing the declaration", () => {
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", MONEY.replace(
+ "money-never-negative: amounts must never be negative",
+ "money-finite: amounts must be finite",
+ )));
+ expect(rulesOf(result)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ expect(result.unknowns.join("\n")).toContain("money-never-negative");
+ expect(result.unknowns.join("\n")).not.toContain("[money-finite]");
+ });
+
+ it("blocks losing one invariant even when another original identity survives", () => {
+ const twoGuards = MONEY.replace(" */", " * @invariant money-finite: amounts must be finite\n */");
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", twoGuards.replace(
+ " * @invariant money-never-negative: amounts must never be negative\n",
+ "",
+ )), twoGuards);
+ expect(rulesOf(result)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ expect(result.unknowns.join("\n")).toContain("[money-never-negative]");
+ expect(result.unknowns.join("\n")).not.toContain("[money-never-negative, money-finite]");
+ });
+
+ it("blocks stripping only a security marker without changing the declaration", () => {
+ const root = repository();
+ write(root, "src/money.ts", MONEY.replace("@invariant money-never-negative: amounts must never be negative", "@tag security"));
+ git(root, "add", "src/money.ts");
+ git(root, "commit", "-q", "-m", "security base");
+ git(root, "checkout", "-q", "-b", "pr");
+ write(root, "src/money.ts", MONEY.replace(" * @invariant money-never-negative: amounts must never be negative\n", ""));
+ git(root, "add", "-A");
+ git(root, "commit", "-q", "-m", "remove security");
+ indexRepository(root, "2026-10-11T10:00:00.000Z");
+ const result = runVerify(root, { kind: "range", base: "main" }).result;
+ expect(rulesOf(result)).toContain("security_surface_without_verification");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("keeps security distinct from a replacement critical tag", () => {
+ const security = MONEY.replace("@invariant money-never-negative: amounts must never be negative", "@tag security");
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", security.replace("@tag security", "@tag critical")), security);
+ expect(rulesOf(result)).toContain("security_surface_without_verification");
+ expect(rulesOf(result)).not.toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("blocks stripping the @invariant marker while changing the body", () => {
+ const result = verifyPullRequest((root) =>
+ write(root, "src/money.ts", "export function clamp(x: number): number {\n return x;\n}\n"));
+ expect(rulesOf(result)).toContain("invariant_touched_without_test");
+ expect(result.verdict).toBe("BLOCK");
+ });
+});
+
+describe("a pull request that must keep passing", () => {
+ it("still blocks a body change under a surviving @invariant", () => {
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", MONEY.replace("? 0 : x", "? 1 : x")));
+ expect(result.verdict).toBe("BLOCK");
+ });
+
+ it("preserves an unchanged invariant when an unrelated marker is added", () => {
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", MONEY.replace(
+ " */",
+ " * @tag accounting\n */",
+ )));
+ expect(result.findings).toEqual([]);
+ expect(result.verdict).toBe("PASS");
+ });
+
+ it("preserves the same canonical invariant identity across a statement-only edit", () => {
+ const result = verifyPullRequest((root) => write(root, "src/money.ts", MONEY.replace(
+ "amounts must never be negative",
+ "amounts remain nonnegative",
+ )));
+ expect(result.findings).toEqual([]);
+ expect(result.verdict).toBe("PASS");
+ });
+
+ it("passes an invariant identity replacement when the same PR adds a covering test", () => {
+ const result = verifyPullRequest((root) => {
+ write(root, "src/money.ts", MONEY.replace(
+ "money-never-negative: amounts must never be negative",
+ "money-finite: amounts must be finite",
+ ));
+ write(
+ root,
+ "test/money.test.ts",
+ 'import { expect, test } from "bun:test";\nimport { clamp } from "../src/money";\ntest("clamp", () => expect(clamp(-1)).toBe(0));\n',
+ );
+ });
+ expect(result.findings).toEqual([]);
+ expect(result.verdict).toBe("PASS");
+ });
+
+ it("passes deleting an unguarded function that is not an exported contract", () => {
+ const result = verifyPullRequest((root) => git(root, "rm", "-q", "src/helper.ts"));
+ expect(result.verdict).toBe("PASS");
+ });
+
+ it("passes changing a guarded body when the same PR adds a test that imports it", () => {
+ const result = verifyPullRequest((root) => {
+ write(root, "src/money.ts", MONEY.replace("? 0 : x", "? 1 : x"));
+ write(
+ root,
+ "test/money.test.ts",
+ 'import { expect, test } from "bun:test";\nimport { clamp } from "../src/money";\ntest("clamp", () => expect(clamp(-1)).toBe(1));\n',
+ );
+ });
+ expect(result.verdict).toBe("PASS");
+ });
+
+ it("passes a pure addition", () => {
+ const result = verifyPullRequest((root) => write(root, "src/extra.ts", "export function extra(): number {\n return 3;\n}\n"));
+ expect(result.verdict).toBe("PASS");
+ });
+});
diff --git a/packages/context-engine/src/analysis-admission.ts b/packages/context-engine/src/analysis-admission.ts
new file mode 100644
index 00000000..f093aa0d
--- /dev/null
+++ b/packages/context-engine/src/analysis-admission.ts
@@ -0,0 +1,57 @@
+import type { AnalysisAdmission } from "@semantic-context/core";
+
+/** Pure evidence projection; source discovery and freshness observations belong to callers. */
+export interface AdmissionCandidate {
+ path: string; selectionDecision: "selected" | "excluded";
+ analysisOutcome: string; selectionReasons: string[]; analysisReasons: string[]; admissible: boolean;
+}
+export function evaluateAnalysisAdmission(input: {
+ changedPaths: readonly string[]; links: readonly (readonly [string, string])[];
+ candidates: readonly AdmissionCandidate[];
+ current: readonly { path: string; selectionDecision: "selected" | "excluded"; reason: string }[];
+ unresolvedImports: readonly { path: string; specifier: string }[];
+ scopeReasons: readonly string[]; bindingReasons: readonly string[];
+ bindingStatus: string; canRunHighRiskControl: boolean; freshnessReasons: readonly string[];
+ buildComplete: boolean; checkChanged: boolean; repositoryCoverageStatus: AnalysisAdmission["repositoryCoverage"]["status"];
+}): Pick {
+ const expected = new Set(input.changedPaths);
+ let changed = true;
+ while (changed) {
+ changed = false;
+ for (const [from, to] of input.links) {
+ if (!expected.has(from) && !expected.has(to)) continue;
+ for (const path of [from, to]) if (!expected.has(path)) { expected.add(path); changed = true; }
+ }
+ }
+ const persisted = new Map(input.candidates.map((candidate) => [candidate.path, candidate]));
+ const current = new Map(input.current.map((candidate) => [candidate.path, candidate]));
+ const file = (path: string): AnalysisAdmission["changeCoverage"]["files"][number] => {
+ const candidate = persisted.get(path); const selected = current.get(path);
+ if (candidate?.selectionDecision === "excluded" || selected?.selectionDecision === "excluded") return {
+ path, status: "excluded", reasons: [...(candidate?.selectionReasons ?? []), ...(selected === undefined ? [] : [selected.reason])],
+ };
+ if (candidate === undefined || selected === undefined) return { path, status: "missing", reasons: ["NO_CURRENT_ANALYZED_SOURCE"] };
+ if (candidate.analysisOutcome !== "analyzed") return { path, status: candidate.analysisOutcome === "failed" ? "failed" : "unsupported", reasons: [...candidate.analysisReasons, ...candidate.selectionReasons, `ANALYSIS_OUTCOME:${candidate.analysisOutcome}`] };
+ if (candidate.analysisReasons.length > 0) return { path, status: candidate.analysisReasons.some((reason) => /UNSUPPORTED|UNRESOLVED|SOURCE_CONFIGURATION/.test(reason)) ? "unsupported" : "failed", reasons: candidate.analysisReasons };
+ if (!candidate.admissible) return { path, status: "failed", reasons: ["LOAD_BEARING_EVALUATION_NOT_ADMITTED"] };
+ if (!/\.[cm]?[jt]sx?$/.test(path)) return { path, status: "unsupported", reasons: ["OUTSIDE_BOUNDED_ESM_TYPESCRIPT_PROFILE"] };
+ return { path, status: "analyzed", reasons: [] };
+ };
+ const files = [...expected].sort().map(file);
+ const analyzed = files.filter((entry) => entry.status === "analyzed").map((entry) => entry.path);
+ const reasons = [...input.bindingReasons, ...input.scopeReasons];
+ if (!input.buildComplete) reasons.push("INDEX_BUILD_INCOMPLETE");
+ if (input.bindingStatus !== "valid") reasons.push("INDEX_BINDING_INVALID");
+ if (!input.canRunHighRiskControl) reasons.push(...input.freshnessReasons, "INDEX_NOT_FRESH");
+ if (input.checkChanged) reasons.push("CHECK_INPUT_CHANGED");
+ if (files.length === 0 || analyzed.length === 0) reasons.push("ZERO_ANALYZED_OBLIGATIONS");
+ for (const entry of files) if (entry.status !== "analyzed") reasons.push(`ANALYSIS_SCOPE_INCOMPLETE:${entry.path}:${entry.status}`);
+ for (const entry of input.unresolvedImports) if (expected.has(entry.path)) reasons.push(`UNRESOLVED_IMPORT:${entry.path}:${entry.specifier}`);
+ for (const path of input.changedPaths) if (!/\.(?:[cm]?[jt]sx?|py|sql)$/.test(path)) reasons.push(`UNSUPPORTED_CHANGED_OBLIGATION:${path}`);
+ return {
+ status: reasons.length === 0 ? "admitted" : "rejected",
+ changeCoverage: { expected: [...expected].sort(), analyzed, files },
+ repositoryCoverage: { status: input.repositoryCoverageStatus, files: input.candidates.map((candidate) => file(candidate.path)) },
+ reasons: [...new Set(reasons)],
+ };
+}
diff --git a/packages/context-engine/src/context-pack-builder.ts b/packages/context-engine/src/context-pack-builder.ts
index 2f4dbfea..f30b0bb7 100644
--- a/packages/context-engine/src/context-pack-builder.ts
+++ b/packages/context-engine/src/context-pack-builder.ts
@@ -21,8 +21,9 @@ import { GraphIndex } from "./graph-index";
import { classifyQuestion, policyFor } from "./authority-policies";
import { detectContradictions } from "./contradiction";
import { evaluateClaim, type PriorityContext } from "./priority-engine";
+import cliPackage from "../../../apps/cli/package.json";
-const GENERATOR = "semctx@0.4.2";
+const GENERATOR = `semctx@${cliPackage.version}`;
const CODE_KINDS: ReadonlySet = new Set(["function", "class", "interface", "type", "enum"]);
const REACHABILITY_EDGES: readonly EdgeKind[] = [
diff --git a/packages/context-engine/src/index.ts b/packages/context-engine/src/index.ts
index b1ba1b04..683d2fb4 100644
--- a/packages/context-engine/src/index.ts
+++ b/packages/context-engine/src/index.ts
@@ -21,6 +21,8 @@ export type { BuildPackOptions } from "./context-pack-builder";
export {
analyzeDiff,
+ analyzeLostGuards,
+ diffRemovesBaseLines,
parseUnifiedDiff,
parseUnifiedDiffChanges,
parseUnifiedDiffScopePaths,
@@ -94,3 +96,5 @@ export {
computeConfidence,
WEIGHTS,
} from "./scoring";
+export { evaluateAnalysisAdmission } from "./analysis-admission";
+export type { AdmissionCandidate } from "./analysis-admission";
diff --git a/packages/context-engine/src/verify-diff.ts b/packages/context-engine/src/verify-diff.ts
index bbe5e365..96ce4d0b 100644
--- a/packages/context-engine/src/verify-diff.ts
+++ b/packages/context-engine/src/verify-diff.ts
@@ -56,6 +56,8 @@ export interface VerifyFinding {
severity: "warn" | "block";
message: string;
nodeIds: string[];
+ /** File-only base anchors for recovered guards; not current declaration coordinates. */
+ locations?: VerifyReportLocation[];
}
export interface VerifyResult {
@@ -514,6 +516,34 @@ export function hunkTouchesRange(
return start <= range.end && end >= range.start;
}
+/**
+ * What makes a node subject to each blocking rule. One definition serves the head-side analysis and
+ * the recovery of guards a change removes, so the two cannot disagree about what a guard is.
+ * A contract is *critical* (strict tier) only when its author tagged it `critical`/`security`:
+ * marker-driven and opt-in, never inferred.
+ */
+interface RuleFlags {
+ invariant: boolean;
+ critical: boolean;
+ contract: boolean;
+ security: boolean;
+}
+
+function ruleFlags(index: GraphIndex, n: RepositoryNode): RuleFlags {
+ const exportedContract = (n.kind === "interface" || n.kind === "type") && n.exported === true;
+ const critical = exportedContract && (n.tags.includes("critical") || n.tags.includes("security"));
+ return {
+ invariant: index.outEdges(n.id, ["constrained_by"]).length > 0,
+ critical,
+ contract: exportedContract && !critical,
+ security:
+ n.tags.includes("security")
+ || index.outEdges(n.id, ["related_to"]).some((e) => index.node(e.to)?.tags.includes("security")),
+ };
+}
+
+const NO_FLAGS: RuleFlags = { invariant: false, critical: false, contract: false, security: false };
+
function isTested(index: GraphIndex, nodeId: string): boolean {
return index.outEdges(nodeId, ["tested_by"]).length > 0;
}
@@ -600,22 +630,12 @@ export function analyzeDiff(args: {
.sort((a, b) => compareIds(a.id, b.id));
// Condition evaluators.
- const invariantConstrainedUntested = impactedNodes.filter(
- (n) => index.outEdges(n.id, ["constrained_by"]).length > 0 && !isTested(index, n.id),
- );
- const isExportedContract = (n: RepositoryNode): boolean =>
- (n.kind === "interface" || n.kind === "type") && n.exported === true;
- // A contract is *critical* (strict tier → BLOCK) only when its author tagged it so, via a
- // `critical`/`security` tag. Marker-driven and opt-in — never inferred.
- const isCriticalContract = (n: RepositoryNode): boolean =>
- isExportedContract(n) && (n.tags.includes("critical") || n.tags.includes("security"));
- const criticalContractsUntested = impactedNodes.filter((n) => isCriticalContract(n) && !isTested(index, n.id));
- const contractsUntested = impactedNodes.filter(
- (n) => isExportedContract(n) && !isCriticalContract(n) && !isTested(index, n.id),
- );
- const securityUntested = impactedNodes.filter(
- (n) => (n.tags.includes("security") || index.outEdges(n.id, ["related_to"]).some((e) => index.node(e.to)?.tags.includes("security"))) && !isTested(index, n.id),
- );
+ const untestedWith = (flag: keyof RuleFlags): RepositoryNode[] =>
+ impactedNodes.filter((n) => ruleFlags(index, n)[flag] && !isTested(index, n.id));
+ const invariantConstrainedUntested = untestedWith("invariant");
+ const criticalContractsUntested = untestedWith("critical");
+ const contractsUntested = untestedWith("contract");
+ const securityUntested = untestedWith("security");
const conditionNodes: Record = {
invariant_touched_without_test: invariantConstrainedUntested,
@@ -641,7 +661,7 @@ export function analyzeDiff(args: {
});
}
- const unknowns = buildDiffUnknowns(impactedInvariants, invariantConstrainedUntested, changedFiles, index);
+ const unknowns = buildDiffUnknowns(impactedInvariants, invariantConstrainedUntested, changedFiles, parseUnifiedDiffScopePaths(args.diffText), index);
const verdict: VerdictLevel = findings.some((f) => f.severity === "block")
? "BLOCK"
@@ -664,11 +684,120 @@ export function analyzeDiff(args: {
};
}
-function impactedCandidates(index: GraphIndex, changedFiles: readonly string[]): RepositoryNode[] {
- const fileSet = new Set(changedFiles);
+/** True when the diff can take something away from the base: removed lines, a deletion or a rename. */
+export function diffRemovesBaseLines(diffText: string): boolean {
+ const parsed = parseUnifiedDiffChanges(diffText);
+ return parsed.files.some((f) => f.status === "deleted" || f.status === "renamed" || f.hunks.some((h) => h.oldLines > 0))
+ || parsed.unscoped.some((u) => u.reason === "rename_only" || u.reason === "empty_deleted");
+}
+
+export interface LostGuardsResult {
+ findings: VerifyFinding[];
+ unknowns: string[];
+}
+
+/**
+ * Guards a change takes away, recovered from the graph of the commit it starts from.
+ *
+ * A committed range is indexed at its head, so whatever the change deletes, strips or moves is
+ * absent from that graph and the head-side analysis cannot see it. This compares guarded nodes in
+ * changed base files against the head and reports a node when a canonical invariant identity or a
+ * rule flag it carried at the base is gone at the head. Replacing one invariant with another does
+ * not preserve the old guard. Exported contracts and security tags retain their rule predicates.
+ * Surviving guards are the head-side analysis's business. Tests are judged at the head when the node
+ * survives, so a change that strips a guard and adds a covering test in the same range is not refused.
+ * A deleted node keeps its former test exemption only when a covering test still exists at the head.
+ */
+export function analyzeLostGuards(args: {
+ baseIndex: GraphIndex;
+ headIndex: GraphIndex;
+ config: SemctxConfig;
+ diffText: string;
+}): LostGuardsResult {
+ const { baseIndex, headIndex } = args;
+ const parsed = parseUnifiedDiffChanges(args.diffText);
+ const baseFiles = new Map();
+ for (const f of parsed.files) {
+ if (f.status === "added") continue;
+ baseFiles.set(f.oldPath ?? f.filePath, f.status === "renamed" ? { movedTo: f.filePath } : {});
+ }
+ for (const u of parsed.unscoped) {
+ if (u.reason === "rename_only" && u.paths.length === 2) {
+ baseFiles.set(u.paths[0]!, { movedTo: u.paths[1]! });
+ } else if (u.reason === "empty_deleted") {
+ for (const path of u.paths) baseFiles.set(path, {});
+ }
+ }
+
+ const counterpart = (node: RepositoryNode): RepositoryNode | undefined => {
+ const same = headIndex.node(node.id);
+ if (same !== undefined) return same;
+ const movedTo = node.filePath === undefined ? undefined : baseFiles.get(node.filePath)?.movedTo;
+ if (movedTo === undefined) return undefined;
+ const moved = headIndex.nodesOfKind(node.kind).filter((n) => n.filePath === movedTo && n.name === node.name);
+ return moved.length === 1 ? moved[0] : undefined;
+ };
+
+ const lost: Partial> = {};
+ const unknowns: string[] = [];
+ const note = (rule: BlockingCondition, node: RepositoryNode): void => {
+ (lost[rule] ??= []).push(node);
+ };
+ for (const node of impactedCandidates(baseIndex)) {
+ const file = node.filePath === undefined ? undefined : baseFiles.get(node.filePath);
+ if (file === undefined) continue;
+ const survivor = counterpart(node);
+ const before = ruleFlags(baseIndex, node);
+ const kept = survivor === undefined ? NO_FLAGS : ruleFlags(headIndex, survivor);
+ // An invariant or security tag travels with the code, but an exported contract is an address:
+ // importers of the old path break whether or not the declaration survives elsewhere.
+ const moved = survivor !== undefined && survivor.id !== node.id;
+ const after: RuleFlags = moved ? { ...kept, critical: false, contract: false } : kept;
+ const tested = survivor === undefined
+ ? baseIndex.outEdges(node.id, ["tested_by"]).some((edge) => headIndex.node(edge.to)?.kind === "test")
+ : isTested(headIndex, survivor.id);
+ if (tested) continue;
+ const keptInvariants = new Set(survivor === undefined
+ ? [] : headIndex.outEdges(survivor.id, ["constrained_by"]).map((e) => e.to));
+ const removedInvariants = baseIndex.outEdges(node.id, ["constrained_by"])
+ .filter((e) => !keptInvariants.has(e.to)).sort((a, b) => compareIds(a.to, b.to));
+ if (removedInvariants.length > 0) {
+ note("invariant_touched_without_test", node);
+ const names = removedInvariants.map((e) => baseIndex.node(e.to)?.name ?? e.to);
+ unknowns.push(`"${node.name}" was constrained by [${names.join(", ")}] and no longer is; the guard was removed without a test.`);
+ }
+ if (before.critical && !after.critical) note("critical_contract_changed_without_test", node);
+ if (before.contract && !after.contract) note("contract_changed_without_test", node);
+ if (before.security && !after.security) note("security_surface_without_verification", node);
+ }
+
+ const findings: VerifyFinding[] = [];
+ for (const rule of args.config.blockingRules) {
+ const nodes = lost[rule.when] ?? [];
+ if (nodes.length === 0) continue;
+ findings.push({
+ rule: rule.when,
+ severity: rule.severity,
+ message: `${describeCondition(rule.when, nodes, 0)} (base guard removed, moved or no longer guarded by this change)`,
+ nodeIds: nodes.map((n) => n.id),
+ locations: [...new Set(nodes.flatMap((node) => node.filePath === undefined ? [] : [node.filePath]))]
+ .sort(compareIds).map((file) => ({ file })),
+ });
+ }
+ return { findings, unknowns: [...new Set(unknowns)] };
+}
+
+
+/** Nodes a diff can touch, restricted to `changedFiles` when given. */
+function impactedCandidates(index: GraphIndex, changedFiles?: readonly string[]): RepositoryNode[] {
+ const fileSet = changedFiles === undefined ? undefined : new Set(changedFiles);
const kinds: RepositoryNode["kind"][] = ["function", "class", "interface", "type", "enum", "module", "test", "document", "migration"];
const out: RepositoryNode[] = [];
- for (const kind of kinds) for (const node of index.nodesOfKind(kind)) if (node.filePath !== undefined && fileSet.has(node.filePath)) out.push(node);
+ for (const kind of kinds) {
+ for (const node of index.nodesOfKind(kind)) {
+ if (node.filePath !== undefined && (fileSet === undefined || fileSet.has(node.filePath))) out.push(node);
+ }
+ }
return out;
}
@@ -696,10 +825,17 @@ function buildDiffUnknowns(
impactedInvariants: readonly Claim[],
untested: readonly RepositoryNode[],
changedFiles: readonly string[],
+ scopePaths: readonly string[],
index: GraphIndex,
): string[] {
const unknowns: string[] = [];
- if (changedFiles.length === 0) unknowns.push("No changes detected in the diff.");
+ if (changedFiles.length === 0) {
+ unknowns.push(
+ scopePaths.length === 0
+ ? "No changes detected in the diff."
+ : `The diff names paths with no content hunks (rename, binary or mode-only change); their contents were not compared:${scopePaths.join(", ")}.`,
+ );
+ }
for (const inv of impactedInvariants) {
if (inv.verificationStatus !== "tested") unknowns.push(`Impacted invariant is not test-backed: ${inv.statement}`);
}
@@ -756,7 +892,7 @@ export function buildVerifyReport(
severity: f.severity,
message: f.message,
nodeIds: f.nodeIds,
- locations: locationsFor(f.nodeIds),
+ locations: f.locations ?? locationsFor(f.nodeIds),
}));
const claim = (c: Claim) => ({ statement: c.statement, kind: c.kind, verificationStatus: c.verificationStatus });
diff --git a/packages/context-engine/test/analysis-admission.test.ts b/packages/context-engine/test/analysis-admission.test.ts
new file mode 100644
index 00000000..d09d9640
--- /dev/null
+++ b/packages/context-engine/test/analysis-admission.test.ts
@@ -0,0 +1,17 @@
+import { expect, test } from "bun:test";
+import { evaluateAnalysisAdmission } from "../src/analysis-admission";
+
+test("a healthy changed file cannot admit an inadmissible transitive dependent", () => {
+ const candidates = ["src/changed.mjs", "src/unchanged.ts"].map((path) => ({ path, selectionDecision: "selected" as const, analysisOutcome: "analyzed", selectionReasons: [], analysisReasons: [], admissible: true }));
+ const input: Parameters[0] = {
+ changedPaths: ["src/changed.mjs"], links: [["src/unchanged.ts", "src/changed.mjs"]], candidates,
+ current: candidates.map((candidate) => ({ path: candidate.path, selectionDecision: "selected", reason: "SELECTED" })),
+ unresolvedImports: [], scopeReasons: [], bindingReasons: [], bindingStatus: "valid", canRunHighRiskControl: true,
+ freshnessReasons: [], buildComplete: true, checkChanged: false, repositoryCoverageStatus: "complete",
+ };
+ expect(evaluateAnalysisAdmission(input).status).toBe("admitted");
+ candidates[1]!.admissible = false;
+ const rejected = evaluateAnalysisAdmission(input);
+ expect(rejected.status).toBe("rejected");
+ expect(rejected.changeCoverage.files).toContainEqual({ path: "src/unchanged.ts", status: "failed", reasons: ["LOAD_BEARING_EVALUATION_NOT_ADMITTED"] });
+});
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
index ceae7f81..9e0a7e42 100644
--- a/packages/core/src/schemas.ts
+++ b/packages/core/src/schemas.ts
@@ -75,19 +75,35 @@ const SemctxConfigBaseSchema = z.object({
semantic: SemanticPolicyConfigSchema.optional(),
});
-export const SemctxConfigV1Schema = SemctxConfigBaseSchema.extend({
+const SemctxConfigV1ShapeSchema = SemctxConfigBaseSchema.extend({
version: z.literal(1),
});
+function rejectLegacyQualifiedMarkers(value: unknown, context: z.RefinementCtx): unknown {
+ if (typeof value === "object" && value !== null && "version" in value && value.version === 1
+ && (("analysisProfile" in value && value.analysisProfile !== undefined)
+ || ("selectionMode" in value && value.selectionMode === "qualified-static-v1"))) {
+ context.addIssue({ code: z.ZodIssueCode.custom, message: "qualified static analysis requires configuration version 2" });
+ }
+ return value;
+}
+export const SemctxConfigV1Schema = z.preprocess(rejectLegacyQualifiedMarkers, SemctxConfigV1ShapeSchema);
-export const SemctxConfigV2Schema = SemctxConfigBaseSchema.extend({
+const SemctxConfigV2ShapeSchema = SemctxConfigBaseSchema.extend({
version: z.literal(2),
- selectionMode: z.literal("globs-v1"),
+ analysisProfile: z.literal("modelo-suite-static-v1").optional(),
+ selectionMode: z.enum(["globs-v1", "qualified-static-v1"]),
languages: z.record(z.enum(["on", "off"])),
});
+function validateAnalysisProfile(value: { version: number; selectionMode?: string; analysisProfile?: string }, context: z.RefinementCtx): void {
+ if ((value.selectionMode === "qualified-static-v1") !== (value.analysisProfile === "modelo-suite-static-v1")) {
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["analysisProfile"], message: "modelo-suite-static-v1 requires the versioned qualified-static-v1 selection mode and vice versa" });
+ }
+}
+export const SemctxConfigV2Schema = SemctxConfigV2ShapeSchema.superRefine(validateAnalysisProfile);
-export const SemctxConfigSchema = z.discriminatedUnion("version", [
- SemctxConfigV1Schema,
- SemctxConfigV2Schema,
-]);
+export const SemctxConfigSchema = z.preprocess(rejectLegacyQualifiedMarkers, z.discriminatedUnion("version", [
+ SemctxConfigV1ShapeSchema,
+ SemctxConfigV2ShapeSchema,
+]).superRefine(validateAnalysisProfile));
export type SemctxConfigParsed = z.infer;
diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts
index beaa4b2e..5ff043ee 100644
--- a/packages/core/src/types/config.ts
+++ b/packages/core/src/types/config.ts
@@ -80,7 +80,9 @@ export type LanguageAnalysisMode = "on" | "off";
*/
export interface SemctxConfigV2 extends SemctxConfigBase {
version: 2;
- selectionMode: "globs-v1";
+ /** Opt-in fail-closed static-analysis admission contract. */
+ analysisProfile?: "modelo-suite-static-v1";
+ selectionMode: "globs-v1" | "qualified-static-v1";
/** Language analysis is explicit. A selected language absent from this registry is unsupported. */
languages: Record;
}
diff --git a/packages/core/src/verify-report.ts b/packages/core/src/verify-report.ts
index 3d39e053..28866409 100644
--- a/packages/core/src/verify-report.ts
+++ b/packages/core/src/verify-report.ts
@@ -12,6 +12,57 @@ import type { SeverityTier } from "./types/config";
export const VERIFY_REPORT_SCHEMA_VERSION = 1 as const;
+const AdmissionFileSchema = z.object({
+ path: z.string(),
+ status: z.enum(["analyzed", "excluded", "unsupported", "failed", "missing"]),
+ reasons: z.array(z.string()),
+}).strict();
+const AdmissionDigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/);
+const AdmissionCommitSchema = z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
+const AdmissionFreshnessSchema = z.object({ verdict: z.enum(["FRESH", "DIRTY_KNOWN", "STALE", "UNSEALED"]), reasons: z.array(z.string()) }).strict();
+export const AnalysisAdmissionSchema = z.object({
+ schemaVersion: z.literal(1),
+ profile: z.literal("modelo-suite-static-v1"),
+ status: z.enum(["admitted", "rejected"]),
+ identity: z.object({ source: AdmissionDigestSchema, sourceCommits: z.array(AdmissionCommitSchema), baseCommit: AdmissionCommitSchema.nullable(), diff: AdmissionDigestSchema, config: AdmissionDigestSchema, analyzer: AdmissionDigestSchema, indexSnapshot: AdmissionDigestSchema }).strict(),
+ binding: z.object({ status: z.enum(["valid", "invalid", "absent"]), reasons: z.array(z.string()) }).strict(),
+ indexFreshness: AdmissionFreshnessSchema,
+ checkFreshness: z.object({ status: z.enum(["current", "changed"]), reasons: z.array(z.string()) }).strict(),
+ controlFreshness: AdmissionFreshnessSchema,
+ repositoryCoverage: z.object({ status: z.enum(["complete", "partial", "insufficient"]), files: z.array(AdmissionFileSchema) }).strict(),
+ changeCoverage: z.object({ expected: z.array(z.string()), analyzed: z.array(z.string()), files: z.array(AdmissionFileSchema) }).strict(),
+ reasons: z.array(z.string()),
+ limitations: z.array(z.string()),
+ proofObligations: z.array(z.object({ id: z.string(), status: z.literal("not_observed"), observation: z.string() }).strict()),
+}).strict().superRefine((value, context) => {
+ if (value.repositoryCoverage.status === "complete"
+ && value.repositoryCoverage.files.some((file) =>
+ !["analyzed", "excluded"].includes(file.status)
+ || (file.status === "analyzed" && file.reasons.length > 0))) {
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["repositoryCoverage"], message: "complete repository coverage cannot carry incomplete file outcomes" });
+ }
+ if (value.status !== "admitted") return;
+ const expected = value.changeCoverage.expected;
+ const actual = value.changeCoverage.files;
+ if (value.binding.status !== "valid" || value.binding.reasons.length > 0
+ || value.identity.sourceCommits.length === 0 || new Set(value.identity.sourceCommits).size !== value.identity.sourceCommits.length
+ || [value.indexFreshness, value.controlFreshness].some((freshness) => freshness.verdict === "FRESH" ? freshness.reasons.length > 0 : freshness.verdict === "DIRTY_KNOWN" && (freshness.reasons.length !== 1 || freshness.reasons[0] !== "WORKING_TREE_DIRTY"))
+ || !["FRESH", "DIRTY_KNOWN"].includes(value.indexFreshness.verdict)
+ || !["FRESH", "DIRTY_KNOWN"].includes(value.controlFreshness.verdict)
+ || value.checkFreshness.status !== "current" || value.checkFreshness.reasons.length > 0
+ || value.reasons.length > 0 || expected.length === 0
+ || new Set(expected).size !== expected.length
+ || actual.length !== expected.length
+ || actual.some((file) => file.status !== "analyzed" || file.reasons.length > 0 || !expected.includes(file.path))
+ || new Set(actual.map((file) => file.path)).size !== expected.length
+ || value.changeCoverage.analyzed.length !== expected.length
+ || value.changeCoverage.analyzed.some((path) => !expected.includes(path))
+ || new Set(value.changeCoverage.analyzed).size !== expected.length) {
+ context.addIssue({ code: z.ZodIssueCode.custom, message: "admitted analysis requires current compatible binding and complete non-empty analyzed obligations" });
+ }
+});
+export type AnalysisAdmission = z.infer;
+
export interface VerifyReportSymbol {
id: string;
name: string;
@@ -64,6 +115,7 @@ export interface VerifyReportCoChange {
}
export interface VerifyReport {
+ analysisAdmission?: AnalysisAdmission;
schemaVersion: typeof VERIFY_REPORT_SCHEMA_VERSION;
verdict: "PASS" | "WARN" | "BLOCK";
/** The git base ref requested, or null when the diff came from --staged/--from-file/HEAD. */
@@ -143,6 +195,7 @@ const VerifyReportCoChangeSchema = z.object({
}).passthrough();
export const VerifyReportSchema = z.object({
+ analysisAdmission: AnalysisAdmissionSchema.optional(),
schemaVersion: z.literal(VERIFY_REPORT_SCHEMA_VERSION),
verdict: z.enum(["PASS", "WARN", "BLOCK"]),
base: z.string().nullable(),
@@ -160,4 +213,12 @@ export const VerifyReportSchema = z.object({
impactedConsumers: z.array(VerifyReportConsumerSchema).optional(),
coChangedFiles: z.array(VerifyReportCoChangeSchema).optional(),
summary: z.object({ blockCount: z.number(), warnCount: z.number() }).passthrough(),
-}).passthrough();
+}).passthrough().superRefine((value, context) => {
+ if (value.analysisAdmission?.status === "rejected" && value.verdict !== "BLOCK") {
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["verdict"], message: "rejected qualified analysis must produce BLOCK" });
+ }
+ if (value.analysisAdmission?.status === "admitted"
+ && value.changedFiles.some((path) => !value.analysisAdmission!.changeCoverage.expected.includes(path))) {
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["changedFiles"], message: "admitted analysis must cover every reported changed file" });
+ }
+});
diff --git a/packages/core/test/qualified-report-scope.test.ts b/packages/core/test/qualified-report-scope.test.ts
new file mode 100644
index 00000000..4272eb0e
--- /dev/null
+++ b/packages/core/test/qualified-report-scope.test.ts
@@ -0,0 +1,69 @@
+import { describe, expect, test } from "bun:test";
+import { VerifyReportSchema, type VerifyReport } from "../src";
+
+function report(): VerifyReport {
+ const digest = `sha256:${"a".repeat(64)}`;
+ const file = { path: "scripts/leaf.mjs", status: "analyzed" as const, reasons: [] };
+ return {
+ schemaVersion: 1, verdict: "PASS", base: null, head: "HEAD", mergeBase: null, range: null,
+ changedFiles: [file.path], changedSymbols: [{ id: "function:leaf", name: "leaf", kind: "function", file: file.path }],
+ impactedContracts: [], impactedInvariants: [], recommendedTests: [], contradictions: [], unknowns: [], findings: [], summary: { blockCount: 0, warnCount: 0 },
+ analysisAdmission: {
+ schemaVersion: 1, profile: "modelo-suite-static-v1", status: "admitted",
+ identity: { source: digest, sourceCommits: ["a".repeat(40)], baseCommit: null, diff: digest, config: digest, analyzer: digest, indexSnapshot: digest },
+ binding: { status: "valid", reasons: [] }, indexFreshness: { verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] },
+ controlFreshness: { verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] }, checkFreshness: { status: "current", reasons: [] },
+ repositoryCoverage: { status: "partial", files: [file] }, changeCoverage: { expected: [file.path], analyzed: [file.path], files: [file] },
+ reasons: [], limitations: ["Static source/dependency analysis only."], proofObligations: [],
+ },
+ };
+}
+describe("qualified report scope consistency", () => {
+ test("keeps the existing admitted public fixture and dependency closure valid", () => {
+ expect(VerifyReportSchema.safeParse(report()).success).toBe(true);
+ const closure = report();
+ const dependency = { path: "scripts/dependency.ts", status: "analyzed" as const, reasons: [] };
+ closure.analysisAdmission!.changeCoverage.expected.push(dependency.path);
+ closure.analysisAdmission!.changeCoverage.analyzed.push(dependency.path);
+ closure.analysisAdmission!.changeCoverage.files.push(dependency);
+ closure.analysisAdmission!.repositoryCoverage.files.push(dependency);
+ expect(VerifyReportSchema.safeParse(closure).success).toBe(true);
+ });
+ test("rejects an admitted changed source omitted from its obligations", () => {
+ const omitted = report();
+ omitted.changedFiles.push("src/unreviewed.ts");
+ expect(VerifyReportSchema.safeParse(omitted).success).toBe(false);
+ });
+ test("rejects complete repository coverage carrying failed or missing records", () => {
+ for (const status of ["failed", "missing", "unsupported"] as const) {
+ const contradictory = report();
+ contradictory.analysisAdmission!.repositoryCoverage.status = "complete";
+ contradictory.analysisAdmission!.repositoryCoverage.files.push({ path: "src/other.ts", status, reasons: ["NO_CURRENT_ANALYZED_SOURCE"] });
+ expect(VerifyReportSchema.safeParse(contradictory).success).toBe(false);
+ }
+ });
+ test("allows partial repository failures outside the admitted change and complete excluded files", () => {
+ const partial = report();
+ partial.analysisAdmission!.repositoryCoverage.files.push({ path: "src/unrelated.ts", status: "failed", reasons: ["PRODUCER_FAILED"] });
+ expect(VerifyReportSchema.safeParse(partial).success).toBe(true);
+ const uncertain = report();
+ uncertain.analysisAdmission!.repositoryCoverage.status = "complete";
+ uncertain.analysisAdmission!.repositoryCoverage.files.push({ path: "src/other.ts", status: "analyzed", reasons: ["PARTIAL_REFERENCE_COVERAGE"] });
+ expect(VerifyReportSchema.safeParse(uncertain).success).toBe(false);
+ const excluded = report();
+ excluded.analysisAdmission!.repositoryCoverage.status = "complete";
+ excluded.analysisAdmission!.repositoryCoverage.files.push({ path: "docs/guide.md", status: "excluded", reasons: ["INCLUDE_MISS"] });
+ expect(VerifyReportSchema.safeParse(excluded).success).toBe(true);
+ });
+ test("keeps legacy and rejected noneligible or unknown changed paths diagnostic", () => {
+ const legacy = report();
+ delete legacy.analysisAdmission;
+ legacy.changedFiles = ["docs/guide.md", "package.json"];
+ expect(VerifyReportSchema.safeParse(legacy).success).toBe(true);
+ const rejected = report();
+ rejected.verdict = "BLOCK";
+ rejected.analysisAdmission!.status = "rejected";
+ rejected.changedFiles.push("src/untracked.ts", "docs/guide.md", "package.json");
+ expect(VerifyReportSchema.safeParse(rejected).success).toBe(true);
+ });
+});
diff --git a/packages/github-action/README.md b/packages/github-action/README.md
index 60948637..31d456d8 100644
--- a/packages/github-action/README.md
+++ b/packages/github-action/README.md
@@ -1,5 +1,10 @@
# Semctx GitHub Action
+The report step hands a qualified CLI rejection (exit 3) to the adapter only after
+this invocation produced a valid rejected report. The adapter emits annotations,
+summary and outputs, then enforces qualified refusal even with `fail-on: none`.
+Operational failures remain failures. Qualified staged/range verification requires Git >= 2.41.
+
Run `semctx verify diff` on a pull request: map the diff to affected symbols, exported
contracts, declared invariants and relevant tests, and gate the PR on a **PASS / WARN / BLOCK**
verdict. `WARN` never fails the check by default; `BLOCK` does. **No PR comments, no secrets, no
@@ -26,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # merge-base with the base must be available locally
- - uses: hoklims/semctx/packages/github-action@v0.4.2
+ - uses: hoklims/semctx/packages/github-action@v0.4.3
with:
base: ${{ github.event.pull_request.base.sha }}
head: ${{ github.sha }}
@@ -44,9 +49,23 @@ jobs:
| `fail-on` | `block` | fail the job on `block`, `warn`, or `none` |
| `working-directory` | `.` | repository directory to analyse; must stay inside the job workspace |
| `config-path` | `""` | optional `config.json` to use instead of the generated default |
-| `report-path` | `semctx-report.json` | where the JSON report is written (in `working-directory`) |
+| `report-path` | `.semctx/reports/verify.json` | where the JSON report is written (in `working-directory`) |
+| `require-qualified` | `false` | require admitted `modelo-suite-static-v1` evidence from trusted workflow policy |
| `upload-report` | `false` | upload the JSON report as a workflow artifact |
+For qualified analysis, report output must remain under `.semctx/reports` or outside the analysed
+repository. A custom output that could create or replace a retained repository input is
+refused before publication; the adapter still receives valid qualified-refusal reports.
+The default reserved report directory is also created safely for ordinary v1 analysis.
+Its writer checks repository ancestors before creating missing parents and replaces the
+report atomically. Custom ordinary outputs keep their existing parent-directory policy.
+Set `require-qualified: true` in a trusted workflow when qualification is mandatory.
+A repository-controlled configuration file alone cannot enforce that expectation: a
+downgrade to ordinary mode produces no admission. With this input, a missing matching
+admission causes an Action policy BLOCK and exit 1 even with `fail-on: none`; the producer
+report stays unchanged. The input accepts only `true` or `false`. Ordinary default use
+remains available, and an actual qualified rejection always blocks independently.
+
## Outputs
| output | description |
diff --git a/packages/github-action/action.yml b/packages/github-action/action.yml
index fe33ab5d..28ce68e6 100644
--- a/packages/github-action/action.yml
+++ b/packages/github-action/action.yml
@@ -17,6 +17,10 @@ inputs:
description: "Fail the job on this verdict or worse: block | warn | none."
required: false
default: "block"
+ require-qualified:
+ description: "Require admitted modelo-suite-static-v1 analysis; set this from trusted workflow policy, not repository config."
+ required: false
+ default: "false"
working-directory:
description: "Directory of the repository to analyse."
required: false
@@ -28,7 +32,7 @@ inputs:
report-path:
description: "Path (relative to working-directory) for the JSON report artifact."
required: false
- default: "semctx-report.json"
+ default: ".semctx/reports/verify.json"
upload-report:
description: "If 'true', upload the JSON report as a workflow artifact."
required: false
@@ -117,9 +121,30 @@ runs:
fi
bun "$SEMCTX_CLI" index --root "$SEMCTX_TARGET"
# Always produce the report (fail-on none); the adapter owns the job exit code.
+ # Exit 3 is a qualified refusal only when this invocation wrote a valid rejected report.
+ report_identity="$(node -e '
+ const fs = require("node:fs");
+ try { const stat = fs.lstatSync(process.argv[1], { bigint: true }); process.stdout.write(`${stat.dev}:${stat.ino}`); }
+ catch (error) { if (error.code !== "ENOENT") throw error; process.stdout.write("missing"); }
+ ' "$report")"
+ verify_status=0
bun "$SEMCTX_CLI" verify diff --root "$SEMCTX_TARGET" \
--base "$SEMCTX_BASE" --head "$SEMCTX_HEAD" \
- --format json --output "$report" --fail-on none
+ --format json --output "$report" --fail-on none || verify_status=$?
+ if [ "$verify_status" -ne 0 ]; then
+ [ "$verify_status" -eq 3 ] || exit "$verify_status"
+ node --input-type=module -e '
+ import { lstatSync, readFileSync } from "node:fs";
+ import { pathToFileURL } from "node:url";
+ try {
+ const stat = lstatSync(process.argv[1], { bigint: true });
+ if (`${stat.dev}:${stat.ino}` === process.argv[3]) process.exit(3);
+ const { VerifyReportSchema } = await import(pathToFileURL(process.argv[2]).href);
+ const parsed = VerifyReportSchema.safeParse(JSON.parse(readFileSync(process.argv[1], "utf8")));
+ if (!parsed.success || parsed.data.analysisAdmission?.status !== "rejected") process.exit(3);
+ } catch { process.exit(3); }
+ ' "$report" "$GITHUB_ACTION_PATH/dist/verify-report.mjs" "$report_identity"
+ fi
- name: Annotate, summarise, enforce
id: adapter
@@ -127,6 +152,7 @@ runs:
working-directory: ${{ inputs.working-directory }}
env:
INPUT_FAIL_ON: ${{ inputs.fail-on }}
+ INPUT_REQUIRE_QUALIFIED: ${{ inputs.require-qualified }}
INPUT_REPORT_PATH: ${{ inputs.report-path }}
run: node "$GITHUB_ACTION_PATH/src/adapter.mjs" "$INPUT_REPORT_PATH"
diff --git a/packages/github-action/dist/verify-report.mjs b/packages/github-action/dist/verify-report.mjs
index aa74efe4..4ee27825 100644
--- a/packages/github-action/dist/verify-report.mjs
+++ b/packages/github-action/dist/verify-report.mjs
@@ -1,2 +1,2 @@
// GENERATED by 'bun run plugin:build' from packages/core/src/verify-report.ts.
-var De=Object.defineProperty;var ze=(e)=>e;function Le(e,t){this[e]=ze.bind(null,t)}var Pe=(e,t)=>{for(var r in t)De(e,r,{get:t[r],enumerable:!0,configurable:!0,set:Le.bind(t,r)})};var h={};Pe(h,{BRAND:()=>ft,DIRTY:()=>B,EMPTY_PATH:()=>Ue,INVALID:()=>p,NEVER:()=>Qt,OK:()=>b,ParseStatus:()=>k,Schema:()=>y,ZodAny:()=>U,ZodArray:()=>R,ZodBigInt:()=>P,ZodBoolean:()=>X,ZodBranded:()=>ye,ZodCatch:()=>ie,ZodDate:()=>W,ZodDefault:()=>ne,ZodDiscriminatedUnion:()=>_e,ZodEffects:()=>O,ZodEnum:()=>F,ZodError:()=>w,ZodFirstPartyTypeKind:()=>m,ZodFunction:()=>Q,ZodIntersection:()=>te,ZodIssueCode:()=>o,ZodLazy:()=>re,ZodLiteral:()=>se,ZodMap:()=>fe,ZodNaN:()=>he,ZodNativeEnum:()=>ae,ZodNever:()=>I,ZodNull:()=>Z,ZodNullable:()=>M,ZodNumber:()=>L,ZodObject:()=>x,ZodOptional:()=>S,ZodParsedType:()=>c,ZodPipeline:()=>pe,ZodPromise:()=>J,ZodReadonly:()=>oe,ZodRecord:()=>le,ZodSchema:()=>y,ZodSet:()=>q,ZodString:()=>T,ZodSymbol:()=>ue,ZodTransformer:()=>O,ZodTuple:()=>E,ZodType:()=>y,ZodUndefined:()=>K,ZodUnion:()=>ee,ZodUnknown:()=>z,ZodVoid:()=>de,addIssueToContext:()=>l,any:()=>kt,array:()=>Tt,bigint:()=>gt,boolean:()=>Me,coerce:()=>Gt,custom:()=>Ne,date:()=>_t,datetimeRegex:()=>Ie,defaultErrorMap:()=>N,discriminatedUnion:()=>Ot,effect:()=>Ft,enum:()=>zt,function:()=>$t,getErrorMap:()=>Y,getParsedType:()=>j,instanceof:()=>pt,intersection:()=>jt,isAborted:()=>me,isAsync:()=>H,isDirty:()=>ge,isValid:()=>D,late:()=>ht,lazy:()=>Mt,literal:()=>Dt,makeIssue:()=>ce,map:()=>Nt,nan:()=>mt,nativeEnum:()=>Lt,never:()=>wt,null:()=>xt,nullable:()=>Wt,number:()=>$e,object:()=>Rt,objectUtil:()=>xe,oboolean:()=>Ht,onumber:()=>Yt,optional:()=>Bt,ostring:()=>Jt,pipeline:()=>qt,preprocess:()=>Ut,promise:()=>Pt,quotelessJson:()=>Fe,record:()=>Et,set:()=>Vt,setErrorMap:()=>We,strictObject:()=>St,string:()=>Ve,symbol:()=>yt,transformer:()=>Ft,tuple:()=>It,undefined:()=>vt,union:()=>At,unknown:()=>bt,util:()=>v,void:()=>Ct});var v;(function(e){e.assertEqual=(s)=>{};function t(s){}e.assertIs=t;function r(s){throw Error()}e.assertNever=r,e.arrayToEnum=(s)=>{let i={};for(let n of s)i[n]=n;return i},e.getValidEnumValues=(s)=>{let i=e.objectKeys(s).filter((u)=>typeof s[s[u]]!=="number"),n={};for(let u of i)n[u]=s[u];return e.objectValues(n)},e.objectValues=(s)=>e.objectKeys(s).map(function(i){return s[i]}),e.objectKeys=typeof Object.keys==="function"?(s)=>Object.keys(s):(s)=>{let i=[];for(let n in s)if(Object.prototype.hasOwnProperty.call(s,n))i.push(n);return i},e.find=(s,i)=>{for(let n of s)if(i(n))return n;return},e.isInteger=typeof Number.isInteger==="function"?(s)=>Number.isInteger(s):(s)=>typeof s==="number"&&Number.isFinite(s)&&Math.floor(s)===s;function a(s,i=" | "){return s.map((n)=>typeof n==="string"?`'${n}'`:n).join(i)}e.joinValues=a,e.jsonStringifyReplacer=(s,i)=>{if(typeof i==="bigint")return i.toString();return i}})(v||(v={}));var xe;(function(e){e.mergeShapes=(t,r)=>({...t,...r})})(xe||(xe={}));var c=v.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),j=(e)=>{switch(typeof e){case"undefined":return c.undefined;case"string":return c.string;case"number":return Number.isNaN(e)?c.nan:c.number;case"boolean":return c.boolean;case"function":return c.function;case"bigint":return c.bigint;case"symbol":return c.symbol;case"object":if(Array.isArray(e))return c.array;if(e===null)return c.null;if(e.then&&typeof e.then==="function"&&e.catch&&typeof e.catch==="function")return c.promise;if(typeof Map<"u"&&e instanceof Map)return c.map;if(typeof Set<"u"&&e instanceof Set)return c.set;if(typeof Date<"u"&&e instanceof Date)return c.date;return c.object;default:return c.unknown}};var o=v.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]),Fe=(e)=>JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:");class w extends Error{get errors(){return this.issues}constructor(e){super();this.issues=[],this.addIssue=(r)=>{this.issues=[...this.issues,r]},this.addIssues=(r=[])=>{this.issues=[...this.issues,...r]};let t=new.target.prototype;if(Object.setPrototypeOf)Object.setPrototypeOf(this,t);else this.__proto__=t;this.name="ZodError",this.issues=e}format(e){let t=e||function(s){return s.message},r={_errors:[]},a=(s)=>{for(let i of s.issues)if(i.code==="invalid_union")i.unionErrors.map(a);else if(i.code==="invalid_return_type")a(i.returnTypeError);else if(i.code==="invalid_arguments")a(i.argumentsError);else if(i.path.length===0)r._errors.push(t(i));else{let n=r,u=0;while(ut.message){let t={},r=[];for(let a of this.issues)if(a.path.length>0){let s=a.path[0];t[s]=t[s]||[],t[s].push(e(a))}else r.push(e(a));return{formErrors:r,fieldErrors:t}}get formErrors(){return this.flatten()}}w.create=(e)=>new w(e);var Be=(e,t)=>{let r;switch(e.code){case o.invalid_type:if(e.received===c.undefined)r="Required";else r=`Expected ${e.expected}, received ${e.received}`;break;case o.invalid_literal:r=`Invalid literal value, expected ${JSON.stringify(e.expected,v.jsonStringifyReplacer)}`;break;case o.unrecognized_keys:r=`Unrecognized key(s) in object: ${v.joinValues(e.keys,", ")}`;break;case o.invalid_union:r="Invalid input";break;case o.invalid_union_discriminator:r=`Invalid discriminator value. Expected ${v.joinValues(e.options)}`;break;case o.invalid_enum_value:r=`Invalid enum value. Expected ${v.joinValues(e.options)}, received '${e.received}'`;break;case o.invalid_arguments:r="Invalid function arguments";break;case o.invalid_return_type:r="Invalid function return type";break;case o.invalid_date:r="Invalid date";break;case o.invalid_string:if(typeof e.validation==="object")if("includes"in e.validation){if(r=`Invalid input: must include "${e.validation.includes}"`,typeof e.validation.position==="number")r=`${r} at one or more positions greater than or equal to ${e.validation.position}`}else if("startsWith"in e.validation)r=`Invalid input: must start with "${e.validation.startsWith}"`;else if("endsWith"in e.validation)r=`Invalid input: must end with "${e.validation.endsWith}"`;else v.assertNever(e.validation);else if(e.validation!=="regex")r=`Invalid ${e.validation}`;else r="Invalid";break;case o.too_small:if(e.type==="array")r=`Array must contain ${e.exact?"exactly":e.inclusive?"at least":"more than"} ${e.minimum} element(s)`;else if(e.type==="string")r=`String must contain ${e.exact?"exactly":e.inclusive?"at least":"over"} ${e.minimum} character(s)`;else if(e.type==="number")r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`;else if(e.type==="bigint")r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`;else if(e.type==="date")r=`Date must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${new Date(Number(e.minimum))}`;else r="Invalid input";break;case o.too_big:if(e.type==="array")r=`Array must contain ${e.exact?"exactly":e.inclusive?"at most":"less than"} ${e.maximum} element(s)`;else if(e.type==="string")r=`String must contain ${e.exact?"exactly":e.inclusive?"at most":"under"} ${e.maximum} character(s)`;else if(e.type==="number")r=`Number must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`;else if(e.type==="bigint")r=`BigInt must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`;else if(e.type==="date")r=`Date must be ${e.exact?"exactly":e.inclusive?"smaller than or equal to":"smaller than"} ${new Date(Number(e.maximum))}`;else r="Invalid input";break;case o.custom:r="Invalid input";break;case o.invalid_intersection_types:r="Intersection results could not be merged";break;case o.not_multiple_of:r=`Number must be a multiple of ${e.multipleOf}`;break;case o.not_finite:r="Number must be finite";break;default:r=t.defaultError,v.assertNever(e)}return{message:r}},N=Be;var Re=N;function We(e){Re=e}function Y(){return Re}var ce=(e)=>{let{data:t,path:r,errorMaps:a,issueData:s}=e,i=[...r,...s.path||[]],n={...s,path:i};if(s.message!==void 0)return{...s,path:i,message:s.message};let u="",d=a.filter((_)=>!!_).slice().reverse();for(let _ of d)u=_(n,{data:t,defaultError:u}).message;return{...s,path:i,message:u}},Ue=[];function l(e,t){let r=Y(),a=ce({issueData:t,data:e.data,path:e.path,errorMaps:[e.common.contextualErrorMap,e.schemaErrorMap,r,r===N?void 0:N].filter((s)=>!!s)});e.common.issues.push(a)}class k{constructor(){this.value="valid"}dirty(){if(this.value==="valid")this.value="dirty"}abort(){if(this.value!=="aborted")this.value="aborted"}static mergeArray(e,t){let r=[];for(let a of t){if(a.status==="aborted")return p;if(a.status==="dirty")e.dirty();r.push(a.value)}return{status:e.value,value:r}}static async mergeObjectAsync(e,t){let r=[];for(let a of t){let s=await a.key,i=await a.value;r.push({key:s,value:i})}return k.mergeObjectSync(e,r)}static mergeObjectSync(e,t){let r={};for(let a of t){let{key:s,value:i}=a;if(s.status==="aborted")return p;if(i.status==="aborted")return p;if(s.status==="dirty")e.dirty();if(i.status==="dirty")e.dirty();if(s.value!=="__proto__"&&(typeof i.value<"u"||a.alwaysSet))r[s.value]=i.value}return{status:e.value,value:r}}}var p=Object.freeze({status:"aborted"}),B=(e)=>({status:"dirty",value:e}),b=(e)=>({status:"valid",value:e}),me=(e)=>e.status==="aborted",ge=(e)=>e.status==="dirty",D=(e)=>e.status==="valid",H=(e)=>typeof Promise<"u"&&e instanceof Promise;var f;(function(e){e.errToObj=(t)=>typeof t==="string"?{message:t}:t||{},e.toString=(t)=>typeof t==="string"?t:t?.message})(f||(f={}));class A{constructor(e,t,r,a){this._cachedPath=[],this.parent=e,this.data=t,this._path=r,this._key=a}get path(){if(!this._cachedPath.length)if(Array.isArray(this._key))this._cachedPath.push(...this._path,...this._key);else this._cachedPath.push(...this._path,this._key);return this._cachedPath}}var Se=(e,t)=>{if(D(t))return{success:!0,data:t.value};else{if(!e.common.issues.length)throw Error("Validation failed but no issues detected.");return{success:!1,get error(){if(this._error)return this._error;let r=new w(e.common.issues);return this._error=r,this._error}}}};function g(e){if(!e)return{};let{errorMap:t,invalid_type_error:r,required_error:a,description:s}=e;if(t&&(r||a))throw Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);if(t)return{errorMap:t,description:s};return{errorMap:(n,u)=>{let{message:d}=e;if(n.code==="invalid_enum_value")return{message:d??u.defaultError};if(typeof u.data>"u")return{message:d??a??u.defaultError};if(n.code!=="invalid_type")return{message:u.defaultError};return{message:d??r??u.defaultError}},description:s}}class y{get description(){return this._def.description}_getType(e){return j(e.data)}_getOrReturnCtx(e,t){return t||{common:e.parent.common,data:e.data,parsedType:j(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}_processInputParams(e){return{status:new k,ctx:{common:e.parent.common,data:e.data,parsedType:j(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}}_parseSync(e){let t=this._parse(e);if(H(t))throw Error("Synchronous parse encountered promise.");return t}_parseAsync(e){let t=this._parse(e);return Promise.resolve(t)}parse(e,t){let r=this.safeParse(e,t);if(r.success)return r.data;throw r.error}safeParse(e,t){let r={common:{issues:[],async:t?.async??!1,contextualErrorMap:t?.errorMap},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)},a=this._parseSync({data:e,path:r.path,parent:r});return Se(r,a)}"~validate"(e){let t={common:{issues:[],async:!!this["~standard"].async},path:[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)};if(!this["~standard"].async)try{let r=this._parseSync({data:e,path:[],parent:t});return D(r)?{value:r.value}:{issues:t.common.issues}}catch(r){if(r?.message?.toLowerCase()?.includes("encountered"))this["~standard"].async=!0;t.common={issues:[],async:!0}}return this._parseAsync({data:e,path:[],parent:t}).then((r)=>D(r)?{value:r.value}:{issues:t.common.issues})}async parseAsync(e,t){let r=await this.safeParseAsync(e,t);if(r.success)return r.data;throw r.error}async safeParseAsync(e,t){let r={common:{issues:[],contextualErrorMap:t?.errorMap,async:!0},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)},a=this._parse({data:e,path:r.path,parent:r}),s=await(H(a)?a:Promise.resolve(a));return Se(r,s)}refine(e,t){let r=(a)=>{if(typeof t==="string"||typeof t>"u")return{message:t};else if(typeof t==="function")return t(a);else return t};return this._refinement((a,s)=>{let i=e(a),n=()=>s.addIssue({code:o.custom,...r(a)});if(typeof Promise<"u"&&i instanceof Promise)return i.then((u)=>{if(!u)return n(),!1;else return!0});if(!i)return n(),!1;else return!0})}refinement(e,t){return this._refinement((r,a)=>{if(!e(r))return a.addIssue(typeof t==="function"?t(r,a):t),!1;else return!0})}_refinement(e){return new O({schema:this,typeName:m.ZodEffects,effect:{type:"refinement",refinement:e}})}superRefine(e){return this._refinement(e)}constructor(e){this.spa=this.safeParseAsync,this._def=e,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.brand=this.brand.bind(this),this.default=this.default.bind(this),this.catch=this.catch.bind(this),this.describe=this.describe.bind(this),this.pipe=this.pipe.bind(this),this.readonly=this.readonly.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this),this["~standard"]={version:1,vendor:"zod",validate:(t)=>this["~validate"](t)}}optional(){return S.create(this,this._def)}nullable(){return M.create(this,this._def)}nullish(){return this.nullable().optional()}array(){return R.create(this)}promise(){return J.create(this,this._def)}or(e){return ee.create([this,e],this._def)}and(e){return te.create(this,e,this._def)}transform(e){return new O({...g(this._def),schema:this,typeName:m.ZodEffects,effect:{type:"transform",transform:e}})}default(e){let t=typeof e==="function"?e:()=>e;return new ne({...g(this._def),innerType:this,defaultValue:t,typeName:m.ZodDefault})}brand(){return new ye({typeName:m.ZodBranded,type:this,...g(this._def)})}catch(e){let t=typeof e==="function"?e:()=>e;return new ie({...g(this._def),innerType:this,catchValue:t,typeName:m.ZodCatch})}describe(e){return new this.constructor({...this._def,description:e})}pipe(e){return pe.create(this,e)}readonly(){return oe.create(this)}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}}var qe=/^c[^\s-]{8,}$/i,Je=/^[0-9a-z]+$/,Ye=/^[0-9A-HJKMNP-TV-Z]{26}$/i,He=/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i,Ge=/^[a-z0-9_-]{21}$/i,Qe=/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,Xe=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,Ke=/^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i,Ze="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$",ke,et=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,tt=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,rt=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,st=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,at=/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,nt=/^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,Oe="((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))",it=new RegExp(`^${Oe}$`);function je(e){let t="[0-5]\\d";if(e.precision)t=`${t}\\.\\d{${e.precision}}`;else if(e.precision==null)t=`${t}(\\.\\d+)?`;let r=e.precision?"+":"?";return`([01]\\d|2[0-3]):[0-5]\\d(:${t})${r}`}function ot(e){return new RegExp(`^${je(e)}$`)}function Ie(e){let t=`${Oe}T${je(e)}`,r=[];if(r.push(e.local?"Z?":"Z"),e.offset)r.push("([+-]\\d{2}:?\\d{2})");return t=`${t}(${r.join("|")})`,new RegExp(`^${t}$`)}function ct(e,t){if((t==="v4"||!t)&&et.test(e))return!0;if((t==="v6"||!t)&&rt.test(e))return!0;return!1}function ut(e,t){if(!Qe.test(e))return!1;try{let[r]=e.split(".");if(!r)return!1;let a=r.replace(/-/g,"+").replace(/_/g,"/").padEnd(r.length+(4-r.length%4)%4,"="),s=JSON.parse(atob(a));if(typeof s!=="object"||s===null)return!1;if("typ"in s&&s?.typ!=="JWT")return!1;if(!s.alg)return!1;if(t&&s.alg!==t)return!1;return!0}catch{return!1}}function dt(e,t){if((t==="v4"||!t)&&tt.test(e))return!0;if((t==="v6"||!t)&&st.test(e))return!0;return!1}class T extends y{_parse(e){if(this._def.coerce)e.data=String(e.data);if(this._getType(e)!==c.string){let s=this._getOrReturnCtx(e);return l(s,{code:o.invalid_type,expected:c.string,received:s.parsedType}),p}let r=new k,a=void 0;for(let s of this._def.checks)if(s.kind==="min"){if(e.data.lengths.value)a=this._getOrReturnCtx(e,a),l(a,{code:o.too_big,maximum:s.value,type:"string",inclusive:!0,exact:!1,message:s.message}),r.dirty()}else if(s.kind==="length"){let i=e.data.length>s.value,n=e.data.lengthe.test(a),{validation:t,code:o.invalid_string,...f.errToObj(r)})}_addCheck(e){return new T({...this._def,checks:[...this._def.checks,e]})}email(e){return this._addCheck({kind:"email",...f.errToObj(e)})}url(e){return this._addCheck({kind:"url",...f.errToObj(e)})}emoji(e){return this._addCheck({kind:"emoji",...f.errToObj(e)})}uuid(e){return this._addCheck({kind:"uuid",...f.errToObj(e)})}nanoid(e){return this._addCheck({kind:"nanoid",...f.errToObj(e)})}cuid(e){return this._addCheck({kind:"cuid",...f.errToObj(e)})}cuid2(e){return this._addCheck({kind:"cuid2",...f.errToObj(e)})}ulid(e){return this._addCheck({kind:"ulid",...f.errToObj(e)})}base64(e){return this._addCheck({kind:"base64",...f.errToObj(e)})}base64url(e){return this._addCheck({kind:"base64url",...f.errToObj(e)})}jwt(e){return this._addCheck({kind:"jwt",...f.errToObj(e)})}ip(e){return this._addCheck({kind:"ip",...f.errToObj(e)})}cidr(e){return this._addCheck({kind:"cidr",...f.errToObj(e)})}datetime(e){if(typeof e==="string")return this._addCheck({kind:"datetime",precision:null,offset:!1,local:!1,message:e});return this._addCheck({kind:"datetime",precision:typeof e?.precision>"u"?null:e?.precision,offset:e?.offset??!1,local:e?.local??!1,...f.errToObj(e?.message)})}date(e){return this._addCheck({kind:"date",message:e})}time(e){if(typeof e==="string")return this._addCheck({kind:"time",precision:null,message:e});return this._addCheck({kind:"time",precision:typeof e?.precision>"u"?null:e?.precision,...f.errToObj(e?.message)})}duration(e){return this._addCheck({kind:"duration",...f.errToObj(e)})}regex(e,t){return this._addCheck({kind:"regex",regex:e,...f.errToObj(t)})}includes(e,t){return this._addCheck({kind:"includes",value:e,position:t?.position,...f.errToObj(t?.message)})}startsWith(e,t){return this._addCheck({kind:"startsWith",value:e,...f.errToObj(t)})}endsWith(e,t){return this._addCheck({kind:"endsWith",value:e,...f.errToObj(t)})}min(e,t){return this._addCheck({kind:"min",value:e,...f.errToObj(t)})}max(e,t){return this._addCheck({kind:"max",value:e,...f.errToObj(t)})}length(e,t){return this._addCheck({kind:"length",value:e,...f.errToObj(t)})}nonempty(e){return this.min(1,f.errToObj(e))}trim(){return new T({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}toLowerCase(){return new T({...this._def,checks:[...this._def.checks,{kind:"toLowerCase"}]})}toUpperCase(){return new T({...this._def,checks:[...this._def.checks,{kind:"toUpperCase"}]})}get isDatetime(){return!!this._def.checks.find((e)=>e.kind==="datetime")}get isDate(){return!!this._def.checks.find((e)=>e.kind==="date")}get isTime(){return!!this._def.checks.find((e)=>e.kind==="time")}get isDuration(){return!!this._def.checks.find((e)=>e.kind==="duration")}get isEmail(){return!!this._def.checks.find((e)=>e.kind==="email")}get isURL(){return!!this._def.checks.find((e)=>e.kind==="url")}get isEmoji(){return!!this._def.checks.find((e)=>e.kind==="emoji")}get isUUID(){return!!this._def.checks.find((e)=>e.kind==="uuid")}get isNANOID(){return!!this._def.checks.find((e)=>e.kind==="nanoid")}get isCUID(){return!!this._def.checks.find((e)=>e.kind==="cuid")}get isCUID2(){return!!this._def.checks.find((e)=>e.kind==="cuid2")}get isULID(){return!!this._def.checks.find((e)=>e.kind==="ulid")}get isIP(){return!!this._def.checks.find((e)=>e.kind==="ip")}get isCIDR(){return!!this._def.checks.find((e)=>e.kind==="cidr")}get isBase64(){return!!this._def.checks.find((e)=>e.kind==="base64")}get isBase64url(){return!!this._def.checks.find((e)=>e.kind==="base64url")}get minLength(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxLength(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew T({checks:[],typeName:m.ZodString,coerce:e?.coerce??!1,...g(e)});function lt(e,t){let r=(e.toString().split(".")[1]||"").length,a=(t.toString().split(".")[1]||"").length,s=r>a?r:a,i=Number.parseInt(e.toFixed(s).replace(".","")),n=Number.parseInt(t.toFixed(s).replace(".",""));return i%n/10**s}class L extends y{constructor(){super(...arguments);this.min=this.gte,this.max=this.lte,this.step=this.multipleOf}_parse(e){if(this._def.coerce)e.data=Number(e.data);if(this._getType(e)!==c.number){let s=this._getOrReturnCtx(e);return l(s,{code:o.invalid_type,expected:c.number,received:s.parsedType}),p}let r=void 0,a=new k;for(let s of this._def.checks)if(s.kind==="int"){if(!v.isInteger(e.data))r=this._getOrReturnCtx(e,r),l(r,{code:o.invalid_type,expected:"integer",received:"float",message:s.message}),a.dirty()}else if(s.kind==="min"){if(s.inclusive?e.datas.value:e.data>=s.value)r=this._getOrReturnCtx(e,r),l(r,{code:o.too_big,maximum:s.value,type:"number",inclusive:s.inclusive,exact:!1,message:s.message}),a.dirty()}else if(s.kind==="multipleOf"){if(lt(e.data,s.value)!==0)r=this._getOrReturnCtx(e,r),l(r,{code:o.not_multiple_of,multipleOf:s.value,message:s.message}),a.dirty()}else if(s.kind==="finite"){if(!Number.isFinite(e.data))r=this._getOrReturnCtx(e,r),l(r,{code:o.not_finite,message:s.message}),a.dirty()}else v.assertNever(s);return{status:a.value,value:e.data}}gte(e,t){return this.setLimit("min",e,!0,f.toString(t))}gt(e,t){return this.setLimit("min",e,!1,f.toString(t))}lte(e,t){return this.setLimit("max",e,!0,f.toString(t))}lt(e,t){return this.setLimit("max",e,!1,f.toString(t))}setLimit(e,t,r,a){return new L({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:f.toString(a)}]})}_addCheck(e){return new L({...this._def,checks:[...this._def.checks,e]})}int(e){return this._addCheck({kind:"int",message:f.toString(e)})}positive(e){return this._addCheck({kind:"min",value:0,inclusive:!1,message:f.toString(e)})}negative(e){return this._addCheck({kind:"max",value:0,inclusive:!1,message:f.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:0,inclusive:!0,message:f.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:0,inclusive:!0,message:f.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:f.toString(t)})}finite(e){return this._addCheck({kind:"finite",message:f.toString(e)})}safe(e){return this._addCheck({kind:"min",inclusive:!0,value:Number.MIN_SAFE_INTEGER,message:f.toString(e)})._addCheck({kind:"max",inclusive:!0,value:Number.MAX_SAFE_INTEGER,message:f.toString(e)})}get minValue(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxValue(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuee.kind==="int"||e.kind==="multipleOf"&&v.isInteger(e.value))}get isFinite(){let e=null,t=null;for(let r of this._def.checks)if(r.kind==="finite"||r.kind==="int"||r.kind==="multipleOf")return!0;else if(r.kind==="min"){if(t===null||r.value>t)t=r.value}else if(r.kind==="max"){if(e===null||r.valuenew L({checks:[],typeName:m.ZodNumber,coerce:e?.coerce||!1,...g(e)});class P extends y{constructor(){super(...arguments);this.min=this.gte,this.max=this.lte}_parse(e){if(this._def.coerce)try{e.data=BigInt(e.data)}catch{return this._getInvalidInput(e)}if(this._getType(e)!==c.bigint)return this._getInvalidInput(e);let r=void 0,a=new k;for(let s of this._def.checks)if(s.kind==="min"){if(s.inclusive?e.datas.value:e.data>=s.value)r=this._getOrReturnCtx(e,r),l(r,{code:o.too_big,type:"bigint",maximum:s.value,inclusive:s.inclusive,message:s.message}),a.dirty()}else if(s.kind==="multipleOf"){if(e.data%s.value!==BigInt(0))r=this._getOrReturnCtx(e,r),l(r,{code:o.not_multiple_of,multipleOf:s.value,message:s.message}),a.dirty()}else v.assertNever(s);return{status:a.value,value:e.data}}_getInvalidInput(e){let t=this._getOrReturnCtx(e);return l(t,{code:o.invalid_type,expected:c.bigint,received:t.parsedType}),p}gte(e,t){return this.setLimit("min",e,!0,f.toString(t))}gt(e,t){return this.setLimit("min",e,!1,f.toString(t))}lte(e,t){return this.setLimit("max",e,!0,f.toString(t))}lt(e,t){return this.setLimit("max",e,!1,f.toString(t))}setLimit(e,t,r,a){return new P({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:f.toString(a)}]})}_addCheck(e){return new P({...this._def,checks:[...this._def.checks,e]})}positive(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!1,message:f.toString(e)})}negative(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!1,message:f.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!0,message:f.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!0,message:f.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:f.toString(t)})}get minValue(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxValue(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew P({checks:[],typeName:m.ZodBigInt,coerce:e?.coerce??!1,...g(e)});class X extends y{_parse(e){if(this._def.coerce)e.data=Boolean(e.data);if(this._getType(e)!==c.boolean){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.boolean,received:r.parsedType}),p}return b(e.data)}}X.create=(e)=>new X({typeName:m.ZodBoolean,coerce:e?.coerce||!1,...g(e)});class W extends y{_parse(e){if(this._def.coerce)e.data=new Date(e.data);if(this._getType(e)!==c.date){let s=this._getOrReturnCtx(e);return l(s,{code:o.invalid_type,expected:c.date,received:s.parsedType}),p}if(Number.isNaN(e.data.getTime())){let s=this._getOrReturnCtx(e);return l(s,{code:o.invalid_date}),p}let r=new k,a=void 0;for(let s of this._def.checks)if(s.kind==="min"){if(e.data.getTime()s.value)a=this._getOrReturnCtx(e,a),l(a,{code:o.too_big,message:s.message,inclusive:!0,exact:!1,maximum:s.value,type:"date"}),r.dirty()}else v.assertNever(s);return{status:r.value,value:new Date(e.data.getTime())}}_addCheck(e){return new W({...this._def,checks:[...this._def.checks,e]})}min(e,t){return this._addCheck({kind:"min",value:e.getTime(),message:f.toString(t)})}max(e,t){return this._addCheck({kind:"max",value:e.getTime(),message:f.toString(t)})}get minDate(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e!=null?new Date(e):null}get maxDate(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew W({checks:[],coerce:e?.coerce||!1,typeName:m.ZodDate,...g(e)});class ue extends y{_parse(e){if(this._getType(e)!==c.symbol){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.symbol,received:r.parsedType}),p}return b(e.data)}}ue.create=(e)=>new ue({typeName:m.ZodSymbol,...g(e)});class K extends y{_parse(e){if(this._getType(e)!==c.undefined){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.undefined,received:r.parsedType}),p}return b(e.data)}}K.create=(e)=>new K({typeName:m.ZodUndefined,...g(e)});class Z extends y{_parse(e){if(this._getType(e)!==c.null){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.null,received:r.parsedType}),p}return b(e.data)}}Z.create=(e)=>new Z({typeName:m.ZodNull,...g(e)});class U extends y{constructor(){super(...arguments);this._any=!0}_parse(e){return b(e.data)}}U.create=(e)=>new U({typeName:m.ZodAny,...g(e)});class z extends y{constructor(){super(...arguments);this._unknown=!0}_parse(e){return b(e.data)}}z.create=(e)=>new z({typeName:m.ZodUnknown,...g(e)});class I extends y{_parse(e){let t=this._getOrReturnCtx(e);return l(t,{code:o.invalid_type,expected:c.never,received:t.parsedType}),p}}I.create=(e)=>new I({typeName:m.ZodNever,...g(e)});class de extends y{_parse(e){if(this._getType(e)!==c.undefined){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.void,received:r.parsedType}),p}return b(e.data)}}de.create=(e)=>new de({typeName:m.ZodVoid,...g(e)});class R extends y{_parse(e){let{ctx:t,status:r}=this._processInputParams(e),a=this._def;if(t.parsedType!==c.array)return l(t,{code:o.invalid_type,expected:c.array,received:t.parsedType}),p;if(a.exactLength!==null){let i=t.data.length>a.exactLength.value,n=t.data.lengtha.maxLength.value)l(t,{code:o.too_big,maximum:a.maxLength.value,type:"array",inclusive:!0,exact:!1,message:a.maxLength.message}),r.dirty()}if(t.common.async)return Promise.all([...t.data].map((i,n)=>a.type._parseAsync(new A(t,i,t.path,n)))).then((i)=>k.mergeArray(r,i));let s=[...t.data].map((i,n)=>a.type._parseSync(new A(t,i,t.path,n)));return k.mergeArray(r,s)}get element(){return this._def.type}min(e,t){return new R({...this._def,minLength:{value:e,message:f.toString(t)}})}max(e,t){return new R({...this._def,maxLength:{value:e,message:f.toString(t)}})}length(e,t){return new R({...this._def,exactLength:{value:e,message:f.toString(t)}})}nonempty(e){return this.min(1,e)}}R.create=(e,t)=>new R({type:e,minLength:null,maxLength:null,exactLength:null,typeName:m.ZodArray,...g(t)});function G(e){if(e instanceof x){let t={};for(let r in e.shape){let a=e.shape[r];t[r]=S.create(G(a))}return new x({...e._def,shape:()=>t})}else if(e instanceof R)return new R({...e._def,type:G(e.element)});else if(e instanceof S)return S.create(G(e.unwrap()));else if(e instanceof M)return M.create(G(e.unwrap()));else if(e instanceof E)return E.create(e.items.map((t)=>G(t)));else return e}class x extends y{constructor(){super(...arguments);this._cached=null,this.nonstrict=this.passthrough,this.augment=this.extend}_getCached(){if(this._cached!==null)return this._cached;let e=this._def.shape(),t=v.objectKeys(e);return this._cached={shape:e,keys:t},this._cached}_parse(e){if(this._getType(e)!==c.object){let d=this._getOrReturnCtx(e);return l(d,{code:o.invalid_type,expected:c.object,received:d.parsedType}),p}let{status:r,ctx:a}=this._processInputParams(e),{shape:s,keys:i}=this._getCached(),n=[];if(!(this._def.catchall instanceof I&&this._def.unknownKeys==="strip")){for(let d in a.data)if(!i.includes(d))n.push(d)}let u=[];for(let d of i){let _=s[d],C=a.data[d];u.push({key:{status:"valid",value:d},value:_._parse(new A(a,C,a.path,d)),alwaysSet:d in a.data})}if(this._def.catchall instanceof I){let d=this._def.unknownKeys;if(d==="passthrough")for(let _ of n)u.push({key:{status:"valid",value:_},value:{status:"valid",value:a.data[_]}});else if(d==="strict"){if(n.length>0)l(a,{code:o.unrecognized_keys,keys:n}),r.dirty()}else if(d==="strip");else throw Error("Internal ZodObject error: invalid unknownKeys value.")}else{let d=this._def.catchall;for(let _ of n){let C=a.data[_];u.push({key:{status:"valid",value:_},value:d._parse(new A(a,C,a.path,_)),alwaysSet:_ in a.data})}}if(a.common.async)return Promise.resolve().then(async()=>{let d=[];for(let _ of u){let C=await _.key,Te=await _.value;d.push({key:C,value:Te,alwaysSet:_.alwaysSet})}return d}).then((d)=>k.mergeObjectSync(r,d));else return k.mergeObjectSync(r,u)}get shape(){return this._def.shape()}strict(e){return f.errToObj,new x({...this._def,unknownKeys:"strict",...e!==void 0?{errorMap:(t,r)=>{let a=this._def.errorMap?.(t,r).message??r.defaultError;if(t.code==="unrecognized_keys")return{message:f.errToObj(e).message??a};return{message:a}}}:{}})}strip(){return new x({...this._def,unknownKeys:"strip"})}passthrough(){return new x({...this._def,unknownKeys:"passthrough"})}extend(e){return new x({...this._def,shape:()=>({...this._def.shape(),...e})})}merge(e){return new x({unknownKeys:e._def.unknownKeys,catchall:e._def.catchall,shape:()=>({...this._def.shape(),...e._def.shape()}),typeName:m.ZodObject})}setKey(e,t){return this.augment({[e]:t})}catchall(e){return new x({...this._def,catchall:e})}pick(e){let t={};for(let r of v.objectKeys(e))if(e[r]&&this.shape[r])t[r]=this.shape[r];return new x({...this._def,shape:()=>t})}omit(e){let t={};for(let r of v.objectKeys(this.shape))if(!e[r])t[r]=this.shape[r];return new x({...this._def,shape:()=>t})}deepPartial(){return G(this)}partial(e){let t={};for(let r of v.objectKeys(this.shape)){let a=this.shape[r];if(e&&!e[r])t[r]=a;else t[r]=a.optional()}return new x({...this._def,shape:()=>t})}required(e){let t={};for(let r of v.objectKeys(this.shape))if(e&&!e[r])t[r]=this.shape[r];else{let s=this.shape[r];while(s instanceof S)s=s._def.innerType;t[r]=s}return new x({...this._def,shape:()=>t})}keyof(){return Ee(v.objectKeys(this.shape))}}x.create=(e,t)=>new x({shape:()=>e,unknownKeys:"strip",catchall:I.create(),typeName:m.ZodObject,...g(t)});x.strictCreate=(e,t)=>new x({shape:()=>e,unknownKeys:"strict",catchall:I.create(),typeName:m.ZodObject,...g(t)});x.lazycreate=(e,t)=>new x({shape:e,unknownKeys:"strip",catchall:I.create(),typeName:m.ZodObject,...g(t)});class ee extends y{_parse(e){let{ctx:t}=this._processInputParams(e),r=this._def.options;function a(s){for(let n of s)if(n.result.status==="valid")return n.result;for(let n of s)if(n.result.status==="dirty")return t.common.issues.push(...n.ctx.common.issues),n.result;let i=s.map((n)=>new w(n.ctx.common.issues));return l(t,{code:o.invalid_union,unionErrors:i}),p}if(t.common.async)return Promise.all(r.map(async(s)=>{let i={...t,common:{...t.common,issues:[]},parent:null};return{result:await s._parseAsync({data:t.data,path:t.path,parent:i}),ctx:i}})).then(a);else{let s=void 0,i=[];for(let u of r){let d={...t,common:{...t.common,issues:[]},parent:null},_=u._parseSync({data:t.data,path:t.path,parent:d});if(_.status==="valid")return _;else if(_.status==="dirty"&&!s)s={result:_,ctx:d};if(d.common.issues.length)i.push(d.common.issues)}if(s)return t.common.issues.push(...s.ctx.common.issues),s.result;let n=i.map((u)=>new w(u));return l(t,{code:o.invalid_union,unionErrors:n}),p}}get options(){return this._def.options}}ee.create=(e,t)=>new ee({options:e,typeName:m.ZodUnion,...g(t)});var V=(e)=>{if(e instanceof re)return V(e.schema);else if(e instanceof O)return V(e.innerType());else if(e instanceof se)return[e.value];else if(e instanceof F)return e.options;else if(e instanceof ae)return v.objectValues(e.enum);else if(e instanceof ne)return V(e._def.innerType);else if(e instanceof K)return[void 0];else if(e instanceof Z)return[null];else if(e instanceof S)return[void 0,...V(e.unwrap())];else if(e instanceof M)return[null,...V(e.unwrap())];else if(e instanceof ye)return V(e.unwrap());else if(e instanceof oe)return V(e.unwrap());else if(e instanceof ie)return V(e._def.innerType);else return[]};class _e extends y{_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==c.object)return l(t,{code:o.invalid_type,expected:c.object,received:t.parsedType}),p;let r=this.discriminator,a=t.data[r],s=this.optionsMap.get(a);if(!s)return l(t,{code:o.invalid_union_discriminator,options:Array.from(this.optionsMap.keys()),path:[r]}),p;if(t.common.async)return s._parseAsync({data:t.data,path:t.path,parent:t});else return s._parseSync({data:t.data,path:t.path,parent:t})}get discriminator(){return this._def.discriminator}get options(){return this._def.options}get optionsMap(){return this._def.optionsMap}static create(e,t,r){let a=new Map;for(let s of t){let i=V(s.shape[e]);if(!i.length)throw Error(`A discriminator value for key \`${e}\` could not be extracted from all schema options`);for(let n of i){if(a.has(n))throw Error(`Discriminator property ${String(e)} has duplicate value ${String(n)}`);a.set(n,s)}}return new _e({typeName:m.ZodDiscriminatedUnion,discriminator:e,options:t,optionsMap:a,...g(r)})}}function be(e,t){let r=j(e),a=j(t);if(e===t)return{valid:!0,data:e};else if(r===c.object&&a===c.object){let s=v.objectKeys(t),i=v.objectKeys(e).filter((u)=>s.indexOf(u)!==-1),n={...e,...t};for(let u of i){let d=be(e[u],t[u]);if(!d.valid)return{valid:!1};n[u]=d.data}return{valid:!0,data:n}}else if(r===c.array&&a===c.array){if(e.length!==t.length)return{valid:!1};let s=[];for(let i=0;i{if(me(s)||me(i))return p;let n=be(s.value,i.value);if(!n.valid)return l(r,{code:o.invalid_intersection_types}),p;if(ge(s)||ge(i))t.dirty();return{status:t.value,value:n.data}};if(r.common.async)return Promise.all([this._def.left._parseAsync({data:r.data,path:r.path,parent:r}),this._def.right._parseAsync({data:r.data,path:r.path,parent:r})]).then(([s,i])=>a(s,i));else return a(this._def.left._parseSync({data:r.data,path:r.path,parent:r}),this._def.right._parseSync({data:r.data,path:r.path,parent:r}))}}te.create=(e,t,r)=>new te({left:e,right:t,typeName:m.ZodIntersection,...g(r)});class E extends y{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==c.array)return l(r,{code:o.invalid_type,expected:c.array,received:r.parsedType}),p;if(r.data.lengththis._def.items.length)l(r,{code:o.too_big,maximum:this._def.items.length,inclusive:!0,exact:!1,type:"array"}),t.dirty();let s=[...r.data].map((i,n)=>{let u=this._def.items[n]||this._def.rest;if(!u)return null;return u._parse(new A(r,i,r.path,n))}).filter((i)=>!!i);if(r.common.async)return Promise.all(s).then((i)=>k.mergeArray(t,i));else return k.mergeArray(t,s)}get items(){return this._def.items}rest(e){return new E({...this._def,rest:e})}}E.create=(e,t)=>{if(!Array.isArray(e))throw Error("You must pass an array of schemas to z.tuple([ ... ])");return new E({items:e,typeName:m.ZodTuple,rest:null,...g(t)})};class le extends y{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==c.object)return l(r,{code:o.invalid_type,expected:c.object,received:r.parsedType}),p;let a=[],s=this._def.keyType,i=this._def.valueType;for(let n in r.data)a.push({key:s._parse(new A(r,n,r.path,n)),value:i._parse(new A(r,r.data[n],r.path,n)),alwaysSet:n in r.data});if(r.common.async)return k.mergeObjectAsync(t,a);else return k.mergeObjectSync(t,a)}get element(){return this._def.valueType}static create(e,t,r){if(t instanceof y)return new le({keyType:e,valueType:t,typeName:m.ZodRecord,...g(r)});return new le({keyType:T.create(),valueType:e,typeName:m.ZodRecord,...g(t)})}}class fe extends y{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==c.map)return l(r,{code:o.invalid_type,expected:c.map,received:r.parsedType}),p;let a=this._def.keyType,s=this._def.valueType,i=[...r.data.entries()].map(([n,u],d)=>({key:a._parse(new A(r,n,r.path,[d,"key"])),value:s._parse(new A(r,u,r.path,[d,"value"]))}));if(r.common.async){let n=new Map;return Promise.resolve().then(async()=>{for(let u of i){let d=await u.key,_=await u.value;if(d.status==="aborted"||_.status==="aborted")return p;if(d.status==="dirty"||_.status==="dirty")t.dirty();n.set(d.value,_.value)}return{status:t.value,value:n}})}else{let n=new Map;for(let u of i){let{key:d,value:_}=u;if(d.status==="aborted"||_.status==="aborted")return p;if(d.status==="dirty"||_.status==="dirty")t.dirty();n.set(d.value,_.value)}return{status:t.value,value:n}}}}fe.create=(e,t,r)=>new fe({valueType:t,keyType:e,typeName:m.ZodMap,...g(r)});class q extends y{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==c.set)return l(r,{code:o.invalid_type,expected:c.set,received:r.parsedType}),p;let a=this._def;if(a.minSize!==null){if(r.data.sizea.maxSize.value)l(r,{code:o.too_big,maximum:a.maxSize.value,type:"set",inclusive:!0,exact:!1,message:a.maxSize.message}),t.dirty()}let s=this._def.valueType;function i(u){let d=new Set;for(let _ of u){if(_.status==="aborted")return p;if(_.status==="dirty")t.dirty();d.add(_.value)}return{status:t.value,value:d}}let n=[...r.data.values()].map((u,d)=>s._parse(new A(r,u,r.path,d)));if(r.common.async)return Promise.all(n).then((u)=>i(u));else return i(n)}min(e,t){return new q({...this._def,minSize:{value:e,message:f.toString(t)}})}max(e,t){return new q({...this._def,maxSize:{value:e,message:f.toString(t)}})}size(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}}q.create=(e,t)=>new q({valueType:e,minSize:null,maxSize:null,typeName:m.ZodSet,...g(t)});class Q extends y{constructor(){super(...arguments);this.validate=this.implement}_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==c.function)return l(t,{code:o.invalid_type,expected:c.function,received:t.parsedType}),p;function r(n,u){return ce({data:n,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,Y(),N].filter((d)=>!!d),issueData:{code:o.invalid_arguments,argumentsError:u}})}function a(n,u){return ce({data:n,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,Y(),N].filter((d)=>!!d),issueData:{code:o.invalid_return_type,returnTypeError:u}})}let s={errorMap:t.common.contextualErrorMap},i=t.data;if(this._def.returns instanceof J){let n=this;return b(async function(...u){let d=new w([]),_=await n._def.args.parseAsync(u,s).catch((ve)=>{throw d.addIssue(r(u,ve)),d}),C=await Reflect.apply(i,this,_);return await n._def.returns._def.type.parseAsync(C,s).catch((ve)=>{throw d.addIssue(a(C,ve)),d})})}else{let n=this;return b(function(...u){let d=n._def.args.safeParse(u,s);if(!d.success)throw new w([r(u,d.error)]);let _=Reflect.apply(i,this,d.data),C=n._def.returns.safeParse(_,s);if(!C.success)throw new w([a(_,C.error)]);return C.data})}}parameters(){return this._def.args}returnType(){return this._def.returns}args(...e){return new Q({...this._def,args:E.create(e).rest(z.create())})}returns(e){return new Q({...this._def,returns:e})}implement(e){return this.parse(e)}strictImplement(e){return this.parse(e)}static create(e,t,r){return new Q({args:e?e:E.create([]).rest(z.create()),returns:t||z.create(),typeName:m.ZodFunction,...g(r)})}}class re extends y{get schema(){return this._def.getter()}_parse(e){let{ctx:t}=this._processInputParams(e);return this._def.getter()._parse({data:t.data,path:t.path,parent:t})}}re.create=(e,t)=>new re({getter:e,typeName:m.ZodLazy,...g(t)});class se extends y{_parse(e){if(e.data!==this._def.value){let t=this._getOrReturnCtx(e);return l(t,{received:t.data,code:o.invalid_literal,expected:this._def.value}),p}return{status:"valid",value:e.data}}get value(){return this._def.value}}se.create=(e,t)=>new se({value:e,typeName:m.ZodLiteral,...g(t)});function Ee(e,t){return new F({values:e,typeName:m.ZodEnum,...g(t)})}class F extends y{_parse(e){if(typeof e.data!=="string"){let t=this._getOrReturnCtx(e),r=this._def.values;return l(t,{expected:v.joinValues(r),received:t.parsedType,code:o.invalid_type}),p}if(!this._cache)this._cache=new Set(this._def.values);if(!this._cache.has(e.data)){let t=this._getOrReturnCtx(e),r=this._def.values;return l(t,{received:t.data,code:o.invalid_enum_value,options:r}),p}return b(e.data)}get options(){return this._def.values}get enum(){let e={};for(let t of this._def.values)e[t]=t;return e}get Values(){let e={};for(let t of this._def.values)e[t]=t;return e}get Enum(){let e={};for(let t of this._def.values)e[t]=t;return e}extract(e,t=this._def){return F.create(e,{...this._def,...t})}exclude(e,t=this._def){return F.create(this.options.filter((r)=>!e.includes(r)),{...this._def,...t})}}F.create=Ee;class ae extends y{_parse(e){let t=v.getValidEnumValues(this._def.values),r=this._getOrReturnCtx(e);if(r.parsedType!==c.string&&r.parsedType!==c.number){let a=v.objectValues(t);return l(r,{expected:v.joinValues(a),received:r.parsedType,code:o.invalid_type}),p}if(!this._cache)this._cache=new Set(v.getValidEnumValues(this._def.values));if(!this._cache.has(e.data)){let a=v.objectValues(t);return l(r,{received:r.data,code:o.invalid_enum_value,options:a}),p}return b(e.data)}get enum(){return this._def.values}}ae.create=(e,t)=>new ae({values:e,typeName:m.ZodNativeEnum,...g(t)});class J extends y{unwrap(){return this._def.type}_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==c.promise&&t.common.async===!1)return l(t,{code:o.invalid_type,expected:c.promise,received:t.parsedType}),p;let r=t.parsedType===c.promise?t.data:Promise.resolve(t.data);return b(r.then((a)=>this._def.type.parseAsync(a,{path:t.path,errorMap:t.common.contextualErrorMap})))}}J.create=(e,t)=>new J({type:e,typeName:m.ZodPromise,...g(t)});class O extends y{innerType(){return this._def.schema}sourceType(){return this._def.schema._def.typeName===m.ZodEffects?this._def.schema.sourceType():this._def.schema}_parse(e){let{status:t,ctx:r}=this._processInputParams(e),a=this._def.effect||null,s={addIssue:(i)=>{if(l(r,i),i.fatal)t.abort();else t.dirty()},get path(){return r.path}};if(s.addIssue=s.addIssue.bind(s),a.type==="preprocess"){let i=a.transform(r.data,s);if(r.common.async)return Promise.resolve(i).then(async(n)=>{if(t.value==="aborted")return p;let u=await this._def.schema._parseAsync({data:n,path:r.path,parent:r});if(u.status==="aborted")return p;if(u.status==="dirty")return B(u.value);if(t.value==="dirty")return B(u.value);return u});else{if(t.value==="aborted")return p;let n=this._def.schema._parseSync({data:i,path:r.path,parent:r});if(n.status==="aborted")return p;if(n.status==="dirty")return B(n.value);if(t.value==="dirty")return B(n.value);return n}}if(a.type==="refinement"){let i=(n)=>{let u=a.refinement(n,s);if(r.common.async)return Promise.resolve(u);if(u instanceof Promise)throw Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return n};if(r.common.async===!1){let n=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});if(n.status==="aborted")return p;if(n.status==="dirty")t.dirty();return i(n.value),{status:t.value,value:n.value}}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then((n)=>{if(n.status==="aborted")return p;if(n.status==="dirty")t.dirty();return i(n.value).then(()=>({status:t.value,value:n.value}))})}if(a.type==="transform")if(r.common.async===!1){let i=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});if(!D(i))return p;let n=a.transform(i.value,s);if(n instanceof Promise)throw Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:t.value,value:n}}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then((i)=>{if(!D(i))return p;return Promise.resolve(a.transform(i.value,s)).then((n)=>({status:t.value,value:n}))});v.assertNever(a)}}O.create=(e,t,r)=>new O({schema:e,typeName:m.ZodEffects,effect:t,...g(r)});O.createWithPreprocess=(e,t,r)=>new O({schema:t,effect:{type:"preprocess",transform:e},typeName:m.ZodEffects,...g(r)});class S extends y{_parse(e){if(this._getType(e)===c.undefined)return b(void 0);return this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}S.create=(e,t)=>new S({innerType:e,typeName:m.ZodOptional,...g(t)});class M extends y{_parse(e){if(this._getType(e)===c.null)return b(null);return this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}M.create=(e,t)=>new M({innerType:e,typeName:m.ZodNullable,...g(t)});class ne extends y{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;if(t.parsedType===c.undefined)r=this._def.defaultValue();return this._def.innerType._parse({data:r,path:t.path,parent:t})}removeDefault(){return this._def.innerType}}ne.create=(e,t)=>new ne({innerType:e,typeName:m.ZodDefault,defaultValue:typeof t.default==="function"?t.default:()=>t.default,...g(t)});class ie extends y{_parse(e){let{ctx:t}=this._processInputParams(e),r={...t,common:{...t.common,issues:[]}},a=this._def.innerType._parse({data:r.data,path:r.path,parent:{...r}});if(H(a))return a.then((s)=>({status:"valid",value:s.status==="valid"?s.value:this._def.catchValue({get error(){return new w(r.common.issues)},input:r.data})}));else return{status:"valid",value:a.status==="valid"?a.value:this._def.catchValue({get error(){return new w(r.common.issues)},input:r.data})}}removeCatch(){return this._def.innerType}}ie.create=(e,t)=>new ie({innerType:e,typeName:m.ZodCatch,catchValue:typeof t.catch==="function"?t.catch:()=>t.catch,...g(t)});class he extends y{_parse(e){if(this._getType(e)!==c.nan){let r=this._getOrReturnCtx(e);return l(r,{code:o.invalid_type,expected:c.nan,received:r.parsedType}),p}return{status:"valid",value:e.data}}}he.create=(e)=>new he({typeName:m.ZodNaN,...g(e)});var ft=Symbol("zod_brand");class ye extends y{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;return this._def.type._parse({data:r,path:t.path,parent:t})}unwrap(){return this._def.type}}class pe extends y{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.common.async)return(async()=>{let s=await this._def.in._parseAsync({data:r.data,path:r.path,parent:r});if(s.status==="aborted")return p;if(s.status==="dirty")return t.dirty(),B(s.value);else return this._def.out._parseAsync({data:s.value,path:r.path,parent:r})})();else{let a=this._def.in._parseSync({data:r.data,path:r.path,parent:r});if(a.status==="aborted")return p;if(a.status==="dirty")return t.dirty(),{status:"dirty",value:a.value};else return this._def.out._parseSync({data:a.value,path:r.path,parent:r})}}static create(e,t){return new pe({in:e,out:t,typeName:m.ZodPipeline})}}class oe extends y{_parse(e){let t=this._def.innerType._parse(e),r=(a)=>{if(D(a))a.value=Object.freeze(a.value);return a};return H(t)?t.then((a)=>r(a)):r(t)}unwrap(){return this._def.innerType}}oe.create=(e,t)=>new oe({innerType:e,typeName:m.ZodReadonly,...g(t)});function Ae(e,t){let r=typeof e==="function"?e(t):typeof e==="string"?{message:e}:e;return typeof r==="string"?{message:r}:r}function Ne(e,t={},r){if(e)return U.create().superRefine((a,s)=>{let i=e(a);if(i instanceof Promise)return i.then((n)=>{if(!n){let u=Ae(t,a),d=u.fatal??r??!0;s.addIssue({code:"custom",...u,fatal:d})}});if(!i){let n=Ae(t,a),u=n.fatal??r??!0;s.addIssue({code:"custom",...n,fatal:u})}return});return U.create()}var ht={object:x.lazycreate},m;(function(e){e.ZodString="ZodString",e.ZodNumber="ZodNumber",e.ZodNaN="ZodNaN",e.ZodBigInt="ZodBigInt",e.ZodBoolean="ZodBoolean",e.ZodDate="ZodDate",e.ZodSymbol="ZodSymbol",e.ZodUndefined="ZodUndefined",e.ZodNull="ZodNull",e.ZodAny="ZodAny",e.ZodUnknown="ZodUnknown",e.ZodNever="ZodNever",e.ZodVoid="ZodVoid",e.ZodArray="ZodArray",e.ZodObject="ZodObject",e.ZodUnion="ZodUnion",e.ZodDiscriminatedUnion="ZodDiscriminatedUnion",e.ZodIntersection="ZodIntersection",e.ZodTuple="ZodTuple",e.ZodRecord="ZodRecord",e.ZodMap="ZodMap",e.ZodSet="ZodSet",e.ZodFunction="ZodFunction",e.ZodLazy="ZodLazy",e.ZodLiteral="ZodLiteral",e.ZodEnum="ZodEnum",e.ZodEffects="ZodEffects",e.ZodNativeEnum="ZodNativeEnum",e.ZodOptional="ZodOptional",e.ZodNullable="ZodNullable",e.ZodDefault="ZodDefault",e.ZodCatch="ZodCatch",e.ZodPromise="ZodPromise",e.ZodBranded="ZodBranded",e.ZodPipeline="ZodPipeline",e.ZodReadonly="ZodReadonly"})(m||(m={}));var pt=(e,t={message:`Input not instance of ${e.name}`})=>Ne((r)=>r instanceof e,t),Ve=T.create,$e=L.create,mt=he.create,gt=P.create,Me=X.create,_t=W.create,yt=ue.create,vt=K.create,xt=Z.create,kt=U.create,bt=z.create,wt=I.create,Ct=de.create,Tt=R.create,Rt=x.create,St=x.strictCreate,At=ee.create,Ot=_e.create,jt=te.create,It=E.create,Et=le.create,Nt=fe.create,Vt=q.create,$t=Q.create,Mt=re.create,Dt=se.create,zt=F.create,Lt=ae.create,Pt=J.create,Ft=O.create,Bt=S.create,Wt=M.create,Ut=O.createWithPreprocess,qt=pe.create,Jt=()=>Ve().optional(),Yt=()=>$e().optional(),Ht=()=>Me().optional(),Gt={string:(e)=>T.create({...e,coerce:!0}),number:(e)=>L.create({...e,coerce:!0}),boolean:(e)=>X.create({...e,coerce:!0}),bigint:(e)=>P.create({...e,coerce:!0}),date:(e)=>W.create({...e,coerce:!0})};var Qt=p;var Xt=1,Ce=h.object({id:h.string(),name:h.string(),kind:h.string(),file:h.string().optional()}).passthrough(),we=h.object({statement:h.string(),kind:h.string(),verificationStatus:h.string()}).passthrough(),Kt=h.object({name:h.string(),file:h.string().optional()}).passthrough(),Zt=h.object({file:h.string(),line:h.number().optional()}).passthrough(),er=h.object({rule:h.string(),tier:h.enum(["strict","advisory"]),severity:h.enum(["warn","block"]),message:h.string(),nodeIds:h.array(h.string()),locations:h.array(Zt)}).passthrough(),tr=h.object({symbol:Ce,consumers:h.array(Ce)}).passthrough(),rr=h.object({file:h.string(),coChanged:h.array(h.object({file:h.string(),commits:h.number()}).passthrough())}).passthrough(),sr=h.object({schemaVersion:h.literal(Xt),verdict:h.enum(["PASS","WARN","BLOCK"]),base:h.string().nullable(),head:h.string(),mergeBase:h.string().nullable(),range:h.string().nullable(),changedFiles:h.array(h.string()),changedSymbols:h.array(Ce),impactedContracts:h.array(we),impactedInvariants:h.array(we),recommendedTests:h.array(Kt),contradictions:h.array(we),unknowns:h.array(h.string()),findings:h.array(er),impactedConsumers:h.array(tr).optional(),coChangedFiles:h.array(rr).optional(),summary:h.object({blockCount:h.number(),warnCount:h.number()}).passthrough()}).passthrough();export{sr as VerifyReportSchema};
+var Pe=Object.defineProperty;var We=(e)=>e;function Be(e,t){this[e]=We.bind(null,t)}var Ue=(e,t)=>{for(var r in t)Pe(e,r,{get:t[r],enumerable:!0,configurable:!0,set:Be.bind(t,r)})};var o={};Ue(o,{BRAND:()=>gt,DIRTY:()=>W,EMPTY_PATH:()=>Je,INVALID:()=>p,NEVER:()=>er,OK:()=>b,ParseStatus:()=>k,Schema:()=>_,ZodAny:()=>U,ZodArray:()=>R,ZodBigInt:()=>L,ZodBoolean:()=>Q,ZodBranded:()=>ve,ZodCatch:()=>ie,ZodDate:()=>B,ZodDefault:()=>ae,ZodDiscriminatedUnion:()=>_e,ZodEffects:()=>O,ZodEnum:()=>P,ZodError:()=>w,ZodFirstPartyTypeKind:()=>m,ZodFunction:()=>G,ZodIntersection:()=>te,ZodIssueCode:()=>c,ZodLazy:()=>re,ZodLiteral:()=>se,ZodMap:()=>fe,ZodNaN:()=>he,ZodNativeEnum:()=>ne,ZodNever:()=>I,ZodNull:()=>Z,ZodNullable:()=>M,ZodNumber:()=>D,ZodObject:()=>x,ZodOptional:()=>T,ZodParsedType:()=>u,ZodPipeline:()=>pe,ZodPromise:()=>Y,ZodReadonly:()=>oe,ZodRecord:()=>le,ZodSchema:()=>_,ZodSet:()=>q,ZodString:()=>S,ZodSymbol:()=>ue,ZodTransformer:()=>O,ZodTuple:()=>E,ZodType:()=>_,ZodUndefined:()=>X,ZodUnion:()=>ee,ZodUnknown:()=>F,ZodVoid:()=>de,addIssueToContext:()=>f,any:()=>St,array:()=>Ot,bigint:()=>xt,boolean:()=>ze,coerce:()=>Zt,custom:()=>Ve,date:()=>kt,datetimeRegex:()=>Ee,defaultErrorMap:()=>N,discriminatedUnion:()=>Nt,effect:()=>qt,enum:()=>Wt,function:()=>Dt,getErrorMap:()=>H,getParsedType:()=>j,instanceof:()=>_t,intersection:()=>Vt,isAborted:()=>ge,isAsync:()=>J,isDirty:()=>ye,isValid:()=>z,late:()=>yt,lazy:()=>Lt,literal:()=>Pt,makeIssue:()=>ce,map:()=>zt,nan:()=>vt,nativeEnum:()=>Bt,never:()=>Tt,null:()=>Ct,nullable:()=>Ht,number:()=>Me,object:()=>jt,objectUtil:()=>ke,oboolean:()=>Xt,onumber:()=>Qt,optional:()=>Yt,ostring:()=>Gt,pipeline:()=>Kt,preprocess:()=>Jt,promise:()=>Ut,quotelessJson:()=>qe,record:()=>Mt,set:()=>Ft,setErrorMap:()=>He,strictObject:()=>It,string:()=>$e,symbol:()=>bt,transformer:()=>qt,tuple:()=>$t,undefined:()=>wt,union:()=>Et,unknown:()=>Rt,util:()=>v,void:()=>At});var v;(function(e){e.assertEqual=(s)=>{};function t(s){}e.assertIs=t;function r(s){throw Error()}e.assertNever=r,e.arrayToEnum=(s)=>{let i={};for(let a of s)i[a]=a;return i},e.getValidEnumValues=(s)=>{let i=e.objectKeys(s).filter((d)=>typeof s[s[d]]!=="number"),a={};for(let d of i)a[d]=s[d];return e.objectValues(a)},e.objectValues=(s)=>e.objectKeys(s).map(function(i){return s[i]}),e.objectKeys=typeof Object.keys==="function"?(s)=>Object.keys(s):(s)=>{let i=[];for(let a in s)if(Object.prototype.hasOwnProperty.call(s,a))i.push(a);return i},e.find=(s,i)=>{for(let a of s)if(i(a))return a;return},e.isInteger=typeof Number.isInteger==="function"?(s)=>Number.isInteger(s):(s)=>typeof s==="number"&&Number.isFinite(s)&&Math.floor(s)===s;function n(s,i=" | "){return s.map((a)=>typeof a==="string"?`'${a}'`:a).join(i)}e.joinValues=n,e.jsonStringifyReplacer=(s,i)=>{if(typeof i==="bigint")return i.toString();return i}})(v||(v={}));var ke;(function(e){e.mergeShapes=(t,r)=>({...t,...r})})(ke||(ke={}));var u=v.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),j=(e)=>{switch(typeof e){case"undefined":return u.undefined;case"string":return u.string;case"number":return Number.isNaN(e)?u.nan:u.number;case"boolean":return u.boolean;case"function":return u.function;case"bigint":return u.bigint;case"symbol":return u.symbol;case"object":if(Array.isArray(e))return u.array;if(e===null)return u.null;if(e.then&&typeof e.then==="function"&&e.catch&&typeof e.catch==="function")return u.promise;if(typeof Map<"u"&&e instanceof Map)return u.map;if(typeof Set<"u"&&e instanceof Set)return u.set;if(typeof Date<"u"&&e instanceof Date)return u.date;return u.object;default:return u.unknown}};var c=v.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]),qe=(e)=>JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:");class w extends Error{get errors(){return this.issues}constructor(e){super();this.issues=[],this.addIssue=(r)=>{this.issues=[...this.issues,r]},this.addIssues=(r=[])=>{this.issues=[...this.issues,...r]};let t=new.target.prototype;if(Object.setPrototypeOf)Object.setPrototypeOf(this,t);else this.__proto__=t;this.name="ZodError",this.issues=e}format(e){let t=e||function(s){return s.message},r={_errors:[]},n=(s)=>{for(let i of s.issues)if(i.code==="invalid_union")i.unionErrors.map(n);else if(i.code==="invalid_return_type")n(i.returnTypeError);else if(i.code==="invalid_arguments")n(i.argumentsError);else if(i.path.length===0)r._errors.push(t(i));else{let a=r,d=0;while(dt.message){let t={},r=[];for(let n of this.issues)if(n.path.length>0){let s=n.path[0];t[s]=t[s]||[],t[s].push(e(n))}else r.push(e(n));return{formErrors:r,fieldErrors:t}}get formErrors(){return this.flatten()}}w.create=(e)=>new w(e);var Ye=(e,t)=>{let r;switch(e.code){case c.invalid_type:if(e.received===u.undefined)r="Required";else r=`Expected ${e.expected}, received ${e.received}`;break;case c.invalid_literal:r=`Invalid literal value, expected ${JSON.stringify(e.expected,v.jsonStringifyReplacer)}`;break;case c.unrecognized_keys:r=`Unrecognized key(s) in object: ${v.joinValues(e.keys,", ")}`;break;case c.invalid_union:r="Invalid input";break;case c.invalid_union_discriminator:r=`Invalid discriminator value. Expected ${v.joinValues(e.options)}`;break;case c.invalid_enum_value:r=`Invalid enum value. Expected ${v.joinValues(e.options)}, received '${e.received}'`;break;case c.invalid_arguments:r="Invalid function arguments";break;case c.invalid_return_type:r="Invalid function return type";break;case c.invalid_date:r="Invalid date";break;case c.invalid_string:if(typeof e.validation==="object")if("includes"in e.validation){if(r=`Invalid input: must include "${e.validation.includes}"`,typeof e.validation.position==="number")r=`${r} at one or more positions greater than or equal to ${e.validation.position}`}else if("startsWith"in e.validation)r=`Invalid input: must start with "${e.validation.startsWith}"`;else if("endsWith"in e.validation)r=`Invalid input: must end with "${e.validation.endsWith}"`;else v.assertNever(e.validation);else if(e.validation!=="regex")r=`Invalid ${e.validation}`;else r="Invalid";break;case c.too_small:if(e.type==="array")r=`Array must contain ${e.exact?"exactly":e.inclusive?"at least":"more than"} ${e.minimum} element(s)`;else if(e.type==="string")r=`String must contain ${e.exact?"exactly":e.inclusive?"at least":"over"} ${e.minimum} character(s)`;else if(e.type==="number")r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`;else if(e.type==="bigint")r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`;else if(e.type==="date")r=`Date must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${new Date(Number(e.minimum))}`;else r="Invalid input";break;case c.too_big:if(e.type==="array")r=`Array must contain ${e.exact?"exactly":e.inclusive?"at most":"less than"} ${e.maximum} element(s)`;else if(e.type==="string")r=`String must contain ${e.exact?"exactly":e.inclusive?"at most":"under"} ${e.maximum} character(s)`;else if(e.type==="number")r=`Number must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`;else if(e.type==="bigint")r=`BigInt must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`;else if(e.type==="date")r=`Date must be ${e.exact?"exactly":e.inclusive?"smaller than or equal to":"smaller than"} ${new Date(Number(e.maximum))}`;else r="Invalid input";break;case c.custom:r="Invalid input";break;case c.invalid_intersection_types:r="Intersection results could not be merged";break;case c.not_multiple_of:r=`Number must be a multiple of ${e.multipleOf}`;break;case c.not_finite:r="Number must be finite";break;default:r=t.defaultError,v.assertNever(e)}return{message:r}},N=Ye;var Te=N;function He(e){Te=e}function H(){return Te}var ce=(e)=>{let{data:t,path:r,errorMaps:n,issueData:s}=e,i=[...r,...s.path||[]],a={...s,path:i};if(s.message!==void 0)return{...s,path:i,message:s.message};let d="",l=n.filter((y)=>!!y).slice().reverse();for(let y of l)d=y(a,{data:t,defaultError:d}).message;return{...s,path:i,message:d}},Je=[];function f(e,t){let r=H(),n=ce({issueData:t,data:e.data,path:e.path,errorMaps:[e.common.contextualErrorMap,e.schemaErrorMap,r,r===N?void 0:N].filter((s)=>!!s)});e.common.issues.push(n)}class k{constructor(){this.value="valid"}dirty(){if(this.value==="valid")this.value="dirty"}abort(){if(this.value!=="aborted")this.value="aborted"}static mergeArray(e,t){let r=[];for(let n of t){if(n.status==="aborted")return p;if(n.status==="dirty")e.dirty();r.push(n.value)}return{status:e.value,value:r}}static async mergeObjectAsync(e,t){let r=[];for(let n of t){let s=await n.key,i=await n.value;r.push({key:s,value:i})}return k.mergeObjectSync(e,r)}static mergeObjectSync(e,t){let r={};for(let n of t){let{key:s,value:i}=n;if(s.status==="aborted")return p;if(i.status==="aborted")return p;if(s.status==="dirty")e.dirty();if(i.status==="dirty")e.dirty();if(s.value!=="__proto__"&&(typeof i.value<"u"||n.alwaysSet))r[s.value]=i.value}return{status:e.value,value:r}}}var p=Object.freeze({status:"aborted"}),W=(e)=>({status:"dirty",value:e}),b=(e)=>({status:"valid",value:e}),ge=(e)=>e.status==="aborted",ye=(e)=>e.status==="dirty",z=(e)=>e.status==="valid",J=(e)=>typeof Promise<"u"&&e instanceof Promise;var h;(function(e){e.errToObj=(t)=>typeof t==="string"?{message:t}:t||{},e.toString=(t)=>typeof t==="string"?t:t?.message})(h||(h={}));class A{constructor(e,t,r,n){this._cachedPath=[],this.parent=e,this.data=t,this._path=r,this._key=n}get path(){if(!this._cachedPath.length)if(Array.isArray(this._key))this._cachedPath.push(...this._path,...this._key);else this._cachedPath.push(...this._path,this._key);return this._cachedPath}}var Ae=(e,t)=>{if(z(t))return{success:!0,data:t.value};else{if(!e.common.issues.length)throw Error("Validation failed but no issues detected.");return{success:!1,get error(){if(this._error)return this._error;let r=new w(e.common.issues);return this._error=r,this._error}}}};function g(e){if(!e)return{};let{errorMap:t,invalid_type_error:r,required_error:n,description:s}=e;if(t&&(r||n))throw Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);if(t)return{errorMap:t,description:s};return{errorMap:(a,d)=>{let{message:l}=e;if(a.code==="invalid_enum_value")return{message:l??d.defaultError};if(typeof d.data>"u")return{message:l??n??d.defaultError};if(a.code!=="invalid_type")return{message:d.defaultError};return{message:l??r??d.defaultError}},description:s}}class _{get description(){return this._def.description}_getType(e){return j(e.data)}_getOrReturnCtx(e,t){return t||{common:e.parent.common,data:e.data,parsedType:j(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}_processInputParams(e){return{status:new k,ctx:{common:e.parent.common,data:e.data,parsedType:j(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}}_parseSync(e){let t=this._parse(e);if(J(t))throw Error("Synchronous parse encountered promise.");return t}_parseAsync(e){let t=this._parse(e);return Promise.resolve(t)}parse(e,t){let r=this.safeParse(e,t);if(r.success)return r.data;throw r.error}safeParse(e,t){let r={common:{issues:[],async:t?.async??!1,contextualErrorMap:t?.errorMap},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)},n=this._parseSync({data:e,path:r.path,parent:r});return Ae(r,n)}"~validate"(e){let t={common:{issues:[],async:!!this["~standard"].async},path:[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)};if(!this["~standard"].async)try{let r=this._parseSync({data:e,path:[],parent:t});return z(r)?{value:r.value}:{issues:t.common.issues}}catch(r){if(r?.message?.toLowerCase()?.includes("encountered"))this["~standard"].async=!0;t.common={issues:[],async:!0}}return this._parseAsync({data:e,path:[],parent:t}).then((r)=>z(r)?{value:r.value}:{issues:t.common.issues})}async parseAsync(e,t){let r=await this.safeParseAsync(e,t);if(r.success)return r.data;throw r.error}async safeParseAsync(e,t){let r={common:{issues:[],contextualErrorMap:t?.errorMap,async:!0},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:j(e)},n=this._parse({data:e,path:r.path,parent:r}),s=await(J(n)?n:Promise.resolve(n));return Ae(r,s)}refine(e,t){let r=(n)=>{if(typeof t==="string"||typeof t>"u")return{message:t};else if(typeof t==="function")return t(n);else return t};return this._refinement((n,s)=>{let i=e(n),a=()=>s.addIssue({code:c.custom,...r(n)});if(typeof Promise<"u"&&i instanceof Promise)return i.then((d)=>{if(!d)return a(),!1;else return!0});if(!i)return a(),!1;else return!0})}refinement(e,t){return this._refinement((r,n)=>{if(!e(r))return n.addIssue(typeof t==="function"?t(r,n):t),!1;else return!0})}_refinement(e){return new O({schema:this,typeName:m.ZodEffects,effect:{type:"refinement",refinement:e}})}superRefine(e){return this._refinement(e)}constructor(e){this.spa=this.safeParseAsync,this._def=e,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.brand=this.brand.bind(this),this.default=this.default.bind(this),this.catch=this.catch.bind(this),this.describe=this.describe.bind(this),this.pipe=this.pipe.bind(this),this.readonly=this.readonly.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this),this["~standard"]={version:1,vendor:"zod",validate:(t)=>this["~validate"](t)}}optional(){return T.create(this,this._def)}nullable(){return M.create(this,this._def)}nullish(){return this.nullable().optional()}array(){return R.create(this)}promise(){return Y.create(this,this._def)}or(e){return ee.create([this,e],this._def)}and(e){return te.create(this,e,this._def)}transform(e){return new O({...g(this._def),schema:this,typeName:m.ZodEffects,effect:{type:"transform",transform:e}})}default(e){let t=typeof e==="function"?e:()=>e;return new ae({...g(this._def),innerType:this,defaultValue:t,typeName:m.ZodDefault})}brand(){return new ve({typeName:m.ZodBranded,type:this,...g(this._def)})}catch(e){let t=typeof e==="function"?e:()=>e;return new ie({...g(this._def),innerType:this,catchValue:t,typeName:m.ZodCatch})}describe(e){return new this.constructor({...this._def,description:e})}pipe(e){return pe.create(this,e)}readonly(){return oe.create(this)}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}}var Ke=/^c[^\s-]{8,}$/i,Ge=/^[0-9a-z]+$/,Qe=/^[0-9A-HJKMNP-TV-Z]{26}$/i,Xe=/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i,Ze=/^[a-z0-9_-]{21}$/i,et=/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,tt=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,rt=/^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i,st="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$",be,nt=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,at=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,it=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,ot=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,ct=/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,ut=/^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,je="((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))",dt=new RegExp(`^${je}$`);function Ie(e){let t="[0-5]\\d";if(e.precision)t=`${t}\\.\\d{${e.precision}}`;else if(e.precision==null)t=`${t}(\\.\\d+)?`;let r=e.precision?"+":"?";return`([01]\\d|2[0-3]):[0-5]\\d(:${t})${r}`}function lt(e){return new RegExp(`^${Ie(e)}$`)}function Ee(e){let t=`${je}T${Ie(e)}`,r=[];if(r.push(e.local?"Z?":"Z"),e.offset)r.push("([+-]\\d{2}:?\\d{2})");return t=`${t}(${r.join("|")})`,new RegExp(`^${t}$`)}function ft(e,t){if((t==="v4"||!t)&&nt.test(e))return!0;if((t==="v6"||!t)&&it.test(e))return!0;return!1}function ht(e,t){if(!et.test(e))return!1;try{let[r]=e.split(".");if(!r)return!1;let n=r.replace(/-/g,"+").replace(/_/g,"/").padEnd(r.length+(4-r.length%4)%4,"="),s=JSON.parse(atob(n));if(typeof s!=="object"||s===null)return!1;if("typ"in s&&s?.typ!=="JWT")return!1;if(!s.alg)return!1;if(t&&s.alg!==t)return!1;return!0}catch{return!1}}function pt(e,t){if((t==="v4"||!t)&&at.test(e))return!0;if((t==="v6"||!t)&&ot.test(e))return!0;return!1}class S extends _{_parse(e){if(this._def.coerce)e.data=String(e.data);if(this._getType(e)!==u.string){let s=this._getOrReturnCtx(e);return f(s,{code:c.invalid_type,expected:u.string,received:s.parsedType}),p}let r=new k,n=void 0;for(let s of this._def.checks)if(s.kind==="min"){if(e.data.lengths.value)n=this._getOrReturnCtx(e,n),f(n,{code:c.too_big,maximum:s.value,type:"string",inclusive:!0,exact:!1,message:s.message}),r.dirty()}else if(s.kind==="length"){let i=e.data.length>s.value,a=e.data.lengthe.test(n),{validation:t,code:c.invalid_string,...h.errToObj(r)})}_addCheck(e){return new S({...this._def,checks:[...this._def.checks,e]})}email(e){return this._addCheck({kind:"email",...h.errToObj(e)})}url(e){return this._addCheck({kind:"url",...h.errToObj(e)})}emoji(e){return this._addCheck({kind:"emoji",...h.errToObj(e)})}uuid(e){return this._addCheck({kind:"uuid",...h.errToObj(e)})}nanoid(e){return this._addCheck({kind:"nanoid",...h.errToObj(e)})}cuid(e){return this._addCheck({kind:"cuid",...h.errToObj(e)})}cuid2(e){return this._addCheck({kind:"cuid2",...h.errToObj(e)})}ulid(e){return this._addCheck({kind:"ulid",...h.errToObj(e)})}base64(e){return this._addCheck({kind:"base64",...h.errToObj(e)})}base64url(e){return this._addCheck({kind:"base64url",...h.errToObj(e)})}jwt(e){return this._addCheck({kind:"jwt",...h.errToObj(e)})}ip(e){return this._addCheck({kind:"ip",...h.errToObj(e)})}cidr(e){return this._addCheck({kind:"cidr",...h.errToObj(e)})}datetime(e){if(typeof e==="string")return this._addCheck({kind:"datetime",precision:null,offset:!1,local:!1,message:e});return this._addCheck({kind:"datetime",precision:typeof e?.precision>"u"?null:e?.precision,offset:e?.offset??!1,local:e?.local??!1,...h.errToObj(e?.message)})}date(e){return this._addCheck({kind:"date",message:e})}time(e){if(typeof e==="string")return this._addCheck({kind:"time",precision:null,message:e});return this._addCheck({kind:"time",precision:typeof e?.precision>"u"?null:e?.precision,...h.errToObj(e?.message)})}duration(e){return this._addCheck({kind:"duration",...h.errToObj(e)})}regex(e,t){return this._addCheck({kind:"regex",regex:e,...h.errToObj(t)})}includes(e,t){return this._addCheck({kind:"includes",value:e,position:t?.position,...h.errToObj(t?.message)})}startsWith(e,t){return this._addCheck({kind:"startsWith",value:e,...h.errToObj(t)})}endsWith(e,t){return this._addCheck({kind:"endsWith",value:e,...h.errToObj(t)})}min(e,t){return this._addCheck({kind:"min",value:e,...h.errToObj(t)})}max(e,t){return this._addCheck({kind:"max",value:e,...h.errToObj(t)})}length(e,t){return this._addCheck({kind:"length",value:e,...h.errToObj(t)})}nonempty(e){return this.min(1,h.errToObj(e))}trim(){return new S({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}toLowerCase(){return new S({...this._def,checks:[...this._def.checks,{kind:"toLowerCase"}]})}toUpperCase(){return new S({...this._def,checks:[...this._def.checks,{kind:"toUpperCase"}]})}get isDatetime(){return!!this._def.checks.find((e)=>e.kind==="datetime")}get isDate(){return!!this._def.checks.find((e)=>e.kind==="date")}get isTime(){return!!this._def.checks.find((e)=>e.kind==="time")}get isDuration(){return!!this._def.checks.find((e)=>e.kind==="duration")}get isEmail(){return!!this._def.checks.find((e)=>e.kind==="email")}get isURL(){return!!this._def.checks.find((e)=>e.kind==="url")}get isEmoji(){return!!this._def.checks.find((e)=>e.kind==="emoji")}get isUUID(){return!!this._def.checks.find((e)=>e.kind==="uuid")}get isNANOID(){return!!this._def.checks.find((e)=>e.kind==="nanoid")}get isCUID(){return!!this._def.checks.find((e)=>e.kind==="cuid")}get isCUID2(){return!!this._def.checks.find((e)=>e.kind==="cuid2")}get isULID(){return!!this._def.checks.find((e)=>e.kind==="ulid")}get isIP(){return!!this._def.checks.find((e)=>e.kind==="ip")}get isCIDR(){return!!this._def.checks.find((e)=>e.kind==="cidr")}get isBase64(){return!!this._def.checks.find((e)=>e.kind==="base64")}get isBase64url(){return!!this._def.checks.find((e)=>e.kind==="base64url")}get minLength(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxLength(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew S({checks:[],typeName:m.ZodString,coerce:e?.coerce??!1,...g(e)});function mt(e,t){let r=(e.toString().split(".")[1]||"").length,n=(t.toString().split(".")[1]||"").length,s=r>n?r:n,i=Number.parseInt(e.toFixed(s).replace(".","")),a=Number.parseInt(t.toFixed(s).replace(".",""));return i%a/10**s}class D extends _{constructor(){super(...arguments);this.min=this.gte,this.max=this.lte,this.step=this.multipleOf}_parse(e){if(this._def.coerce)e.data=Number(e.data);if(this._getType(e)!==u.number){let s=this._getOrReturnCtx(e);return f(s,{code:c.invalid_type,expected:u.number,received:s.parsedType}),p}let r=void 0,n=new k;for(let s of this._def.checks)if(s.kind==="int"){if(!v.isInteger(e.data))r=this._getOrReturnCtx(e,r),f(r,{code:c.invalid_type,expected:"integer",received:"float",message:s.message}),n.dirty()}else if(s.kind==="min"){if(s.inclusive?e.datas.value:e.data>=s.value)r=this._getOrReturnCtx(e,r),f(r,{code:c.too_big,maximum:s.value,type:"number",inclusive:s.inclusive,exact:!1,message:s.message}),n.dirty()}else if(s.kind==="multipleOf"){if(mt(e.data,s.value)!==0)r=this._getOrReturnCtx(e,r),f(r,{code:c.not_multiple_of,multipleOf:s.value,message:s.message}),n.dirty()}else if(s.kind==="finite"){if(!Number.isFinite(e.data))r=this._getOrReturnCtx(e,r),f(r,{code:c.not_finite,message:s.message}),n.dirty()}else v.assertNever(s);return{status:n.value,value:e.data}}gte(e,t){return this.setLimit("min",e,!0,h.toString(t))}gt(e,t){return this.setLimit("min",e,!1,h.toString(t))}lte(e,t){return this.setLimit("max",e,!0,h.toString(t))}lt(e,t){return this.setLimit("max",e,!1,h.toString(t))}setLimit(e,t,r,n){return new D({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:h.toString(n)}]})}_addCheck(e){return new D({...this._def,checks:[...this._def.checks,e]})}int(e){return this._addCheck({kind:"int",message:h.toString(e)})}positive(e){return this._addCheck({kind:"min",value:0,inclusive:!1,message:h.toString(e)})}negative(e){return this._addCheck({kind:"max",value:0,inclusive:!1,message:h.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:0,inclusive:!0,message:h.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:0,inclusive:!0,message:h.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:h.toString(t)})}finite(e){return this._addCheck({kind:"finite",message:h.toString(e)})}safe(e){return this._addCheck({kind:"min",inclusive:!0,value:Number.MIN_SAFE_INTEGER,message:h.toString(e)})._addCheck({kind:"max",inclusive:!0,value:Number.MAX_SAFE_INTEGER,message:h.toString(e)})}get minValue(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxValue(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuee.kind==="int"||e.kind==="multipleOf"&&v.isInteger(e.value))}get isFinite(){let e=null,t=null;for(let r of this._def.checks)if(r.kind==="finite"||r.kind==="int"||r.kind==="multipleOf")return!0;else if(r.kind==="min"){if(t===null||r.value>t)t=r.value}else if(r.kind==="max"){if(e===null||r.valuenew D({checks:[],typeName:m.ZodNumber,coerce:e?.coerce||!1,...g(e)});class L extends _{constructor(){super(...arguments);this.min=this.gte,this.max=this.lte}_parse(e){if(this._def.coerce)try{e.data=BigInt(e.data)}catch{return this._getInvalidInput(e)}if(this._getType(e)!==u.bigint)return this._getInvalidInput(e);let r=void 0,n=new k;for(let s of this._def.checks)if(s.kind==="min"){if(s.inclusive?e.datas.value:e.data>=s.value)r=this._getOrReturnCtx(e,r),f(r,{code:c.too_big,type:"bigint",maximum:s.value,inclusive:s.inclusive,message:s.message}),n.dirty()}else if(s.kind==="multipleOf"){if(e.data%s.value!==BigInt(0))r=this._getOrReturnCtx(e,r),f(r,{code:c.not_multiple_of,multipleOf:s.value,message:s.message}),n.dirty()}else v.assertNever(s);return{status:n.value,value:e.data}}_getInvalidInput(e){let t=this._getOrReturnCtx(e);return f(t,{code:c.invalid_type,expected:u.bigint,received:t.parsedType}),p}gte(e,t){return this.setLimit("min",e,!0,h.toString(t))}gt(e,t){return this.setLimit("min",e,!1,h.toString(t))}lte(e,t){return this.setLimit("max",e,!0,h.toString(t))}lt(e,t){return this.setLimit("max",e,!1,h.toString(t))}setLimit(e,t,r,n){return new L({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:h.toString(n)}]})}_addCheck(e){return new L({...this._def,checks:[...this._def.checks,e]})}positive(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!1,message:h.toString(e)})}negative(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!1,message:h.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!0,message:h.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!0,message:h.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:h.toString(t)})}get minValue(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e}get maxValue(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew L({checks:[],typeName:m.ZodBigInt,coerce:e?.coerce??!1,...g(e)});class Q extends _{_parse(e){if(this._def.coerce)e.data=Boolean(e.data);if(this._getType(e)!==u.boolean){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.boolean,received:r.parsedType}),p}return b(e.data)}}Q.create=(e)=>new Q({typeName:m.ZodBoolean,coerce:e?.coerce||!1,...g(e)});class B extends _{_parse(e){if(this._def.coerce)e.data=new Date(e.data);if(this._getType(e)!==u.date){let s=this._getOrReturnCtx(e);return f(s,{code:c.invalid_type,expected:u.date,received:s.parsedType}),p}if(Number.isNaN(e.data.getTime())){let s=this._getOrReturnCtx(e);return f(s,{code:c.invalid_date}),p}let r=new k,n=void 0;for(let s of this._def.checks)if(s.kind==="min"){if(e.data.getTime()s.value)n=this._getOrReturnCtx(e,n),f(n,{code:c.too_big,message:s.message,inclusive:!0,exact:!1,maximum:s.value,type:"date"}),r.dirty()}else v.assertNever(s);return{status:r.value,value:new Date(e.data.getTime())}}_addCheck(e){return new B({...this._def,checks:[...this._def.checks,e]})}min(e,t){return this._addCheck({kind:"min",value:e.getTime(),message:h.toString(t)})}max(e,t){return this._addCheck({kind:"max",value:e.getTime(),message:h.toString(t)})}get minDate(){let e=null;for(let t of this._def.checks)if(t.kind==="min"){if(e===null||t.value>e)e=t.value}return e!=null?new Date(e):null}get maxDate(){let e=null;for(let t of this._def.checks)if(t.kind==="max"){if(e===null||t.valuenew B({checks:[],coerce:e?.coerce||!1,typeName:m.ZodDate,...g(e)});class ue extends _{_parse(e){if(this._getType(e)!==u.symbol){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.symbol,received:r.parsedType}),p}return b(e.data)}}ue.create=(e)=>new ue({typeName:m.ZodSymbol,...g(e)});class X extends _{_parse(e){if(this._getType(e)!==u.undefined){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.undefined,received:r.parsedType}),p}return b(e.data)}}X.create=(e)=>new X({typeName:m.ZodUndefined,...g(e)});class Z extends _{_parse(e){if(this._getType(e)!==u.null){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.null,received:r.parsedType}),p}return b(e.data)}}Z.create=(e)=>new Z({typeName:m.ZodNull,...g(e)});class U extends _{constructor(){super(...arguments);this._any=!0}_parse(e){return b(e.data)}}U.create=(e)=>new U({typeName:m.ZodAny,...g(e)});class F extends _{constructor(){super(...arguments);this._unknown=!0}_parse(e){return b(e.data)}}F.create=(e)=>new F({typeName:m.ZodUnknown,...g(e)});class I extends _{_parse(e){let t=this._getOrReturnCtx(e);return f(t,{code:c.invalid_type,expected:u.never,received:t.parsedType}),p}}I.create=(e)=>new I({typeName:m.ZodNever,...g(e)});class de extends _{_parse(e){if(this._getType(e)!==u.undefined){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.void,received:r.parsedType}),p}return b(e.data)}}de.create=(e)=>new de({typeName:m.ZodVoid,...g(e)});class R extends _{_parse(e){let{ctx:t,status:r}=this._processInputParams(e),n=this._def;if(t.parsedType!==u.array)return f(t,{code:c.invalid_type,expected:u.array,received:t.parsedType}),p;if(n.exactLength!==null){let i=t.data.length>n.exactLength.value,a=t.data.lengthn.maxLength.value)f(t,{code:c.too_big,maximum:n.maxLength.value,type:"array",inclusive:!0,exact:!1,message:n.maxLength.message}),r.dirty()}if(t.common.async)return Promise.all([...t.data].map((i,a)=>n.type._parseAsync(new A(t,i,t.path,a)))).then((i)=>k.mergeArray(r,i));let s=[...t.data].map((i,a)=>n.type._parseSync(new A(t,i,t.path,a)));return k.mergeArray(r,s)}get element(){return this._def.type}min(e,t){return new R({...this._def,minLength:{value:e,message:h.toString(t)}})}max(e,t){return new R({...this._def,maxLength:{value:e,message:h.toString(t)}})}length(e,t){return new R({...this._def,exactLength:{value:e,message:h.toString(t)}})}nonempty(e){return this.min(1,e)}}R.create=(e,t)=>new R({type:e,minLength:null,maxLength:null,exactLength:null,typeName:m.ZodArray,...g(t)});function K(e){if(e instanceof x){let t={};for(let r in e.shape){let n=e.shape[r];t[r]=T.create(K(n))}return new x({...e._def,shape:()=>t})}else if(e instanceof R)return new R({...e._def,type:K(e.element)});else if(e instanceof T)return T.create(K(e.unwrap()));else if(e instanceof M)return M.create(K(e.unwrap()));else if(e instanceof E)return E.create(e.items.map((t)=>K(t)));else return e}class x extends _{constructor(){super(...arguments);this._cached=null,this.nonstrict=this.passthrough,this.augment=this.extend}_getCached(){if(this._cached!==null)return this._cached;let e=this._def.shape(),t=v.objectKeys(e);return this._cached={shape:e,keys:t},this._cached}_parse(e){if(this._getType(e)!==u.object){let l=this._getOrReturnCtx(e);return f(l,{code:c.invalid_type,expected:u.object,received:l.parsedType}),p}let{status:r,ctx:n}=this._processInputParams(e),{shape:s,keys:i}=this._getCached(),a=[];if(!(this._def.catchall instanceof I&&this._def.unknownKeys==="strip")){for(let l in n.data)if(!i.includes(l))a.push(l)}let d=[];for(let l of i){let y=s[l],C=n.data[l];d.push({key:{status:"valid",value:l},value:y._parse(new A(n,C,n.path,l)),alwaysSet:l in n.data})}if(this._def.catchall instanceof I){let l=this._def.unknownKeys;if(l==="passthrough")for(let y of a)d.push({key:{status:"valid",value:y},value:{status:"valid",value:n.data[y]}});else if(l==="strict"){if(a.length>0)f(n,{code:c.unrecognized_keys,keys:a}),r.dirty()}else if(l==="strip");else throw Error("Internal ZodObject error: invalid unknownKeys value.")}else{let l=this._def.catchall;for(let y of a){let C=n.data[y];d.push({key:{status:"valid",value:y},value:l._parse(new A(n,C,n.path,y)),alwaysSet:y in n.data})}}if(n.common.async)return Promise.resolve().then(async()=>{let l=[];for(let y of d){let C=await y.key,Re=await y.value;l.push({key:C,value:Re,alwaysSet:y.alwaysSet})}return l}).then((l)=>k.mergeObjectSync(r,l));else return k.mergeObjectSync(r,d)}get shape(){return this._def.shape()}strict(e){return h.errToObj,new x({...this._def,unknownKeys:"strict",...e!==void 0?{errorMap:(t,r)=>{let n=this._def.errorMap?.(t,r).message??r.defaultError;if(t.code==="unrecognized_keys")return{message:h.errToObj(e).message??n};return{message:n}}}:{}})}strip(){return new x({...this._def,unknownKeys:"strip"})}passthrough(){return new x({...this._def,unknownKeys:"passthrough"})}extend(e){return new x({...this._def,shape:()=>({...this._def.shape(),...e})})}merge(e){return new x({unknownKeys:e._def.unknownKeys,catchall:e._def.catchall,shape:()=>({...this._def.shape(),...e._def.shape()}),typeName:m.ZodObject})}setKey(e,t){return this.augment({[e]:t})}catchall(e){return new x({...this._def,catchall:e})}pick(e){let t={};for(let r of v.objectKeys(e))if(e[r]&&this.shape[r])t[r]=this.shape[r];return new x({...this._def,shape:()=>t})}omit(e){let t={};for(let r of v.objectKeys(this.shape))if(!e[r])t[r]=this.shape[r];return new x({...this._def,shape:()=>t})}deepPartial(){return K(this)}partial(e){let t={};for(let r of v.objectKeys(this.shape)){let n=this.shape[r];if(e&&!e[r])t[r]=n;else t[r]=n.optional()}return new x({...this._def,shape:()=>t})}required(e){let t={};for(let r of v.objectKeys(this.shape))if(e&&!e[r])t[r]=this.shape[r];else{let s=this.shape[r];while(s instanceof T)s=s._def.innerType;t[r]=s}return new x({...this._def,shape:()=>t})}keyof(){return Ne(v.objectKeys(this.shape))}}x.create=(e,t)=>new x({shape:()=>e,unknownKeys:"strip",catchall:I.create(),typeName:m.ZodObject,...g(t)});x.strictCreate=(e,t)=>new x({shape:()=>e,unknownKeys:"strict",catchall:I.create(),typeName:m.ZodObject,...g(t)});x.lazycreate=(e,t)=>new x({shape:e,unknownKeys:"strip",catchall:I.create(),typeName:m.ZodObject,...g(t)});class ee extends _{_parse(e){let{ctx:t}=this._processInputParams(e),r=this._def.options;function n(s){for(let a of s)if(a.result.status==="valid")return a.result;for(let a of s)if(a.result.status==="dirty")return t.common.issues.push(...a.ctx.common.issues),a.result;let i=s.map((a)=>new w(a.ctx.common.issues));return f(t,{code:c.invalid_union,unionErrors:i}),p}if(t.common.async)return Promise.all(r.map(async(s)=>{let i={...t,common:{...t.common,issues:[]},parent:null};return{result:await s._parseAsync({data:t.data,path:t.path,parent:i}),ctx:i}})).then(n);else{let s=void 0,i=[];for(let d of r){let l={...t,common:{...t.common,issues:[]},parent:null},y=d._parseSync({data:t.data,path:t.path,parent:l});if(y.status==="valid")return y;else if(y.status==="dirty"&&!s)s={result:y,ctx:l};if(l.common.issues.length)i.push(l.common.issues)}if(s)return t.common.issues.push(...s.ctx.common.issues),s.result;let a=i.map((d)=>new w(d));return f(t,{code:c.invalid_union,unionErrors:a}),p}}get options(){return this._def.options}}ee.create=(e,t)=>new ee({options:e,typeName:m.ZodUnion,...g(t)});var V=(e)=>{if(e instanceof re)return V(e.schema);else if(e instanceof O)return V(e.innerType());else if(e instanceof se)return[e.value];else if(e instanceof P)return e.options;else if(e instanceof ne)return v.objectValues(e.enum);else if(e instanceof ae)return V(e._def.innerType);else if(e instanceof X)return[void 0];else if(e instanceof Z)return[null];else if(e instanceof T)return[void 0,...V(e.unwrap())];else if(e instanceof M)return[null,...V(e.unwrap())];else if(e instanceof ve)return V(e.unwrap());else if(e instanceof oe)return V(e.unwrap());else if(e instanceof ie)return V(e._def.innerType);else return[]};class _e extends _{_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==u.object)return f(t,{code:c.invalid_type,expected:u.object,received:t.parsedType}),p;let r=this.discriminator,n=t.data[r],s=this.optionsMap.get(n);if(!s)return f(t,{code:c.invalid_union_discriminator,options:Array.from(this.optionsMap.keys()),path:[r]}),p;if(t.common.async)return s._parseAsync({data:t.data,path:t.path,parent:t});else return s._parseSync({data:t.data,path:t.path,parent:t})}get discriminator(){return this._def.discriminator}get options(){return this._def.options}get optionsMap(){return this._def.optionsMap}static create(e,t,r){let n=new Map;for(let s of t){let i=V(s.shape[e]);if(!i.length)throw Error(`A discriminator value for key \`${e}\` could not be extracted from all schema options`);for(let a of i){if(n.has(a))throw Error(`Discriminator property ${String(e)} has duplicate value ${String(a)}`);n.set(a,s)}}return new _e({typeName:m.ZodDiscriminatedUnion,discriminator:e,options:t,optionsMap:n,...g(r)})}}function we(e,t){let r=j(e),n=j(t);if(e===t)return{valid:!0,data:e};else if(r===u.object&&n===u.object){let s=v.objectKeys(t),i=v.objectKeys(e).filter((d)=>s.indexOf(d)!==-1),a={...e,...t};for(let d of i){let l=we(e[d],t[d]);if(!l.valid)return{valid:!1};a[d]=l.data}return{valid:!0,data:a}}else if(r===u.array&&n===u.array){if(e.length!==t.length)return{valid:!1};let s=[];for(let i=0;i{if(ge(s)||ge(i))return p;let a=we(s.value,i.value);if(!a.valid)return f(r,{code:c.invalid_intersection_types}),p;if(ye(s)||ye(i))t.dirty();return{status:t.value,value:a.data}};if(r.common.async)return Promise.all([this._def.left._parseAsync({data:r.data,path:r.path,parent:r}),this._def.right._parseAsync({data:r.data,path:r.path,parent:r})]).then(([s,i])=>n(s,i));else return n(this._def.left._parseSync({data:r.data,path:r.path,parent:r}),this._def.right._parseSync({data:r.data,path:r.path,parent:r}))}}te.create=(e,t,r)=>new te({left:e,right:t,typeName:m.ZodIntersection,...g(r)});class E extends _{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==u.array)return f(r,{code:c.invalid_type,expected:u.array,received:r.parsedType}),p;if(r.data.lengththis._def.items.length)f(r,{code:c.too_big,maximum:this._def.items.length,inclusive:!0,exact:!1,type:"array"}),t.dirty();let s=[...r.data].map((i,a)=>{let d=this._def.items[a]||this._def.rest;if(!d)return null;return d._parse(new A(r,i,r.path,a))}).filter((i)=>!!i);if(r.common.async)return Promise.all(s).then((i)=>k.mergeArray(t,i));else return k.mergeArray(t,s)}get items(){return this._def.items}rest(e){return new E({...this._def,rest:e})}}E.create=(e,t)=>{if(!Array.isArray(e))throw Error("You must pass an array of schemas to z.tuple([ ... ])");return new E({items:e,typeName:m.ZodTuple,rest:null,...g(t)})};class le extends _{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==u.object)return f(r,{code:c.invalid_type,expected:u.object,received:r.parsedType}),p;let n=[],s=this._def.keyType,i=this._def.valueType;for(let a in r.data)n.push({key:s._parse(new A(r,a,r.path,a)),value:i._parse(new A(r,r.data[a],r.path,a)),alwaysSet:a in r.data});if(r.common.async)return k.mergeObjectAsync(t,n);else return k.mergeObjectSync(t,n)}get element(){return this._def.valueType}static create(e,t,r){if(t instanceof _)return new le({keyType:e,valueType:t,typeName:m.ZodRecord,...g(r)});return new le({keyType:S.create(),valueType:e,typeName:m.ZodRecord,...g(t)})}}class fe extends _{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==u.map)return f(r,{code:c.invalid_type,expected:u.map,received:r.parsedType}),p;let n=this._def.keyType,s=this._def.valueType,i=[...r.data.entries()].map(([a,d],l)=>({key:n._parse(new A(r,a,r.path,[l,"key"])),value:s._parse(new A(r,d,r.path,[l,"value"]))}));if(r.common.async){let a=new Map;return Promise.resolve().then(async()=>{for(let d of i){let l=await d.key,y=await d.value;if(l.status==="aborted"||y.status==="aborted")return p;if(l.status==="dirty"||y.status==="dirty")t.dirty();a.set(l.value,y.value)}return{status:t.value,value:a}})}else{let a=new Map;for(let d of i){let{key:l,value:y}=d;if(l.status==="aborted"||y.status==="aborted")return p;if(l.status==="dirty"||y.status==="dirty")t.dirty();a.set(l.value,y.value)}return{status:t.value,value:a}}}}fe.create=(e,t,r)=>new fe({valueType:t,keyType:e,typeName:m.ZodMap,...g(r)});class q extends _{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==u.set)return f(r,{code:c.invalid_type,expected:u.set,received:r.parsedType}),p;let n=this._def;if(n.minSize!==null){if(r.data.sizen.maxSize.value)f(r,{code:c.too_big,maximum:n.maxSize.value,type:"set",inclusive:!0,exact:!1,message:n.maxSize.message}),t.dirty()}let s=this._def.valueType;function i(d){let l=new Set;for(let y of d){if(y.status==="aborted")return p;if(y.status==="dirty")t.dirty();l.add(y.value)}return{status:t.value,value:l}}let a=[...r.data.values()].map((d,l)=>s._parse(new A(r,d,r.path,l)));if(r.common.async)return Promise.all(a).then((d)=>i(d));else return i(a)}min(e,t){return new q({...this._def,minSize:{value:e,message:h.toString(t)}})}max(e,t){return new q({...this._def,maxSize:{value:e,message:h.toString(t)}})}size(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}}q.create=(e,t)=>new q({valueType:e,minSize:null,maxSize:null,typeName:m.ZodSet,...g(t)});class G extends _{constructor(){super(...arguments);this.validate=this.implement}_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==u.function)return f(t,{code:c.invalid_type,expected:u.function,received:t.parsedType}),p;function r(a,d){return ce({data:a,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,H(),N].filter((l)=>!!l),issueData:{code:c.invalid_arguments,argumentsError:d}})}function n(a,d){return ce({data:a,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,H(),N].filter((l)=>!!l),issueData:{code:c.invalid_return_type,returnTypeError:d}})}let s={errorMap:t.common.contextualErrorMap},i=t.data;if(this._def.returns instanceof Y){let a=this;return b(async function(...d){let l=new w([]),y=await a._def.args.parseAsync(d,s).catch((xe)=>{throw l.addIssue(r(d,xe)),l}),C=await Reflect.apply(i,this,y);return await a._def.returns._def.type.parseAsync(C,s).catch((xe)=>{throw l.addIssue(n(C,xe)),l})})}else{let a=this;return b(function(...d){let l=a._def.args.safeParse(d,s);if(!l.success)throw new w([r(d,l.error)]);let y=Reflect.apply(i,this,l.data),C=a._def.returns.safeParse(y,s);if(!C.success)throw new w([n(y,C.error)]);return C.data})}}parameters(){return this._def.args}returnType(){return this._def.returns}args(...e){return new G({...this._def,args:E.create(e).rest(F.create())})}returns(e){return new G({...this._def,returns:e})}implement(e){return this.parse(e)}strictImplement(e){return this.parse(e)}static create(e,t,r){return new G({args:e?e:E.create([]).rest(F.create()),returns:t||F.create(),typeName:m.ZodFunction,...g(r)})}}class re extends _{get schema(){return this._def.getter()}_parse(e){let{ctx:t}=this._processInputParams(e);return this._def.getter()._parse({data:t.data,path:t.path,parent:t})}}re.create=(e,t)=>new re({getter:e,typeName:m.ZodLazy,...g(t)});class se extends _{_parse(e){if(e.data!==this._def.value){let t=this._getOrReturnCtx(e);return f(t,{received:t.data,code:c.invalid_literal,expected:this._def.value}),p}return{status:"valid",value:e.data}}get value(){return this._def.value}}se.create=(e,t)=>new se({value:e,typeName:m.ZodLiteral,...g(t)});function Ne(e,t){return new P({values:e,typeName:m.ZodEnum,...g(t)})}class P extends _{_parse(e){if(typeof e.data!=="string"){let t=this._getOrReturnCtx(e),r=this._def.values;return f(t,{expected:v.joinValues(r),received:t.parsedType,code:c.invalid_type}),p}if(!this._cache)this._cache=new Set(this._def.values);if(!this._cache.has(e.data)){let t=this._getOrReturnCtx(e),r=this._def.values;return f(t,{received:t.data,code:c.invalid_enum_value,options:r}),p}return b(e.data)}get options(){return this._def.values}get enum(){let e={};for(let t of this._def.values)e[t]=t;return e}get Values(){let e={};for(let t of this._def.values)e[t]=t;return e}get Enum(){let e={};for(let t of this._def.values)e[t]=t;return e}extract(e,t=this._def){return P.create(e,{...this._def,...t})}exclude(e,t=this._def){return P.create(this.options.filter((r)=>!e.includes(r)),{...this._def,...t})}}P.create=Ne;class ne extends _{_parse(e){let t=v.getValidEnumValues(this._def.values),r=this._getOrReturnCtx(e);if(r.parsedType!==u.string&&r.parsedType!==u.number){let n=v.objectValues(t);return f(r,{expected:v.joinValues(n),received:r.parsedType,code:c.invalid_type}),p}if(!this._cache)this._cache=new Set(v.getValidEnumValues(this._def.values));if(!this._cache.has(e.data)){let n=v.objectValues(t);return f(r,{received:r.data,code:c.invalid_enum_value,options:n}),p}return b(e.data)}get enum(){return this._def.values}}ne.create=(e,t)=>new ne({values:e,typeName:m.ZodNativeEnum,...g(t)});class Y extends _{unwrap(){return this._def.type}_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==u.promise&&t.common.async===!1)return f(t,{code:c.invalid_type,expected:u.promise,received:t.parsedType}),p;let r=t.parsedType===u.promise?t.data:Promise.resolve(t.data);return b(r.then((n)=>this._def.type.parseAsync(n,{path:t.path,errorMap:t.common.contextualErrorMap})))}}Y.create=(e,t)=>new Y({type:e,typeName:m.ZodPromise,...g(t)});class O extends _{innerType(){return this._def.schema}sourceType(){return this._def.schema._def.typeName===m.ZodEffects?this._def.schema.sourceType():this._def.schema}_parse(e){let{status:t,ctx:r}=this._processInputParams(e),n=this._def.effect||null,s={addIssue:(i)=>{if(f(r,i),i.fatal)t.abort();else t.dirty()},get path(){return r.path}};if(s.addIssue=s.addIssue.bind(s),n.type==="preprocess"){let i=n.transform(r.data,s);if(r.common.async)return Promise.resolve(i).then(async(a)=>{if(t.value==="aborted")return p;let d=await this._def.schema._parseAsync({data:a,path:r.path,parent:r});if(d.status==="aborted")return p;if(d.status==="dirty")return W(d.value);if(t.value==="dirty")return W(d.value);return d});else{if(t.value==="aborted")return p;let a=this._def.schema._parseSync({data:i,path:r.path,parent:r});if(a.status==="aborted")return p;if(a.status==="dirty")return W(a.value);if(t.value==="dirty")return W(a.value);return a}}if(n.type==="refinement"){let i=(a)=>{let d=n.refinement(a,s);if(r.common.async)return Promise.resolve(d);if(d instanceof Promise)throw Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return a};if(r.common.async===!1){let a=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});if(a.status==="aborted")return p;if(a.status==="dirty")t.dirty();return i(a.value),{status:t.value,value:a.value}}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then((a)=>{if(a.status==="aborted")return p;if(a.status==="dirty")t.dirty();return i(a.value).then(()=>({status:t.value,value:a.value}))})}if(n.type==="transform")if(r.common.async===!1){let i=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});if(!z(i))return p;let a=n.transform(i.value,s);if(a instanceof Promise)throw Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:t.value,value:a}}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then((i)=>{if(!z(i))return p;return Promise.resolve(n.transform(i.value,s)).then((a)=>({status:t.value,value:a}))});v.assertNever(n)}}O.create=(e,t,r)=>new O({schema:e,typeName:m.ZodEffects,effect:t,...g(r)});O.createWithPreprocess=(e,t,r)=>new O({schema:t,effect:{type:"preprocess",transform:e},typeName:m.ZodEffects,...g(r)});class T extends _{_parse(e){if(this._getType(e)===u.undefined)return b(void 0);return this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}T.create=(e,t)=>new T({innerType:e,typeName:m.ZodOptional,...g(t)});class M extends _{_parse(e){if(this._getType(e)===u.null)return b(null);return this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}M.create=(e,t)=>new M({innerType:e,typeName:m.ZodNullable,...g(t)});class ae extends _{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;if(t.parsedType===u.undefined)r=this._def.defaultValue();return this._def.innerType._parse({data:r,path:t.path,parent:t})}removeDefault(){return this._def.innerType}}ae.create=(e,t)=>new ae({innerType:e,typeName:m.ZodDefault,defaultValue:typeof t.default==="function"?t.default:()=>t.default,...g(t)});class ie extends _{_parse(e){let{ctx:t}=this._processInputParams(e),r={...t,common:{...t.common,issues:[]}},n=this._def.innerType._parse({data:r.data,path:r.path,parent:{...r}});if(J(n))return n.then((s)=>({status:"valid",value:s.status==="valid"?s.value:this._def.catchValue({get error(){return new w(r.common.issues)},input:r.data})}));else return{status:"valid",value:n.status==="valid"?n.value:this._def.catchValue({get error(){return new w(r.common.issues)},input:r.data})}}removeCatch(){return this._def.innerType}}ie.create=(e,t)=>new ie({innerType:e,typeName:m.ZodCatch,catchValue:typeof t.catch==="function"?t.catch:()=>t.catch,...g(t)});class he extends _{_parse(e){if(this._getType(e)!==u.nan){let r=this._getOrReturnCtx(e);return f(r,{code:c.invalid_type,expected:u.nan,received:r.parsedType}),p}return{status:"valid",value:e.data}}}he.create=(e)=>new he({typeName:m.ZodNaN,...g(e)});var gt=Symbol("zod_brand");class ve extends _{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;return this._def.type._parse({data:r,path:t.path,parent:t})}unwrap(){return this._def.type}}class pe extends _{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.common.async)return(async()=>{let s=await this._def.in._parseAsync({data:r.data,path:r.path,parent:r});if(s.status==="aborted")return p;if(s.status==="dirty")return t.dirty(),W(s.value);else return this._def.out._parseAsync({data:s.value,path:r.path,parent:r})})();else{let n=this._def.in._parseSync({data:r.data,path:r.path,parent:r});if(n.status==="aborted")return p;if(n.status==="dirty")return t.dirty(),{status:"dirty",value:n.value};else return this._def.out._parseSync({data:n.value,path:r.path,parent:r})}}static create(e,t){return new pe({in:e,out:t,typeName:m.ZodPipeline})}}class oe extends _{_parse(e){let t=this._def.innerType._parse(e),r=(n)=>{if(z(n))n.value=Object.freeze(n.value);return n};return J(t)?t.then((n)=>r(n)):r(t)}unwrap(){return this._def.innerType}}oe.create=(e,t)=>new oe({innerType:e,typeName:m.ZodReadonly,...g(t)});function Oe(e,t){let r=typeof e==="function"?e(t):typeof e==="string"?{message:e}:e;return typeof r==="string"?{message:r}:r}function Ve(e,t={},r){if(e)return U.create().superRefine((n,s)=>{let i=e(n);if(i instanceof Promise)return i.then((a)=>{if(!a){let d=Oe(t,n),l=d.fatal??r??!0;s.addIssue({code:"custom",...d,fatal:l})}});if(!i){let a=Oe(t,n),d=a.fatal??r??!0;s.addIssue({code:"custom",...a,fatal:d})}return});return U.create()}var yt={object:x.lazycreate},m;(function(e){e.ZodString="ZodString",e.ZodNumber="ZodNumber",e.ZodNaN="ZodNaN",e.ZodBigInt="ZodBigInt",e.ZodBoolean="ZodBoolean",e.ZodDate="ZodDate",e.ZodSymbol="ZodSymbol",e.ZodUndefined="ZodUndefined",e.ZodNull="ZodNull",e.ZodAny="ZodAny",e.ZodUnknown="ZodUnknown",e.ZodNever="ZodNever",e.ZodVoid="ZodVoid",e.ZodArray="ZodArray",e.ZodObject="ZodObject",e.ZodUnion="ZodUnion",e.ZodDiscriminatedUnion="ZodDiscriminatedUnion",e.ZodIntersection="ZodIntersection",e.ZodTuple="ZodTuple",e.ZodRecord="ZodRecord",e.ZodMap="ZodMap",e.ZodSet="ZodSet",e.ZodFunction="ZodFunction",e.ZodLazy="ZodLazy",e.ZodLiteral="ZodLiteral",e.ZodEnum="ZodEnum",e.ZodEffects="ZodEffects",e.ZodNativeEnum="ZodNativeEnum",e.ZodOptional="ZodOptional",e.ZodNullable="ZodNullable",e.ZodDefault="ZodDefault",e.ZodCatch="ZodCatch",e.ZodPromise="ZodPromise",e.ZodBranded="ZodBranded",e.ZodPipeline="ZodPipeline",e.ZodReadonly="ZodReadonly"})(m||(m={}));var _t=(e,t={message:`Input not instance of ${e.name}`})=>Ve((r)=>r instanceof e,t),$e=S.create,Me=D.create,vt=he.create,xt=L.create,ze=Q.create,kt=B.create,bt=ue.create,wt=X.create,Ct=Z.create,St=U.create,Rt=F.create,Tt=I.create,At=de.create,Ot=R.create,jt=x.create,It=x.strictCreate,Et=ee.create,Nt=_e.create,Vt=te.create,$t=E.create,Mt=le.create,zt=fe.create,Ft=q.create,Dt=G.create,Lt=re.create,Pt=se.create,Wt=P.create,Bt=ne.create,Ut=Y.create,qt=O.create,Yt=T.create,Ht=M.create,Jt=O.createWithPreprocess,Kt=pe.create,Gt=()=>$e().optional(),Qt=()=>Me().optional(),Xt=()=>ze().optional(),Zt={string:(e)=>S.create({...e,coerce:!0}),number:(e)=>D.create({...e,coerce:!0}),boolean:(e)=>Q.create({...e,coerce:!0}),bigint:(e)=>L.create({...e,coerce:!0}),date:(e)=>B.create({...e,coerce:!0})};var er=p;var tr=1,Fe=o.object({path:o.string(),status:o.enum(["analyzed","excluded","unsupported","failed","missing"]),reasons:o.array(o.string())}).strict(),me=o.string().regex(/^sha256:[a-f0-9]{64}$/),De=o.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/),Le=o.object({verdict:o.enum(["FRESH","DIRTY_KNOWN","STALE","UNSEALED"]),reasons:o.array(o.string())}).strict(),rr=o.object({schemaVersion:o.literal(1),profile:o.literal("modelo-suite-static-v1"),status:o.enum(["admitted","rejected"]),identity:o.object({source:me,sourceCommits:o.array(De),baseCommit:De.nullable(),diff:me,config:me,analyzer:me,indexSnapshot:me}).strict(),binding:o.object({status:o.enum(["valid","invalid","absent"]),reasons:o.array(o.string())}).strict(),indexFreshness:Le,checkFreshness:o.object({status:o.enum(["current","changed"]),reasons:o.array(o.string())}).strict(),controlFreshness:Le,repositoryCoverage:o.object({status:o.enum(["complete","partial","insufficient"]),files:o.array(Fe)}).strict(),changeCoverage:o.object({expected:o.array(o.string()),analyzed:o.array(o.string()),files:o.array(Fe)}).strict(),reasons:o.array(o.string()),limitations:o.array(o.string()),proofObligations:o.array(o.object({id:o.string(),status:o.literal("not_observed"),observation:o.string()}).strict())}).strict().superRefine((e,t)=>{if(e.repositoryCoverage.status==="complete"&&e.repositoryCoverage.files.some((s)=>!["analyzed","excluded"].includes(s.status)||s.status==="analyzed"&&s.reasons.length>0))t.addIssue({code:o.ZodIssueCode.custom,path:["repositoryCoverage"],message:"complete repository coverage cannot carry incomplete file outcomes"});if(e.status!=="admitted")return;let r=e.changeCoverage.expected,n=e.changeCoverage.files;if(e.binding.status!=="valid"||e.binding.reasons.length>0||e.identity.sourceCommits.length===0||new Set(e.identity.sourceCommits).size!==e.identity.sourceCommits.length||[e.indexFreshness,e.controlFreshness].some((s)=>s.verdict==="FRESH"?s.reasons.length>0:s.verdict==="DIRTY_KNOWN"&&(s.reasons.length!==1||s.reasons[0]!=="WORKING_TREE_DIRTY"))||!["FRESH","DIRTY_KNOWN"].includes(e.indexFreshness.verdict)||!["FRESH","DIRTY_KNOWN"].includes(e.controlFreshness.verdict)||e.checkFreshness.status!=="current"||e.checkFreshness.reasons.length>0||e.reasons.length>0||r.length===0||new Set(r).size!==r.length||n.length!==r.length||n.some((s)=>s.status!=="analyzed"||s.reasons.length>0||!r.includes(s.path))||new Set(n.map((s)=>s.path)).size!==r.length||e.changeCoverage.analyzed.length!==r.length||e.changeCoverage.analyzed.some((s)=>!r.includes(s))||new Set(e.changeCoverage.analyzed).size!==r.length)t.addIssue({code:o.ZodIssueCode.custom,message:"admitted analysis requires current compatible binding and complete non-empty analyzed obligations"})}),Se=o.object({id:o.string(),name:o.string(),kind:o.string(),file:o.string().optional()}).passthrough(),Ce=o.object({statement:o.string(),kind:o.string(),verificationStatus:o.string()}).passthrough(),sr=o.object({name:o.string(),file:o.string().optional()}).passthrough(),nr=o.object({file:o.string(),line:o.number().optional()}).passthrough(),ar=o.object({rule:o.string(),tier:o.enum(["strict","advisory"]),severity:o.enum(["warn","block"]),message:o.string(),nodeIds:o.array(o.string()),locations:o.array(nr)}).passthrough(),ir=o.object({symbol:Se,consumers:o.array(Se)}).passthrough(),or=o.object({file:o.string(),coChanged:o.array(o.object({file:o.string(),commits:o.number()}).passthrough())}).passthrough(),cr=o.object({analysisAdmission:rr.optional(),schemaVersion:o.literal(tr),verdict:o.enum(["PASS","WARN","BLOCK"]),base:o.string().nullable(),head:o.string(),mergeBase:o.string().nullable(),range:o.string().nullable(),changedFiles:o.array(o.string()),changedSymbols:o.array(Se),impactedContracts:o.array(Ce),impactedInvariants:o.array(Ce),recommendedTests:o.array(sr),contradictions:o.array(Ce),unknowns:o.array(o.string()),findings:o.array(ar),impactedConsumers:o.array(ir).optional(),coChangedFiles:o.array(or).optional(),summary:o.object({blockCount:o.number(),warnCount:o.number()}).passthrough()}).passthrough().superRefine((e,t)=>{if(e.analysisAdmission?.status==="rejected"&&e.verdict!=="BLOCK")t.addIssue({code:o.ZodIssueCode.custom,path:["verdict"],message:"rejected qualified analysis must produce BLOCK"});if(e.analysisAdmission?.status==="admitted"&&e.changedFiles.some((r)=>!e.analysisAdmission.changeCoverage.expected.includes(r)))t.addIssue({code:o.ZodIssueCode.custom,path:["changedFiles"],message:"admitted analysis must cover every reported changed file"})});export{cr as VerifyReportSchema};
diff --git a/packages/github-action/package.json b/packages/github-action/package.json
index b9339ec2..b7319b51 100644
--- a/packages/github-action/package.json
+++ b/packages/github-action/package.json
@@ -1,6 +1,6 @@
{
"name": "@semantic-context/github-action",
- "version": "0.4.2",
+ "version": "0.4.3",
"private": true,
"type": "module",
"description": "GitHub Action adapter for semctx verify diff (composite action; ADR 0006). Consumes the verify JSON report and emits annotations, a job summary, and outputs.",
diff --git a/packages/github-action/src/adapter.mjs b/packages/github-action/src/adapter.mjs
index bc885904..30b34e0b 100644
--- a/packages/github-action/src/adapter.mjs
+++ b/packages/github-action/src/adapter.mjs
@@ -64,15 +64,19 @@ function summaryFor(report) {
}
/** Pure decision function — testable without any GitHub environment. */
-export function renderAction(report, failOn) {
+export function renderAction(report, failOn, requireQualified = false) {
+ const missingRequiredAdmission = requireQualified && report.analysisAdmission?.profile !== "modelo-suite-static-v1";
+ const actionReport = missingRequiredAdmission ? { ...report, verdict: "BLOCK" } : report;
const shouldFail =
+ missingRequiredAdmission ||
+ report.analysisAdmission?.status === "rejected" ||
(report.verdict === "BLOCK" && (failOn === "block" || failOn === "warn")) ||
(report.verdict === "WARN" && failOn === "warn");
return {
- annotations: annotationsFor(report),
- summary: summaryFor(report),
+ annotations: [...annotationsFor(report), ...(missingRequiredAdmission ? ["::error title=semctx%3A qualification_required::Trusted Action policy requires modelo-suite-static-v1 admission; no matching admission is present."] : [])],
+ summary: summaryFor(actionReport) + (missingRequiredAdmission ? "\n**Action policy BLOCK:** required modelo-suite-static-v1 admission is absent. The producer report is unchanged.\n" : ""),
outputs: {
- verdict: report.verdict,
+ verdict: actionReport.verdict,
"block-count": String(report.summary?.blockCount ?? 0),
"warn-count": String(report.summary?.warnCount ?? 0),
"changed-symbol-count": String((report.changedSymbols ?? []).length),
@@ -102,6 +106,7 @@ function main() {
process.exit(2);
}
const failOn = (process.env.INPUT_FAIL_ON || "block").trim();
+ const requireQualified = (process.env.INPUT_REQUIRE_QUALIFIED ?? "false").trim();
let report;
try {
report = JSON.parse(readFileSync(reportPath, "utf8"));
@@ -119,7 +124,11 @@ function main() {
process.stderr.write(`adapter: unknown fail-on policy: ${failOn}\n`);
process.exit(2);
}
- const { annotations, summary, outputs, exitCode } = renderAction(report, failOn);
+ if (!["true", "false"].includes(requireQualified)) {
+ process.stderr.write("adapter: unknown require-qualified policy (expected true or false)\n");
+ process.exit(2);
+ }
+ const { annotations, summary, outputs, exitCode } = renderAction(report, failOn, requireQualified === "true");
for (const line of annotations) process.stdout.write(line + "\n");
writeSummary(summary);
setOutputs(outputs, reportPath);
diff --git a/packages/github-action/test/adapter.test.ts b/packages/github-action/test/adapter.test.ts
index f05dc052..3be38836 100644
--- a/packages/github-action/test/adapter.test.ts
+++ b/packages/github-action/test/adapter.test.ts
@@ -40,7 +40,7 @@ const WARN = report("WARN", [
]);
const PASS = report("PASS", []);
-function runAdapter(rep: unknown, failOn: string): { code: number; out: string; err: string; outputs: string; summary: string } {
+function runAdapter(rep: unknown, failOn: string, requireQualified = "false"): { code: number; out: string; err: string; outputs: string; summary: string } {
const dir = mkdtempSync(join(tmpdir(), "semctx-action-"));
const reportPath = join(dir, "report.json");
const outFile = join(dir, "gh_output");
@@ -49,7 +49,7 @@ function runAdapter(rep: unknown, failOn: string): { code: number; out: string;
const p = Bun.spawnSync(["node", ADAPTER, reportPath], {
stdout: "pipe",
stderr: "pipe",
- env: { ...process.env, INPUT_FAIL_ON: failOn, GITHUB_OUTPUT: outFile, GITHUB_STEP_SUMMARY: sumFile },
+ env: { ...process.env, INPUT_FAIL_ON: failOn, INPUT_REQUIRE_QUALIFIED: requireQualified, GITHUB_OUTPUT: outFile, GITHUB_STEP_SUMMARY: sumFile },
});
return {
code: p.exitCode ?? 1,
@@ -60,7 +60,73 @@ function runAdapter(rep: unknown, failOn: string): { code: number; out: string;
};
}
+function admittedReport(): Report {
+ const digest = `sha256:${"0".repeat(64)}`;
+ const file = { path: "src/a.ts", status: "analyzed" as const, reasons: [] };
+ return { ...PASS, changedFiles: [...PASS.changedFiles], analysisAdmission: {
+ schemaVersion: 1, profile: "modelo-suite-static-v1", status: "admitted",
+ identity: { source: digest, sourceCommits: ["0".repeat(40)], baseCommit: null, diff: digest, config: digest, analyzer: digest, indexSnapshot: digest },
+ binding: { status: "valid", reasons: [] }, indexFreshness: { verdict: "FRESH", reasons: [] }, controlFreshness: { verdict: "FRESH", reasons: [] }, checkFreshness: { status: "current", reasons: [] },
+ repositoryCoverage: { status: "complete", files: [file] },
+ changeCoverage: { expected: [file.path], analyzed: [file.path], files: [file] }, reasons: [], limitations: [], proofObligations: [],
+ } };
+}
+
describe("github-action adapter", () => {
+ it("rejects admitted changed files outside coverage before emitting Action outputs", () => {
+ expect(runAdapter(admittedReport(), "none", "true").code).toBe(0);
+ const uncovered = admittedReport(); uncovered.changedFiles.push("src/unreviewed.ts");
+ const failed = admittedReport();
+ failed.analysisAdmission!.repositoryCoverage.files.push({ path: "src/failed.ts", status: "failed", reasons: ["PRODUCER_FAILED"] });
+ for (const contradictory of [uncovered, failed]) {
+ const result = runAdapter(contradictory, "none", "true");
+ expect(result.code).toBe(2);
+ expect(result.err).toContain("adapter: unusable verify report");
+ expect(result.out).toBe("");
+ expect(result.outputs).toBe("");
+ expect(result.summary).toBe("");
+ }
+ });
+
+ it("trusted require-qualified refuses an ordinary PASS report without admission", () => {
+ const result = runAdapter(PASS, "none", "true");
+ expect(result.code).toBe(1);
+ expect(result.outputs).toContain("verdict=BLOCK");
+ expect(result.summary).toContain("Action policy BLOCK");
+ expect(result.out).toContain("qualification_required");
+ expect(PASS.verdict).toBe("PASS");
+ });
+ it("ordinary policy stays optional and malformed requirements fail closed", () => {
+ expect(runAdapter(PASS,"none","false").code).toBe(0);
+ const result=runAdapter(PASS,"none","yes\n::warning::untrusted-policy");
+ expect(result.code).toBe(2);expect(result.err).toContain("unknown require-qualified policy");
+ expect(result.err).not.toContain("::warning");
+ expect(result.outputs).toBe("");
+ });
+ it("trusted requirement accepts an admitted named profile and refuses malformed profiles", () => {
+ const digest=`sha256:${"0".repeat(64)}`;
+ const qualified:Report={...PASS,analysisAdmission:{
+ schemaVersion:1,profile:"modelo-suite-static-v1",status:"admitted",
+ identity:{source:digest,sourceCommits:["0".repeat(40)],baseCommit:null,diff:digest,config:digest,analyzer:digest,indexSnapshot:digest},
+ binding:{status:"valid",reasons:[]},indexFreshness:{verdict:"FRESH",reasons:[]},controlFreshness:{verdict:"FRESH",reasons:[]},checkFreshness:{status:"current",reasons:[]},
+ repositoryCoverage:{status:"complete",files:[{path:"src/a.ts",status:"analyzed",reasons:[]}]},
+ changeCoverage:{expected:["src/a.ts"],analyzed:["src/a.ts"],files:[{path:"src/a.ts",status:"analyzed",reasons:[]}]},reasons:[],limitations:[],proofObligations:[],
+ }};
+ expect(runAdapter(qualified,"none","true").code).toBe(0);
+ expect(runAdapter({...qualified,analysisAdmission:{...qualified.analysisAdmission,profile:"unknown"}},"none","true").code).toBe(2);
+ });
+ it("refuses qualified rejection even with advisory none policy", () => {
+ const digest = `sha256:${"0".repeat(64)}`;
+ const qualified: Report = { ...BLOCK, analysisAdmission: {
+ schemaVersion: 1, profile: "modelo-suite-static-v1", status: "rejected",
+ identity: { source: digest, sourceCommits: ["0".repeat(40)], baseCommit: null, diff: digest, config: digest, analyzer: digest, indexSnapshot: digest },
+ binding: { status: "valid", reasons: [] }, indexFreshness: { verdict: "STALE", reasons: ["ANALYSIS_INPUT_MISMATCH"] },
+ controlFreshness: { verdict: "STALE", reasons: ["ANALYSIS_INPUT_MISMATCH"] }, checkFreshness: { status: "current", reasons: [] },
+ repositoryCoverage: { status: "partial", files: [] }, changeCoverage: { expected: ["src/a.ts"], analyzed: [], files: [{ path: "src/a.ts", status: "missing", reasons: ["NO_CURRENT_ANALYZED_SOURCE"] }] },
+ reasons: ["INDEX_NOT_FRESH"], limitations: ["Static analysis only."], proofObligations: [],
+ } };
+ expect(runAdapter(qualified, "none").code).toBe(1);
+ });
it("emits annotations, summary and outputs for a BLOCK report", () => {
const r = runAdapter(BLOCK, "block");
expect(r.out).toMatch(/^::error /m);
@@ -154,7 +220,7 @@ describe("github-action adapter", () => {
it("action.yml declares the required inputs and outputs", () => {
const yml = readFileSync(ACTION_YML, "utf8");
expect(yml).toContain('bun-version: "1.4.0"');
- for (const input of ["base:", "head:", "fail-on:", "working-directory:", "config-path:", "report-path:", "upload-report:"]) {
+ for (const input of ["base:", "head:", "fail-on:", "require-qualified:", "working-directory:", "config-path:", "report-path:", "upload-report:"]) {
expect(yml).toContain(input);
}
for (const output of ["verdict:", "block-count:", "warn-count:", "changed-symbol-count:", "recommended-test-count:", "report-path:"]) {
@@ -167,6 +233,7 @@ describe("github-action adapter", () => {
// script by the ${{ }} template engine (GitHub Actions injection).
expect(yml).toContain("SEMCTX_BASE: ${{ inputs.base }}");
expect(yml).toContain("INPUT_REPORT_PATH: ${{ inputs.report-path }}");
+ expect(yml).toContain("INPUT_REQUIRE_QUALIFIED: ${{ inputs.require-qualified }}");
expect(yml).not.toContain("--base \"${{ inputs.base }}\"");
expect(yml).not.toContain("--head \"${{ inputs.head }}\"");
expect(yml).not.toContain("adapter.mjs\" \"${{ inputs.report-path }}\"");
diff --git a/packages/github-action/test/qualification-flow.test.ts b/packages/github-action/test/qualification-flow.test.ts
new file mode 100644
index 00000000..c432aa6a
--- /dev/null
+++ b/packages/github-action/test/qualification-flow.test.ts
@@ -0,0 +1,130 @@
+import { expect, test } from "bun:test";
+import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createDefaultConfig, createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace, toDiskConfig } from "@semantic-context/repository-store";
+import { indexRepository, runVerify } from "@semantic-context/app-services";
+import { replaceLocalReportFile } from "../../../apps/cli/src/report-output";
+
+test("Action default report remains outside qualified inputs and reaches the adapter", () => {
+ const parsed = Bun.YAML.parse(readFileSync(join(import.meta.dir, "../action.yml"), "utf8")) as { inputs: { "report-path": { default: string } }; runs: { steps: { id?: string; env?: Record; run?: string }[] } };
+ expect(parsed.inputs["report-path"].default).toBe(".semctx/reports/verify.json");
+ expect(parsed.runs.steps.find(step => step.id === "verify")?.env?.SEMCTX_REPORT).toBe("${{ inputs.report-path }}");
+ expect(parsed.runs.steps.find(step => step.id === "adapter")?.env?.INPUT_REPORT_PATH).toBe("${{ inputs.report-path }}");
+});
+
+test("ordinary Action default report is created and reaches the real adapter", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-action-ordinary-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main(){return 1;}\n");
+ const git = (...args: string[]) => {
+ const child = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ expect(child.exitCode, new TextDecoder().decode(child.stderr)).toBe(0);
+ };
+ git("init", "-q"); git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "base");
+ writeFileSync(join(root, "src/main.ts"), "export function main(){return 2;}\n");
+ git("add", "."); git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "change");
+ const parsed = Bun.YAML.parse(readFileSync(join(import.meta.dir, "../action.yml"), "utf8")) as { inputs: { "report-path": { default: string } }; runs: { steps: { id?: string; run?: string }[] } };
+ const script = parsed.runs.steps.find(step => step.id === "verify")!.run!;
+ const report = join(root, parsed.inputs["report-path"].default);
+ expect(existsSync(join(root, ".semctx/reports"))).toBe(false);
+ const bash = process.platform === "win32" && existsSync("C:/Program Files/Git/bin/bash.exe") ? "C:/Program Files/Git/bin/bash.exe" : "bash";
+ for (const reportPath of [parsed.inputs["report-path"].default, report.replaceAll("\\", "/")]) {
+ const step = Bun.spawnSync([bash, "--noprofile", "--norc", "-c", 'bun() { "$SEMCTX_TEST_BUN" "$@"; }\n' + script], {
+ cwd: join(import.meta.dir, "../../.."), stdout: "pipe", stderr: "pipe", env: {
+ ...process.env, SEMCTX_TEST_BUN: process.execPath.replaceAll("\\", "/"), SEMCTX_CLI: join(import.meta.dir, "../../../apps/cli/src/index.ts").replaceAll("\\", "/"),
+ SEMCTX_TARGET: root.replaceAll("\\", "/"), SEMCTX_CONFIG: "", SEMCTX_REPORT: reportPath,
+ SEMCTX_BASE: "HEAD^", SEMCTX_HEAD: "HEAD", GITHUB_ACTION_PATH: join(import.meta.dir, "..").replaceAll("\\", "/"),
+ },
+ });
+ console.info(JSON.stringify({ exitCode: step.exitCode, stderr: new TextDecoder().decode(step.stderr) }));
+ expect(step.exitCode).toBe(0);
+ const content = JSON.parse(readFileSync(report, "utf8")) as { analysisAdmission?: unknown; verdict: string };
+ expect(content.analysisAdmission).toBeUndefined();
+ const outputs = join(root, "outputs"); const summary = join(root, "summary");
+ const adapter = Bun.spawnSync(["node", join(import.meta.dir, "../src/adapter.mjs"), report], { stdout: "pipe", stderr: "pipe", env: { ...process.env, INPUT_FAIL_ON: "none", GITHUB_OUTPUT: outputs, GITHUB_STEP_SUMMARY: summary } });
+ expect(adapter.exitCode).toBe(0);
+ expect(readFileSync(outputs, "utf8")).toContain(`verdict=${content.verdict}`);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("trusted Action qualification requirement rejects a repository-owned config downgrade", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-action-downgrade-"));
+ try {
+ mkdirSync(join(root,"src"));writeFileSync(join(root,".gitignore"),".semctx/\n");
+ writeFileSync(join(root,"src/main.ts"),"export function main(){return 1;}\n");
+ const policy = join(root,"ci-policy.json");
+ writeFileSync(policy,JSON.stringify(toDiskConfig({...createGlobSelectionConfig(root),selectionMode:"qualified-static-v1",analysisProfile:"modelo-suite-static-v1",include:["src/**/*"]})));
+ const git = (...args: string[]) => { const result=Bun.spawnSync(["git",...args],{cwd:root,stdout:"pipe",stderr:"pipe"});expect(result.exitCode).toBe(0); };
+ git("init","-q");git("add",".");git("-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","qualified policy");
+ writeFileSync(policy,JSON.stringify(toDiskConfig(createDefaultConfig(root))));
+ git("add","ci-policy.json");git("-c","user.name=Fixture","-c","user.email=fixture@example.invalid","commit","-qm","downgraded policy");
+ const parsed=Bun.YAML.parse(readFileSync(join(import.meta.dir,"../action.yml"),"utf8")) as {runs:{steps:{id?:string;run?:string}[]}};
+ const script=parsed.runs.steps.find(step=>step.id==="verify")!.run!;
+ const bash=process.platform==="win32"&&existsSync("C:/Program Files/Git/bin/bash.exe")?"C:/Program Files/Git/bin/bash.exe":"bash";
+ const step=Bun.spawnSync([bash,"--noprofile","--norc","-c",'bun() { "$SEMCTX_TEST_BUN" "$@"; }\n'+script],{cwd:join(import.meta.dir,"../../.."),stdout:"pipe",stderr:"pipe",env:{...process.env,SEMCTX_TEST_BUN:process.execPath.replaceAll("\\","/"),SEMCTX_CLI:join(import.meta.dir,"../../../apps/cli/src/index.ts").replaceAll("\\","/"),SEMCTX_TARGET:root.replaceAll("\\","/"),SEMCTX_CONFIG:"ci-policy.json",SEMCTX_REPORT:".semctx/reports/verify.json",SEMCTX_BASE:"HEAD^",SEMCTX_HEAD:"HEAD",GITHUB_ACTION_PATH:join(import.meta.dir,"..").replaceAll("\\","/")}});
+ expect(step.exitCode).toBe(0);
+ const report=join(root,".semctx/reports/verify.json");const value=JSON.parse(readFileSync(report,"utf8")) as {analysisAdmission?:unknown;verdict:string};
+ expect(value.analysisAdmission).toBeUndefined();
+ const adapter=Bun.spawnSync(["node",join(import.meta.dir,"../src/adapter.mjs"),report],{stdout:"pipe",stderr:"pipe",env:{...process.env,INPUT_FAIL_ON:"none",INPUT_REQUIRE_QUALIFIED:"true"}});
+ console.info(JSON.stringify({stepExit:step.exitCode,reportVerdict:value.verdict,admission:value.analysisAdmission??null,adapterExit:adapter.exitCode}));
+ expect(adapter.exitCode).toBe(1);
+ const ordinary=Bun.spawnSync(["node",join(import.meta.dir,"../src/adapter.mjs"),report],{stdout:"pipe",stderr:"pipe",env:{...process.env,INPUT_FAIL_ON:"none",INPUT_REQUIRE_QUALIFIED:"false"}});
+ expect(ordinary.exitCode).toBe(0);
+ expect(JSON.parse(readFileSync(report,"utf8")).analysisAdmission).toBeUndefined();
+ } finally {rmSync(root,{recursive:true,force:true});}
+},60_000);
+
+test("Action delegates a fresh qualified rejection to the adapter and preserves operational errors", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-action-qualified-"));
+ try {
+ mkdirSync(join(root, "src")); writeFileSync(join(root, ".gitignore"), ".semctx/\n");
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 1; }\n");
+ writeFileSync(join(root, "hidden.mjs"), "export default () => 1;\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) expect(Bun.spawnSync(["git", ...args], { cwd: root }).exitCode).toBe(0);
+ initWorkspace(root, { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1", include: ["src/**/*"], languages: { typescript: "on", javascript: "on" } });
+ writeFileSync(join(root, "src/main.ts"), "export function main() { return 2; }\n");
+ indexRepository(root, "2026-10-10T10:00:00.000Z");
+ const report = runVerify(root, { kind: "working-tree" }).report;
+ expect(report.analysisAdmission?.status).toBe("rejected");
+ const fixture = join(root, "fixture.json"); writeFileSync(fixture, JSON.stringify(report));
+ const parsed = Bun.YAML.parse(readFileSync(join(import.meta.dir, "../action.yml"), "utf8")) as { runs: { steps: { id?: string; run?: string }[] } };
+ const script = parsed.runs.steps.find(step => step.id === "verify")!.run!;
+ const bash = process.platform === "win32" && existsSync("C:/Program Files/Git/bin/bash.exe") ? "C:/Program Files/Git/bin/bash.exe" : "bash";
+ const output = join(root, ".semctx/reports/verify.json");
+ mkdirSync(join(root, ".semctx/reports"));
+ for (const [code, mode, expected] of [[3, "valid", 0], [1, "valid", 1], [2, "valid", 2], [3, "invalid", 3], [3, "missing", 3]] as const) {
+ writeFileSync(output, JSON.stringify(report)); // A prior valid artifact must not substitute for this invocation.
+ const stub = "bun() { if [ \"$2\" = verify ]; then case \"$FIXTURE_MODE\" in valid) cp \"$FIXTURE_REPORT\" \"$report.new\"; mv -f -- \"$report.new\" \"$report\" ;; invalid) printf '{}' > \"$report.new\"; mv -f -- \"$report.new\" \"$report\" ;; esac; return \"$FIXTURE_CODE\"; fi; return 0; }\n";
+ const result = Bun.spawnSync([bash, "--noprofile", "--norc", "-c", stub + script], { cwd: root, stdout: "pipe", stderr: "pipe", env: { ...process.env, SEMCTX_CLI: "fixture-cli", SEMCTX_TARGET: root.replaceAll("\\", "/"), SEMCTX_CONFIG: "", SEMCTX_REPORT: ".semctx/reports/verify.json", SEMCTX_BASE: "HEAD^", SEMCTX_HEAD: "HEAD", GITHUB_ACTION_PATH: join(import.meta.dir, "..").replaceAll("\\", "/"), FIXTURE_REPORT: fixture.replaceAll("\\", "/"), FIXTURE_CODE: String(code), FIXTURE_MODE: mode } });
+ expect(result.exitCode).toBe(expected);
+ if (expected === 0) {
+ const outputs = join(root, "outputs"); const summary = join(root, "summary");
+ const adapter = Bun.spawnSync(["node", join(import.meta.dir, "../src/adapter.mjs"), output], { stdout: "pipe", stderr: "pipe", env: { ...process.env, INPUT_FAIL_ON: "none", GITHUB_OUTPUT: outputs, GITHUB_STEP_SUMMARY: summary } });
+ expect(adapter.exitCode).toBe(1);
+ expect(readFileSync(outputs, "utf8")).toContain("verdict=BLOCK");
+ expect(readFileSync(summary, "utf8")).toContain("BLOCK");
+ }
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+}, 60_000);
+
+test("Action preserves an outside report behind a consumer-owned linked parent on verification failure", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-action-link-"));
+ const outside = mkdtempSync(join(tmpdir(), "semctx-action-outside-"));
+ const linked = join(root, "reports"); const target = join(outside, "report.json");
+ try {
+ writeFileSync(target, "outside sentinel"); symlinkSync(outside, linked, "junction");
+ const parsed = Bun.YAML.parse(readFileSync(join(import.meta.dir, "../action.yml"), "utf8")) as { runs: { steps: { id?: string; run?: string }[] } };
+ const script = parsed.runs.steps.find(step => step.id === "verify")!.run!;
+ const bash = process.platform === "win32" && existsSync("C:/Program Files/Git/bin/bash.exe") ? "C:/Program Files/Git/bin/bash.exe" : "bash";
+ const result = Bun.spawnSync([bash, "--noprofile", "--norc", "-c", "bun() { if [ \"$2\" = verify ]; then return 1; fi; return 0; }\n" + script], { cwd: root, stdout: "pipe", stderr: "pipe", env: { ...process.env, SEMCTX_CLI: "fixture-cli", SEMCTX_TARGET: root.replaceAll("\\", "/"), SEMCTX_CONFIG: "", SEMCTX_REPORT: "reports/report.json", SEMCTX_BASE: "HEAD^", SEMCTX_HEAD: "HEAD" } });
+ expect(result.exitCode).toBe(1);
+ expect(existsSync(target)).toBe(true);
+ expect(() => replaceLocalReportFile(join(root, "reports/report.json"), "new report", root)).toThrow("existing symlink");
+ expect(readFileSync(target, "utf8")).toBe("outside sentinel");
+ } finally { if (existsSync(linked)) unlinkSync(linked); rmSync(root, { recursive: true, force: true }); rmSync(outside, { recursive: true, force: true }); }
+});
diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json
index 29ae9af4..8ea7494a 100644
--- a/packages/mcp-server/package.json
+++ b/packages/mcp-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@semantic-context/mcp-server",
- "version": "0.4.2",
+ "version": "0.4.3",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -24,13 +24,13 @@
"@semantic-context/control-engine": "workspace:*",
"@semantic-context/change-authorization-verifier": "workspace:*",
"@semantic-context/app-services": "workspace:*",
- "@modelcontextprotocol/server": "2.2.0",
+ "@modelcontextprotocol/server": "2.3.1",
"zod": "^3.23.8",
"zod-to-json-schema": "3.25.2",
"zod-v4": "npm:zod@4.2.0"
},
"devDependencies": {
"@semantic-context/test-fixtures": "workspace:*",
- "@modelcontextprotocol/client": "2.2.0"
+ "@modelcontextprotocol/client": "2.3.1"
}
}
diff --git a/packages/mcp-server/src/tool-output-schemas.ts b/packages/mcp-server/src/tool-output-schemas.ts
index 3d45e10d..17ca7437 100644
--- a/packages/mcp-server/src/tool-output-schemas.ts
+++ b/packages/mcp-server/src/tool-output-schemas.ts
@@ -1,4 +1,6 @@
import { z } from "zod-v4";
+import { DISCOVERY_CANDIDATE_REASONS } from "@semantic-context/ts-analyzer";
+import { AnalysisAdmissionSchema, VerifyReportSchema as CoreVerifyReportSchema } from "@semantic-context/core";
import {
AgentLifecycleReportV1Schema,
AltitudeAuthorityReportV1Schema,
@@ -125,6 +127,7 @@ const VerifyReportClaimSchema = z.object({
}).strict();
const VerifyReportSchema = z.object({
+ analysisAdmission: described(mcpSchema(AnalysisAdmissionSchema).optional(), "Opt-in qualified static analysis admission; independent freshness, coverage and runtime proof obligations."),
schemaVersion: described(z.literal(1), "Verify-report schema version."),
verdict: described(z.enum(["PASS", "WARN", "BLOCK"]), "Overall deterministic verdict."),
base: described(z.string().nullable(), "Requested base ref, if any."),
@@ -167,7 +170,10 @@ const VerifyReportSchema = z.object({
blockCount: described(z.number().int().nonnegative(), "Blocking finding count."),
warnCount: described(z.number().int().nonnegative(), "Warning finding count."),
}).strict(), "Finding counts."),
-}).strict();
+}).strict().superRefine((value, context) => {
+ const result = CoreVerifyReportSchema.safeParse(value);
+ if (!result.success) for (const issue of result.error.issues) context.addIssue({ code: "custom", path: issue.path, message: issue.message });
+});
const InspectionResultSchema = z.object({
query: described(z.string(), "Original inspection query."),
@@ -789,9 +795,9 @@ const SetupScopeCountsSchema = z.object({
unavailable: SetupScopeCountSchema,
}).strict();
const SetupScopeReasonCountsSchema = z.array(z.object({
- reason: z.enum(["LEGACY_UNSUPPORTED_EXTENSION", "INCLUDE_MISS", "EXCLUDE_MATCH", "LANGUAGE_DISABLED", "LANGUAGE_UNSUPPORTED", "READ_FAILED", "IMPORT_OUTSIDE_REPOSITORY", "REFERENCE_OUTSIDE_REPOSITORY", "SOURCE_LINK_OUTSIDE_REPOSITORY", "SELECTED"]),
+ reason: z.enum(DISCOVERY_CANDIDATE_REASONS),
count: z.number().int().positive(),
-}).strict()).max(10);
+}).strict()).max(DISCOVERY_CANDIDATE_REASONS.length);
const SetupScopeSchema = z.object({
schemaVersion: z.literal(1),
basis: z.literal("observed-discovery"),
diff --git a/packages/mcp-server/test/mcp-v2-boundary.test.ts b/packages/mcp-server/test/mcp-v2-boundary.test.ts
index 9ca84b86..2b849c8e 100644
--- a/packages/mcp-server/test/mcp-v2-boundary.test.ts
+++ b/packages/mcp-server/test/mcp-v2-boundary.test.ts
@@ -20,8 +20,8 @@ describe("MCP 2026 SDK boundary", () => {
devDependencies?: Record;
};
- expect(manifest.dependencies?.["@modelcontextprotocol/server"]).toBe("2.2.0");
- expect(manifest.devDependencies?.["@modelcontextprotocol/client"]).toBe("2.2.0");
+ expect(manifest.dependencies?.["@modelcontextprotocol/server"]).toBe("2.3.1");
+ expect(manifest.devDependencies?.["@modelcontextprotocol/client"]).toBe("2.3.1");
expect(manifest.dependencies?.["@modelcontextprotocol/sdk"]).toBeUndefined();
expect(manifest.dependencies?.["zod"]).toBe("^3.23.8");
expect(manifest.dependencies?.["zod-v4"]).toBe("npm:zod@4.2.0");
diff --git a/packages/mcp-server/test/qualified-analysis-schema.test.ts b/packages/mcp-server/test/qualified-analysis-schema.test.ts
new file mode 100644
index 00000000..d9034e18
--- /dev/null
+++ b/packages/mcp-server/test/qualified-analysis-schema.test.ts
@@ -0,0 +1,43 @@
+import { expect, test } from "bun:test";
+import type { VerifyReport } from "@semantic-context/core";
+import { TOOL_OUTPUT_SCHEMAS } from "../src/tool-output-schemas";
+
+function report(): VerifyReport {
+ const digest = `sha256:${"a".repeat(64)}`;
+ const file = { path: "scripts/leaf.mjs", status: "analyzed" as const, reasons: [] };
+ return {
+ schemaVersion: 1, verdict: "PASS", base: null, head: "HEAD", mergeBase: null, range: null,
+ changedFiles: [file.path], changedSymbols: [{ id: "function:leaf", name: "leaf", kind: "function", file: file.path }],
+ impactedContracts: [], impactedInvariants: [], recommendedTests: [], contradictions: [], unknowns: [], findings: [], summary: { blockCount: 0, warnCount: 0 },
+ analysisAdmission: {
+ schemaVersion: 1, profile: "modelo-suite-static-v1", status: "admitted",
+ identity: { source: digest, sourceCommits: ["a".repeat(40)], baseCommit: null, diff: digest, config: digest, analyzer: digest, indexSnapshot: digest },
+ binding: { status: "valid", reasons: [] }, indexFreshness: { verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] },
+ controlFreshness: { verdict: "DIRTY_KNOWN", reasons: ["WORKING_TREE_DIRTY"] }, checkFreshness: { status: "current", reasons: [] },
+ repositoryCoverage: { status: "partial", files: [file] }, changeCoverage: { expected: [file.path], analyzed: [file.path], files: [file] },
+ reasons: [], limitations: ["Static source/dependency analysis only."], proofObligations: [],
+ },
+ };
+}
+test("public MCP output rejects stale, empty or unsupported positive admission and rejected PASS", () => {
+ const schema = TOOL_OUTPUT_SCHEMAS.semctx_verify_change;
+ expect(schema.safeParse(report()).success).toBe(true);
+ const stale = report(); stale.analysisAdmission!.indexFreshness = { verdict: "STALE", reasons: ["ANALYSIS_INPUT_MISMATCH"] };
+ expect(schema.safeParse(stale).success).toBe(false);
+ const empty = report(); empty.analysisAdmission!.changeCoverage = { expected: [], analyzed: [], files: [] };
+ expect(schema.safeParse(empty).success).toBe(false);
+ const unsupported = report(); unsupported.analysisAdmission!.changeCoverage.files[0]!.status = "unsupported";
+ expect(schema.safeParse(unsupported).success).toBe(false);
+ const rejected = report(); rejected.analysisAdmission!.status = "rejected";
+ expect(schema.safeParse(rejected).success).toBe(false);
+});
+test("public MCP output rejects uncovered admitted changes and complete failed repository scope", () => {
+ const schema = TOOL_OUTPUT_SCHEMAS.semctx_verify_change;
+ const valid = report();
+ expect(schema.safeParse(valid).success).toBe(true);
+ const uncovered = report(); uncovered.changedFiles.push("src/unreviewed.ts");
+ expect(schema.safeParse(uncovered).success).toBe(false);
+ const failed = report(); failed.analysisAdmission!.repositoryCoverage.status = "complete";
+ failed.analysisAdmission!.repositoryCoverage.files.push({ path: "src/unrelated.ts", status: "failed", reasons: ["PRODUCER_FAILED"] });
+ expect(schema.safeParse(failed).success).toBe(false);
+});
diff --git a/packages/mcp-server/test/setup-scope-tools.test.ts b/packages/mcp-server/test/setup-scope-tools.test.ts
index 4d966767..040562b4 100644
--- a/packages/mcp-server/test/setup-scope-tools.test.ts
+++ b/packages/mcp-server/test/setup-scope-tools.test.ts
@@ -1,12 +1,87 @@
import { afterEach, expect, test } from "bun:test";
-import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { planSetupRepository } from "@semantic-context/app-services";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { initWorkspace } from "@semantic-context/repository-store";
+import { DISCOVERY_CANDIDATE_REASONS } from "@semantic-context/ts-analyzer";
+import { Client } from "@modelcontextprotocol/client";
+import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { setupTool, isSetupAgentSuccess } from "../src/setup-tools";
import { TOOL_OUTPUT_SCHEMAS } from "../src/tool-output-schemas";
const roots: string[] = [];
+function qualifiedFixture(): string {
+ const root = mkdtempSync(join(tmpdir(), "semctx-scope-mcp-"));
+ roots.push(root);
+ mkdirSync(join(root, "src"));
+ mkdirSync(join(root, "build"));
+ writeFileSync(join(root, "src/index.ts"), "export const value = 1;\n");
+ writeFileSync(join(root, "build/generated.ts"), "export const generated = 1;\n");
+ writeFileSync(join(root, ".gitignore"), "build/\n.semctx/\n");
+ for (const args of [["init", "-q"], ["add", "."], ["-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "fixture"]]) {
+ const result = Bun.spawnSync(["git", ...args], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+ }
+ initWorkspace(root, {
+ ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1",
+ analysisProfile: "modelo-suite-static-v1", include: ["src/**/*.ts"],
+ languages: { typescript: "on", javascript: "on" },
+ });
+ return root;
+}
+
+test("qualified setup scope validates actual ignored generated output through MCP", () => {
+ const root = qualifiedFixture();
+ const plan = planSetupRepository(root);
+ if (plan.kind !== "setup_plan") throw new Error("unexpected refusal");
+ const preflight = setupTool(root);
+ if (preflight.kind !== "setup_preflight") throw new Error("unexpected response");
+ expect(preflight.scope).toEqual(plan.scope);
+ expect(preflight.scope?.reasonCounts).toContainEqual({ reason: "IGNORED_GENERATED_OUTPUT", count: 1 });
+ expect(preflight.scope?.proposedIncludes).not.toContain("build/generated.ts");
+ const cli = Bun.spawnSync([process.execPath, join(import.meta.dir, "../../../apps/cli/src/index.ts"), "setup", "--root", root, "--dry-run", "--json"], { stdout: "pipe", stderr: "pipe" });
+ expect(cli.exitCode, new TextDecoder().decode(cli.stderr)).toBe(0);
+ expect(JSON.parse(new TextDecoder().decode(cli.stdout)).scope).toEqual(preflight.scope);
+ expect(TOOL_OUTPUT_SCHEMAS.semctx_setup.safeParse(preflight).success).toBe(true);
+ const reasonCounts = DISCOVERY_CANDIDATE_REASONS.map((reason) => ({ reason, count: 1 }));
+ expect(reasonCounts).toHaveLength(11);
+ const scope = { ...preflight.scope!, reasonCounts, roots: preflight.scope!.roots.map((entry) => ({ ...entry, reasonCounts })) };
+ const schema = TOOL_OUTPUT_SCHEMAS.semctx_setup;
+ expect(schema.safeParse({ ...preflight, scope }).success).toBe(true);
+ expect(schema.safeParse({ ...preflight, scope: { ...scope, reasonCounts: [...reasonCounts, reasonCounts[0]] } }).success).toBe(false);
+ expect(schema.safeParse({ ...preflight, scope: { ...scope, roots: [{ ...scope.roots[0], reasonCounts: [...reasonCounts, reasonCounts[0]] }] } }).success).toBe(false);
+ expect(schema.safeParse({ ...preflight, scope: { ...scope, reasonCounts: [{ reason: "UNKNOWN_REASON", count: 1 }] } }).success).toBe(false);
+ expect(schema.safeParse({ ...preflight, scope: { ...scope, roots: [{ ...scope.roots[0], reasonCounts: [{ reason: "UNKNOWN_REASON", count: 1 }] }] } }).success).toBe(false);
+});
+
+test("qualified generated output survives MCP stdio preflight through an aliased root", async () => {
+ const root = qualifiedFixture();
+ const aliasParent = mkdtempSync(join(tmpdir(), "semctx-scope-mcp-"));
+ roots.push(aliasParent);
+ const requestedRoot = join(aliasParent, "repository");
+ // Exercise canonicalization on every host, including macOS's /var -> /private/var.
+ symlinkSync(root, requestedRoot, process.platform === "win32" ? "junction" : "dir");
+ const client = new Client({ name: "semctx-scope-contract", version: "0.1.0" });
+ try {
+ const canonicalRoot = realpathSync.native(requestedRoot);
+ expect(canonicalRoot).not.toBe(requestedRoot);
+ await client.connect(new StdioClientTransport({
+ command: "bun", args: [join(import.meta.dir, "../src/index.ts")],
+ cwd: join(import.meta.dir, "../../.."), stderr: "pipe",
+ }));
+ const response = await client.callTool({ name: "semctx_setup", arguments: { repositoryRoot: requestedRoot } });
+ expect(response.isError).not.toBe(true);
+ expect(response.structuredContent).toEqual(setupTool(canonicalRoot));
+ } finally {
+ try {
+ await client.close();
+ } finally {
+ unlinkSync(requestedRoot);
+ }
+ }
+}, 30_000);
afterEach(() => {
for (const root of roots.splice(0)) {
if (!root.startsWith(join(tmpdir(), "semctx-scope-mcp-"))) throw new Error("unsafe fixture cleanup");
diff --git a/packages/repository-store/src/store.ts b/packages/repository-store/src/store.ts
index 213faa65..e7c7b36a 100644
--- a/packages/repository-store/src/store.ts
+++ b/packages/repository-store/src/store.ts
@@ -1,4 +1,4 @@
-import { EvidenceRecordSchema, ClaimSchema, TaskFrameSchema, ContextPackSchema } from "@semantic-context/core";
+import { EvidenceRecordSchema, ClaimSchema, TaskFrameSchema, ContextPackSchema, NodeKindSchema, EdgeKindSchema, EvidenceSourceKindSchema } from "@semantic-context/core";
import { constants, Database } from "bun:sqlite";
import { existsSync, lstatSync, realpathSync } from "node:fs";
import { pathToFileURL } from "node:url";
@@ -100,6 +100,7 @@ interface WalCheckpointRow {
}
const openStoreCounts = new Map();
+const WRITER_BUSY_TIMEOUT_MS = 1_000;
export interface RepositoryIndexSnapshot {
graph: RepositoryGraph;
@@ -214,7 +215,9 @@ export class SqliteRepositoryStore implements RepositoryStore {
assertUnlinkedDatabase(dbPath);
const db = new Database(dbPath, { create: true });
try {
+ db.exec(`PRAGMA busy_timeout = ${WRITER_BUSY_TIMEOUT_MS};`);
db.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = OFF;");
+ db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
db.exec(SCHEMA_SQL);
const databasePath = realpathSync(dbPath);
const store = new SqliteRepositoryStore(db, databasePath);
@@ -473,10 +476,54 @@ function isIndexed(db: Database): boolean {
return row !== null && row.c > 0;
}
-function parseJsonArray(text: string): unknown[] {
- const value = JSON.parse(text) as unknown;
- if (!Array.isArray(value)) throw new SemctxError("STORE_ERROR", "expected JSON array", { text });
- return value;
+const nodeKinds = new Set(NodeKindSchema.options);
+const edgeKinds = new Set(EdgeKindSchema.options);
+const evidenceSourceKinds = new Set(EvidenceSourceKindSchema.options);
+
+// These decoders only receive freshly parsed JSON owned by this module. JSON arrays
+// are dense and cannot carry replaced iterators or non-JSON object instances.
+function decodeTags(text: string): string[] {
+ if (typeof text !== "string") throw new Error("tags must be JSON text");
+ if (text === "[]") return [];
+ const value: unknown = JSON.parse(text);
+ if (!Array.isArray(value)) throw new Error("tags must be an array");
+ for (let index = 0; index < value.length; index++) {
+ if (typeof value[index] !== "string") throw new Error("tags must contain strings");
+ }
+ return value as string[];
+}
+
+function decodeEvidence(text: string): RepositoryNode["evidence"] {
+ if (typeof text !== "string") throw new Error("evidence must be JSON text");
+ const value: unknown = JSON.parse(text);
+ if (!Array.isArray(value)) throw new Error("evidence must be an array");
+ for (let index = 0; index < value.length; index++) {
+ const item: unknown = value[index];
+ if (item === null || typeof item !== "object" || Array.isArray(item)) throw new Error("evidence must contain records");
+ const ref = item as Record;
+ if (typeof ref["filePath"] !== "string" || typeof ref["sourceKind"] !== "string"
+ || !evidenceSourceKinds.has(ref["sourceKind"])
+ || (ref["startLine"] !== undefined && !(typeof ref["startLine"] === "number" && Number.isSafeInteger(ref["startLine"]) && ref["startLine"] > 0))
+ || (ref["endLine"] !== undefined && !(typeof ref["endLine"] === "number" && Number.isSafeInteger(ref["endLine"]) && ref["endLine"] > 0))
+ || (ref["excerpt"] !== undefined && typeof ref["excerpt"] !== "string")) {
+ throw new Error("invalid evidence reference");
+ }
+ }
+ return value as RepositoryNode["evidence"];
+}
+
+function decodeMetadata(text: string): RepositoryNode["metadata"] {
+ if (typeof text !== "string") throw new Error("metadata must be JSON text");
+ const value: unknown = text === "{}" ? {} : JSON.parse(text);
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("metadata must be a record");
+ const record = value as Record;
+ // Match the canonical schema's inherited enumerable property semantics.
+ for (const key in record) {
+ const item = record[key];
+ if (typeof item !== "string" && typeof item !== "boolean"
+ && !(typeof item === "number" && Number.isFinite(item))) throw new Error("invalid metadata value");
+ }
+ return record as RepositoryNode["metadata"];
}
function loadRow(table: string, id: string, schema: { parse(value: unknown): T }, value: () => unknown): T {
@@ -490,28 +537,49 @@ function loadRow(table: string, id: string, schema: { parse(value: unknown):
}
function rowToNode(row: NodeRow): RepositoryNode {
- return {
- id: row.id,
- kind: row.kind as RepositoryNode["kind"],
- name: row.name,
- ...(row.file_path !== null ? { filePath: row.file_path } : {}),
- ...(row.bounded_context !== null ? { boundedContext: row.bounded_context } : {}),
- ...(row.exported !== null ? { exported: row.exported === 1 } : {}),
- evidence: parseJsonArray(row.evidence) as RepositoryNode["evidence"],
- tags: parseJsonArray(row.tags) as string[],
- metadata: JSON.parse(row.metadata) as RepositoryNode["metadata"],
- };
+ try {
+ if (typeof row.id !== "string" || row.id.length === 0
+ || typeof row.kind !== "string" || !nodeKinds.has(row.kind) || typeof row.name !== "string"
+ || (row.file_path !== null && typeof row.file_path !== "string")
+ || (row.bounded_context !== null && typeof row.bounded_context !== "string")
+ || (row.exported !== null && row.exported !== 0 && row.exported !== 1)) throw new Error("invalid node scalar");
+ return {
+ id: row.id,
+ kind: row.kind as RepositoryNode["kind"],
+ name: row.name,
+ ...(row.file_path !== null ? { filePath: row.file_path } : {}),
+ ...(row.bounded_context !== null ? { boundedContext: row.bounded_context } : {}),
+ ...(row.exported !== null ? { exported: row.exported === 1 } : {}),
+ evidence: decodeEvidence(row.evidence),
+ tags: decodeTags(row.tags),
+ metadata: decodeMetadata(row.metadata),
+ };
+ } catch (cause) {
+ throw new SemctxError("STORE_ERROR", "invalid persisted repository row", {
+ table: "nodes", id: row.id, cause: cause instanceof Error ? cause.message : String(cause),
+ });
+ }
}
function rowToEdge(row: EdgeRow): RepositoryEdge {
- return {
- id: row.id,
- kind: row.kind as RepositoryEdge["kind"],
- from: row.from_id,
- to: row.to_id,
- evidence: parseJsonArray(row.evidence) as RepositoryEdge["evidence"],
- metadata: JSON.parse(row.metadata) as RepositoryEdge["metadata"],
- };
+ try {
+ if (typeof row.id !== "string" || row.id.length === 0
+ || typeof row.kind !== "string" || !edgeKinds.has(row.kind)
+ || typeof row.from_id !== "string" || row.from_id.length === 0
+ || typeof row.to_id !== "string" || row.to_id.length === 0) throw new Error("invalid edge scalar");
+ return {
+ id: row.id,
+ kind: row.kind as RepositoryEdge["kind"],
+ from: row.from_id,
+ to: row.to_id,
+ evidence: decodeEvidence(row.evidence),
+ metadata: decodeMetadata(row.metadata),
+ };
+ } catch (cause) {
+ throw new SemctxError("STORE_ERROR", "invalid persisted repository row", {
+ table: "edges", id: row.id, cause: cause instanceof Error ? cause.message : String(cause),
+ });
+ }
}
function rowToEvidence(row: EvidenceRow): EvidenceRecord {
diff --git a/packages/repository-store/test/fixtures/locked-store.ts b/packages/repository-store/test/fixtures/locked-store.ts
new file mode 100644
index 00000000..91c07b49
--- /dev/null
+++ b/packages/repository-store/test/fixtures/locked-store.ts
@@ -0,0 +1,17 @@
+import { constants, Database } from "bun:sqlite";
+
+const [path, mode] = process.argv.slice(2);
+if (path === undefined) throw new Error("database path is required");
+const db = new Database(path);
+db.exec("PRAGMA journal_mode = WAL;");
+db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
+db.exec(mode === "reader" ? "BEGIN;" : "BEGIN IMMEDIATE;");
+if (mode === "reader") db.query("SELECT value FROM meta WHERE key = 'probe'").get();
+else db.query("INSERT OR REPLACE INTO meta (key, value) VALUES ('holder', 'committed')").run();
+console.log("locked");
+for await (const chunk of Bun.stdin.stream()) {
+ if (new TextDecoder().decode(chunk).includes("delayed")) await Bun.sleep(500);
+ db.exec("COMMIT;");
+ db.close();
+ break;
+}
diff --git a/packages/repository-store/test/load-validation.test.ts b/packages/repository-store/test/load-validation.test.ts
index c350eb68..afec75d9 100644
--- a/packages/repository-store/test/load-validation.test.ts
+++ b/packages/repository-store/test/load-validation.test.ts
@@ -3,7 +3,7 @@ import { Database } from "bun:sqlite";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
-import { ClaimSchema, ContextPackSchema, RepositoryNodeSchema, SemctxError, TaskFrameSchema, type Claim, type ContextPack, type EvidenceRecord, type RepositoryGraph, type TaskFrame } from "@semantic-context/core";
+import { ClaimSchema, ContextPackSchema, RepositoryEdgeSchema, RepositoryNodeSchema, SemctxError, TaskFrameSchema, type Claim, type ContextPack, type EvidenceRecord, type RepositoryGraph, type TaskFrame } from "@semantic-context/core";
import { SqliteRepositoryReader, SqliteRepositoryStore } from "@semantic-context/repository-store";
const graph: RepositoryGraph = {
@@ -55,6 +55,19 @@ function expectLoadError(load: () => unknown, table: string, id: string): void {
}
const corruptions = [
+ ["nodes", "node:1", "kind", "unknown"],
+ ["nodes", "node:1", "exported", 2],
+ ["nodes", "node:1", "exported", "bad"],
+ ["nodes", "node:1", "evidence", "{"],
+ ["nodes", "node:1", "evidence", "null"],
+ ["nodes", "node:1", "evidence", "[null]"],
+ ["nodes", "node:1", "evidence", '[{"filePath":"a.ts","sourceKind":"unknown"}]'],
+ ["nodes", "node:1", "evidence", '[{"filePath":"a.ts","sourceKind":"code","startLine":"1"}]'],
+ ["nodes", "node:1", "tags", "[1]"],
+ ["nodes", "node:1", "metadata", '{"nested":{}}'],
+ ["edges", "edge:1", "kind", "unknown"],
+ ["edges", "edge:1", "evidence", "[{}]"],
+ ["edges", "edge:1", "metadata", "[]"],
["evidence", "evidence:1", "source_kind", "unknown"],
["evidence", "evidence:1", "start_line", "zero"],
["claims", "claim:1", "kind", "unknown"],
@@ -261,7 +274,8 @@ describe("SQLite load validation", () => {
db.close();
const store = SqliteRepositoryStore.open(path);
try {
- const load = table === "claims" ? () => store.loadClaims()
+ const load = table === "nodes" || table === "edges" ? () => store.loadGraph()
+ : table === "claims" ? () => store.loadClaims()
: table === "evidence" ? () => store.loadEvidence()
: table === "task_frames" ? () => store.getTaskFrame(id)
: () => store.getContextPack(id);
@@ -272,6 +286,7 @@ describe("SQLite load validation", () => {
}
const reader = SqliteRepositoryReader.openExisting(path);
try {
+ if (table === "nodes" || table === "edges") expectLoadError(() => reader.loadGraph(), table, id);
if (table === "claims") expectLoadError(() => reader.loadClaims(), table, id);
if (table === "evidence") expectLoadError(() => reader.loadEvidence(), table, id);
if (table === "task_frames") expectLoadError(() => reader.getTaskFrame(id), table, id);
@@ -302,3 +317,127 @@ describe("SQLite load validation", () => {
}
}));
});
+
+describe("owned graph decoding matches canonical JSON schemas", () => {
+ it("keeps empty collections independent across rows and successive snapshots", () => database((path) => {
+ const emptyGraph: RepositoryGraph = {
+ nodes: [graph.nodes[0]!, { ...graph.nodes[0]!, id: "node:2" }],
+ edges: [graph.edges[0]!, { ...graph.edges[0]!, id: "edge:2" }],
+ };
+ const writer = SqliteRepositoryStore.open(path);
+ try { writer.saveGraph(emptyGraph, []); } finally { writer.close(); }
+ for (const open of [(p: string) => SqliteRepositoryStore.open(p), (p: string) => SqliteRepositoryReader.openExisting(p)]) {
+ const reader = open(path);
+ try {
+ const loaded = reader.loadGraph();
+ loaded.nodes[0]!.tags.push("modified");
+ loaded.nodes[0]!.metadata["value"] = "modified";
+ loaded.edges[0]!.metadata["value"] = "modified";
+ expect(loaded.nodes[1]).toEqual(emptyGraph.nodes[1]);
+ expect(loaded.edges[1]).toEqual(emptyGraph.edges[1]);
+ expect(reader.loadGraph()).toEqual(emptyGraph);
+ } finally { reader.close(); }
+ }
+ }));
+
+ it("retains canonical inherited enumerable metadata semantics", () => database((path) => {
+ const key = "semctxOwnedMetadataInheritedTest";
+ try {
+ for (const value of ["accepted", {}]) {
+ Object.defineProperty(Object.prototype, key, { value, enumerable: true, configurable: true });
+ const valid = typeof value === "string";
+ expect(RepositoryNodeSchema.safeParse(graph.nodes[0]).success).toBe(valid);
+ expect(RepositoryEdgeSchema.safeParse(graph.edges[0]).success).toBe(valid);
+ for (const open of [(p: string) => SqliteRepositoryStore.open(p), (p: string) => SqliteRepositoryReader.openExisting(p)]) {
+ const reader = open(path);
+ try {
+ if (valid) expect(reader.loadGraph()).toEqual(graph);
+ else expectLoadError(() => reader.loadGraph(), "nodes", "node:1");
+ } finally { reader.close(); }
+ }
+ }
+ } finally { Reflect.deleteProperty(Object.prototype, key); }
+ }));
+
+ const cases = [
+ ["nodes", "name", 42, true], // TEXT affinity returns a string.
+ ["nodes", "file_path", null, true],
+ ["nodes", "bounded_context", null, true],
+ ["nodes", "exported", "1", true], // INTEGER affinity returns 1.
+ ["nodes", "exported", 0, true],
+ ["nodes", "exported", null, true],
+ ["nodes", "exported", 1.5, false],
+ ["nodes", "name", new Uint8Array([97]), false],
+ ["nodes", "file_path", new Uint8Array([97]), false],
+ ["nodes", "bounded_context", new Uint8Array([97]), false],
+ ["nodes", "id", "", false],
+ ["nodes", "id", null, false],
+ ["edges", "id", "", false],
+ ["edges", "from_id", "", false],
+ ["edges", "to_id", new Uint8Array([97]), false],
+ ["nodes", "tags", "null", false],
+ ["nodes", "tags", "{}", false],
+ ["nodes", "tags", '"[]"', false],
+ ["nodes", "tags", '["", "x", "x"]', true],
+ ["nodes", "evidence", '[[]]', false],
+ ["nodes", "evidence", '[{"filePath":1,"sourceKind":"code"}]', false],
+ ["nodes", "evidence", '[{"filePath":"","sourceKind":"manual","startLine":9007199254740991,"endLine":1,"excerpt":"","extension":{"n":1e400},"__proto__":{"keep":true}}]', true],
+ ["nodes", "evidence", '[{"filePath":"a","sourceKind":"code","startLine":9007199254740992}]', false],
+ ["nodes", "evidence", '[{"filePath":"a","sourceKind":"code","endLine":1e400}]', false],
+ ["nodes", "evidence", '[{"filePath":"a","sourceKind":"code","startLine":0}]', false],
+ ["edges", "evidence", '[{"filePath":"a","sourceKind":"git","endLine":-1}]', false],
+ ["edges", "evidence", '[{"filePath":"a","sourceKind":"runtime","excerpt":null}]', false],
+ ["edges", "evidence", '[{"filePath":"","sourceKind":"test","extension":[1,null],"__proto__":"keep"}]', true],
+ ["nodes", "metadata", 'null', false],
+ ["nodes", "metadata", '"{}"', false],
+ ["nodes", "metadata", '{"n":1e400}', false],
+ ["edges", "metadata", '{"n":-1e400}', false],
+ ["edges", "metadata", '{"n":null}', false],
+ ["nodes", "metadata", '{"__proto__":"keep","n":9007199254740992,"b":false,"s":""}', true],
+ ["edges", "metadata", '{"__proto__":{"nested":true}}', false],
+ ] as const;
+
+ for (const [index, [table, column, value, valid]] of cases.entries()) {
+ it(`matches canonical ${table}.${column} case ${index + 1}`, () => database((path) => {
+ const originalId = table === "nodes" ? "node:1" : "edge:1";
+ const db = new Database(path);
+ let raw: Record;
+ try {
+ db.query(`UPDATE ${table} SET ${column} = ? WHERE id = ?`).run(value, originalId);
+ raw = db.query(`SELECT * FROM ${table}`).get() as Record;
+ } finally { db.close(); }
+ const expected = table === "nodes" ? {
+ id: raw["id"], kind: raw["kind"], name: raw["name"],
+ ...(raw["file_path"] !== null ? { filePath: raw["file_path"] } : {}),
+ ...(raw["bounded_context"] !== null ? { boundedContext: raw["bounded_context"] } : {}),
+ ...(raw["exported"] !== null ? { exported: raw["exported"] === 1 } : {}),
+ evidence: JSON.parse(raw["evidence"] as string) as unknown,
+ tags: JSON.parse(raw["tags"] as string) as unknown,
+ metadata: JSON.parse(raw["metadata"] as string) as unknown,
+ } : {
+ id: raw["id"], kind: raw["kind"], from: raw["from_id"], to: raw["to_id"],
+ evidence: JSON.parse(raw["evidence"] as string) as unknown,
+ metadata: JSON.parse(raw["metadata"] as string) as unknown,
+ };
+ const canonical = (table === "nodes" ? RepositoryNodeSchema : RepositoryEdgeSchema).safeParse(expected);
+ // SQLite's exported encoding is stricter than a decoded boolean.
+ if (column === "exported" && !valid) expect(canonical.success).toBe(true);
+ else expect(canonical.success).toBe(valid);
+ for (const open of [(p: string) => SqliteRepositoryStore.open(p), (p: string) => SqliteRepositoryReader.openExisting(p)]) {
+ const reader = open(path);
+ try {
+ if (valid) {
+ const loaded = reader.loadGraph()[table][0]!;
+ expect(loaded as unknown).toEqual(expected);
+ expect(JSON.stringify(loaded)).toBe(JSON.stringify(expected));
+ } else {
+ expect(() => reader.loadGraph()).toThrow(SemctxError);
+ try { reader.loadGraph(); } catch (error) {
+ expect(error).toMatchObject({ code: "STORE_ERROR", details: { table, id: raw["id"], cause: expect.any(String) } });
+ }
+ }
+ } finally { reader.close(); }
+ }
+ }));
+ }
+});
diff --git a/packages/repository-store/test/readonly-store.test.ts b/packages/repository-store/test/readonly-store.test.ts
index c2808a4c..985c2485 100644
--- a/packages/repository-store/test/readonly-store.test.ts
+++ b/packages/repository-store/test/readonly-store.test.ts
@@ -240,6 +240,34 @@ describe("SqliteRepositoryReader", () => {
expect(existsSync(`${dbFile}-shm`)).toBe(false);
expect(() => SqliteRepositoryReader.openExisting(dbFile).close()).not.toThrow();
});
+
+ it("leaves the existing database readable when writer initialization fails", () => {
+ const directory = temporaryDirectory();
+ const dbFile = join(directory, "index.db");
+ const writer = SqliteRepositoryStore.open(dbFile);
+ writer.setMeta("initialization_probe", "preserved");
+ writer.close();
+ const db = new Database(dbFile);
+ db.exec(`
+ CREATE TRIGGER reject_schema_version
+ BEFORE INSERT ON meta
+ WHEN NEW.key = 'schema_version'
+ BEGIN
+ SELECT RAISE(ABORT, 'schema version rejected');
+ END;
+ `);
+ db.close();
+
+ expect(() => SqliteRepositoryStore.open(dbFile)).toThrow("schema version rejected");
+ expect(existsSync(`${dbFile}-wal`)).toBe(false);
+ expect(existsSync(`${dbFile}-shm`)).toBe(false);
+ const reader = SqliteRepositoryReader.openExisting(dbFile);
+ try {
+ expect(reader.getMeta("initialization_probe")).toBe("preserved");
+ } finally {
+ reader.close();
+ }
+ });
});
function temporaryDirectory(): string {
diff --git a/packages/repository-store/test/store-contention.test.ts b/packages/repository-store/test/store-contention.test.ts
new file mode 100644
index 00000000..bb7c5e40
--- /dev/null
+++ b/packages/repository-store/test/store-contention.test.ts
@@ -0,0 +1,135 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { existsSync, mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { SqliteRepositoryReader, SqliteRepositoryStore } from "@semantic-context/repository-store";
+
+const directories: string[] = [];
+afterEach(() => {
+ for (const directory of directories.splice(0)) rmSync(directory, { recursive: true, force: true });
+});
+
+function databasePath(): string {
+ const directory = mkdtempSync(join(tmpdir(), "semctx-contention-"));
+ directories.push(directory);
+ return join(directory, "index.db");
+}
+
+async function lockDatabase(path: string, mode = "writer") {
+ const child = Bun.spawn([process.execPath, join(import.meta.dir, "fixtures", "locked-store.ts"), path, mode], {
+ stdin: "pipe", stdout: "pipe", stderr: "pipe", timeout: 10_000,
+ });
+ const reader = child.stdout.getReader();
+ const ready = await reader.read();
+ reader.releaseLock();
+ expect(new TextDecoder().decode(ready.value).trim()).toBe("locked");
+ return child;
+}
+
+async function finish(child: Awaited>): Promise {
+ expect(await child.exited).toBe(0);
+ expect(await new Response(child.stderr).text()).toBe("");
+}
+
+function readProbe(path: string): string | undefined {
+ expect(existsSync(`${path}-wal`)).toBe(false);
+ expect(existsSync(`${path}-shm`)).toBe(false);
+ const reader = SqliteRepositoryReader.openExisting(path);
+ try {
+ return reader.getMeta("probe");
+ } finally {
+ reader.close();
+ }
+}
+
+describe("repository store contention across processes", () => {
+ for (const operation of ["open", "write"]) {
+ it(`bounds ${operation} contention and leaves the database recoverable`, async () => {
+ const path = databasePath();
+ const store = SqliteRepositoryStore.open(path);
+ if (operation === "open") store.close();
+ const holder = await lockDatabase(path);
+ const startedAt = performance.now();
+ try {
+ expect(() => operation === "open" ? SqliteRepositoryStore.open(path) : store.setMeta("probe", "lost"))
+ .toThrow(expect.objectContaining({ code: "SQLITE_BUSY" }));
+ const elapsed = performance.now() - startedAt;
+ expect(elapsed).toBeGreaterThanOrEqual(800);
+ expect(elapsed).toBeLessThan(4_000);
+ } finally {
+ await holder.stdin.write("release\n");
+ await holder.stdin.end();
+ await finish(holder);
+ store.close();
+ }
+ expect(readProbe(path)).toBeUndefined();
+ const reader = SqliteRepositoryReader.openExisting(path);
+ try {
+ expect(reader.getMeta("holder")).toBe("committed");
+ } finally {
+ reader.close();
+ }
+ });
+ }
+
+ it("opens a writer after a concurrent transaction releases its lock", async () => {
+ const path = databasePath();
+ SqliteRepositoryStore.open(path).close();
+ const holder = await lockDatabase(path);
+ await holder.stdin.write("delayed\n");
+ await holder.stdin.flush();
+ await holder.stdin.end();
+ try {
+ const startedAt = performance.now();
+ const store = SqliteRepositoryStore.open(path);
+ try {
+ expect(performance.now() - startedAt).toBeGreaterThanOrEqual(100);
+ store.setMeta("probe", "opened");
+ } finally {
+ await finish(holder);
+ store.close();
+ }
+ expect(readProbe(path)).toBe("opened");
+ } finally {
+ if (holder.exitCode === null) await finish(holder);
+ }
+ });
+
+ it("waits for a concurrent writer before persisting a value", async () => {
+ const path = databasePath();
+ const store = SqliteRepositoryStore.open(path);
+ const holder = await lockDatabase(path);
+ await holder.stdin.write("delayed\n");
+ await holder.stdin.flush();
+ await holder.stdin.end();
+ try {
+ const startedAt = performance.now();
+ store.setMeta("probe", "written");
+ expect(performance.now() - startedAt).toBeGreaterThanOrEqual(100);
+ } finally {
+ await finish(holder);
+ store.close();
+ }
+ expect(readProbe(path)).toBe("written");
+ });
+
+ it("waits for a transient reader before checkpointing and removing sidecars", async () => {
+ const path = databasePath();
+ const store = SqliteRepositoryStore.open(path);
+ store.setMeta("probe", "before");
+ const holder = await lockDatabase(path, "reader");
+ store.setMeta("probe", "after");
+ await holder.stdin.write("delayed\n");
+ await holder.stdin.flush();
+ await holder.stdin.end();
+ try {
+ const startedAt = performance.now();
+ store.close();
+ expect(performance.now() - startedAt).toBeGreaterThanOrEqual(100);
+ } finally {
+ await finish(holder);
+ store.close();
+ }
+ expect(readProbe(path)).toBe("after");
+ });
+});
diff --git a/packages/repository-store/test/workspace.test.ts b/packages/repository-store/test/workspace.test.ts
index f04fb62c..f29ed5b6 100644
--- a/packages/repository-store/test/workspace.test.ts
+++ b/packages/repository-store/test/workspace.test.ts
@@ -42,7 +42,7 @@ describe("disk config projection types (#243)", () => {
expectTypeOf(policy).toEqualTypeOf>();
expectTypeOf(policy.version).toEqualTypeOf<2>();
- expectTypeOf(policy.selectionMode).toEqualTypeOf<"globs-v1">();
+ expectTypeOf(policy.selectionMode).toEqualTypeOf();
expectTypeOf(policy.languages).toEqualTypeOf();
expectTypeOf(policy).not.toHaveProperty("repositoryRoot");
});
@@ -60,7 +60,7 @@ describe("disk config projection types (#243)", () => {
expectTypeOf(policy).not.toHaveProperty("repositoryRoot");
if (policy.version === 2) {
expectTypeOf(policy).toEqualTypeOf>();
- expectTypeOf(policy.selectionMode).toEqualTypeOf<"globs-v1">();
+ expectTypeOf(policy.selectionMode).toEqualTypeOf();
expectTypeOf(policy.languages).toEqualTypeOf();
} else {
expectTypeOf(policy).toEqualTypeOf>();
diff --git a/packages/ts-analyzer/src/analyze.ts b/packages/ts-analyzer/src/analyze.ts
index 6f9f22c5..cbfd2716 100644
--- a/packages/ts-analyzer/src/analyze.ts
+++ b/packages/ts-analyzer/src/analyze.ts
@@ -1,4 +1,5 @@
import { readFileSync } from "node:fs";
+import ts from "typescript";
import { basename } from "node:path";
import {
repositoryId,
@@ -46,8 +47,12 @@ import {
type IndexWorkerSelection,
type TsExtraction,
type TypeScriptParallelism,
+ type CompilerInputSnapshot,
+ retainedCompilerSystem,
} from "./ts-symbols";
import { groupSymbols } from "./symbol-grouping";
+import { inspectJavaScriptSource } from "./javascript-diagnostics";
+import { registerOpaqueCallbackOwnership } from "./semantic-dependency-diagnostics";
import {
degradeDivergentMarkerNodes,
detectMarkerDivergence,
@@ -68,12 +73,22 @@ export interface AsyncAnalysisResult {
parallelism: TypeScriptParallelism;
}
+const qualifiedCallReasons = new WeakMap>();
+/** Private integration seam; reasons never alter legacy serialized analysis shapes. */
+export function getQualifiedCallIntegrityReasons(analysis: AnalysisResult): ReadonlyMap {
+ return qualifiedCallReasons.get(analysis) ?? new Map();
+}
+
type GraphBuilder = DeterministicGraphAssembler;
const TYPESCRIPT_PRODUCER: ProducerIdentity = {
identity: "@semantic-context/ts-analyzer",
version: "0.1.0",
};
+const JAVASCRIPT_PRODUCER: ProducerIdentity = {
+ identity: "@semantic-context/ts-analyzer/javascript",
+ version: "0.1.0",
+};
const PLANE_A_FACT_SCHEMA = {
schemaVersion: 1,
@@ -85,30 +100,35 @@ const SYMBOL_EDGE_EVIDENCE = (relPath: string, line: number, kind: EvidenceRef["
{ filePath: relPath, startLine: line, sourceKind: kind },
];
-export function analyzeRepository(config: SemctxConfig, discoveredFiles?: readonly DiscoveredFile[]): AnalysisResult {
+export function analyzeRepository(config: SemctxConfig, discoveredFiles?: readonly DiscoveredFile[], compilerInputs?: CompilerInputSnapshot): AnalysisResult {
const files = discoveredFiles === undefined ? discoverFiles(config) : [...discoveredFiles];
const tsFiles = files.filter(isTypeScriptSource);
+ assertSnapshotInputs(tsFiles, compilerInputs, config);
const extraction = extractTypeScript(
tsFiles.map((file) => file.absPath),
config.repositoryRoot,
+ compilerInputs,
);
- return assembleRepository(config, files, extraction);
+ return assembleRepository(config, files, extraction, compilerInputs);
}
export async function analyzeRepositoryAsync(
config: SemctxConfig,
discoveredFiles?: readonly DiscoveredFile[],
workers: IndexWorkerSelection = "auto",
+ compilerInputs?: CompilerInputSnapshot,
): Promise {
const files = discoveredFiles === undefined ? discoverFiles(config) : [...discoveredFiles];
const tsFiles = files.filter(isTypeScriptSource);
+ assertSnapshotInputs(tsFiles, compilerInputs, config);
const result = await extractTypeScriptParallel(
tsFiles.map((file) => file.absPath),
config.repositoryRoot,
workers,
+ compilerInputs,
);
return {
- analysis: assembleRepository(config, files, result.extraction),
+ analysis: assembleRepository(config, files, result.extraction, compilerInputs),
parallelism: result.parallelism,
};
}
@@ -118,6 +138,7 @@ export function assembleRepository(
config: SemctxConfig,
files: readonly DiscoveredFile[],
extraction: TsExtraction,
+ compilerInputs?: CompilerInputSnapshot,
): AnalysisResult {
const tsFiles = files.filter(isTypeScriptSource);
const analyzedFiles = files.filter((file) =>
@@ -142,11 +163,13 @@ export function assembleRepository(
const isTest = file.role === "test";
const id = isTest ? testId(file.relPath) : moduleId(file.relPath);
nodeIdByRel.set(file.relPath, id);
+ const javascriptFacts = file.language === "javascript" ? inspectJavaScriptSource(file.absPath, file.content, config.repositoryRoot, compilerInputs) : undefined;
builder.node({
id,
kind: isTest ? "test" : "module",
name: basename(file.relPath),
filePath: file.relPath,
+ ...(javascriptFacts ? { metadata: { staticExports: JSON.stringify(javascriptFacts.staticExports), staticModuleLinks: JSON.stringify(javascriptFacts.staticModuleLinks) } } : {}),
evidence: [{ filePath: file.relPath, sourceKind: isTest ? "test" : "code" }],
});
builder.edge("belongs_to", id, repoNodeId, [{ filePath: file.relPath, sourceKind: "code" }]);
@@ -229,7 +252,47 @@ export function assembleRepository(
// Call edges between resolved symbols (best-effort static). Both endpoints are matched on the
// exact scope-qualified coordinate; anything that resolves to more than one node is dropped —
// an approximate call edge is worse than a missing one.
+ const callReasons = new Map();
+ const checkLocalCalls = config.version === 2 && config.selectionMode === "qualified-static-v1" && config.analysisProfile === "modelo-suite-static-v1";
+ const dynamicTestImports = new Set();
+ if (checkLocalCalls && compilerInputs !== undefined) for (const file of tsFiles.filter(file => file.role === "test")) {
+ const text = compilerInputs.get(file.absPath);
+ if (text === undefined) continue;
+ const source = ts.createSourceFile(file.absPath, text, ts.ScriptTarget.Latest, true);
+ const visit = (node: ts.Node): void => {
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length === 1
+ && ts.isStringLiteral(node.arguments[0]!)) dynamicTestImports.add(`${file.relPath}:${source.getLineAndCharacterOfPosition(node.getStart()).line + 1}:${node.arguments[0]!.text}`);
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ }
+ const modeledModules = new Set(extraction.modules);
+ const checkCoordinate = (callerPath: string, role: "CALLER" | "CALLEE", path: string | undefined, coordinate: string | undefined): void => {
+ if (!checkLocalCalls || path === undefined || coordinate === undefined || !modeledModules.has(path)) return;
+ const matches = byQualified.get(path)?.get(coordinate)?.length ?? 0;
+ if (matches === 1) return;
+ const reason = `QUALIFIED_CALL_COORDINATE_${role}_${matches === 0 ? "MISSING" : "AMBIGUOUS"}:${path}:${coordinate}`;
+ const reasons = callReasons.get(callerPath) ?? [];
+ if (!reasons.includes(reason)) reasons.push(reason);
+ callReasons.set(callerPath, reasons);
+ };
for (const call of extraction.calls) {
+ if (checkLocalCalls && call.callerUnmodeledReason !== undefined && modeledModules.has(call.callerRelPath)) {
+ const reasons = callReasons.get(call.callerRelPath) ?? [];
+ const reason = `QUALIFIED_CALL_COORDINATE_CALLER_UNMODELED:${call.callerRelPath}:${call.callerUnmodeledReason}`;
+ if (!reasons.includes(reason)) reasons.push(reason);
+ callReasons.set(call.callerRelPath, reasons);
+ continue;
+ }
+ if (checkLocalCalls && call.calleeUnmodeledReason !== undefined && call.calleeRelPath !== undefined && modeledModules.has(call.calleeRelPath)) {
+ const reasons = callReasons.get(call.callerRelPath) ?? [];
+ const reason = `QUALIFIED_CALL_COORDINATE_CALLEE_UNMODELED:${call.calleeRelPath}:${call.calleeUnmodeledReason}`;
+ if (!reasons.includes(reason)) reasons.push(reason);
+ callReasons.set(call.callerRelPath, reasons);
+ continue;
+ }
+ checkCoordinate(call.callerRelPath, "CALLER", call.callerRelPath, call.callerSymbolPath);
+ checkCoordinate(call.callerRelPath, "CALLEE", call.calleeRelPath, call.calleeSymbolPath);
if (call.calleeRelPath === undefined || call.calleeSymbolPath === undefined) continue;
const callee = unique(byQualified.get(call.calleeRelPath)?.get(call.calleeSymbolPath));
if (callee === undefined) continue;
@@ -249,9 +312,39 @@ export function assembleRepository(
// tested_by / covers via test-file imports resolving to symbols.
for (const imp of extraction.imports) {
+ if (checkLocalCalls && imp.resolvedRelPath !== undefined && imp.bindingTargets === undefined
+ && dynamicTestImports.has(`${imp.fromRelPath}:${imp.line}:${imp.moduleSpecifier}`)) {
+ const reason = `QUALIFIED_TEST_DYNAMIC_IMPORT_BINDING_UNPROVEN:${imp.resolvedRelPath}`;
+ const reasons = callReasons.get(imp.fromRelPath) ?? [];
+ if (!reasons.includes(reason)) reasons.push(reason);
+ callReasons.set(imp.fromRelPath, reasons);
+ }
+ if (checkLocalCalls) for (const binding of imp.bindingTargets ?? []) {
+ if (binding.runtimeUsed === false || !modeledModules.has(binding.relPath)) continue;
+ const matches = byQualified.get(binding.relPath)?.get(binding.symbolPath)?.length ?? 0;
+ if (matches === 1 && binding.unmodeledReason === undefined) continue;
+ const prefix = roleByRel.get(imp.fromRelPath) === "test" ? "QUALIFIED_TEST_BINDING" : "QUALIFIED_IMPORT_BINDING";
+ const kind = matches === 0 ? "MISSING" : matches > 1 ? "AMBIGUOUS" : "UNMODELED";
+ const reason = `${prefix}_${kind}:${binding.relPath}:${binding.symbolPath}${kind === "UNMODELED" ? `:${binding.unmodeledReason}` : ""}`;
+ const reasons = callReasons.get(imp.fromRelPath) ?? [];
+ if (!reasons.includes(reason)) reasons.push(reason);
+ callReasons.set(imp.fromRelPath, reasons);
+ }
if (roleByRel.get(imp.fromRelPath) !== "test") continue;
- if (imp.resolvedRelPath === undefined) continue;
const testNodeId = nodeIdByRel.get(imp.fromRelPath);
+ if (testNodeId === undefined) continue;
+ if (imp.bindingTargets !== undefined) {
+ for (const binding of imp.bindingTargets) {
+ if (binding.unmodeledReason !== undefined) continue;
+ const target = unique(byQualified.get(binding.relPath)?.get(binding.symbolPath));
+ if (target === undefined) continue;
+ const ev = SYMBOL_EDGE_EVIDENCE(imp.fromRelPath, imp.line, "test");
+ builder.edge("tested_by", target.id, testNodeId, ev);
+ builder.edge("covers", testNodeId, target.id, ev);
+ }
+ continue;
+ }
+ if (imp.resolvedRelPath === undefined) continue;
const perModule = byImportableName.get(imp.resolvedRelPath);
if (testNodeId === undefined || perModule === undefined) continue;
for (const name of imp.names) {
@@ -279,9 +372,11 @@ export function assembleRepository(
degradeDivergentMarkerNodes(builder.nodes.values(), markerDivergences);
const analysis = builder.build();
+ if (checkLocalCalls && compilerInputs !== undefined) registerOpaqueCallbackOwnership(compilerInputs, analysis.graph, config.repositoryRoot);
+ if (checkLocalCalls) qualifiedCallReasons.set(analysis, callReasons);
return attachPlaneASidecar(
analysis,
- buildTypeScriptSidecar(config, analyzedFiles, builder, repoNodeId),
+ buildTypeScriptSidecar(config, analyzedFiles, builder, repoNodeId, compilerInputs),
);
}
@@ -308,7 +403,18 @@ function unique(nodes: readonly RepositoryNode[] | undefined): RepositoryNode |
function isTypeScriptSource(file: DiscoveredFile): boolean {
return (file.role === "source" || file.role === "test")
- && (file.language === undefined || file.language === "typescript");
+ && (file.language === undefined || file.language === "typescript" || file.language === "javascript");
+}
+
+function assertSnapshotInputs(files: readonly DiscoveredFile[], compilerInputs: CompilerInputSnapshot | undefined, config: SemctxConfig): void {
+ if (String(config.version === 2 ? config.selectionMode : "") === "qualified-static-v1" && compilerInputs === undefined) {
+ throw new Error("SOURCE_SNAPSHOT_REQUIRED: qualified analysis requires retained source and configuration inputs");
+ }
+ if (compilerInputs === undefined) return;
+ const system = retainedCompilerSystem(compilerInputs);
+ for (const file of files) {
+ if (system.readFile(file.absPath) !== file.content) throw new Error(`SOURCE_SNAPSHOT_MISMATCH: ${file.relPath}`);
+ }
}
function buildTypeScriptSidecar(
@@ -316,6 +422,7 @@ function buildTypeScriptSidecar(
files: readonly DiscoveredFile[],
builder: DeterministicGraphAssembler,
repositoryIdentity: string,
+ compilerInputs?: CompilerInputSnapshot,
): PlaneASidecarV1 {
const selectedPaths = files.map((file) => file.relPath).sort();
const sourceInputs = files
@@ -389,6 +496,12 @@ function buildTypeScriptSidecar(
const languageFiles = files.filter((file) =>
(file.language ?? sourceLanguage(file.relPath)) === language);
const languagePaths = languageFiles.map((file) => file.relPath).sort();
+ const javascriptDiagnostics = language === "javascript"
+ ? languageFiles.map(file => inspectJavaScriptSource(file.absPath, file.content, config.repositoryRoot, compilerInputs))
+ : [];
+ const analysisReasons = [...new Set(javascriptDiagnostics.flatMap(result => result.reasons))].sort();
+ const parsingFailed = javascriptDiagnostics.some(result => result.parseFailed);
+ const producer = language === "javascript" ? JAVASCRIPT_PRODUCER : TYPESCRIPT_PRODUCER;
const languageSourceDigest = digestCanonical(sourceInputs.filter((input) =>
languagePaths.includes(input.relPath)));
const languageScope: ArtifactScope = {
@@ -397,7 +510,7 @@ function buildTypeScriptSidecar(
selectedPathSetDigest: digestCanonical(languagePaths),
selectedPaths: languagePaths,
language,
- ...(language === "typescript"
+ ...(language === "typescript" || language === "javascript"
? { dialectVersion: TYPESCRIPT_DIALECT_VERSION }
: {}),
};
@@ -406,29 +519,29 @@ function buildTypeScriptSidecar(
profileId: `${language}:${factKind}:${languageScope.selectedPathSetDigest}:${factSchemaDigest}`,
factKind,
scope: languageScope,
- producer: TYPESCRIPT_PRODUCER,
+ producer,
producerConfigurationDigest,
factSchemaDigest,
evidenceContract: "source-lines-v1",
- resolutionSemantics: language === "typescript"
+ resolutionSemantics: language === "javascript" ? "javascript-static-esm-v1" : language === "typescript"
? "typescript-static-v1"
: "structural-source-v1",
soundnessClaim: "best-effort-static",
- completenessClaim: "producer-declared",
+ completenessClaim: analysisReasons.length > 0 ? "partial" : "producer-declared",
negativeEvidenceEligible: false,
- label: "structural",
+ label: analysisReasons.length > 0 ? "partial" : "structural",
}));
const batch: FactBatchV1 = {
schemaVersion: 1,
batchId: digestCanonical({
scope: languageScope,
- producer: TYPESCRIPT_PRODUCER,
+ producer,
producerConfigurationDigest,
factSchemaDigest,
facts: languageFacts,
}),
scope: languageScope,
- producer: TYPESCRIPT_PRODUCER,
+ producer,
producerConfigurationDigest,
factSchemaDigest,
sourceDigest: languageSourceDigest,
@@ -443,10 +556,10 @@ function buildTypeScriptSidecar(
candidateIdentity: `${language}:${languageScope.selectedPathSetDigest}`,
scope: languageScope,
selectionDecision: "selected",
- analysisOutcome: "analyzed",
+ analysisOutcome: parsingFailed ? "failed" : "analyzed",
selectionReasons: [],
- analysisReasons: [],
- selectedProducer: TYPESCRIPT_PRODUCER,
+ analysisReasons: parsingFailed ? ["PRODUCER_FAILED", ...analysisReasons] : analysisReasons,
+ selectedProducer: producer,
});
}
return {
@@ -465,7 +578,7 @@ function buildTypeScriptSidecar(
factSchemaDigest,
}),
status: "completed",
- producer: TYPESCRIPT_PRODUCER,
+ producer: batch.producer,
scope: batch.scope,
factBatchId: batch.batchId,
})),
diff --git a/packages/ts-analyzer/src/discovery.ts b/packages/ts-analyzer/src/discovery.ts
index 3294440b..329ba9a0 100644
--- a/packages/ts-analyzer/src/discovery.ts
+++ b/packages/ts-analyzer/src/discovery.ts
@@ -1,12 +1,12 @@
-import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
+import { lstatSync, readFileSync, readdirSync } from "node:fs";
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
import { normalizePath, SemctxError } from "@semantic-context/core";
import type { SemctxConfig } from "@semantic-context/core";
import ts from "typescript";
-import { resolveTypeScriptModule } from "./ts-symbols";
+import { canonicalFilesystemPath as canonicalPath, resolveTypeScriptModule } from "./ts-symbols";
export type FileRole = "source" | "test" | "document" | "migration" | "other";
-export type SourceLanguage = "typescript" | "python" | "markdown" | "sql" | "unknown";
+export type SourceLanguage = "typescript" | "javascript" | "python" | "markdown" | "sql" | "unknown";
export interface DiscoveredFile {
absPath: string;
@@ -21,6 +21,20 @@ export interface DiscoveredFile {
language?: Exclude;
}
+export const DISCOVERY_CANDIDATE_REASONS = [
+ "LEGACY_UNSUPPORTED_EXTENSION",
+ "INCLUDE_MISS",
+ "EXCLUDE_MATCH",
+ "IGNORED_GENERATED_OUTPUT",
+ "LANGUAGE_DISABLED",
+ "LANGUAGE_UNSUPPORTED",
+ "READ_FAILED",
+ "IMPORT_OUTSIDE_REPOSITORY",
+ "REFERENCE_OUTSIDE_REPOSITORY",
+ "SOURCE_LINK_OUTSIDE_REPOSITORY",
+ "SELECTED",
+] as const;
+
export interface DiscoveryCandidate {
relPath: string;
language: SourceLanguage;
@@ -30,17 +44,7 @@ export interface DiscoveryCandidate {
* by the Plane-A producer ledger after analysis.
*/
analysisOutcome?: "not_applicable" | "disabled" | "unsupported" | "failed";
- reason:
- | "LEGACY_UNSUPPORTED_EXTENSION"
- | "INCLUDE_MISS"
- | "EXCLUDE_MATCH"
- | "LANGUAGE_DISABLED"
- | "LANGUAGE_UNSUPPORTED"
- | "READ_FAILED"
- | "IMPORT_OUTSIDE_REPOSITORY"
- | "REFERENCE_OUTSIDE_REPOSITORY"
- | "SOURCE_LINK_OUTSIDE_REPOSITORY"
- | "SELECTED";
+ reason: (typeof DISCOVERY_CANDIDATE_REASONS)[number];
}
export interface DiscoveryResult {
@@ -58,11 +62,14 @@ const IGNORED_SEGMENTS = new Set([
".turbo",
".next",
]);
+const HARD_IGNORED_SEGMENTS = new Set(["node_modules", ".git", ".semctx"]);
+const OUTPUT_SEGMENTS = new Set(["dist", "build", "coverage", ".turbo", ".next"]);
-const TEST_FILENAME_RE = /\.(test|spec)\.(ts|tsx|mts|cts)$/;
+const TEST_FILENAME_RE = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
const TEST_DIR_SEGMENTS = new Set(["test", "tests", "__tests__"]);
const TEST_IMPORT_RE = /from\s+["'](vitest|bun:test|node:test)["']/;
const TS_FILE_RE = /\.(ts|tsx|mts|cts)$/;
+const JS_FILE_RE = /\.(js|jsx|mjs|cjs)$/;
const PYTHON_FILE_RE = /\.py$/;
const MARKDOWN_RE = /\.mdx?$/;
const SQL_RE = /\.sql$/;
@@ -96,7 +103,7 @@ function classify(relPath: string, content: string, config: SemctxConfig): FileR
return isTest ? "test" : "source";
}
- if (TS_FILE_RE.test(relPath)) {
+ if (TS_FILE_RE.test(relPath) || JS_FILE_RE.test(relPath)) {
const isTest =
TEST_FILENAME_RE.test(relPath) ||
parts.some((p) => TEST_DIR_SEGMENTS.has(p)) ||
@@ -108,6 +115,7 @@ function classify(relPath: string, content: string, config: SemctxConfig): FileR
export function sourceLanguage(relPath: string): SourceLanguage {
if (TS_FILE_RE.test(relPath)) return "typescript";
+ if (JS_FILE_RE.test(relPath)) return "javascript";
if (PYTHON_FILE_RE.test(relPath)) return "python";
if (MARKDOWN_RE.test(relPath)) return "markdown";
if (SQL_RE.test(relPath)) return "sql";
@@ -130,6 +138,57 @@ export function isPathSelected(config: SemctxConfig, inputPath: string): boolean
&& !matchesAny(relPath, config.exclude);
}
+/** Metadata and installed dependencies are outside the qualified repository source boundary. */
+export function isHardExcludedPath(inputPath: string): boolean {
+ return segments(normalizePath(inputPath)).some((part) => HARD_IGNORED_SEGMENTS.has(part));
+}
+
+/**
+ * Output basenames are only hints, never evidence that an authored source is generated.
+ * Use repository-local ignore files only: host-global and shared Git excludes cannot silently
+ * change this profile's source scope. The caller retains these ignore files in its input seal.
+ * Keep the original selector when broadening discovery for inbound dependency closure.
+ */
+export function createQualifiedPathEligibility(config: SemctxConfig): (relPath: string) => boolean {
+ const result = Bun.spawnSync(["git", "ls-files", "-z", "--cached", "--others", "--exclude-per-directory=.gitignore"], {
+ cwd: config.repositoryRoot, stdout: "pipe", stderr: "pipe",
+ });
+ if (result.exitCode !== 0) {
+ throw new SemctxError("IO_ERROR", "qualified discovery could not establish Git source eligibility");
+ }
+ const authored = new Set(new TextDecoder().decode(result.stdout).split("\0").filter(Boolean).map(normalizePath));
+ // Compiler/manifests are causal inputs even when Git ignores their output directory.
+ // Retain local extends chains as well; absence in the snapshot must not become defaults.
+ const paths: string[] = [];
+ walk(config.repositoryRoot, config.repositoryRoot, paths, true, undefined, true);
+ const available = new Map(paths.map(path => [normalizePath(relative(config.repositoryRoot, path)), path]));
+ const namedMetadata = /(?:^|\/)(?:tsconfig[^/]*\.json|package\.json|pyproject\.toml|bun\.lockb?|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$/;
+ const metadata = new Set([...available.keys()].filter(path => namedMetadata.test(path)));
+ const pending = [...metadata].filter(path => /(?:^|\/)tsconfig[^/]*\.json$/.test(path));
+ for (const relPath of pending) {
+ const absPath = available.get(relPath)!;
+ const parsed = ts.readConfigFile(absPath, path => readFileSync(path, "utf8"));
+ const configValue = parsed.config as { extends?: unknown } | undefined;
+ const extended = configValue?.extends;
+ for (const reference of Array.isArray(extended) ? extended : [extended]) {
+ if (typeof reference !== "string" || !reference.startsWith(".")) continue;
+ const target = resolve(dirname(absPath), reference);
+ const targetPath = normalizePath(relative(config.repositoryRoot, /\.json$/.test(target) ? target : `${target}.json`));
+ if (!available.has(targetPath) || metadata.has(targetPath)) continue;
+ metadata.add(targetPath);
+ pending.push(targetPath);
+ }
+ }
+ return (inputPath) => {
+ const relPath = normalizePath(inputPath);
+ if (isHardExcludedPath(relPath)) return false;
+ if (!segments(relPath).some((part) => OUTPUT_SEGMENTS.has(part))) return true;
+ // Ignore controls themselves must remain bound, including inside generated directories.
+ return /(?:^|\/)\.(?:gitignore|gitattributes)$/.test(relPath)
+ || namedMetadata.test(relPath) || metadata.has(relPath) || authored.has(relPath) || isPathSelected(config, relPath);
+ };
+}
+
function enabledLanguage(
config: Extract,
language: SourceLanguage,
@@ -157,14 +216,6 @@ function isContained(root: string, candidate: string): boolean {
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`));
}
-function canonicalPath(path: string): string {
- try {
- return realpathSync.native(path);
- } catch {
- return resolve(path);
- }
-}
-
function escapedDependencyReason(
absPath: string,
root: string,
@@ -188,7 +239,7 @@ function escapedDependencyReason(
return undefined;
}
-function walk(dir: string, root: string, acc: string[], strict = false, refusedLinks?: string[]): void {
+function walk(dir: string, root: string, acc: string[], strict = false, refusedLinks?: string[], qualified = false): void {
let entries: string[];
try {
entries = readdirSync(dir).sort();
@@ -202,7 +253,7 @@ function walk(dir: string, root: string, acc: string[], strict = false, refusedL
return;
}
for (const entry of entries) {
- if (IGNORED_SEGMENTS.has(entry)) continue;
+ if ((qualified ? HARD_IGNORED_SEGMENTS : IGNORED_SEGMENTS).has(entry)) continue;
const abs = join(dir, entry);
let stat;
try {
@@ -226,7 +277,7 @@ function walk(dir: string, root: string, acc: string[], strict = false, refusedL
}
if (stat.isDirectory()) {
if (isNestedGitWorktree(abs, root)) continue;
- walk(abs, root, acc, strict, refusedLinks);
+ walk(abs, root, acc, strict, refusedLinks, qualified);
} else if (stat.isFile()) {
acc.push(abs);
}
@@ -349,7 +400,7 @@ function discoverLegacyRepository(config: Extract)
* Version 1 preserves the historical selected file set. Version 2 applies normalized include
* globs first, then lets excludes win. Producer execution finalizes enabled selected candidates.
*/
-export function discoverRepository(config: SemctxConfig): DiscoveryResult {
+export function discoverRepository(config: SemctxConfig, eligibilityConfig: SemctxConfig = config): DiscoveryResult {
if (config.version === 1) {
return discoverLegacyRepository(config);
}
@@ -357,13 +408,19 @@ export function discoverRepository(config: SemctxConfig): DiscoveryResult {
const root = config.repositoryRoot;
const absPaths: string[] = [];
const refusedLinks: string[] = [];
- walk(root, root, absPaths, true, refusedLinks);
+ const qualified = eligibilityConfig.version === 2 && eligibilityConfig.analysisProfile === "modelo-suite-static-v1";
+ const eligible = qualified ? createQualifiedPathEligibility(eligibilityConfig) : undefined;
+ walk(root, root, absPaths, true, refusedLinks, qualified);
const files: DiscoveredFile[] = [];
const candidates: DiscoveryCandidate[] = [];
for (const absPath of absPaths.sort()) {
const relPath = normalizePath(relative(root, absPath));
const language = sourceLanguage(relPath);
+ if (eligible !== undefined && !eligible(relPath)) {
+ candidates.push({ relPath, language, selectionDecision: "excluded", analysisOutcome: "not_applicable", reason: "IGNORED_GENERATED_OUTPUT" });
+ continue;
+ }
const included = config.include.length > 0 && matchesAny(relPath, config.include);
const excluded = matchesAny(relPath, config.exclude);
if (!included || excluded) {
@@ -412,7 +469,7 @@ export function discoverRepository(config: SemctxConfig): DiscoveryResult {
});
continue;
}
- const boundaryFailure = language === "typescript"
+ const boundaryFailure = language === "typescript" || language === "javascript"
? escapedDependencyReason(absPath, root, content)
: undefined;
if (boundaryFailure !== undefined) {
diff --git a/packages/ts-analyzer/src/index.ts b/packages/ts-analyzer/src/index.ts
index ab8b15a5..9961d87f 100644
--- a/packages/ts-analyzer/src/index.ts
+++ b/packages/ts-analyzer/src/index.ts
@@ -1,13 +1,19 @@
/** Public surface of @semantic-context/ts-analyzer. */
-export { analyzeRepository, analyzeRepositoryAsync, assembleRepository } from "./analyze";
+export { analyzeRepository, analyzeRepositoryAsync, assembleRepository, getQualifiedCallIntegrityReasons } from "./analyze";
export type { AnalysisResult, AsyncAnalysisResult } from "./analyze";
+export { inspectJavaScriptSource, inspectSourceParsing, inspectModuleConfiguration, hasNodeCreateRequireUse, inspectNativeModuleBindings } from "./javascript-diagnostics";
+/** Internal app-service integration seam; does not create graph facts or public report fields. */
+export { registerOpaqueCallbackOwnership } from "./semantic-dependency-diagnostics";
export {
+ DISCOVERY_CANDIDATE_REASONS,
discoverFiles,
discoverRepository,
countTypeScriptFiles,
isPathSelected,
sourceLanguage,
+ createQualifiedPathEligibility,
+ isHardExcludedPath,
} from "./discovery";
export type {
DiscoveredFile,
@@ -22,6 +28,7 @@ export {
extractTypeScriptParallel,
resolveWorkerCount,
TYPESCRIPT_DIALECT_VERSION,
+ typeScriptLibraryDirectory,
} from "./ts-symbols";
export type {
TsExtraction,
@@ -31,6 +38,7 @@ export type {
IndexWorkerSelection,
ParallelTsExtraction,
TypeScriptParallelism,
+ CompilerInputSnapshot,
} from "./ts-symbols";
export {
diff --git a/packages/ts-analyzer/src/javascript-diagnostics.ts b/packages/ts-analyzer/src/javascript-diagnostics.ts
new file mode 100644
index 00000000..4b693878
--- /dev/null
+++ b/packages/ts-analyzer/src/javascript-diagnostics.ts
@@ -0,0 +1,500 @@
+import ts from "typescript";
+import { dirname, relative, isAbsolute, join, resolve } from "node:path";
+import { builtinModules } from "node:module";
+import { resolveTypeScriptModule, retainedCompilerSystem, isModeledCallCallee, type CompilerInputSnapshot } from "./ts-symbols";
+import { inspectSemanticDependencies } from "./semantic-dependency-diagnostics";
+
+const BUILTIN_MODULES = new Set(builtinModules.map(name => name.replace(/^node:/, "")));
+const ordinaryProcessMembers = new Set(["argv", "cwd", "env", "execPath", "exit", "exitCode", "platform", "stderr", "stdout", "versions"]);
+/** Internal shared policy; not exported by the package entrypoint. */
+export function isOrdinaryNativeProcessMember(name: string): boolean { return ordinaryProcessMembers.has(name); }
+
+/** Closed native-module eligibility, without inventing dependencies loaded at runtime. */
+export function inspectNativeModuleBindings(source: ts.SourceFile): { commonJsUnsupported: boolean; unmodeledMembers: string[]; runtimeEvaluationUnsupported?: true } {
+ type Origin = "factory" | "namespace" | "global" | "browser-global" | "module-meta" | "process" | "evaluation" | "ordinary" | "unmodeled" | "other";
+ const unmodeledMembers = new Set();
+ let found = false;
+ let runtimeEvaluationUnsupported = false;
+ const result = () => ({ commonJsUnsupported: found, unmodeledMembers: [...unmodeledMembers].sort(),
+ ...(runtimeEvaluationUnsupported ? { runtimeEvaluationUnsupported: true as const } : {}) });
+ const nativeExportOrigin = (name: string | undefined): Origin => {
+ if (name === "createRequire" || name === "_load") return "factory";
+ if (name === "default" || name === "Module") return "namespace";
+ if (name === "isBuiltin" || name === "builtinModules") return "ordinary";
+ unmodeledMembers.add(name ?? "");
+ return "unmodeled";
+ };
+ const staticName = (node: ts.Node | undefined): string | undefined => node !== undefined
+ && (ts.isIdentifier(node) || ts.isStringLiteral(node)) ? node.text : undefined;
+ const isNodeModule = (node: ts.Node | undefined): boolean => node !== undefined && ts.isStringLiteral(node)
+ && ["node:module", "module"].includes(node.text);
+ const isNodeProcess = (node: ts.Node | undefined): boolean => node !== undefined && ts.isStringLiteral(node)
+ && ["node:process", "process"].includes(node.text);
+ const isImportMeta = (node: ts.Node): boolean => ts.isMetaProperty(node)
+ && node.keywordToken === ts.SyntaxKind.ImportKeyword && node.name.text === "meta";
+ const unmodeledResolver = (): Origin => {
+ unmodeledMembers.add("process.getBuiltinModule");
+ return "unmodeled";
+ };
+ const processExportOrigin = (name: string | undefined): Origin => {
+ if (name === "default") return "process";
+ if (name === "getBuiltinModule") return unmodeledResolver();
+ if (name !== undefined && isOrdinaryNativeProcessMember(name)) return "ordinary";
+ unmodeledMembers.add(name === undefined ? "process." : `process.${name}`);
+ return "unmodeled";
+ };
+ const globalMemberOrigin = (name: string | undefined): Origin => {
+ if (name === "eval" || name === "Function") return "evaluation";
+ if (name === "window" || name === "self") return "browser-global";
+ if (name === "require") return "factory";
+ if (name === "module" || name === "exports") { found = true; return "namespace"; }
+ return name === "process" ? "process" : name === "global" || name === "globalThis" ? "global" : "other";
+ };
+ for (const statement of source.statements) {
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly) continue;
+ if (isNodeProcess(statement.moduleSpecifier)) {
+ if (!statement.exportClause || ts.isNamespaceExport(statement.exportClause)) unmodeledResolver();
+ else for (const element of statement.exportClause.elements) {
+ if (!element.isTypeOnly && processExportOrigin(staticName(element.propertyName ?? element.name)) === "process") unmodeledResolver();
+ }
+ continue;
+ }
+ if (!isNodeModule(statement.moduleSpecifier)) continue;
+ if (!statement.exportClause || ts.isNamespaceExport(statement.exportClause)) found = true;
+ else for (const element of statement.exportClause.elements) {
+ if (element.isTypeOnly) continue;
+ const kind = nativeExportOrigin(staticName(element.propertyName ?? element.name));
+ if (kind === "factory" || kind === "namespace") found = true;
+ }
+ }
+ let importsNodeModule = false;
+ const findModule = (node: ts.Node): void => {
+ if (isImportMeta(node)) importsNodeModule = true;
+ if (ts.isStringLiteral(node) && (isNodeModule(node) || isNodeProcess(node))
+ && (ts.isImportDeclaration(node.parent) || (ts.isCallExpression(node.parent) && node.parent.expression.kind === ts.SyntaxKind.ImportKeyword))) importsNodeModule = true;
+ if (ts.isImportDeclaration(node) && isNodeProcess(node.moduleSpecifier)) importsNodeModule = true;
+ if (ts.isIdentifier(node) && ["globalThis", "global", "window", "self", "process", "require", "module", "exports", "eval", "Function"].includes(node.text)) importsNodeModule = true;
+ if ((ts.isPropertyAccessExpression(node) && node.name.text === "getBuiltinModule")
+ || (ts.isElementAccessExpression(node) && staticName(node.argumentExpression) === "getBuiltinModule")
+ || (ts.isBindingElement(node) && staticName(node.propertyName ?? node.name) === "getBuiltinModule")) importsNodeModule = true;
+ ts.forEachChild(node, findModule);
+ };
+ findModule(source);
+ if (!importsNodeModule) return result();
+ // Bind only the supplied AST. No source, dependency or configuration is read from disk.
+ const options: ts.CompilerOptions = { noLib: true, noResolve: true, allowJs: true, target: ts.ScriptTarget.ESNext };
+ const host = ts.createCompilerHost(options);
+ host.getSourceFile = (path) => path === source.fileName ? source : undefined;
+ host.fileExists = (path) => path === source.fileName;
+ host.readFile = (path) => path === source.fileName ? source.text : undefined;
+ host.resolveModuleNames = (names) => names.map(() => undefined);
+ const checker = ts.createProgram([source.fileName], options, host).getTypeChecker();
+ const origin = (node: ts.Node, seen = new Set()): Origin => {
+ if (isImportMeta(node)) return "module-meta";
+ if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node) || ts.isAwaitExpression(node)) return origin(node.expression, seen);
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && isNodeModule(node.arguments[0])) return "namespace";
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && isNodeProcess(node.arguments[0])) return "process";
+ if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
+ const property = ts.isPropertyAccessExpression(node) ? node.name.text : ts.isStringLiteral(node.argumentExpression) ? node.argumentExpression.text : undefined;
+ if (origin(node.expression, seen) === "module-meta") return property === "require" ? "factory" : "ordinary";
+ if (origin(node.expression, seen) === "namespace") return nativeExportOrigin(property);
+ if (origin(node.expression, seen) === "global") return globalMemberOrigin(property);
+ if (origin(node.expression, seen) === "browser-global") {
+ return property === "eval" || property === "Function" ? "evaluation"
+ : property === "window" || property === "self" || property === "globalThis" ? "browser-global" : "other";
+ }
+ if (origin(node.expression, seen) === "process") return processExportOrigin(property);
+ return "other";
+ }
+ if (!ts.isIdentifier(node)) return "other";
+ const symbol = checker.getSymbolAtLocation(node);
+ const syntheticAmbient = symbol === undefined || !symbol.declarations?.length
+ || symbol.declarations.every(declaration => ts.isBinaryExpression(declaration) || ts.isSourceFile(declaration)
+ || ts.isPropertyAccessExpression(declaration) || ts.isElementAccessExpression(declaration));
+ if (syntheticAmbient && ["globalThis", "module", "exports", "require"].includes(node.text)) {
+ // Intrinsic/CommonJS synthetic symbols can hide real lexical declarations. Recover
+ // the nearest binding before classifying the identifier as an ambient primitive.
+ for (let scope: ts.Node | undefined = node.parent; scope !== undefined; scope = scope.parent) {
+ if (ts.isFunctionLike(scope) && scope.parameters.some(parameter => ts.isIdentifier(parameter.name) && parameter.name.text === node.text)) return "other";
+ const statements: readonly ts.Statement[] = ts.isSourceFile(scope) || ts.isBlock(scope) || ts.isModuleBlock(scope) ? scope.statements : [];
+ const local = statements.filter(ts.isVariableStatement).flatMap(statement => statement.declarationList.declarations)
+ .find(declaration => ts.isIdentifier(declaration.name) && declaration.name.text === node.text);
+ if (local !== undefined) return local.initializer === undefined || seen.has(local) ? "other" : origin(local.initializer, new Set(seen).add(local));
+ if (statements.some(statement => (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name?.text === node.text)) return "other";
+ }
+ }
+ // The JS binder synthesizes ambient require without declarations and CommonJS exports
+ // from assignment/source-file nodes. Those are not real local bindings or shadows.
+ if (syntheticAmbient) {
+ if (["globalThis", "global"].includes(node.text)) return "global";
+ return globalMemberOrigin(node.text);
+ }
+ // TypeScript binds the intrinsic globalThis even without libraries. Local names
+ // have declarations and must not acquire the ambient native process origin.
+ if (node.text === "globalThis" && symbol.name === "globalThis" && !symbol.declarations?.length) return "global";
+ if (seen.has(symbol)) return "other";
+ const next = new Set(seen).add(symbol);
+ for (const declaration of symbol.declarations ?? []) {
+ if (ts.isImportSpecifier(declaration) && !declaration.isTypeOnly && !declaration.parent.parent.isTypeOnly
+ && isNodeProcess(declaration.parent.parent.parent.moduleSpecifier)) return processExportOrigin(staticName(declaration.propertyName ?? declaration.name));
+ if (ts.isNamespaceImport(declaration) && !declaration.parent.isTypeOnly && isNodeProcess(declaration.parent.parent.moduleSpecifier)) return "process";
+ if (ts.isImportClause(declaration) && !declaration.isTypeOnly && isNodeProcess(declaration.parent.moduleSpecifier)) return "process";
+ if (ts.isImportSpecifier(declaration) && !declaration.isTypeOnly && !declaration.parent.parent.isTypeOnly
+ && isNodeModule(declaration.parent.parent.parent.moduleSpecifier)) return nativeExportOrigin(staticName(declaration.propertyName ?? declaration.name));
+ if (ts.isNamespaceImport(declaration) && !declaration.parent.isTypeOnly && isNodeModule(declaration.parent.parent.moduleSpecifier)) return "namespace";
+ if (ts.isImportClause(declaration) && !declaration.isTypeOnly && isNodeModule(declaration.parent.moduleSpecifier)) return "namespace";
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) return origin(declaration.initializer, next);
+ if (ts.isBindingElement(declaration) && ts.isObjectBindingPattern(declaration.parent)
+ && ts.isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.initializer) {
+ const container = origin(declaration.parent.parent.initializer, next);
+ const member = staticName(declaration.propertyName ?? declaration.name);
+ if (container === "namespace") return nativeExportOrigin(member);
+ if (container === "module-meta") return member === "require" ? "factory" : "ordinary";
+ if (container === "global") return globalMemberOrigin(member);
+ if (container === "browser-global") return member === "eval" || member === "Function" ? "evaluation" : "other";
+ if (container === "process") return processExportOrigin(member);
+ }
+ }
+ return "other";
+ };
+ const safeNamespaceUse = (node: ts.Node): boolean => {
+ const parent = node.parent;
+ if ((ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && parent.expression === node) {
+ return true; // Unknown/computed members are classified separately as unmodeled.
+ }
+ if ((ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isNonNullExpression(parent) || ts.isAwaitExpression(parent)) && parent.expression === node) return true;
+ if (ts.isTypeOfExpression(parent) || ts.isTypeNode(parent)) return true;
+ if (!ts.isVariableDeclaration(parent) || parent.initializer !== node
+ || (parent.parent.flags & ts.NodeFlags.Const) === 0) return false;
+ return ts.isIdentifier(parent.name) || (ts.isObjectBindingPattern(parent.name) && parent.name.elements.every((element) =>
+ !element.dotDotDotToken && ts.isIdentifier(element.name)
+ && (element.propertyName === undefined || ts.isIdentifier(element.propertyName) || ts.isStringLiteral(element.propertyName))));
+ };
+ const markNativeEscape = (kind: Origin): void => {
+ if (kind === "factory" || kind === "namespace") found = true;
+ if (kind === "global") unmodeledMembers.add("ambient-global");
+ if (kind === "process") unmodeledMembers.add("ambient-process");
+ };
+ const visit = (node: ts.Node): void => {
+ if (ts.isTypeNode(node)) return; // Type queries do not access the native runtime getter.
+ if (ts.isImportDeclaration(node)) return; // An unused binding is not a loader use.
+ if (ts.isShorthandPropertyAssignment(node)) {
+ const target = checker.getShorthandAssignmentValueSymbol(node);
+ for (const declaration of target?.declarations ?? []) {
+ const name = (declaration as ts.NamedDeclaration).name;
+ if (name && ts.isIdentifier(name)) markNativeEscape(origin(name));
+ }
+ }
+ if (ts.isExportAssignment(node)) markNativeEscape(origin(node.expression));
+ if (ts.isVariableStatement(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
+ for (const declaration of node.declarationList.declarations) {
+ if (declaration.initializer) markNativeEscape(origin(declaration.initializer));
+ }
+ }
+ if (ts.isExportSpecifier(node) && !node.isTypeOnly && !node.parent.parent.isTypeOnly) {
+ const target = checker.getExportSpecifierLocalTargetSymbol(node);
+ for (const declaration of target?.declarations ?? []) {
+ const name = (declaration as ts.NamedDeclaration).name;
+ if (name && ts.isIdentifier(name)) markNativeEscape(origin(name));
+ }
+ }
+ const declarationName = ts.isIdentifier(node) && ((ts.isVariableDeclaration(node.parent) && node.parent.name === node)
+ || (ts.isBindingElement(node.parent) && (node.parent.name === node || node.parent.propertyName === node))
+ || (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isPropertyAssignment(node.parent) && node.parent.name === node)
+ || (ts.isParameter(node.parent) && node.parent.name === node));
+ if (!declarationName && origin(node) === "factory") found = true;
+ // Ambient evaluator references can escape through any alias/container/callback route.
+ if (!declarationName && origin(node) === "evaluation") runtimeEvaluationUnsupported = true;
+ if (!declarationName && origin(node) === "module-meta" && (!safeNamespaceUse(node)
+ || (ts.isElementAccessExpression(node.parent) && node.parent.expression === node && !ts.isStringLiteral(node.parent.argumentExpression)))) found = true;
+ if (!declarationName && origin(node) === "browser-global" && (!safeNamespaceUse(node)
+ || (ts.isElementAccessExpression(node.parent) && node.parent.expression === node && !ts.isStringLiteral(node.parent.argumentExpression)))) {
+ runtimeEvaluationUnsupported = true;
+ }
+ // A namespace containing createRequire cannot leave the understood immutable binding
+ // routes: assignment, return, container and callback flows have no modeled load edges.
+ if (!declarationName && origin(node) === "namespace" && !safeNamespaceUse(node)) found = true;
+ if (!declarationName && origin(node) === "global" && (!safeNamespaceUse(node)
+ || (ts.isElementAccessExpression(node.parent) && node.parent.expression === node && !ts.isStringLiteral(node.parent.argumentExpression)))) {
+ unmodeledMembers.add("ambient-global");
+ }
+ if (!declarationName && origin(node) === "process" && !safeNamespaceUse(node)) unmodeledMembers.add("ambient-process");
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ return result();
+}
+
+/** Compatibility predicate: unknown native members are not claimed to execute CommonJS. */
+export function hasNodeCreateRequireUse(source: ts.SourceFile): boolean {
+ return inspectNativeModuleBindings(source).commonJsUnsupported;
+}
+
+/** Diagnostic-only constructs never acquire a complete ESM capability by extension recognition. */
+export function inspectJavaScriptSource(path: string, content: string, repositoryRoot?: string, compilerInputs?: CompilerInputSnapshot): {
+ parseFailed: boolean;
+ reasons: string[];
+ staticExports: { name: string; declarationKind: string }[];
+ staticModuleLinks: { specifier: string; resolution: "resolved" | "external" | "unresolved" }[];
+} {
+ const source = ts.createSourceFile(path, content, ts.ScriptTarget.ES2022, true,
+ path.endsWith(".jsx") ? ts.ScriptKind.JSX : ts.ScriptKind.JS);
+ const diagnostics = ts.transpileModule(content, { fileName: path, compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.Preserve }, reportDiagnostics: true }).diagnostics ?? [];
+ const reasons = new Set(diagnostics.map(diagnostic =>
+ `JAVASCRIPT_PARSE_ERROR:${diagnostic.code}:${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`));
+ for (const reason of inspectSemanticDependencies(source, compilerInputs)) reasons.add(reason.replace("SOURCE_", "JAVASCRIPT_"));
+ if (path.endsWith(".cjs")) reasons.add("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+ const native = inspectNativeModuleBindings(source);
+ if (hasSemanticJSDocImport(source)) reasons.add("JAVASCRIPT_JSDOC_IMPORT_UNSUPPORTED");
+ if (hasUnsupportedDefaultExpression(source)) reasons.add("JAVASCRIPT_DEFAULT_EXPRESSION_UNSUPPORTED");
+ if (native.commonJsUnsupported) reasons.add("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+ if (native.runtimeEvaluationUnsupported) reasons.add("JAVASCRIPT_DYNAMIC_EVALUATION_UNSUPPORTED");
+ for (const member of native.unmodeledMembers) reasons.add(`JAVASCRIPT_NATIVE_MODULE_MEMBER_UNSUPPORTED:${member}`);
+ const staticExports: { name: string; declarationKind: string }[] = [];
+ const staticModuleLinks: { specifier: string; resolution: "resolved" | "external" | "unresolved" }[] = [];
+ const isDeclaredExternal = (specifier: string): boolean => {
+ const packageName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0]!;
+ const system = compilerInputs === undefined ? ts.sys : retainedCompilerSystem(compilerInputs);
+ for (let directory = dirname(path); ; directory = dirname(directory)) {
+ if (repositoryRoot) {
+ const relation = relative(repositoryRoot, directory).replaceAll("\\", "/");
+ if (relation === ".." || relation.startsWith("../") || isAbsolute(relation)) return false;
+ }
+ const text = system.readFile(join(directory, "package.json"));
+ if (text !== undefined) {
+ try {
+ const manifest = JSON.parse(text) as Record;
+ if (manifest.name === packageName) return false;
+ for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
+ const dependencies = manifest[field];
+ if (dependencies && typeof dependencies === "object") {
+ const version = (dependencies as Record)[packageName];
+ if (typeof version === "string") return !/^(workspace:|file:|link:)/.test(version);
+ }
+ }
+ } catch { return false; }
+ }
+ if (dirname(directory) === directory) return false;
+ }
+ };
+ const recordModuleLink = (specifier: string): void => {
+ const resolved = resolveTypeScriptModule(specifier, path, undefined, true, compilerInputs);
+ const external = BUILTIN_MODULES.has(specifier.replace(/^node:/, "")) || (resolved !== undefined && /[/\\]node_modules[/\\]/.test(resolved)) || (resolved === undefined && !specifier.startsWith(".") && isDeclaredExternal(specifier));
+ staticModuleLinks.push({ specifier, resolution: external ? "external" : resolved !== undefined ? "resolved" : "unresolved" });
+ if (resolved === undefined && !external) reasons.add(`JAVASCRIPT_IMPORT_UNRESOLVED:${specifier}`);
+ };
+ for (const statement of source.statements) {
+ if (ts.isExportDeclaration(statement)) {
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
+ for (const element of statement.exportClause.elements) staticExports.push({ name: element.name.text, declarationKind: statement.moduleSpecifier ? "reexport" : "local-binding" });
+ } else if (statement.exportClause && ts.isNamespaceExport(statement.exportClause)) staticExports.push({ name: statement.exportClause.name.text, declarationKind: "namespace-reexport" });
+ else staticExports.push({ name: "*", declarationKind: "reexport" });
+ } else if (ts.isExportAssignment(statement)) staticExports.push({ name: "default", declarationKind: "expression" });
+ else if ((ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) &&
+ ts.getModifiers(statement)?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
+ if (ts.isVariableStatement(statement)) {
+ for (const declaration of statement.declarationList.declarations) {
+ if (ts.isIdentifier(declaration.name)) staticExports.push({ name: declaration.name.text, declarationKind: "variable" });
+ else reasons.add("JAVASCRIPT_EXPORTED_DESTRUCTURING_UNSUPPORTED");
+ }
+ } else if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) {
+ const name = (ts.getCombinedModifierFlags(statement) & ts.ModifierFlags.Default) !== 0 ? "default" : statement.name?.text;
+ if (name) staticExports.push({ name, declarationKind: ts.isFunctionDeclaration(statement) ? "function" : "class" });
+ }
+ }
+ }
+ const visit = (node: ts.Node): void => {
+ if (ts.isTaggedTemplateExpression(node)) reasons.add("JAVASCRIPT_TAGGED_TEMPLATE_UNSUPPORTED");
+ if (ts.isCallExpression(node)) {
+ if (!isModeledCallCallee(node.expression)) reasons.add("JAVASCRIPT_DYNAMIC_CALL_UNSUPPORTED");
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
+ if (node.arguments.length !== 1 || !ts.isStringLiteral(node.arguments[0]!)) reasons.add("JAVASCRIPT_DYNAMIC_IMPORT_UNSUPPORTED");
+ else {
+ const specifier = (node.arguments[0] as ts.StringLiteral).text;
+ recordModuleLink(specifier);
+ }
+ }
+ }
+ if (ts.isWithStatement(node)) reasons.add("JAVASCRIPT_DYNAMIC_SCOPE_UNSUPPORTED");
+ if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
+ const specifier = node.moduleSpecifier.text;
+ recordModuleLink(specifier);
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ for (const reason of inspectModuleConfiguration(path, repositoryRoot, compilerInputs)) reasons.add(reason);
+ return { parseFailed: diagnostics.length > 0, reasons: [...reasons].sort(), staticExports: staticExports.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0), staticModuleLinks };
+}
+
+/** v2 runtime parsing guard for TS as well as JS; compiler recovery is not completed analysis. */
+export function inspectSourceParsing(path: string, content: string): string[] {
+ const kind = path.endsWith(".jsx") ? ts.ScriptKind.JSX : path.endsWith(".tsx") ? ts.ScriptKind.TSX : /\.(js|mjs|cjs)$/.test(path) ? ts.ScriptKind.JS : ts.ScriptKind.TS;
+ const source = ts.createSourceFile(path, content, ts.ScriptTarget.ES2022, true, kind);
+ return ((source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics ?? []).map(diagnostic =>
+ `SOURCE_PARSE_ERROR:${diagnostic.code}:${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`);
+}
+
+const namedConfigurationReasons = new WeakMap();
+function inspectUnconsumedNamedConfigurations(inputs: CompilerInputSnapshot): string[] {
+ const cached = namedConfigurationReasons.get(inputs); if (cached !== undefined) return cached;
+ const normalize = (path: string): string => {
+ const normalized = resolve(path).replaceAll("\\", "/");
+ return ts.sys.useCaseSensitiveFileNames ? normalized : normalized.toLowerCase();
+ };
+ const named = [...inputs.keys()].filter(path => /(?:^|[\\/])tsconfig[^\\/]+\.json$/.test(path));
+ if (named.length === 0) { namedConfigurationReasons.set(inputs, []); return []; }
+ const system = retainedCompilerSystem(inputs); const consumed = new Set(); const configs = new Set();
+ for (const path of inputs.keys()) if (/\.[cm]?[jt]sx?$/.test(path)) {
+ const config = ts.findConfigFile(dirname(path), candidate => system.fileExists(candidate));
+ if (config !== undefined) configs.add(config);
+ }
+ const readFile = (path: string): string | undefined => { consumed.add(normalize(path)); return system.readFile(path); };
+ for (const path of configs) {
+ const config = ts.readConfigFile(path, readFile);
+ if (config.error === undefined) ts.parseJsonConfigFileContent(config.config, { ...system, readFile, readDirectory: () => [] }, dirname(path));
+ }
+ const reasons = named.some(path => !consumed.has(normalize(path))) ? ["SOURCE_NAMED_CONFIGURATION_UNSUPPORTED"] : [];
+ namedConfigurationReasons.set(inputs, reasons); return reasons;
+}
+
+export function inspectModuleConfiguration(path: string, repositoryRoot?: string, compilerInputs?: CompilerInputSnapshot, source?: ts.SourceFile): string[] {
+ const system = compilerInputs === undefined ? ts.sys : retainedCompilerSystem(compilerInputs);
+ const retainedText = source === undefined && compilerInputs !== undefined ? system.readFile(path) : undefined;
+ const inspectedSource = source ?? (retainedText === undefined ? undefined : ts.createSourceFile(path, retainedText, ts.ScriptTarget.Latest, true));
+ const defaultReasons = inspectedSource === undefined ? [] : [
+ ...(compilerInputs !== undefined ? inspectUnconsumedNamedConfigurations(compilerInputs) : []),
+ ...(hasUnsupportedDefaultExpression(inspectedSource) ? ["SOURCE_DEFAULT_EXPRESSION_UNSUPPORTED"] : []),
+ ...(hasSemanticJSDocImport(inspectedSource) ? ["SOURCE_JSDOC_IMPORT_UNSUPPORTED"] : []),
+ ...(compilerInputs !== undefined ? inspectUnmodeledInvocations(inspectedSource) : []),
+ ...(compilerInputs !== undefined && inspectNativeModuleBindings(inspectedSource).commonJsUnsupported ? ["SOURCE_COMMONJS_UNSUPPORTED"] : []),
+ ...(compilerInputs !== undefined ? inspectSemanticDependencies(inspectedSource, compilerInputs, path) : []),
+ ];
+ const configPath = ts.findConfigFile(dirname(path), candidate => system.fileExists(candidate));
+ if (!configPath) return [...defaultReasons, ...(compilerInputs !== undefined && inspectedSource !== undefined ? inspectJsxRuntimeBoundary(inspectedSource, {}) : []), ...(source !== undefined && compilerInputs !== undefined
+ ? inspectQualifiedModuleScope(source, path) : [])];
+ const contained = (candidate: string): boolean => {
+ if (!repositoryRoot) return true;
+ const relation = relative(repositoryRoot, candidate).replaceAll("\\", "/");
+ return relation !== ".." && !relation.startsWith("../") && !isAbsolute(relation);
+ };
+ if (!contained(configPath)) return ["SOURCE_CONFIGURATION_OUTSIDE_REPOSITORY"];
+ const config = ts.readConfigFile(configPath, candidate => system.readFile(candidate));
+ let escaped = false;
+ const parsed = config.error ? undefined : ts.parseJsonConfigFileContent(config.config, { ...system,
+ readDirectory: () => [],
+ readFile: candidate => {
+ if (!contained(candidate)) { escaped = true; return undefined; }
+ return system.readFile(candidate);
+ },
+ }, dirname(configPath));
+ if (escaped) return ["SOURCE_CONFIGURATION_OUTSIDE_REPOSITORY"];
+ const diagnostics = config.error ? [config.error] : parsed?.errors ?? [];
+ const reasons = [...defaultReasons, ...diagnostics.filter(diagnostic => diagnostic.code !== 18003).map(diagnostic =>
+ `SOURCE_CONFIGURATION_INVALID:${diagnostic.code}:${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`)];
+ // The named snapshot profile qualifies ESNext/Bundler, including inherited options. Hashing
+ // NodeNext bytes does not authorize analyzing them under overwritten Bundler semantics.
+ if (compilerInputs !== undefined && parsed !== undefined) {
+ if (inspectedSource !== undefined) reasons.push(...inspectJsxRuntimeBoundary(inspectedSource, parsed.options));
+ if (inspectedSource !== undefined) reasons.push(...inspectImportedRuntimeHelpers(inspectedSource, parsed.options));
+ if (parsed.options.module !== undefined && parsed.options.module !== ts.ModuleKind.ESNext) {
+ reasons.push(`SOURCE_CONFIGURATION_MODULE_UNSUPPORTED:${ts.ModuleKind[parsed.options.module]}`);
+ }
+ if (parsed.options.moduleResolution !== undefined && parsed.options.moduleResolution !== ts.ModuleResolutionKind.Bundler) {
+ reasons.push(`SOURCE_CONFIGURATION_RESOLUTION_UNSUPPORTED:${ts.ModuleResolutionKind[parsed.options.moduleResolution]}`);
+ }
+ }
+ if (source !== undefined && compilerInputs !== undefined) {
+ reasons.push(...inspectQualifiedModuleScope(source, path));
+ }
+ return reasons;
+}
+
+function inspectImportedRuntimeHelpers(source: ts.SourceFile, options: ts.CompilerOptions): string[] {
+ if (options.importHelpers !== true) return [];
+ let externalHelpers = false;
+ const emitted = ts.transpileModule(source.text, {
+ fileName: source.fileName, compilerOptions: options,
+ transformers: { after: [() => node => {
+ externalHelpers ||= ts.getEmitHelpers(node)?.some(helper => !helper.scoped) === true;
+ return node;
+ }] },
+ });
+ return externalHelpers && ts.preProcessFile(emitted.outputText, true, true).importedFiles.some(file => file.fileName === "tslib")
+ ? ["SOURCE_CONFIGURATION_HELPERS_UNSUPPORTED"] : [];
+}
+
+function hasUnsupportedDefaultExpression(source: ts.SourceFile): boolean {
+ // Default declarations have extracted owners; expression assignments have no closed
+ // ownership contract. Refuse the entire assignment form rather than infer a target.
+ return source.statements.some(statement => ts.isExportAssignment(statement) && !statement.isExportEquals);
+}
+
+function inspectUnmodeledInvocations(source: ts.SourceFile): string[] {
+ const reasons = new Set();
+ const visit = (node: ts.Node): void => {
+ if (ts.isTaggedTemplateExpression(node)) reasons.add("SOURCE_TAGGED_TEMPLATE_UNSUPPORTED");
+ if ((source.flags & ts.NodeFlags.JavaScriptFile) === 0 && ts.isCallExpression(node) && !isModeledCallCallee(node.expression)) reasons.add("SOURCE_DYNAMIC_CALL_UNSUPPORTED");
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ return [...reasons].sort();
+}
+
+function inspectJsxRuntimeBoundary(source: ts.SourceFile, options: ts.CompilerOptions): string[] {
+ const jsxSpans = new Set();
+ const visit = (node: ts.Node): void => {
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) jsxSpans.add(`${node.pos}:${node.end}`);
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ // Use the pinned SDK's semantic pragma/configuration interpretation, including classic
+ // overrides and the last repeated pragma. Preserve extraction does not model this import.
+ const sdk = ts as unknown as { getJSXImplicitImportBase(options: ts.CompilerOptions, source: ts.SourceFile): string | undefined };
+ if (jsxSpans.size === 0) return [];
+ if (sdk.getJSXImplicitImportBase(options, source) !== undefined) return ["SOURCE_AUTOMATIC_JSX_RUNTIME_UNSUPPORTED"];
+ let classicFactory = false;
+ ts.transpileModule(source.text, { fileName: source.fileName, compilerOptions: options, transformers: { after: [() => node => {
+ const inspectEmit = (emitted: ts.Node): void => {
+ // The pinned emitter gives each synthesized JSX factory call the original JSX
+ // range. This follows actual options/pragmas without guessing a factory name.
+ if (ts.isCallExpression(emitted) && (emitted.flags & ts.NodeFlags.Synthesized) !== 0
+ && jsxSpans.has(`${emitted.pos}:${emitted.end}`)) classicFactory = true;
+ ts.forEachChild(emitted, inspectEmit);
+ };
+ inspectEmit(node); return node;
+ }] } });
+ return classicFactory ? ["SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED"] : [];
+}
+
+function hasSemanticJSDocImport(source: ts.SourceFile): boolean {
+ if ((source.flags & ts.NodeFlags.JavaScriptFile) === 0) return false;
+ let found = false;
+ const visitDoc = (node: ts.Node): void => {
+ if (ts.isImportTypeNode(node) || ts.isJSDocImportTag(node)) found = true;
+ if (!found) ts.forEachChild(node, visitDoc);
+ };
+ const visit = (node: ts.Node): void => {
+ for (const doc of (node as ts.Node & { jsDoc?: readonly ts.JSDoc[] }).jsDoc ?? []) visitDoc(doc);
+ if (!found) ts.forEachChild(node, visit);
+ };
+ visit(source);
+ return found;
+}
+
+function inspectQualifiedModuleScope(source: ts.SourceFile, path: string): string[] {
+ const reasons: string[] = [];
+ if (/\.(?:cts|cjs)$/.test(path)) reasons.push("SOURCE_COMMONJS_EXTENSION_UNSUPPORTED");
+ const visit = (node: ts.Node): void => {
+ if (ts.isModuleDeclaration(node) && (node.flags & ts.NodeFlags.GlobalAugmentation) !== 0) reasons.push("SOURCE_GLOBAL_AUGMENTATION_UNSUPPORTED");
+ if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) reasons.push("SOURCE_MODULE_AUGMENTATION_UNSUPPORTED");
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ // The current extraction Program does not propagate configured moduleDetection or infer
+ // bare TS/JS module scope from package type under ESNext/Bundler. Qualify its actual modes.
+ const module = ts.isExternalModule(source) || /\.(?:mjs|mts)$/.test(path);
+ if (!module) reasons.push("SOURCE_GLOBAL_SCRIPT_UNSUPPORTED");
+ return [...new Set(reasons)];
+}
diff --git a/packages/ts-analyzer/src/semantic-dependency-diagnostics.ts b/packages/ts-analyzer/src/semantic-dependency-diagnostics.ts
new file mode 100644
index 00000000..abb08914
--- /dev/null
+++ b/packages/ts-analyzer/src/semantic-dependency-diagnostics.ts
@@ -0,0 +1,1358 @@
+import ts from "typescript";
+import { dirname, relative, resolve } from "node:path";
+import type { RepositoryGraph, RepositoryNode } from "@semantic-context/core";
+import { isBuiltin } from "node:module";
+import { extractionContext, unwrapStaticExpression, retainedCallableCoordinate, retainedFunctionOwnerCoordinate, isModeledCallCallee, type CompilerInputSnapshot } from "./ts-symbols";
+import { isOrdinaryNativeProcessMember } from "./javascript-diagnostics";
+
+type Context = { program: ts.Program; retained: Set };
+const contexts = new WeakMap();
+const ownership = new WeakMap }>();
+/** Internal, per-assembly replacement. Never transfers proof between temporal captures. */
+export function registerOpaqueCallbackOwnership(inputs: CompilerInputSnapshot, graph: RepositoryGraph, root: string): void {
+ ownership.set(inputs, { graph, root, callbacks: new Map() });
+}
+const key = (path: string): string => {
+ const normalized = resolve(path).replaceAll("\\", "/");
+ return ts.sys.useCaseSensitiveFileNames ? normalized : normalized.toLowerCase();
+};
+
+/** Semantic refusal only: never invent dependency edges or read uncaptured repository sources. */
+export function inspectSemanticDependencies(source: ts.SourceFile, snapshot?: CompilerInputSnapshot, sourcePath = source.fileName): string[] {
+ const reasons = new Set();
+ if (source.referencedFiles.length > 0) reasons.add("SOURCE_REFERENCE_PATH_UNSUPPORTED");
+ const sdk = ts as unknown as { isIntrinsicJsxName(name: string): boolean };
+ let needsChecker = false;
+ const executionModule = (name: string): string | undefined => ["child_process", "node:child_process"].includes(name) ? "SOURCE_PROCESS_LAUNCH_UNSUPPORTED"
+ : ["vm", "node:vm"].includes(name) ? "SOURCE_RUNTIME_EVALUATION_UNSUPPORTED" : undefined;
+ const assignment = (node: ts.Node): node is ts.BinaryExpression => ts.isBinaryExpression(node)
+ && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment;
+ const numericUnary = (node: ts.Node): node is ts.PrefixUnaryExpression | ts.PostfixUnaryExpression => (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node))
+ && [ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken, ts.SyntaxKind.TildeToken, ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator);
+ const propertyMembership = (node: ts.Node): node is ts.BinaryExpression => ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InKeyword;
+ const coercingBinary = (node: ts.Node): node is ts.BinaryExpression => ts.isBinaryExpression(node) && [
+ ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken, ts.SyntaxKind.AsteriskToken, ts.SyntaxKind.SlashToken, ts.SyntaxKind.PercentToken, ts.SyntaxKind.AsteriskAsteriskToken,
+ ts.SyntaxKind.LessThanLessThanToken, ts.SyntaxKind.GreaterThanGreaterThanToken, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
+ ts.SyntaxKind.AmpersandToken, ts.SyntaxKind.BarToken, ts.SyntaxKind.CaretToken,
+ ts.SyntaxKind.LessThanToken, ts.SyntaxKind.LessThanEqualsToken, ts.SyntaxKind.GreaterThanToken, ts.SyntaxKind.GreaterThanEqualsToken,
+ ts.SyntaxKind.EqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsToken,
+ ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken, ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.SlashEqualsToken, ts.SyntaxKind.PercentEqualsToken,
+ ts.SyntaxKind.AsteriskAsteriskEqualsToken, ts.SyntaxKind.LessThanLessThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
+ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ts.SyntaxKind.AmpersandEqualsToken, ts.SyntaxKind.BarEqualsToken, ts.SyntaxKind.CaretEqualsToken,
+ ].includes(node.operatorToken.kind);
+ const asyncOwner = (node: ts.Node): boolean => {
+ for (let owner: ts.Node | undefined = node.parent; owner !== undefined; owner = owner.parent) if (ts.isFunctionLike(owner)) {
+ return ts.canHaveModifiers(owner) && ts.getModifiers(owner)?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) === true;
+ }
+ return false;
+ };
+ const inspectSyntax = (node: ts.Node): void => {
+ if (ts.isMetaProperty(node) && node.keywordToken === ts.SyntaxKind.ImportKeyword) needsChecker = true;
+ if (ts.isPropertyAssignment(node) && !ts.isComputedPropertyName(node.name)
+ && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) && node.name.text === "__proto__") needsChecker = true;
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && executionModule(node.moduleSpecifier.text) !== undefined) needsChecker = true;
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier)
+ && !(node.exportClause !== undefined && ts.isNamedExports(node.exportClause) && node.exportClause.elements.every(element => element.isTypeOnly))) {
+ const kind = executionModule(node.moduleSpecifier.text); if (kind !== undefined) reasons.add(kind);
+ if (["cluster", "node:cluster"].includes(node.moduleSpecifier.text)
+ && node.exportClause !== undefined && ts.isNamedExports(node.exportClause)
+ && node.exportClause.elements.some(element => !element.isTypeOnly && ["fork", "setupPrimary"].includes((element.propertyName ?? element.name).text))) {
+ reasons.add("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+ }
+ if (ts.isCallExpression(node)) needsChecker = true;
+ if (ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.Using) !== 0) reasons.add("SOURCE_DISPOSAL_UNSUPPORTED");
+ if (ts.isElementAccessExpression(node) || ts.isComputedPropertyName(node)) needsChecker = true;
+ if (ts.isTemplateExpression(node) || coercingBinary(node) || numericUnary(node) || propertyMembership(node)
+ || (ts.isReturnStatement(node) && asyncOwner(node))
+ || (ts.isArrowFunction(node) && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword))) needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && node.text === "register") needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && node.text === "defineProperty") needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && node.text === "importScripts") needsChecker = true;
+ if (ts.isFunctionDeclaration(node) && node.body === undefined) needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && ["Proxy", "revocable"].includes(node.text)) needsChecker = true;
+ if (ts.isDecorator(node)) reasons.add("SOURCE_DECORATOR_UNSUPPORTED");
+ if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))
+ && node.asteriskToken !== undefined) reasons.add("SOURCE_ITERATION_UNSUPPORTED");
+ if (ts.isNewExpression(node)) needsChecker = true;
+ if (ts.isForOfStatement(node) || ts.isSpreadElement(node)) needsChecker = true;
+ if (ts.isAwaitExpression(node) || (ts.isYieldExpression(node) && node.asteriskToken !== undefined)
+ || assignment(node) || (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword)) needsChecker = true;
+ if (ts.isParameter(node) && ts.isArrayBindingPattern(node.name)) needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && ["Reflect", "construct"].includes(node.text)) needsChecker = true;
+ if ((ts.isIdentifier(node) || ts.isStringLiteral(node)) && ["setTimeout", "setInterval"].includes(node.text)) needsChecker = true;
+ if (ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) reasons.add("SOURCE_ACCESSOR_UNSUPPORTED");
+ if (ts.isVariableDeclaration(node) && !ts.isIdentifier(node.name)) needsChecker = true;
+ if (ts.isExportDeclaration(node) || (ts.isVariableStatement(node)
+ && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword))) needsChecker = true;
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
+ const tag = node.tagName;
+ const name = ts.isIdentifier(tag) ? tag.text : ts.isJsxNamespacedName(tag) ? `${tag.namespace.text}:${tag.name.text}` : undefined;
+ if (name === undefined || !sdk.isIntrinsicJsxName(name)) reasons.add("SOURCE_JSX_COMPONENT_UNSUPPORTED");
+ }
+ if ((ts.isPropertyAccessExpression(node) && ["call", "apply", "bind", "constructor"].includes(node.name.text))
+ || (ts.isElementAccessExpression(node) && ts.isStringLiteral(node.argumentExpression) && node.argumentExpression.text === "constructor")) needsChecker = true;
+ if (ts.isHeritageClause(node) && node.token === ts.SyntaxKind.ExtendsKeyword && (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent))) needsChecker = true;
+ ts.forEachChild(node, inspectSyntax);
+ };
+ inspectSyntax(source);
+ if (!needsChecker) return [...reasons].sort();
+ const inputs = snapshot ?? new Map([[source.fileName, source.text]]);
+ let context = contexts.get(inputs);
+ if (context === undefined) {
+ const paths = [...inputs.keys()].filter(path => /\.[cm]?[jt]sx?$/.test(path));
+ context = { program: extractionContext.createProgram(paths, inputs), retained: new Set(paths.map(key)) };
+ contexts.set(inputs, context);
+ }
+ const { program, retained } = context;
+ const bound = program.getSourceFile(sourcePath);
+ if (bound === undefined) return ["SOURCE_SEMANTIC_SOURCE_UNAVAILABLE"];
+ const checker = program.getTypeChecker();
+ const internal = (node: ts.Node): boolean => retained.has(key(node.getSourceFile().fileName)) && !program.isSourceFileDefaultLibrary(node.getSourceFile());
+ const declarations = (expression: ts.Expression): readonly ts.Declaration[] => {
+ let symbol = checker.getSymbolAtLocation(unwrapStaticExpression(expression));
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);
+ return symbol?.getDeclarations() ?? [];
+ };
+ const destructuredOrigin = (declaration: ts.Declaration, seen = new Set()): boolean => {
+ if (seen.has(declaration)) return false;
+ seen.add(declaration);
+ if (ts.isBindingElement(declaration)) return true;
+ if (!ts.isVariableDeclaration(declaration) || declaration.initializer === undefined) return false;
+ const initializer = unwrapStaticExpression(declaration.initializer);
+ return ts.isIdentifier(initializer) && declarations(initializer).some(origin => destructuredOrigin(origin, seen));
+ };
+ const potentiallyCallable = (type: ts.Type): boolean => {
+ if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter)) !== 0) return true;
+ if (type.isUnionOrIntersection()) return type.types.some(potentiallyCallable);
+ return type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0;
+ };
+ const moduleSymbol = checker.getSymbolAtLocation(bound);
+ if (moduleSymbol !== undefined) for (let exported of checker.getExportsOfModule(moduleSymbol)) {
+ if ((exported.flags & ts.SymbolFlags.Alias) !== 0) exported = checker.getAliasedSymbol(exported);
+ const origins = exported.getDeclarations() ?? [];
+ if (origins.some(origin => internal(origin) && destructuredOrigin(origin))
+ && potentiallyCallable(checker.getTypeOfSymbolAtLocation(exported, origins[0] ?? bound))) {
+ reasons.add("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ }
+ if (origins.some(origin => internal(origin) && ts.isVariableDeclaration(origin)
+ && (origin.initializer === undefined || !(ts.isArrowFunction(origin.initializer) || ts.isFunctionExpression(origin.initializer))))
+ && potentiallyCallable(checker.getTypeOfSymbolAtLocation(exported, origins[0] ?? bound))) {
+ reasons.add("SOURCE_CALLABLE_EXPORT_UNSUPPORTED");
+ }
+ }
+ const internalCallable = (expression: ts.Expression, seen = new Set()): boolean => {
+ const receiver = unwrapStaticExpression(expression);
+ if (seen.has(receiver)) return false;
+ seen.add(receiver);
+ const type = checker.getTypeAtLocation(receiver);
+ if ([...type.getCallSignatures(), ...type.getConstructSignatures()].some(signature => {
+ const declaration = signature.getDeclaration(); return declaration !== undefined && internal(declaration);
+ })) return true;
+ return declarations(receiver).some(declaration => ts.isVariableDeclaration(declaration) && declaration.initializer !== undefined
+ && internalCallable(declaration.initializer, seen));
+ };
+ const functionHelper = (node: ts.PropertyAccessExpression): boolean => {
+ if (!["call", "apply", "bind"].includes(node.name.text)) return false;
+ const receiver = unwrapStaticExpression(node.expression);
+ const symbol = checker.getSymbolAtLocation(node) ?? checker.getPropertyOfType(checker.getTypeAtLocation(receiver), node.name.text);
+ return (symbol?.getDeclarations() ?? []).some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && ["Function", "CallableFunction", "NewableFunction"].includes(declaration.parent.name.text)
+ && ["call", "apply", "bind"].includes(symbol!.getName())) && internalCallable(receiver);
+ };
+ const intrinsicConstructor = (node: ts.PropertyAccessExpression | ts.ElementAccessExpression): boolean => {
+ const name = ts.isPropertyAccessExpression(node) ? node.name.text : ts.isStringLiteral(node.argumentExpression) ? node.argumentExpression.text : undefined;
+ if (name !== "constructor") return false;
+ const receiver = unwrapStaticExpression(node.expression);
+ const type = checker.getTypeAtLocation(receiver);
+ if (type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0) return false;
+ const symbol = checker.getSymbolAtLocation(node) ?? checker.getPropertyOfType(type, name);
+ return (symbol?.getDeclarations() ?? []).some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "Object" && symbol!.getName() === "constructor");
+ };
+ const sdkValue = (expression: ts.Expression, seen = new Set()): ts.Symbol | undefined => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value) || !(ts.isIdentifier(value) || ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value))) return undefined;
+ seen.add(value);
+ const origins = declarations(value);
+ if (origins.length > 0 && origins.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile()))) {
+ let symbol = checker.getSymbolAtLocation(value);
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);
+ return symbol;
+ }
+ // An annotation or returned SDK type is not the runtime constructor's origin.
+ // Only immutable transparent value aliases can preserve a proven SDK binding.
+ for (const origin of origins) if (ts.isVariableDeclaration(origin) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0 && origin.initializer !== undefined) {
+ const symbol = sdkValue(origin.initializer, seen); if (symbol !== undefined) return symbol;
+ }
+ for (const origin of origins) if (ts.isBindingElement(origin) && ts.isObjectBindingPattern(origin.parent)
+ && ts.isVariableDeclaration(origin.parent.parent) && origin.parent.parent.initializer !== undefined
+ && ts.isVariableDeclarationList(origin.parent.parent.parent) && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0
+ && sdkValue(origin.parent.parent.initializer, seen) !== undefined) {
+ const member = origin.propertyName ?? origin.name;
+ if (ts.isIdentifier(member) || ts.isStringLiteral(member)) {
+ const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(origin.parent.parent.initializer), member.text);
+ if ((symbol?.getDeclarations()?.length ?? 0) > 0 && symbol!.getDeclarations()!.every(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()))) return symbol;
+ }
+ }
+ return undefined;
+ };
+ const sdkConstruction = (symbol: ts.Symbol | undefined): boolean => symbol !== undefined
+ && checker.getTypeOfSymbolAtLocation(symbol, symbol.getDeclarations()![0]!).getConstructSignatures().some(signature => {
+ const declaration = signature.getDeclaration(); return declaration !== undefined && program.isSourceFileDefaultLibrary(declaration.getSourceFile());
+ });
+ const primitiveValue = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isNumericLiteral(value) || ts.isBigIntLiteral(value) || ts.isStringLiteralLike(value) || ts.isVoidExpression(value)
+ || [ts.SyntaxKind.NullKeyword, ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword].includes(value.kind)) return true;
+ if (ts.isIdentifier(value) && value.text === "undefined" && declarations(value).length === 0
+ && (checker.getTypeAtLocation(value).flags & ts.TypeFlags.Undefined) !== 0) return true;
+ if (ts.isTypeOfExpression(value)) return true;
+ if (ts.isTemplateExpression(value)) return value.templateSpans.every(span => primitiveValue(span.expression, new Set(seen)));
+ if (ts.isBinaryExpression(value) && value.operatorToken.kind === ts.SyntaxKind.PlusToken) return primitiveValue(value.left, new Set(seen)) && primitiveValue(value.right, new Set(seen));
+ if (ts.isPrefixUnaryExpression(value)) return value.operator === ts.SyntaxKind.ExclamationToken || primitiveValue(value.operand, new Set(seen));
+ return declarations(value).some(declaration => ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined && primitiveValue(declaration.initializer, new Set(seen)));
+ };
+ const constantString = (expression: ts.Expression, seen = new Set()): string | undefined => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return undefined;
+ seen.add(value);
+ if (ts.isStringLiteralLike(value)) return value.text;
+ for (const declaration of declarations(value)) if (ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined) {
+ const name = constantString(declaration.initializer, seen); if (name !== undefined) return name;
+ }
+ return undefined;
+ };
+ const staticMemberName = (expression: ts.Expression): string | undefined => ts.isPropertyAccessExpression(expression) ? expression.name.text
+ : ts.isElementAccessExpression(expression) ? constantString(expression.argumentExpression) : undefined;
+ const propertyKey = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (primitiveValue(value)) return true;
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && sdkValue(value.expression)?.getName() === "Symbol") {
+ const symbol = sdkValue(value);
+ if (symbol !== undefined && (checker.getTypeAtLocation(value).flags & ts.TypeFlags.ESSymbolLike) !== 0) return true;
+ }
+ return declarations(value).some(declaration => ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined && propertyKey(declaration.initializer, seen));
+ };
+ const sdkProtocolKey = (expression: ts.Expression, names: readonly string[], seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && sdkValue(value.expression)?.getName() === "Symbol") {
+ return names.includes(sdkValue(value)?.getName() ?? "");
+ }
+ return declarations(value).some(origin => {
+ if (ts.isVariableDeclaration(origin) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0 && origin.initializer !== undefined) return sdkProtocolKey(origin.initializer, names, seen);
+ return ts.isBindingElement(origin) && ts.isObjectBindingPattern(origin.parent)
+ && ts.isVariableDeclaration(origin.parent.parent) && origin.parent.parent.initializer !== undefined
+ && ts.isVariableDeclarationList(origin.parent.parent.parent) && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0
+ && sdkValue(origin.parent.parent.initializer)?.getName() === "Symbol"
+ && names.includes(sdkValue(value)?.getName() ?? "");
+ });
+ };
+ const iteratorKey = (expression: ts.Expression): boolean => sdkProtocolKey(expression, ["iterator", "asyncIterator"]);
+ const protocolKey = (expression: ts.Expression): boolean => sdkProtocolKey(expression, ["iterator", "asyncIterator", "hasInstance", "toPrimitive", "match", "matchAll", "replace", "search", "split", "species"]);
+ const propertyName = (name: ts.PropertyName | ts.BindingName): string | undefined => ts.isComputedPropertyName(name) ? constantString(name.expression)
+ : ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name) ? name.text : undefined;
+ const sdkCallableMember = (expression: ts.Expression, name: string | undefined): boolean => {
+ if (name === undefined) return false;
+ const receiver = unwrapStaticExpression(expression);
+ const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(receiver), name);
+ const origins = symbol?.getDeclarations() ?? [];
+ if (origins.length === 0 || !origins.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile()))) return false;
+ const type = checker.getTypeOfSymbolAtLocation(symbol!, receiver);
+ return type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0;
+ };
+ const sdkCallableProperty = (expression: ts.Expression): boolean => {
+ const target = unwrapStaticExpression(expression);
+ return (ts.isPropertyAccessExpression(target) || ts.isElementAccessExpression(target)) && sdkCallableMember(target.expression, staticMemberName(target));
+ };
+ const actualValue = (expression: ts.Expression, seen = new Set()): ts.Expression => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return value;
+ seen.add(value);
+ for (const origin of declarations(value)) if (ts.isVariableDeclaration(origin) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0 && origin.initializer !== undefined) return actualValue(origin.initializer, seen);
+ return value;
+ };
+ const sdkRuntimeBinding = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = actualValue(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) {
+ return sdkRuntimeBinding(value.expression, seen) && sdkValue(value) !== undefined;
+ }
+ const origins = declarations(value);
+ if (origins.length > 0 && origins.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile()))) return true;
+ if (ts.isIdentifier(value) && value.text === "globalThis" && origins.length === 0 && checker.getSymbolAtLocation(value) !== undefined) return true;
+ return sdkValue(value) !== undefined && origins.some(origin => ts.isBindingElement(origin) && ts.isObjectBindingPattern(origin.parent)
+ && ts.isVariableDeclaration(origin.parent.parent) && origin.parent.parent.initializer !== undefined
+ && ts.isVariableDeclarationList(origin.parent.parent.parent) && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0
+ && sdkRuntimeBinding(origin.parent.parent.initializer, seen));
+ };
+ const sdkConstructorValue = (expression: ts.Expression): ts.Symbol | undefined => {
+ const symbol = sdkValue(expression);
+ // Library member types alone cannot establish the runtime receiver's identity.
+ return sdkConstruction(symbol) && sdkRuntimeBinding(expression) ? symbol : undefined;
+ };
+ const intrinsicDateCopy = (node: ts.CallExpression | ts.NewExpression): boolean => {
+ if (!ts.isNewExpression(node) || sdkConstructorValue(node.expression)?.getName() !== "Date" || node.arguments?.length !== 1) return false;
+ const value = actualValue(node.arguments[0]!);
+ return ts.isNewExpression(value) && sdkConstructorValue(value.expression)?.getName() === "Date";
+ };
+ const sdkOrigin = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = actualValue(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isArrayLiteralExpression(value) || ts.isStringLiteralLike(value)) return true;
+ if (ts.isNewExpression(value)) return sdkConstructorValue(value.expression) !== undefined;
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) return sdkOrigin(value.expression, seen) && sdkValue(value) !== undefined;
+ const origins = declarations(value);
+ return (origins.length > 0 && origins.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())))
+ || (ts.isIdentifier(value) && value.text === "globalThis" && origins.length === 0 && checker.getSymbolAtLocation(value) !== undefined);
+ };
+ const callableType = (expression: ts.Expression): ts.Type => {
+ const value = actualValue(expression);
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) {
+ const receiver = actualValue(value.expression); const name = staticMemberName(value);
+ const symbol = name === undefined ? undefined : checker.getPropertyOfType(checker.getTypeAtLocation(receiver), name);
+ if (symbol !== undefined) return checker.getTypeOfSymbolAtLocation(symbol, receiver);
+ }
+ return checker.getTypeAtLocation(value);
+ };
+ const callbackType = (type: ts.Type): boolean => type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0
+ || (type.isUnionOrIntersection() && type.types.some(callbackType))
+ || (type.getSymbol()?.getName() === "Function" && type.getSymbol()!.getDeclarations()?.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())) === true);
+ const iterableType = (type: ts.Type, seen = new Set()): boolean => {
+ if (seen.has(type)) return false;
+ seen.add(type);
+ if ((type.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.ESSymbolLike | ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0) return false;
+ if (type.isUnionOrIntersection()) return type.types.some(part => iterableType(part, new Set(seen)));
+ const constraint = checker.getBaseConstraintOfType(type);
+ if (constraint !== undefined && constraint !== type && iterableType(constraint, new Set(seen))) return true;
+ return type.getProperties().some(property => property.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && (ts.isMethodSignature(declaration) || ts.isPropertySignature(declaration)) && ts.isComputedPropertyName(declaration.name)
+ && iteratorKey(declaration.name.expression)));
+ };
+ const iterableParameter = (parameter: ts.Symbol, declaration: ts.SignatureDeclaration): boolean => !parameter.getDeclarations()?.some(origin => ts.isParameter(origin) && origin.dotDotDotToken !== undefined)
+ && iterableType(checker.getTypeOfSymbolAtLocation(parameter, declaration));
+ const sdkCallbackSignature = (type: ts.Type): boolean => [...type.getCallSignatures(), ...type.getConstructSignatures()].some(signature => {
+ const declaration = signature.getDeclaration();
+ return declaration !== undefined && program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && signature.getParameters().some(parameter => callbackType(checker.getTypeOfSymbolAtLocation(parameter, declaration)));
+ });
+ const sdkCallbackDictionary = (type: ts.Type, declaration: ts.SignatureDeclaration): boolean => type.isUnionOrIntersection()
+ ? type.types.some(part => sdkCallbackDictionary(part, declaration))
+ : (type.flags & ts.TypeFlags.Object) !== 0 && !callbackType(type) && type.getProperties().some(member => (member.getDeclarations()?.length ?? 0) > 0
+ && member.getDeclarations()!.every(origin => ts.isPropertySignature(origin) && program.isSourceFileDefaultLibrary(origin.getSourceFile()))
+ && callbackType(checker.getTypeOfSymbolAtLocation(member, declaration)));
+ const inspectSdkCallbacks = (node: ts.CallExpression | ts.NewExpression): void => {
+ const callee = actualValue(node.expression);
+ if ((ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) && ["call", "apply", "bind"].includes(staticMemberName(callee) ?? "")) {
+ const receiver = actualValue(callee.expression); const name = staticMemberName(callee)!;
+ const helper = checker.getPropertyOfType(callableType(receiver), name);
+ if (helper?.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && ["Function", "CallableFunction", "NewableFunction"].includes(declaration.parent.name.text))
+ && sdkCallbackSignature(callableType(receiver))) reasons.add("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ if (helper?.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && ["Function", "CallableFunction", "NewableFunction"].includes(declaration.parent.name.text))
+ && [...callableType(receiver).getCallSignatures(), ...callableType(receiver).getConstructSignatures()].some(signature => {
+ const declaration = signature.getDeclaration();
+ return declaration !== undefined && program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && signature.getParameters().some(parameter => iterableParameter(parameter, declaration));
+ })) reasons.add("SOURCE_SDK_ITERATION_UNSUPPORTED");
+ }
+ const type = callableType(callee);
+ const signatures = ts.isNewExpression(node) ? type.getConstructSignatures() : type.getCallSignatures();
+ for (const signature of signatures) {
+ const declaration = signature.getDeclaration();
+ if (declaration === undefined || !program.isSourceFileDefaultLibrary(declaration.getSourceFile())) continue;
+ for (const [index, parameter] of signature.getParameters().entries()) {
+ const argument = node.arguments?.[index]; if (argument === undefined) continue;
+ if (sdkOrigin(callee) && sdkCallbackDictionary(checker.getTypeOfSymbolAtLocation(parameter, declaration), declaration)
+ && !(index === 0 && intrinsicDateCopy(node)) && !literalData(argument)) reasons.add("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ if (iterableParameter(parameter, declaration) && !intrinsicIterable(argument)) reasons.add("SOURCE_SDK_ITERATION_UNSUPPORTED");
+ if (callbackType(checker.getTypeOfSymbolAtLocation(parameter, declaration)) && (!sdkOrigin(callee)
+ || (!primitiveValue(actualValue(argument)) && !(sdkOrigin(argument) && callbackType(callableType(argument)))))) reasons.add("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ }
+ }
+ };
+ const mutationFactory = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "assign"
+ && sdkValue(actualValue(value.expression))?.getName() === "Object") return true;
+ const symbol = sdkValue(value);
+ return symbol?.getName() === "assign" && symbol.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "ObjectConstructor") === true;
+ };
+ const inspectMutationUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (!mutationFactory(node)) return;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const container = usage.parent;
+ if (ts.isVariableDeclaration(container) && container.initializer === usage && ts.isIdentifier(container.name)
+ && ts.isVariableDeclarationList(container.parent) && (container.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (!ts.isCallExpression(container) || container.expression !== usage || container.arguments[0] === undefined) {
+ reasons.add("SOURCE_SDK_MUTATION_UNSUPPORTED"); return;
+ }
+ const target = actualValue(container.arguments[0]);
+ for (const argument of container.arguments.slice(1)) {
+ const source = unwrapStaticExpression(argument);
+ if (!ts.isObjectLiteralExpression(source)) {
+ if (sdkOrigin(target)) reasons.add("SOURCE_SDK_MUTATION_UNSUPPORTED");
+ continue;
+ }
+ for (const property of source.properties) {
+ if (ts.isSpreadAssignment(property)) {
+ if (sdkOrigin(target)) reasons.add("SOURCE_SDK_MUTATION_UNSUPPORTED");
+ continue;
+ }
+ const name = propertyName(property.name);
+ const value = ts.isPropertyAssignment(property) ? property.initializer : ts.isShorthandPropertyAssignment(property) ? property.name : undefined;
+ if (sdkGlobalMember(target, name)) reasons.add("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED");
+ if ((ts.isComputedPropertyName(property.name) && protocolKey(property.name.expression))
+ || (name === "then" && (value === undefined || !primitiveValue(value)))
+ || (name !== undefined && sdkCallableMember(target, name) && (value === undefined || !primitiveValue(value)))
+ || (name === undefined && sdkOrigin(target))) reasons.add("SOURCE_SDK_MUTATION_UNSUPPORTED");
+ }
+ }
+ };
+ const descriptorFactory = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "defineProperty"
+ && ["Object", "Reflect"].includes(sdkValue(actualValue(value.expression))?.getName() ?? "")) return true;
+ const symbol = sdkValue(value);
+ if (symbol?.getName() === "defineProperty" && symbol.getDeclarations()?.some(origin => ts.isModuleBlock(origin.parent)
+ && ts.isModuleDeclaration(origin.parent.parent) && origin.parent.parent.name.getText() === "Reflect" && program.isSourceFileDefaultLibrary(origin.getSourceFile()))) return true;
+ return declarations(value).some(origin => {
+ if (ts.isVariableDeclaration(origin) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0 && origin.initializer !== undefined) return descriptorFactory(origin.initializer, seen);
+ return ts.isBindingElement(origin) && ts.isObjectBindingPattern(origin.parent)
+ && propertyName(origin.propertyName ?? origin.name) === "defineProperty" && ts.isVariableDeclaration(origin.parent.parent)
+ && origin.parent.parent.initializer !== undefined && ts.isVariableDeclarationList(origin.parent.parent.parent)
+ && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0 && ["Object", "Reflect"].includes(sdkValue(origin.parent.parent.initializer)?.getName() ?? "");
+ });
+ };
+ const dataDescriptor = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ return ts.isObjectLiteralExpression(value) && value.properties.every(property => {
+ if (ts.isSpreadAssignment(property)) return false;
+ const name = propertyName(property.name);
+ return name !== undefined && ["value", "writable", "enumerable", "configurable"].includes(name);
+ });
+ };
+ const sdkMetaOperation = (expression: ts.Expression, names: readonly string[], owners: readonly string[]): boolean => {
+ const value = actualValue(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && names.includes(staticMemberName(value) ?? "")
+ && owners.includes(sdkValue(actualValue(value.expression))?.getName() ?? "")) return true;
+ const symbol = sdkValue(value);
+ return names.includes(symbol?.getName() ?? "") && symbol?.getDeclarations()?.some(declaration => {
+ if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile())) return false;
+ const parent = declaration.parent;
+ return (ts.isInterfaceDeclaration(parent) && owners.some(owner => parent.name.text === `${owner}Constructor`))
+ || (ts.isModuleBlock(parent) && ts.isModuleDeclaration(parent.parent) && owners.includes(parent.parent.name.getText()));
+ }) === true;
+ };
+ const inspectMetaObjectUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ if (sdkMetaOperation(node, ["setPrototypeOf"], ["Object", "Reflect"])) { reasons.add("SOURCE_PROTOTYPE_MUTATION_UNSUPPORTED"); return; }
+ if (!sdkMetaOperation(node, ["defineProperties", "create"], ["Object"])) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const parent = usage.parent;
+ if (ts.isVariableDeclaration(parent) && parent.initializer === usage && ts.isIdentifier(parent.name)
+ && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (!ts.isCallExpression(parent) || parent.expression !== usage) { reasons.add("SOURCE_DESCRIPTOR_UNSUPPORTED"); return; }
+ const create = sdkMetaOperation(node, ["create"], ["Object"]);
+ if (create && (parent.arguments[0] === undefined || !literalData(parent.arguments[0]))) reasons.add("SOURCE_PROTOTYPE_MUTATION_UNSUPPORTED");
+ const map = parent.arguments[1];
+ if (map === undefined && create) return;
+ const descriptors = map === undefined ? undefined : unwrapStaticExpression(map);
+ if (descriptors === undefined || !ts.isObjectLiteralExpression(descriptors)) { reasons.add("SOURCE_DESCRIPTOR_UNSUPPORTED"); return; }
+ for (const entry of descriptors.properties) {
+ if (!ts.isPropertyAssignment(entry)) { reasons.add("SOURCE_DESCRIPTOR_UNSUPPORTED"); continue; }
+ if (ts.isComputedPropertyName(entry.name) && protocolKey(entry.name.expression)) reasons.add("SOURCE_PROTOCOL_DESCRIPTOR_UNSUPPORTED");
+ const name = propertyName(entry.name);
+ if (!create && parent.arguments[0] !== undefined && sdkGlobalMember(parent.arguments[0], name)) reasons.add("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED");
+ const knownKey = name !== undefined || (ts.isComputedPropertyName(entry.name) && propertyKey(entry.name.expression));
+ if (!knownKey || name === "__proto__" || !dataDescriptor(entry.initializer)) reasons.add("SOURCE_DESCRIPTOR_UNSUPPORTED");
+ const descriptor = unwrapStaticExpression(entry.initializer);
+ if (name !== undefined && ts.isObjectLiteralExpression(descriptor)) {
+ const value = descriptor.properties.find(property => !ts.isSpreadAssignment(property) && propertyName(property.name) === "value");
+ if (sdkCallableMember(parent.arguments[0]!, name) && value !== undefined
+ && (!ts.isPropertyAssignment(value) || !primitiveValue(value.initializer))) reasons.add("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+ }
+ }
+ };
+ const inspectDescriptorUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ const parent = node.parent;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(parent) && parent.name === node)
+ || (ts.isVariableDeclaration(parent) && parent.name === node) || ts.isBindingElement(parent))) return;
+ if (!descriptorFactory(node)) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const container = usage.parent;
+ if (ts.isVariableDeclaration(container) && container.initializer === usage && ts.isIdentifier(container.name)
+ && ts.isVariableDeclarationList(container.parent) && (container.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (ts.isCallExpression(container) && container.expression === usage && container.arguments[2] !== undefined && dataDescriptor(container.arguments[2])) {
+ if (container.arguments[1] !== undefined && (protocolKey(container.arguments[1]) || constantString(container.arguments[1]) === "then")) {
+ reasons.add("SOURCE_PROTOCOL_DESCRIPTOR_UNSUPPORTED"); return;
+ }
+ if (container.arguments[0] !== undefined && container.arguments[1] !== undefined && sdkGlobalMember(container.arguments[0], constantString(container.arguments[1]))) {
+ reasons.add("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED"); return;
+ }
+ if (container.arguments[0] !== undefined && container.arguments[1] !== undefined && sdkCallableMember(container.arguments[0], constantString(container.arguments[1]))) {
+ reasons.add("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED"); return;
+ }
+ return;
+ }
+ reasons.add("SOURCE_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+ };
+ const sdkReflectSet = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "set"
+ && sdkValue(actualValue(value.expression))?.getName() === "Reflect") return true;
+ const symbol = sdkValue(value);
+ return symbol?.getName() === "set" && symbol.getDeclarations()?.some(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())
+ && ts.isModuleBlock(origin.parent) && ts.isModuleDeclaration(origin.parent.parent) && origin.parent.parent.name.getText() === "Reflect") === true;
+ };
+ const inspectReflectSetUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (!sdkReflectSet(node)) return;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const container = usage.parent;
+ if (ts.isVariableDeclaration(container) && container.initializer === usage && ts.isIdentifier(container.name)
+ && ts.isVariableDeclarationList(container.parent) && (container.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (!ts.isCallExpression(container) || container.expression !== usage || container.arguments[0] === undefined || container.arguments[1] === undefined) {
+ reasons.add("SOURCE_PROTOCOL_MUTATION_UNSUPPORTED"); return;
+ }
+ const target = actualValue(container.arguments[0]); const name = container.arguments[1]; const value = container.arguments[2];
+ if (sdkGlobalMember(target, constantString(name))) reasons.add("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED");
+ if (protocolKey(name) || (constantString(name) === "then" && (value === undefined || !primitiveValue(value)))) reasons.add("SOURCE_PROTOCOL_MUTATION_UNSUPPORTED");
+ else if (sdkCallableMember(target, constantString(name)) && (value === undefined || !primitiveValue(value))) reasons.add("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+ };
+ const sdkPromiseResolve = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "resolve"
+ && sdkValue(actualValue(value.expression))?.getName() === "Promise") return true;
+ const symbol = sdkValue(value);
+ return symbol?.getName() === "resolve" && symbol.getDeclarations()?.some(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())
+ && ts.isInterfaceDeclaration(origin.parent) && origin.parent.name.text === "PromiseConstructor") === true;
+ };
+ const inspectPromiseResolveUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (!sdkPromiseResolve(node)) return;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const container = usage.parent;
+ if (ts.isVariableDeclaration(container) && container.initializer === usage && ts.isIdentifier(container.name)
+ && ts.isVariableDeclarationList(container.parent) && (container.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (ts.isCallExpression(container) && container.expression === usage && (container.arguments[0] === undefined || safeAwaitValue(container.arguments[0]))) return;
+ reasons.add("SOURCE_PROMISE_ASSIMILATION_UNSUPPORTED");
+ };
+ const opaqueCallee = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if (sdkOrigin(value)) return false;
+ return !declarations(value).some(origin => internal(origin) && ((ts.isFunctionDeclaration(origin) && origin.body !== undefined)
+ || (ts.isMethodDeclaration(origin) && origin.body !== undefined) || (ts.isVariableDeclaration(origin) && origin.initializer !== undefined
+ && (ts.isArrowFunction(origin.initializer) || ts.isFunctionExpression(origin.initializer)))));
+ };
+ const builtinModuleValue = (expression: ts.Expression, seen = new Set()): string | undefined => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return undefined;
+ seen.add(value);
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) return builtinModuleValue(value.expression, seen);
+ if (ts.isAwaitExpression(value)) return builtinModuleValue(value.expression, seen);
+ if (ts.isCallExpression(value) && value.expression.kind === ts.SyntaxKind.ImportKeyword && value.arguments[0] !== undefined
+ && ts.isStringLiteralLike(value.arguments[0])) return value.arguments[0].text;
+ const origins = checker.getSymbolAtLocation(value)?.getDeclarations() ?? [];
+ for (const origin of origins) {
+ if (ts.isImportSpecifier(origin) || ts.isNamespaceImport(origin) || ts.isImportClause(origin)) {
+ if ((ts.isImportSpecifier(origin) && origin.isTypeOnly) || (ts.isImportClause(origin) && origin.isTypeOnly)) continue;
+ let statement: ts.Node | undefined = origin;
+ while (statement !== undefined && !ts.isImportDeclaration(statement)) statement = statement.parent;
+ if (statement !== undefined && ts.isImportDeclaration(statement) && statement.importClause?.isTypeOnly !== true
+ && ts.isStringLiteral(statement.moduleSpecifier)) {
+ return statement.moduleSpecifier.text;
+ }
+ }
+ if (ts.isVariableDeclaration(origin) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0 && origin.initializer !== undefined) {
+ const module = builtinModuleValue(origin.initializer, seen); if (module !== undefined) return module;
+ }
+ if (ts.isBindingElement(origin) && ts.isObjectBindingPattern(origin.parent) && ts.isVariableDeclaration(origin.parent.parent)
+ && origin.parent.parent.initializer !== undefined && ts.isVariableDeclarationList(origin.parent.parent.parent)
+ && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0) {
+ const module = builtinModuleValue(origin.parent.parent.initializer, seen); if (module !== undefined) return module;
+ }
+ }
+ return undefined;
+ };
+ const clusterLauncherValue = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ const names = ["fork", "setupPrimary"];
+ if (names.includes(staticMemberName(value) ?? "")) return true;
+ return (checker.getSymbolAtLocation(value)?.getDeclarations() ?? []).some(origin => {
+ const member = ts.isImportSpecifier(origin) ? origin.propertyName ?? origin.name
+ : ts.isBindingElement(origin) ? origin.propertyName ?? origin.name : undefined;
+ return member !== undefined && (ts.isIdentifier(member) || ts.isStringLiteral(member)) && names.includes(member.text);
+ });
+ };
+ const inspectBuiltinProcessUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (ts.isIdentifier(node) && (ts.isImportSpecifier(node.parent) || ts.isNamespaceImport(node.parent) || ts.isImportClause(node.parent)
+ || ts.isBindingElement(node.parent) || (ts.isVariableDeclaration(node.parent) && node.parent.name === node)
+ || (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node))) return;
+ const module = builtinModuleValue(node);
+ const kind = executionModule(module ?? "");
+ if (kind !== undefined) reasons.add(kind);
+ else if (["cluster", "node:cluster"].includes(module ?? "") && clusterLauncherValue(node)) reasons.add("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ };
+ const inspectDefaultSort = (node: ts.CallExpression): void => {
+ const callee = unwrapStaticExpression(node.expression); const symbol = sdkValue(callee);
+ if (symbol?.getName() !== "sort" || !symbol.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "Array")) return;
+ const comparator = node.arguments[0] === undefined ? undefined : actualValue(node.arguments[0]);
+ if (comparator !== undefined && !((checker.getTypeAtLocation(comparator).flags & ts.TypeFlags.Undefined) !== 0 && primitiveValue(comparator))) return;
+ const receiver = ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee) ? unwrapStaticExpression(callee.expression) : undefined;
+ if (receiver === undefined || !ts.isArrayLiteralExpression(receiver) || !receiver.elements.every(element => ts.isExpression(element) && primitiveValue(element))) {
+ reasons.add("SOURCE_SORT_COERCION_UNSUPPORTED");
+ }
+ };
+ const inspectCombinatorUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ const symbol = sdkValue(node);
+ if (!["all", "allSettled", "race", "any"].includes(symbol?.getName() ?? "") || !symbol?.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "PromiseConstructor")) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const parent = usage.parent;
+ if (ts.isVariableDeclaration(parent) && parent.initializer === usage && ts.isIdentifier(parent.name)
+ && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ const argument = ts.isCallExpression(parent) && parent.expression === usage ? parent.arguments[0] : undefined;
+ const array = argument === undefined ? undefined : unwrapStaticExpression(argument);
+ if (array === undefined || !ts.isArrayLiteralExpression(array) || !array.elements.every(element => ts.isExpression(element) && safeAwaitValue(element))) {
+ reasons.add("SOURCE_PROMISE_ASSIMILATION_UNSUPPORTED");
+ }
+ };
+ const inlineOwned = (callback: ts.ArrowFunction | ts.FunctionExpression): boolean => {
+ const proof = ownership.get(inputs);
+ if (proof === undefined) return false;
+ const span = `${key(bound.fileName)}:${callback.getStart()}:${callback.getEnd()}`;
+ const cached = proof.callbacks.get(span); if (cached !== undefined) return cached;
+ const path = relative(proof.root, bound.fileName).replaceAll("\\", "/");
+ const lookup = (coordinate: { relPath?: string; symbolPath?: string; unmodeledReason?: string } | undefined): RepositoryNode | undefined => {
+ if (coordinate?.relPath === undefined || coordinate.symbolPath === undefined || coordinate.unmodeledReason !== undefined) return undefined;
+ const matches = proof.graph.nodes.filter(node => ["function", "class"].includes(node.kind) && node.filePath === coordinate.relPath
+ && `${typeof node.metadata["scope"] === "string" ? `${node.metadata["scope"]}.` : ""}${node.name}` === coordinate.symbolPath);
+ return matches.length === 1 ? matches[0] : undefined;
+ };
+ let owner: RepositoryNode | undefined; let namedOwner = false;
+ for (let parent: ts.Node | undefined = callback.parent; parent !== undefined; parent = parent.parent) {
+ if (ts.isFunctionDeclaration(parent)) { namedOwner = true; owner = lookup(retainedFunctionOwnerCoordinate(parent, proof.root)); break; }
+ if (ts.isMethodDeclaration(parent) && ts.isIdentifier(parent.name)) { namedOwner = true; owner = lookup(retainedCallableCoordinate(checker, parent.name, proof.root, inputs)); break; }
+ if ((ts.isArrowFunction(parent) || ts.isFunctionExpression(parent)) && ts.isVariableDeclaration(parent.parent) && ts.isIdentifier(parent.parent.name)) {
+ namedOwner = true; owner = lookup(retainedCallableCoordinate(checker, parent.parent.name, proof.root, inputs)); break;
+ }
+ if (ts.isPropertyAssignment(parent) || ts.isPropertyDeclaration(parent)) { proof.callbacks.set(span, false); return false; }
+ }
+ if (!namedOwner) owner = proof.graph.nodes.find(node => node.filePath === path && (node.kind === "test" || node.kind === "module"));
+ let valid = owner !== undefined; let count = 0;
+ const originalImport = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ const symbol = checker.getSymbolAtLocation(value);
+ return symbol?.getDeclarations()?.some(origin => {
+ if (!(ts.isImportSpecifier(origin) || ts.isImportClause(origin) || ts.isNamespaceImport(origin))) return false;
+ let statement: ts.Node | undefined = origin;
+ while (statement !== undefined && !ts.isImportDeclaration(statement)) statement = statement.parent;
+ if (statement === undefined || !ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) return false;
+ const name = statement.moduleSpecifier.text;
+ if (statement.importClause?.isTypeOnly || (ts.isImportSpecifier(origin) && origin.isTypeOnly)) return false;
+ if (isBuiltin(name)) return true;
+ const packageName = name.startsWith("@") ? name.split("/").slice(0, 2).join("/") : name.split("/")[0]!;
+ return [...inputs].some(([file, text]) => {
+ if (!file.replaceAll("\\", "/").endsWith("/package.json")) return false;
+ try {
+ const manifest = JSON.parse(text) as Record;
+ return ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].some(field => {
+ const values = manifest[field] as Record | undefined;
+ const version = values?.[packageName]; return typeof version === "string" && !/^(?:workspace:|file:|link:)/.test(version);
+ });
+ } catch { return false; }
+ });
+ }) === true;
+ };
+ const externalValue = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = actualValue(expression);
+ if (seen.has(value)) return false; seen.add(value);
+ if (originalImport(value) && declarations(value).length === 0) return true;
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) return externalValue(value.expression, seen);
+ if (ts.isCallExpression(value)) return externalValue(value.expression, seen);
+ return false;
+ };
+ const visitReference = (node: ts.Node): void => {
+ if (!valid || ts.isTypeNode(node)) return;
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
+ const parent = node.parent;
+ const binding = ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(parent) && parent.name === node)
+ || ("name" in parent && parent.name === node && !ts.isShorthandPropertyAssignment(parent)) || ts.isBindingElement(parent));
+ if (!binding && potentiallyCallable(checker.getTypeAtLocation(node)) && !sdkOrigin(node) && !externalValue(node)) {
+ const coordinate = retainedCallableCoordinate(checker, node, proof.root, inputs); const target = lookup(coordinate);
+ if (target === undefined) { valid = false; return; }
+ count += 1;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const pairedTest = owner!.kind === "test" && proof.graph.edges.some(edge => edge.kind === "covers" && edge.from === owner!.id && edge.to === target.id)
+ && proof.graph.edges.some(edge => edge.kind === "tested_by" && edge.from === target.id && edge.to === owner!.id);
+ const invoked = ts.isCallExpression(usage.parent) && usage.parent.expression === usage && isModeledCallCallee(usage)
+ && proof.graph.edges.some(edge => edge.kind === "calls" && edge.from === owner!.id && edge.to === target.id
+ && edge.evidence.some(evidence => evidence.filePath === path && evidence.startLine === bound.getLineAndCharacterOfPosition(usage.getStart()).line + 1));
+ if (!pairedTest && !invoked) { valid = false; return; }
+ }
+ }
+ ts.forEachChild(node, visitReference);
+ };
+ visitReference(callback.body);
+ valid &&= count > 0;
+ proof.callbacks.set(span, valid); return valid;
+ };
+ const bindingWritten = (symbol: ts.Symbol, source: ts.SourceFile): boolean => {
+ let written = false;
+ const writesBinding = (node: ts.Node): boolean => {
+ if (ts.isShorthandPropertyAssignment(node) && checker.getShorthandAssignmentValueSymbol(node) === symbol) return true;
+ if (checker.getSymbolAtLocation(node) === symbol) return true;
+ let found = false;
+ ts.forEachChild(node, child => { found ||= writesBinding(child); });
+ return found;
+ };
+ const inspectWrites = (node: ts.Node): void => {
+ if ((assignment(node) && writesBinding(node.left))
+ || ((ts.isForOfStatement(node) || ts.isForInStatement(node)) && !ts.isVariableDeclarationList(node.initializer) && writesBinding(node.initializer))
+ || ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node))
+ && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(node.operator)
+ && checker.getSymbolAtLocation(unwrapStaticExpression(node.operand)) === symbol)) written = true;
+ ts.forEachChild(node, inspectWrites);
+ };
+ inspectWrites(source);
+ return written;
+ };
+ const runtimePrimitiveValue = (expression: ts.Expression, requiredKind?: string): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (!ts.isIdentifier(value)) return false;
+ const symbol = checker.getSymbolAtLocation(value);
+ const origins = symbol?.getDeclarations() ?? [];
+ if (symbol === undefined || origins.length !== 1) return false;
+ const origin = origins[0]!;
+ if (!(ts.isParameter(origin) && ts.isIdentifier(origin.name))
+ && !(ts.isVariableDeclaration(origin) && ts.isIdentifier(origin.name) && ts.isVariableDeclarationList(origin.parent)
+ && (origin.parent.flags & ts.NodeFlags.Const) !== 0)) return false;
+ // A type annotation is not value provenance. The same binding must survive
+ // an actual dominating typeof refusal, with no reassignment in this source.
+ if (bindingWritten(symbol, bound)) return false;
+ const throws = (node: ts.Statement): boolean => ts.isThrowStatement(node)
+ || (ts.isBlock(node) && node.statements.length === 1 && ts.isThrowStatement(node.statements[0]!));
+ for (let child: ts.Node = value, parent = child.parent; parent !== undefined; child = parent, parent = parent.parent) {
+ if (ts.isFunctionLike(parent)) break;
+ if (!ts.isBlock(parent) && !ts.isSourceFile(parent)) continue;
+ for (const statement of parent.statements) {
+ if (statement.end > child.pos) break;
+ if (!ts.isIfStatement(statement) || !throws(statement.thenStatement)) continue;
+ const condition = unwrapStaticExpression(statement.expression);
+ if (!ts.isBinaryExpression(condition) || condition.operatorToken.kind !== ts.SyntaxKind.ExclamationEqualsEqualsToken) continue;
+ const left = unwrapStaticExpression(condition.left); const right = unwrapStaticExpression(condition.right);
+ if (ts.isTypeOfExpression(left) && ts.isStringLiteral(right)
+ && ["string", "number", "bigint", "boolean", "symbol", "undefined"].includes(right.text)
+ && (requiredKind === undefined || right.text === requiredKind)
+ && checker.getSymbolAtLocation(unwrapStaticExpression(left.expression)) === symbol
+ && (ts.isParameter(origin) || statement.pos > origin.end)) return true;
+ }
+ }
+ return false;
+ };
+ const returnedPrimitiveValue = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (!ts.isCallExpression(value)) return false;
+ const callee = actualValue(value.expression);
+ const implementations = declarations(callee).flatMap(declaration => {
+ if (ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)) return declaration.body === undefined ? [] : [declaration];
+ if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.initializer !== undefined
+ && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return [declaration.initializer];
+ return [];
+ });
+ if (implementations.length !== 1) return false;
+ const implementation = implementations[0]!;
+ if (!internal(implementation) || (!ts.isArrowFunction(implementation) && implementation.asteriskToken !== undefined)
+ || ts.getModifiers(implementation)?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return false;
+ let symbol = checker.getSymbolAtLocation(callee);
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);
+ if (symbol === undefined || bindingWritten(symbol, implementation.getSourceFile())) return false;
+ const body = implementation.body;
+ if (body === undefined) return false;
+ if (!ts.isBlock(body)) return primitiveValue(body);
+ let count = 0; let valid = true;
+ const inspectReturns = (node: ts.Node): void => {
+ if (node !== body && ts.isFunctionLike(node)) return;
+ if (ts.isReturnStatement(node)) { count++; valid &&= node.expression === undefined || primitiveValue(node.expression); }
+ ts.forEachChild(node, inspectReturns);
+ };
+ inspectReturns(body);
+ return count > 0 && valid;
+ };
+ const literalData = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (primitiveValue(value)) return true;
+ if (ts.isArrayLiteralExpression(value)) return value.elements.every(element => ts.isExpression(element) && !ts.isSpreadElement(element) && literalData(element));
+ if (!ts.isObjectLiteralExpression(value)) return false;
+ return value.properties.every(property => {
+ if (ts.isShorthandPropertyAssignment(property)) return property.objectAssignmentInitializer === undefined && primitiveValue(property.name);
+ if (!ts.isPropertyAssignment(property)) return false;
+ const name = ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : undefined;
+ return name !== undefined && name !== "__proto__" && literalData(property.initializer);
+ });
+ };
+ const inspectOpaqueCallbacks = (node: ts.CallExpression): void => {
+ if (!opaqueCallee(node.expression)) return;
+ const callee = actualValue(node.expression);
+ if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) {
+ const receiver = unwrapStaticExpression(callee.expression);
+ const callerOwned = declarations(receiver).some(origin => internal(origin)
+ && (ts.isVariableDeclaration(origin) || (ts.isBindingElement(origin) && ts.isVariableDeclaration(origin.parent.parent))));
+ const module = builtinModuleValue(receiver);
+ const external = module !== undefined && !module.startsWith(".") && !module.startsWith("/") && !/^[A-Za-z]:/.test(module);
+ if (callerOwned && !external && !sdkOrigin(callee)
+ && !(runtimePrimitiveValue(receiver, "string") && sdkMemberOf(callee, "String", staticMemberName(callee) ?? ""))) {
+ const members = declarations(callee);
+ // Real retained members keep the existing post-extraction coordinate refusal.
+ // Erased or synthetic assignment members have no declaration to extract.
+ const declaredMember = members.length > 0 && members.every(origin => internal(origin)
+ && (ts.isPropertyAssignment(origin) || ts.isShorthandPropertyAssignment(origin) || ts.isMethodDeclaration(origin)
+ || ts.isPropertyDeclaration(origin) || ts.isPropertySignature(origin) || ts.isMethodSignature(origin)));
+ if (!declaredMember) {
+ const root = ownership.get(inputs)?.root ?? dirname(bound.fileName);
+ const coordinate = retainedCallableCoordinate(checker, callee, root, inputs);
+ if (coordinate?.relPath === undefined || coordinate.symbolPath === undefined || coordinate.unmodeledReason !== undefined
+ || !retained.has(key(resolve(root, coordinate.relPath)))) reasons.add("SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED");
+ }
+ }
+ let root = actualValue(callee.expression);
+ const members = [staticMemberName(callee)];
+ const seen = new Set();
+ while (!seen.has(root)) {
+ seen.add(root);
+ if (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root)) {
+ members.push(staticMemberName(root)); root = actualValue(root.expression); continue;
+ }
+ const binding = (checker.getSymbolAtLocation(root)?.getDeclarations() ?? []).find(origin => ts.isBindingElement(origin)
+ && ts.isObjectBindingPattern(origin.parent) && ts.isVariableDeclaration(origin.parent.parent)
+ && origin.parent.parent.initializer !== undefined && ts.isVariableDeclarationList(origin.parent.parent.parent)
+ && (origin.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0);
+ if (binding === undefined || !ts.isBindingElement(binding) || !ts.isVariableDeclaration(binding.parent.parent)) break;
+ members.push(propertyName(binding.propertyName ?? binding.name)); root = actualValue(binding.parent.parent.initializer!);
+ }
+ if (ts.isIdentifier(root)) {
+ const origins = checker.getSymbolAtLocation(root)?.getDeclarations() ?? [];
+ const runtime = origins.some(origin => {
+ if (!internal(origin) || origin.getSourceFile().isDeclarationFile) return false;
+ for (let parent: ts.Node | undefined = origin; parent !== undefined; parent = parent.parent) {
+ if ((ts.canHaveModifiers(parent) && ts.getModifiers(parent)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword))
+ || (ts.isImportClause(parent) && parent.isTypeOnly) || (ts.isImportSpecifier(parent) && parent.isTypeOnly)) return false;
+ }
+ return ts.isVariableDeclaration(origin) || ts.isParameter(origin) || ts.isBindingElement(origin)
+ || (ts.isFunctionDeclaration(origin) && origin.body !== undefined) || ts.isClassDeclaration(origin) || ts.isImportClause(origin)
+ || ts.isNamespaceImport(origin) || ts.isImportSpecifier(origin);
+ });
+ const ambient = origins.length === 0 || origins.every(origin => ts.isBinaryExpression(origin) || ts.isSourceFile(origin)
+ || ts.isPropertyAccessExpression(origin) || ts.isElementAccessExpression(origin)
+ || (ts.isIdentifier(origin) && (ts.isPropertyAccessExpression(origin.parent) || ts.isElementAccessExpression(origin.parent))
+ && origin.parent.expression === origin));
+ const member = root.text === "process" ? members.at(-1)
+ : ["globalThis", "global"].includes(root.text) && members.at(-1) === "process" ? members.at(-2) : undefined;
+ const ordinaryProcess = ambient && member !== undefined && isOrdinaryNativeProcessMember(member);
+ const sdk = sdkOrigin(callee) || (sdkOrigin(root) && sdkValue(callee) !== undefined);
+ if (!runtime && !sdk && !ordinaryProcess) reasons.add("SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED");
+ }
+ }
+ for (const argument of node.arguments) {
+ const value = unwrapStaticExpression(argument);
+ const actual = actualValue(value);
+ const sdkData = sdkOrigin(value) && (potentiallyCallable(checker.getTypeAtLocation(actual))
+ || (checker.getTypeAtLocation(actual).flags & (ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.ESSymbolLike)) !== 0);
+ if (literalData(value) || runtimePrimitiveValue(value) || returnedPrimitiveValue(value) || sdkData) continue;
+ if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && inlineOwned(value)) continue;
+ reasons.add("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ }
+ };
+ const sdkSerializer = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "stringify"
+ && sdkValue(actualValue(value.expression))?.getName() === "JSON") return true;
+ const symbol = sdkValue(value);
+ return symbol?.getName() === "stringify" && symbol.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "JSON") === true;
+ };
+ const inspectSerializerUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (!sdkSerializer(node)) return;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
+ || (ts.isVariableDeclaration(node.parent) && node.parent.name === node) || ts.isBindingElement(node.parent))) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) usage = usage.parent;
+ const parent = usage.parent;
+ if (ts.isVariableDeclaration(parent) && parent.initializer === usage && ts.isIdentifier(parent.name)
+ && ts.isVariableDeclarationList(parent.parent) && (parent.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (ts.isCallExpression(parent) && parent.expression === usage && (parent.arguments[0] === undefined || literalData(parent.arguments[0]))) return;
+ reasons.add("SOURCE_SERIALIZATION_UNSUPPORTED");
+ };
+ const serviceWorkerLoader = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "register"
+ && sdkValue(value.expression)?.getName() === "serviceWorker") return true;
+ const symbol = sdkValue(expression);
+ return symbol?.getName() === "register" && (symbol.getDeclarations() ?? []).some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "ServiceWorkerContainer");
+ };
+ const workletLoader = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ const name = staticMemberName(value);
+ const symbol = (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && name !== undefined
+ ? checker.getPropertyOfType(checker.getTypeAtLocation(actualValue(value.expression)), name) : sdkValue(value);
+ return symbol?.getName() === "addModule" && symbol.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "Worklet") === true;
+ };
+ const proxyFactory = (expression: ts.Expression): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if ((ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) && staticMemberName(value) === "revocable" && sdkValue(value.expression)?.getName() === "Proxy") return true;
+ const symbol = sdkValue(value);
+ return symbol?.getName() === "revocable" && (symbol.getDeclarations() ?? []).some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && declaration.parent.name.text === "ProxyConstructor");
+ };
+ const reflectedInvocation = (expression: ts.Expression): boolean => {
+ const symbol = sdkValue(expression);
+ return symbol !== undefined && ["apply", "construct"].includes(symbol.getName())
+ && (symbol.getDeclarations() ?? []).some(declaration => ts.isModuleBlock(declaration.parent)
+ && ts.isModuleDeclaration(declaration.parent.parent) && declaration.parent.parent.name.getText() === "Reflect"
+ && program.isSourceFileDefaultLibrary(declaration.getSourceFile()));
+ };
+ const intrinsicIterable = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isArrayLiteralExpression(value) || ts.isStringLiteralLike(value) || ts.isTemplateExpression(value)) return true;
+ if (ts.isNewExpression(value) && ["Array", "Map", "Set"].includes(sdkConstructorValue(value.expression)?.getName() ?? "")) return true;
+ return declarations(value).some(declaration => ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined && intrinsicIterable(declaration.initializer, seen));
+ };
+ const literalArray = (expression: ts.Expression, seen = new Set()): ts.ArrayLiteralExpression | undefined => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return undefined;
+ seen.add(value);
+ if (ts.isArrayLiteralExpression(value)) return value;
+ for (const declaration of declarations(value)) if (ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined) {
+ const array = literalArray(declaration.initializer, seen); if (array !== undefined) return array;
+ }
+ return undefined;
+ };
+ const bindingValue = (pattern: ts.ArrayBindingPattern): ts.Expression | undefined => {
+ const parent = pattern.parent;
+ if (ts.isVariableDeclaration(parent)) return parent.initializer;
+ if (!ts.isBindingElement(parent) || !ts.isArrayBindingPattern(parent.parent) || parent.dotDotDotToken !== undefined) return undefined;
+ const outer = bindingValue(parent.parent);
+ const array = outer === undefined ? undefined : literalArray(outer);
+ const index = parent.parent.elements.indexOf(parent);
+ if (array === undefined || array.elements.slice(0, index + 1).some(element => ts.isSpreadElement(element))) return undefined;
+ const item = array.elements[index];
+ return item === undefined || ts.isOmittedExpression(item) ? undefined : item;
+ };
+ const asyncValue = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ const isAsync = (node: ts.Node): boolean => ts.canHaveModifiers(node)
+ && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) === true;
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) return isAsync(value);
+ const symbol = checker.getSymbolAtLocation(value);
+ const binding = symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
+ if (binding === undefined || declarations(value).some(declaration => bindingWritten(binding, declaration.getSourceFile()))) return false;
+ return declarations(value).some(declaration => ((ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)) && isAsync(declaration))
+ || (ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined && asyncValue(declaration.initializer, seen)));
+ };
+ const safeAwaitValue = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isAwaitExpression(value)) return safeAwaitValue(value.expression, seen);
+ // Iterable objects remain mutable even behind a const binding; an initializer is
+ // not proof that a then override or prototype cannot participate in assimilation.
+ if (primitiveValue(value)) return true;
+ if (ts.isNewExpression(value) && sdkConstructorValue(value.expression)?.getName() === "Promise") return true;
+ if (ts.isCallExpression(value)) {
+ const callee = unwrapStaticExpression(value.expression);
+ if (callee.kind === ts.SyntaxKind.ImportKeyword && value.arguments.length === 1 && ts.isStringLiteralLike(value.arguments[0]!)) return true;
+ if (asyncValue(callee)) return true;
+ if (ts.isPropertyAccessExpression(callee) && sdkValue(callee.expression)?.getName() === "Promise"
+ && sdkValue(callee) !== undefined) {
+ if (callee.name.text === "reject") return true;
+ if (callee.name.text === "resolve") return value.arguments[0] === undefined || safeAwaitValue(value.arguments[0], seen);
+ }
+ }
+ return declarations(value).some(declaration => ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined && safeAwaitValue(declaration.initializer, seen));
+ };
+ const timerNames = new Set(["setTimeout", "setInterval"]);
+ const sdkTimer = (symbol: ts.Symbol | undefined): boolean => symbol !== undefined && timerNames.has(symbol.getName())
+ && (symbol.getDeclarations()?.length ?? 0) > 0 && symbol.getDeclarations()!.every(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()));
+ const timerValue = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = unwrapStaticExpression(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (sdkTimer(sdkValue(value))) return true;
+ for (const declaration of declarations(value)) {
+ if (ts.isVariableDeclaration(declaration) && ts.isVariableDeclarationList(declaration.parent)
+ && (declaration.parent.flags & ts.NodeFlags.Const) !== 0 && declaration.initializer !== undefined
+ && timerValue(declaration.initializer, seen)) return true;
+ if (ts.isBindingElement(declaration) && ts.isObjectBindingPattern(declaration.parent)
+ && ts.isVariableDeclaration(declaration.parent.parent) && declaration.parent.parent.initializer !== undefined
+ && ts.isVariableDeclarationList(declaration.parent.parent.parent) && (declaration.parent.parent.parent.flags & ts.NodeFlags.Const) !== 0) {
+ const member = declaration.propertyName ?? declaration.name;
+ if ((ts.isIdentifier(member) || ts.isStringLiteral(member)) && sdkTimer(checker.getPropertyOfType(checker.getTypeAtLocation(declaration.parent.parent.initializer), member.text))) return true;
+ }
+ }
+ return false;
+ };
+ const unsafeHandler = (type: ts.Type): boolean => {
+ if ((type.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter)) !== 0) return true;
+ if (type.isUnion()) return type.types.some(unsafeHandler);
+ if (type.isIntersection() && type.types.some(part => (part.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter)) !== 0)) return true;
+ return type.getCallSignatures().length === 0 && !(type.getSymbol()?.getName() === "Function"
+ && type.getSymbol()!.getDeclarations()?.every(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())));
+ };
+ const inspectTimerUse = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ const parent = node.parent;
+ if (ts.isIdentifier(node) && ((ts.isPropertyAccessExpression(parent) && parent.name === node)
+ || (ts.isVariableDeclaration(parent) && parent.name === node) || ts.isBindingElement(parent))) return;
+ if (!timerValue(node)) return;
+ for (let current: ts.Node | undefined = node; current !== undefined; current = current.parent) if (ts.isTypeNode(current)) return;
+ let usage: ts.Expression = node;
+ while (ts.isParenthesizedExpression(usage.parent) || ts.isAsExpression(usage.parent) || ts.isTypeAssertionExpression(usage.parent)
+ || ts.isNonNullExpression(usage.parent) || ts.isSatisfiesExpression(usage.parent)) {
+ if (unwrapStaticExpression(usage.parent) !== unwrapStaticExpression(node)) break;
+ usage = usage.parent;
+ }
+ const container = usage.parent;
+ if (ts.isVariableDeclaration(container) && container.initializer === usage && ts.isIdentifier(container.name)
+ && ts.isVariableDeclarationList(container.parent) && (container.parent.flags & ts.NodeFlags.Const) !== 0) return;
+ if (ts.isCallExpression(container) && container.expression === usage && container.arguments[0] !== undefined
+ && !unsafeHandler(checker.getTypeAtLocation(unwrapStaticExpression(container.arguments[0])))) {
+ reasons.add("SOURCE_TIMER_CALLBACK_UNSUPPORTED"); return;
+ }
+ reasons.add("SOURCE_TIMER_EVALUATION_UNSUPPORTED");
+ };
+ const internalBase = (expression: ts.Expression, seen = new Set()): boolean => {
+ const base = unwrapStaticExpression(expression);
+ if (seen.has(base)) return false;
+ seen.add(base);
+ if (ts.isConditionalExpression(base)) return internalBase(base.whenTrue, seen) || internalBase(base.whenFalse, seen);
+ if ((checker.getTypeAtLocation(base).getSymbol()?.getDeclarations() ?? []).some(declaration =>
+ (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && internal(declaration))) return true;
+ return declarations(base).some(declaration => ((ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)
+ || ts.isFunctionDeclaration(declaration)) && internal(declaration)) || (ts.isVariableDeclaration(declaration)
+ && declaration.initializer !== undefined && internalBase(declaration.initializer, seen)));
+ };
+ const sdkGlobalOrigins = (origins: readonly ts.Declaration[]): boolean => origins.length > 0
+ && origins.every(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile()))
+ && origins.some(origin => ts.isVariableDeclaration(origin));
+ const sdkGlobalMember = (expression: ts.Expression, name: string | undefined): boolean => {
+ const receiver = actualValue(expression);
+ const origins = declarations(receiver);
+ // JavaScript descriptor inference adds identifier/call declarations to these SDK symbols.
+ const global = ts.isIdentifier(receiver) && receiver.text === "globalThis" && checker.getSymbolAtLocation(receiver) !== undefined
+ && origins.every(origin => ts.isIdentifier(origin) && origin.text === "globalThis");
+ return name !== undefined && (global || sdkOrigin(receiver))
+ && sdkGlobalOrigins((checker.getPropertyOfType(checker.getTypeAtLocation(receiver), name)?.getDeclarations() ?? [])
+ .filter(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())));
+ };
+ const sdkGlobalAssignment = (expression: ts.Expression): boolean => {
+ const target = unwrapStaticExpression(expression);
+ if (ts.isArrayLiteralExpression(target)) return target.elements.some(element =>
+ ts.isSpreadElement(element) ? sdkGlobalAssignment(element.expression) : ts.isExpression(element) && sdkGlobalAssignment(element));
+ if (ts.isObjectLiteralExpression(target)) return target.properties.some(property => {
+ if (ts.isPropertyAssignment(property)) return sdkGlobalAssignment(property.initializer);
+ if (ts.isSpreadAssignment(property)) return sdkGlobalAssignment(property.expression);
+ if (ts.isShorthandPropertyAssignment(property)) return sdkGlobalOrigins(checker.getShorthandAssignmentValueSymbol(property)?.getDeclarations() ?? []);
+ return false;
+ });
+ if (assignment(target)) return sdkGlobalAssignment(target.left);
+ return sdkGlobalOrigins(declarations(target));
+ };
+ const runtimeProperty = (node: ts.Node): boolean => {
+ if (node.getSourceFile().isDeclarationFile) return false;
+ for (let owner: ts.Node | undefined = node; owner !== undefined; owner = owner.parent) {
+ if (ts.canHaveModifiers(owner) && ts.getModifiers(owner)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)) return false;
+ }
+ return true;
+ };
+ const sdkMemberOf = (expression: ts.Expression, owner: string, member: string): boolean => {
+ const symbol = sdkValue(actualValue(expression));
+ return symbol?.getName() === member && symbol.getDeclarations()?.some(origin => {
+ for (let parent: ts.Node | undefined = origin.parent; parent !== undefined; parent = parent.parent) {
+ if ((ts.isInterfaceDeclaration(parent) || ts.isModuleDeclaration(parent)) && parent.name.text === owner) return true;
+ }
+ return false;
+ }) === true;
+ };
+ const sdkScriptElement = (expression: ts.Expression): boolean => {
+ const value = actualValue(expression);
+ if (!ts.isCallExpression(value) || value.arguments[0] === undefined || constantString(value.arguments[0])?.toLowerCase() !== "script") return false;
+ const callee = actualValue(value.expression);
+ return (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee))
+ && sdkOrigin(callee.expression) && sdkMemberOf(callee, "Document", "createElement");
+ };
+ // DOM insertion algorithms reach HTML script post-connection preparation.
+ // Coordinates select inserted nodes, never reference/replaced nodes or position strings.
+ const sdkNodeInsertions = [
+ ["Node", "appendChild", 0], ["Node", "insertBefore", 0], ["Node", "replaceChild", 0],
+ ["ParentNode", "append", "all"], ["ParentNode", "prepend", "all"], ["ParentNode", "replaceChildren", "all"],
+ ["ChildNode", "before", "all"], ["ChildNode", "after", "all"], ["ChildNode", "replaceWith", "all"],
+ ["Element", "insertAdjacentElement", 1], ["Range", "insertNode", 0],
+ ] as const;
+ const importMetaMember = (expression: ts.Expression, seen = new Set()): boolean => {
+ const value = actualValue(expression);
+ if (seen.has(value)) return false;
+ seen.add(value);
+ if (ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) {
+ const receiver = actualValue(value.expression);
+ if (ts.isMetaProperty(receiver) && receiver.keywordToken === ts.SyntaxKind.ImportKeyword) return staticMemberName(value) !== "require";
+ const primitive = (checker.getTypeAtLocation(receiver).flags & (ts.TypeFlags.StringLike | ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike | ts.TypeFlags.BigIntLike | ts.TypeFlags.ESSymbolLike)) !== 0;
+ return !primitive && importMetaMember(receiver, seen);
+ }
+ return (checker.getSymbolAtLocation(value)?.getDeclarations() ?? []).some(origin => ts.isBindingElement(origin)
+ && ts.isObjectBindingPattern(origin.parent) && ts.isVariableDeclaration(origin.parent.parent)
+ && origin.parent.parent.initializer !== undefined
+ && ts.isMetaProperty(actualValue(origin.parent.parent.initializer)) && propertyName(origin.propertyName ?? origin.name) !== "require");
+ };
+ const inspectImportMetaMember = (node: ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression): void => {
+ if (!importMetaMember(node)) return;
+ const value = actualValue(node);
+ const type = checker.getTypeAtLocation(value);
+ if (!potentiallyCallable(type)) return;
+ const parent = node.parent;
+ if ((ts.isPropertyAccessExpression(parent) && parent.name === node) || (ts.isVariableDeclaration(parent) && parent.name === node)
+ || ts.isBindingElement(parent)) return;
+ if ((ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && parent.expression === node) return;
+ if (ts.isVariableDeclaration(parent) && parent.initializer === node && ts.isVariableDeclarationList(parent.parent)
+ && (parent.parent.flags & ts.NodeFlags.Const) !== 0 && ts.isIdentifier(parent.name)) return;
+ reasons.add("SOURCE_IMPORT_META_CALLABLE_UNSUPPORTED");
+ };
+ type SdkInvocation = { callee: ts.Expression; receiver: ts.Expression | undefined; arguments: readonly ts.Expression[] | undefined };
+ const sdkInvocation = (node: ts.CallExpression | ts.NewExpression): SdkInvocation => {
+ const callee = actualValue(node.expression);
+ const receiver = ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee) ? callee.expression : undefined;
+ const direct = { callee, receiver, arguments: node.arguments ?? [] };
+ if (!ts.isCallExpression(node)) return direct;
+ const helperCallee = ts.isCallExpression(callee) ? actualValue(callee.expression) : callee;
+ if (!(ts.isPropertyAccessExpression(helperCallee) || ts.isElementAccessExpression(helperCallee))) return direct;
+ const name = staticMemberName(helperCallee);
+ const target = actualValue(helperCallee.expression);
+ if (!["call", "apply", "bind"].includes(name ?? "") || !sdkOrigin(target)) return direct;
+ const helper = checker.getPropertyOfType(callableType(target), name!);
+ if (!helper?.getDeclarations()?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())
+ && ts.isInterfaceDeclaration(declaration.parent) && ["Function", "CallableFunction", "NewableFunction"].includes(declaration.parent.name.text))) return direct;
+ if (ts.isCallExpression(callee)) return name === "bind"
+ ? { callee: target, receiver: callee.arguments[0], arguments: [...callee.arguments.slice(1), ...node.arguments] } : direct;
+ if (name === "bind") return direct;
+ if (name === "call") return { callee: target, receiver: node.arguments[0], arguments: node.arguments.slice(1) };
+ const list = node.arguments[1] === undefined ? undefined : unwrapStaticExpression(node.arguments[1]);
+ const argumentsToConsume = list === undefined || list.kind === ts.SyntaxKind.NullKeyword ? []
+ : ts.isArrayLiteralExpression(list) && !list.elements.some(ts.isSpreadElement) ? list.elements : undefined;
+ return { callee: target, receiver: node.arguments[0], arguments: argumentsToConsume };
+ };
+ const inspectSdkCoercion = (invocation: SdkInvocation): void => {
+ const { callee, receiver, arguments: args } = invocation;
+ if (!sdkOrigin(callee)) return;
+ if (["String", "Number"].includes(sdkValue(callee)?.getName() ?? "")
+ && (args === undefined || (args[0] !== undefined && !primitiveValue(args[0])))) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ if (sdkMemberOf(callee, "Math", sdkValue(callee)?.getName() ?? "")) {
+ for (const signature of callableType(callee).getCallSignatures()) {
+ const declaration = signature.getDeclaration();
+ if (declaration === undefined || !program.isSourceFileDefaultLibrary(declaration.getSourceFile())) continue;
+ for (const [index, parameter] of signature.getParameters().entries()) {
+ const rest = parameter.getDeclarations()?.some(origin => ts.isParameter(origin) && origin.dotDotDotToken !== undefined) === true;
+ const type = checker.getTypeOfSymbolAtLocation(parameter, declaration);
+ const numeric = rest ? type.getNumberIndexType() : type;
+ if (numeric === undefined || (numeric.flags & ts.TypeFlags.NumberLike) === 0) continue;
+ const consumed = rest ? args?.slice(index) : args?.slice(index, index + 1);
+ if (consumed === undefined || consumed.some(argument => !primitiveValue(argument))) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ }
+ }
+ }
+ if (sdkMemberOf(callee, "Array", "join")) {
+ const array = receiver === undefined ? undefined : unwrapStaticExpression(receiver);
+ if (array === undefined || !ts.isArrayLiteralExpression(array) || !array.elements.every(element => ts.isExpression(element) && primitiveValue(element))
+ || args === undefined || (args[0] !== undefined && !primitiveValue(args[0]))) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ }
+ if (sdkMemberOf(callee, "Array", "toLocaleString")) {
+ const array = receiver === undefined ? undefined : unwrapStaticExpression(receiver);
+ const locales = args?.[0]; const options = args?.[1];
+ const localeValue = locales === undefined ? undefined : unwrapStaticExpression(locales);
+ const optionValue = options === undefined ? undefined : unwrapStaticExpression(options);
+ const safeLocales = locales === undefined || primitiveValue(locales) || (localeValue !== undefined && ts.isArrayLiteralExpression(localeValue)
+ && localeValue.elements.every(element => ts.isExpression(element) && primitiveValue(element)));
+ const safeOptions = options === undefined || primitiveValue(options) || (optionValue !== undefined && ts.isObjectLiteralExpression(optionValue)
+ && literalData(optionValue) && optionValue.properties.every(property => ts.isPropertyAssignment(property) ? primitiveValue(property.initializer)
+ : ts.isShorthandPropertyAssignment(property) && primitiveValue(property.name)));
+ if (array === undefined || !ts.isArrayLiteralExpression(array) || !array.elements.every(element => ts.isExpression(element) && primitiveValue(element))
+ || args === undefined || !safeLocales || !safeOptions) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ }
+ };
+ const inspectRuntimeCode = (node: ts.CallExpression | ts.NewExpression): void => {
+ inspectSdkCoercion(sdkInvocation(node));
+ if (sdkOrigin(node.expression) && ((ts.isCallExpression(node) && ["instantiate", "instantiateStreaming"].some(member => sdkMemberOf(node.expression, "WebAssembly", member)))
+ || (ts.isNewExpression(node) && sdkMemberOf(node.expression, "WebAssembly", "Instance")))) {
+ reasons.add("SOURCE_WASM_INSTANTIATION_UNSUPPORTED");
+ }
+ if (!ts.isCallExpression(node)) return;
+ const callee = actualValue(node.expression);
+ if ((ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) && sdkOrigin(callee.expression)) {
+ if (["write", "writeln"].some(member => sdkMemberOf(callee, "Document", member))) reasons.add("SOURCE_DOCUMENT_WRITE_UNSUPPORTED");
+ const insertion = sdkNodeInsertions.find(([owner, member]) => sdkMemberOf(callee, owner, member));
+ if (insertion !== undefined) {
+ const inserted = insertion[2] === "all" ? node.arguments : node.arguments.slice(insertion[2], insertion[2] + 1);
+ if (inserted.some(sdkScriptElement)) reasons.add("SOURCE_SCRIPT_INSERTION_UNSUPPORTED");
+ }
+ }
+ };
+ const visit = (node: ts.Node): void => {
+ if (ts.isTypeNode(node) || ts.isInterfaceDeclaration(node)) return;
+ if (ts.isCallExpression(node) || ts.isNewExpression(node)) { inspectSdkCallbacks(node); inspectRuntimeCode(node); }
+ if (ts.isCallExpression(node)) { inspectOpaqueCallbacks(node); inspectDefaultSort(node); }
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) inspectImportMetaMember(node);
+ if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.parent)
+ && (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) && node.name.text === "__proto__"
+ && !literalData(node.initializer)) reasons.add("SOURCE_PROTOTYPE_MUTATION_UNSUPPORTED");
+ if (ts.isFunctionDeclaration(node) && node.body === undefined && (node.name === undefined || !declarations(node.name).some(origin =>
+ ts.isFunctionDeclaration(origin) && origin.body !== undefined && internal(origin)
+ && !ts.getModifiers(origin)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)))) reasons.add("SOURCE_AMBIENT_FUNCTION_UNSUPPORTED");
+ if (runtimeProperty(node) && ((ts.isElementAccessExpression(node) && !propertyKey(node.argumentExpression))
+ || (ts.isComputedPropertyName(node) && !propertyKey(node.expression)))) reasons.add("SOURCE_PROPERTY_KEY_UNSUPPORTED");
+ if (ts.isComputedPropertyName(node) && runtimeProperty(node) && protocolKey(node.expression)
+ && (ts.isMethodDeclaration(node.parent)
+ || (ts.isPropertyAssignment(node.parent) && !primitiveValue(node.parent.initializer))
+ || (ts.isPropertyDeclaration(node.parent) && node.parent.initializer !== undefined && !primitiveValue(node.parent.initializer)))) {
+ reasons.add("SOURCE_PROTOCOL_DEFINITION_UNSUPPORTED");
+ }
+ if (assignment(node)
+ && staticMemberName(unwrapStaticExpression(node.left)) === "then" && !primitiveValue(node.right)) reasons.add("SOURCE_THENABLE_MUTATION_UNSUPPORTED");
+ if (assignment(node)) {
+ const target = unwrapStaticExpression(node.left);
+ if ((ts.isPropertyAccessExpression(target) || ts.isElementAccessExpression(target))
+ && staticMemberName(target) === "href" && sdkOrigin(target.expression)) {
+ const member = checker.getPropertyOfType(checker.getTypeAtLocation(actualValue(target.expression)), "href");
+ if (member?.getDeclarations()?.some(origin => program.isSourceFileDefaultLibrary(origin.getSourceFile())
+ && ts.isInterfaceDeclaration(origin.parent) && origin.parent.name.text === "Location")) {
+ const url = constantString(node.right);
+ // URL parsing strips leading C0/space and ASCII tabs/newlines before reading the scheme.
+ const normalized = url?.replace(/[\t\n\r]/g, "");
+ let first = 0;
+ while (normalized !== undefined && first < normalized.length && normalized.charCodeAt(first) <= 0x20) first++;
+ const scheme = normalized?.slice(first).match(/^([a-z][a-z0-9+.-]*):/i)?.[1]?.toLowerCase();
+ if (normalized === undefined || (scheme !== undefined && scheme !== "http" && scheme !== "https")) {
+ reasons.add("SOURCE_SCRIPT_NAVIGATION_UNSUPPORTED");
+ }
+ }
+ }
+ if (sdkGlobalAssignment(target)) reasons.add("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED");
+ if (ts.isElementAccessExpression(target) && iteratorKey(target.argumentExpression)) reasons.add("SOURCE_ITERATOR_MUTATION_UNSUPPORTED");
+ else if (ts.isElementAccessExpression(target) && protocolKey(target.argumentExpression)) reasons.add("SOURCE_PROTOCOL_MUTATION_UNSUPPORTED");
+ if (sdkCallableProperty(target) && !primitiveValue(node.right)) reasons.add("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+ }
+ if ((coercingBinary(node)
+ && (!primitiveValue(node.left) || !primitiveValue(node.right)))
+ || (propertyMembership(node) && !propertyKey(node.left))
+ || (numericUnary(node) && !primitiveValue(node.operand))
+ || (ts.isTemplateExpression(node) && node.templateSpans.some(span => !primitiveValue(span.expression)))) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ if ((ts.isReturnStatement(node) && node.expression !== undefined && asyncOwner(node) && !safeAwaitValue(node.expression))
+ || (ts.isArrowFunction(node) && !ts.isBlock(node.body) && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)
+ && !safeAwaitValue(node.body))) reasons.add("SOURCE_ASYNC_RETURN_UNSUPPORTED");
+ if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && serviceWorkerLoader(node)) reasons.add("SOURCE_SERVICE_WORKER_LOADER_UNSUPPORTED");
+ if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && workletLoader(node)) reasons.add("SOURCE_WORKLET_LOADER_UNSUPPORTED");
+ if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && sdkValue(node)?.getName() === "importScripts") reasons.add("SOURCE_WORKER_LOADER_UNSUPPORTED");
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) inspectDescriptorUse(node);
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
+ inspectReflectSetUse(node);
+ inspectMetaObjectUse(node);
+ inspectBuiltinProcessUse(node);
+ }
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
+ inspectPromiseResolveUse(node);
+ inspectSerializerUse(node);
+ inspectCombinatorUse(node);
+ }
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) inspectMutationUse(node);
+ if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && proxyFactory(node)) reasons.add("SOURCE_PROXY_UNSUPPORTED");
+ if (ts.isAwaitExpression(node) && !safeAwaitValue(node.expression)) reasons.add("SOURCE_AWAIT_UNSUPPORTED");
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword
+ && sdkConstructorValue(node.right) === undefined) reasons.add("SOURCE_INSTANCEOF_UNSUPPORTED");
+ if (ts.isArrayBindingPattern(node)) {
+ const value = bindingValue(node);
+ if (value === undefined || !intrinsicIterable(value)) reasons.add("SOURCE_ITERATION_UNSUPPORTED");
+ }
+ if ((ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isArrayLiteralExpression(node.left) && !intrinsicIterable(node.right))
+ || (ts.isYieldExpression(node) && node.asteriskToken !== undefined && (node.expression === undefined || !intrinsicIterable(node.expression)))) reasons.add("SOURCE_ITERATION_UNSUPPORTED");
+ if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node))
+ && reflectedInvocation(node)) reasons.add("SOURCE_REFLECT_INVOCATION_UNSUPPORTED");
+ if ((ts.isForOfStatement(node) || ts.isSpreadElement(node)) && !intrinsicIterable(node.expression)) reasons.add("SOURCE_ITERATION_UNSUPPORTED");
+ if (ts.isForOfStatement(node) && node.awaitModifier !== undefined) {
+ const iterable = unwrapStaticExpression(node.expression);
+ const safeItems = ts.isArrayLiteralExpression(iterable)
+ && iterable.elements.every(element => ts.isOmittedExpression(element) || (!ts.isSpreadElement(element) && safeAwaitValue(element)));
+ if (!safeItems && !primitiveValue(iterable)) reasons.add("SOURCE_AWAIT_UNSUPPORTED");
+ }
+ if (ts.isNewExpression(node)) {
+ const origin = sdkConstructorValue(node.expression);
+ if (origin === undefined) reasons.add("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ else if (origin.getName() === "Date") {
+ const argumentsToCoerce = node.arguments?.slice(0, 7) ?? [];
+ if (!intrinsicDateCopy(node) && argumentsToCoerce.some(argument => !primitiveValue(argument))) reasons.add("SOURCE_COERCION_UNSUPPORTED");
+ }
+ else if (["Worker", "SharedWorker"].includes(origin.getName())) reasons.add("SOURCE_WORKER_LOADER_UNSUPPORTED");
+ else if (origin.getName() === "Proxy") reasons.add("SOURCE_PROXY_UNSUPPORTED");
+ }
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) inspectTimerUse(node);
+ if (ts.isPropertyAccessExpression(node) && functionHelper(node)) reasons.add("SOURCE_FUNCTION_HELPER_UNSUPPORTED");
+ if ((ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && intrinsicConstructor(node)) reasons.add("SOURCE_DYNAMIC_EVALUATION_UNSUPPORTED");
+ if (ts.isHeritageClause(node) && node.token === ts.SyntaxKind.ExtendsKeyword && (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent))) {
+ for (const base of node.types) {
+ if (internalBase(base.expression)) reasons.add("SOURCE_INTERNAL_HERITAGE_UNSUPPORTED");
+ }
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(bound);
+ return [...reasons].sort();
+}
diff --git a/packages/ts-analyzer/src/ts-symbols.ts b/packages/ts-analyzer/src/ts-symbols.ts
index 046a7267..581b8119 100644
--- a/packages/ts-analyzer/src/ts-symbols.ts
+++ b/packages/ts-analyzer/src/ts-symbols.ts
@@ -7,6 +7,42 @@ import { parseMarkers, type ParsedMarker } from "./markers";
/** Exact TypeScript runtime version bound into Plane A capability scopes. */
export const TYPESCRIPT_DIALECT_VERSION = ts.version;
+/** The exact SDK library directory used by retained compiler hosts. */
+export function typeScriptLibraryDirectory(): string { return dirname(ts.getDefaultLibFilePath(COMPILER_OPTIONS)); }
+export type CompilerInputSnapshot = ReadonlyMap;
+const snapshotSystems = new WeakMap();
+
+function snapshotSystem(snapshot: CompilerInputSnapshot): ts.System {
+ const cached = snapshotSystems.get(snapshot);
+ if (cached) return cached;
+ const retained = new Map([...snapshot].map(([path, content]) => [canonicalTypeScriptFileKey(path), content]));
+ const directories = new Set();
+ for (const path of retained.keys()) {
+ for (let directory = posix.dirname(path); ; directory = posix.dirname(directory)) {
+ directories.add(directory);
+ directories.add(canonicalTypeScriptFileKey(directory));
+ if (posix.dirname(directory) === directory) break;
+ }
+ }
+ const libraryRoot = canonicalTypeScriptFileKey(typeScriptLibraryDirectory());
+ const library = (path: string): boolean => canonicalTypeScriptFileKey(path).startsWith(`${libraryRoot}/`);
+ const system: ts.System = {
+ ...ts.sys,
+ readFile: path => retained.get(canonicalTypeScriptFileKey(path)) ?? (library(path) ? ts.sys.readFile(path) : undefined),
+ fileExists: path => retained.has(canonicalTypeScriptFileKey(path)) || (library(path) && ts.sys.fileExists(path)),
+ directoryExists: path => {
+ return directories.has(canonicalTypeScriptFileKey(path)) || (library(path) && ts.sys.directoryExists(path));
+ },
+ readDirectory: () => [],
+ };
+ snapshotSystems.set(snapshot, system);
+ return system;
+}
+
+/** Internal read seam for diagnostics over the same retained compiler inputs. */
+export function retainedCompilerSystem(snapshot: CompilerInputSnapshot): ts.System {
+ return snapshotSystem(snapshot);
+}
export interface ExtractedSymbol {
name: string;
@@ -32,6 +68,8 @@ export interface ExtractedImport {
moduleSpecifier: string;
resolvedRelPath?: string;
names: string[];
+ /** Canonical value-binding owners, independent of the structurally imported module. */
+ bindingTargets?: { relPath: string; symbolPath: string; runtimeUsed?: boolean; unmodeledReason?: string }[];
line: number;
}
@@ -44,10 +82,13 @@ export interface ExtractedCall {
* helper's calls to whichever same-named symbol the index happened to hold.
*/
callerSymbolPath?: string;
+ callerUnmodeledReason?: string;
calleeName: string;
calleeRelPath?: string;
/** Scope-qualified path of the resolved declaration, for the same reason as `callerSymbolPath`. */
calleeSymbolPath?: string;
+ /** Retained semantic endpoints whose declaration has no extracted owner coordinate. */
+ calleeUnmodeledReason?: string;
line: number;
}
@@ -113,7 +154,8 @@ const COMPILER_OPTIONS: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
- allowJs: false,
+ allowJs: true,
+ jsx: ts.JsxEmit.Preserve,
skipLibCheck: true,
noEmit: true,
strict: false,
@@ -121,11 +163,21 @@ const COMPILER_OPTIONS: ts.CompilerOptions = {
/** Internal semantic context; intentionally not exported from the package root. */
export const extractionContext = {
- createProgram(this: void, rootAbsPaths: string[]): ts.Program {
+ createProgram(this: void, rootAbsPaths: string[], snapshot?: CompilerInputSnapshot): ts.Program {
const host = ts.createCompilerHost(COMPILER_OPTIONS);
+ const system = snapshot === undefined ? ts.sys : snapshotSystem(snapshot);
+ if (snapshot !== undefined) {
+ host.readFile = path => system.readFile(path);
+ host.fileExists = path => system.fileExists(path);
+ host.directoryExists = path => system.directoryExists!(path);
+ }
// Match tsc's semantic parsing: retain type-error JSDoc, avoid prose ASTs in dependencies.
// Semctx reads its JSDoc/markers from source text, independently of these compiler nodes.
host.jsDocParsingMode = ts.JSDocParsingMode.ParseForTypeErrors;
+ if (snapshot !== undefined || rootAbsPaths.some(path => /\.(mjs|cjs|js|jsx)$/.test(path))) {
+ host.resolveModuleNames = (names, containingFile) => names.map(name =>
+ resolveConfiguredModule(name, containingFile, undefined, system)?.resolvedModule);
+ }
return ts.createProgram(rootAbsPaths, COMPILER_OPTIONS, host);
},
};
@@ -156,22 +208,62 @@ function nameOfCallee(expr: ts.Expression): string | undefined {
return undefined;
}
+/** Transparent expression wrappers shared by semantic extraction and eligibility guards. */
+export function unwrapStaticExpression(expression: ts.Expression): ts.Expression {
+ while (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression)
+ || ts.isNonNullExpression(expression) || ts.isSatisfiesExpression(expression)) expression = expression.expression;
+ return expression;
+}
+
+/** CallExpression domain: named callees, or import() handled separately as a module link. */
+export function isModeledCallCallee(expression: ts.Expression): boolean {
+ const callee = unwrapStaticExpression(expression);
+ return nameOfCallee(callee) !== undefined || callee.kind === ts.SyntaxKind.ImportKeyword;
+}
+
/** Internal resolver shared with discovery confinement; intentionally absent from the package root. */
export function resolveTypeScriptModule(
specifier: string,
containingFile: string,
resolutionMode?: ts.ResolutionMode,
+ configured = /\.(mjs|cjs|js|jsx)$/.test(containingFile),
+ snapshot?: CompilerInputSnapshot,
): string | undefined {
- const resolved = ts.resolveModuleName(
+ const system = snapshot === undefined ? ts.sys : snapshotSystem(snapshot);
+ const resolved = configured
+ ? resolveConfiguredModule(specifier, containingFile, resolutionMode, system)
+ : ts.resolveModuleName(specifier, containingFile, COMPILER_OPTIONS, system, undefined, undefined, resolutionMode);
+ // A declaration companion describes the runtime module; dependency edges target the actual
+ // source when it exists, rather than silently ending at the skipped .d.mts/.d.cts artifact.
+ const declarationPath = resolved.resolvedModule?.resolvedFileName;
+ if (configured && declarationPath !== undefined && /\.d\.(mts|cts|ts)$/.test(declarationPath)) {
+ const runtimePath = declarationPath.replace(/\.d\.(mts|cts|ts)$/, (_, extension: string) =>
+ extension === "mts" ? ".mjs" : extension === "cts" ? ".cjs" : ".js");
+ if (system.fileExists(runtimePath)) return runtimePath;
+ }
+ return declarationPath;
+}
+
+function resolveConfiguredModule(specifier: string, containingFile: string, resolutionMode?: ts.ResolutionMode, system: ts.System = ts.sys): ts.ResolvedModuleWithFailedLookupLocations {
+ const configPath = ts.findConfigFile(dirname(containingFile), path => system.fileExists(path));
+ let options = COMPILER_OPTIONS;
+ if (configPath !== undefined) {
+ const config = ts.readConfigFile(configPath, path => system.readFile(path));
+ if (config.error === undefined) {
+ // Module resolution needs inherited options, not a redundant scan of every tsconfig input.
+ const parsed = ts.parseJsonConfigFileContent(config.config, { ...system, readDirectory: () => [] }, dirname(configPath));
+ options = { ...COMPILER_OPTIONS, ...parsed.options };
+ }
+ }
+ return ts.resolveModuleName(
specifier,
containingFile,
- COMPILER_OPTIONS,
- ts.sys,
+ options,
+ system,
undefined,
undefined,
- resolutionMode,
+ resolutionMode ?? ts.getImpliedNodeFormatForFile(containingFile, undefined, system, options),
);
- return resolved.resolvedModule?.resolvedFileName;
}
function canonicalTypeScriptFileKey(filePath: string): string {
@@ -193,7 +285,8 @@ function isContainedTypeScriptPath(repoRoot: string, filePath: string): boolean
return rel === "" || (rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rel));
}
-function canonicalFilesystemPath(filePath: string): string {
+/** Internal confinement helper shared with discovery; not part of the package root API. */
+export function canonicalFilesystemPath(filePath: string): string {
const absolute = resolve(filePath);
let ancestor = absolute;
for (;;) {
@@ -215,7 +308,7 @@ function canonicalFilesystemPath(filePath: string): string {
* Bare package imports, configured type packages and the standard library remain delegated to the
* normal compiler host so the analyzer keeps the same TypeChecker environment for admitted input.
*/
-function assertConfinedTypeScriptSources(rootAbsPaths: readonly string[], repoRoot: string): void {
+function assertConfinedTypeScriptSources(rootAbsPaths: readonly string[], repoRoot: string, snapshot?: CompilerInputSnapshot): void {
const canonicalRoot = canonicalFilesystemPath(repoRoot);
const pending = [...rootAbsPaths];
const visited = new Set();
@@ -228,7 +321,8 @@ function assertConfinedTypeScriptSources(rootAbsPaths: readonly string[], repoRo
const key = canonicalTypeScriptFileKey(canonical);
if (visited.has(key)) continue;
visited.add(key);
- const source = readFileSync(path, "utf8");
+ const source = snapshot === undefined ? readFileSync(path, "utf8") : snapshotSystem(snapshot).readFile(path);
+ if (source === undefined) throw new Error(`SOURCE_SNAPSHOT_MISSING: ${normalizePath(relative(repoRoot, path))}`);
const preprocessed = ts.preProcessFile(source, true, true);
for (const imported of preprocessed.importedFiles) {
if (!imported.fileName.startsWith(".")) continue;
@@ -236,7 +330,7 @@ function assertConfinedTypeScriptSources(rootAbsPaths: readonly string[], repoRo
if (!isContainedTypeScriptPath(canonicalRoot, canonicalFilesystemPath(lexical))) {
throw new Error(`IMPORT_OUTSIDE_REPOSITORY: ${imported.fileName}`);
}
- const resolved = resolveTypeScriptModule(imported.fileName, path);
+ const resolved = resolveTypeScriptModule(imported.fileName, path, undefined, snapshot !== undefined || /\.(mjs|cjs|js|jsx)$/.test(path), snapshot);
if (resolved === undefined) continue;
const resolvedCanonical = canonicalFilesystemPath(resolved);
if (!isContainedTypeScriptPath(canonicalRoot, resolvedCanonical)) {
@@ -253,17 +347,24 @@ function assertConfinedTypeScriptSources(rootAbsPaths: readonly string[], repoRo
if (!isContainedTypeScriptPath(canonicalRoot, referencedCanonical)) {
throw new Error(`REFERENCE_OUTSIDE_REPOSITORY: ${referenced.fileName}`);
}
- if (existsSync(lexical)) pending.push(lexical);
+ // A retained analysis cannot enqueue or read a reference omitted from its snapshot.
+ // Qualified admission reports that missing dependency as a structured scope rejection.
+ if (snapshot === undefined ? existsSync(lexical) : snapshotSystem(snapshot).fileExists(lexical)) pending.push(lexical);
}
}
}
/** Extract modules, symbols, imports and best-effort resolved calls from source/test files. */
-export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsExtraction {
- assertConfinedTypeScriptSources(rootAbsPaths, repoRoot);
- const program = extractionContext.createProgram(rootAbsPaths);
+export function extractTypeScript(rootAbsPaths: string[], repoRoot: string, suppliedSnapshot?: CompilerInputSnapshot): TsExtraction {
+ const snapshot = suppliedSnapshot === undefined ? undefined : new Map(suppliedSnapshot);
+ assertConfinedTypeScriptSources(rootAbsPaths, repoRoot, snapshot);
+ const program = extractionContext.createProgram(rootAbsPaths, snapshot);
const checker = program.getTypeChecker();
const rootSet = new Set(rootAbsPaths.map(canonicalTypeScriptFileKey));
+ const javascriptEnabled = rootAbsPaths.some(path => /\.(mjs|cjs|js|jsx)$/.test(path));
+ // A retained qualified snapshot needs complete static module links even in a TS-only graph.
+ // Keep the historical no-snapshot TS extraction unchanged for legacy callers.
+ const staticModuleLinksEnabled = javascriptEnabled || snapshot !== undefined;
const modules: string[] = [];
const symbols: ExtractedSymbol[] = [];
@@ -278,6 +379,18 @@ export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsE
if (!rootSet.has(canonicalTypeScriptFileKey(sf.fileName))) continue;
const relPath = relOf(sf.fileName);
modules.push(relPath);
+ const exportedDeclarations = new Set();
+ const namespaceReferences = staticModuleLinksEnabled ? namespaceMemberReferences(sf, checker, snapshot !== undefined) : undefined;
+ const runtimeReferences = snapshot === undefined ? undefined : runtimeReferenceSymbols(sf, checker);
+ if (staticModuleLinksEnabled) {
+ const moduleSymbol = checker.getSymbolAtLocation(sf);
+ if (moduleSymbol !== undefined) {
+ for (let symbol of checker.getExportsOfModule(moduleSymbol)) {
+ if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);
+ for (const declaration of symbol.getDeclarations() ?? []) exportedDeclarations.add(declaration);
+ }
+ }
+ }
// Scope-qualified paths of the enclosing extracted symbols, innermost last. Distinct from
// `scopeStack`, which only tracks what can *contain*: a method is a scope but is not itself an
@@ -301,7 +414,7 @@ export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsE
scope: [...scopeStack],
startLine: lineOf(sf, node.getStart()),
endLine: lineOf(sf, node.getEnd()),
- exported: isExported(node),
+ exported: isExported(node) || exportedDeclarations.has(node as ts.Declaration),
...(signatureOnly ? { signatureOnly: true } : {}),
...(jsdoc !== undefined ? { jsdoc } : {}),
markers: jsdoc !== undefined ? parseMarkers(jsdoc) : [],
@@ -312,14 +425,16 @@ export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsE
let pushedSymbol: string | undefined;
let pushedScope: string | undefined;
- if (ts.isFunctionDeclaration(node) && node.name) {
- recordSymbol(node, node.name.text, "function", node.body === undefined);
- pushedSymbol = symbolScopePath(scopeStack, node.name.text);
- pushedScope = node.name.text;
- } else if (ts.isClassDeclaration(node) && node.name) {
- recordSymbol(node, node.name.text, "class");
- pushedSymbol = symbolScopePath(scopeStack, node.name.text);
- pushedScope = node.name.text;
+ if (ts.isFunctionDeclaration(node) && scopeNameOf(node) !== undefined) {
+ const name = scopeNameOf(node)!;
+ recordSymbol(node, name, "function", node.body === undefined);
+ pushedSymbol = symbolScopePath(scopeStack, name);
+ pushedScope = name;
+ } else if (ts.isClassDeclaration(node) && scopeNameOf(node) !== undefined) {
+ const name = scopeNameOf(node)!;
+ recordSymbol(node, name, "class");
+ pushedSymbol = symbolScopePath(scopeStack, name);
+ pushedScope = name;
} else if (ts.isInterfaceDeclaration(node)) {
recordSymbol(node, node.name.text, "interface");
} else if (ts.isTypeAliasDeclaration(node)) {
@@ -347,7 +462,7 @@ export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsE
scope: [...scopeStack],
startLine: lineOf(sf, node.getStart()),
endLine: lineOf(sf, decl.getEnd()),
- exported,
+ exported: exported || exportedDeclarations.has(decl),
...(jsdoc !== undefined ? { jsdoc } : {}),
markers: jsdoc !== undefined ? parseMarkers(jsdoc) : [],
});
@@ -363,27 +478,61 @@ export function extractTypeScript(rootAbsPaths: string[], repoRoot: string): TsE
return;
} else if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
const specifier = node.moduleSpecifier.text;
- const resolvedAbs = resolveTypeScriptModule(specifier, sf.fileName);
- const names = importedNames(node);
+ const resolvedAbs = resolveTypeScriptModule(specifier, sf.fileName, undefined, javascriptEnabled || snapshot !== undefined, snapshot);
+ const names = importedNames(node, staticModuleLinksEnabled);
+ const namespace = node.importClause?.namedBindings;
+ const namespaceSymbol = node.importClause?.isTypeOnly !== true && namespace !== undefined && ts.isNamespaceImport(namespace)
+ ? checker.getSymbolAtLocation(namespace.name) : undefined;
+ const references: { expression: ts.Expression; targetSymbol?: ts.Symbol }[] = [...valueImportBindings(node).map(expression => ({ expression })), ...(namespaceSymbol === undefined ? [] : namespaceReferences?.get(namespaceSymbol) ?? [])];
+ const bindingTargets = staticModuleLinksEnabled ? references.flatMap(binding => {
+ const target = resolveCallTarget(checker, binding.expression, relOf, javascriptEnabled, snapshot, binding.targetSymbol);
+ return target?.relPath !== undefined && target.symbolPath !== undefined ? [{ relPath: target.relPath, symbolPath: target.symbolPath,
+ ...(runtimeReferences !== undefined && ts.isIdentifier(binding.expression) && binding.targetSymbol === undefined
+ && !runtimeReferences.has(checker.getSymbolAtLocation(binding.expression)!) ? { runtimeUsed: false } : {}),
+ ...(target.unmodeledReason === undefined ? {} : { unmodeledReason: target.unmodeledReason }),
+ }] : [];
+ }) : undefined;
imports.push({
fromRelPath: relPath,
moduleSpecifier: specifier,
...(resolvedAbs !== undefined ? { resolvedRelPath: relOf(resolvedAbs) } : {}),
names,
+ ...(bindingTargets === undefined ? {} : { bindingTargets }),
+ line: lineOf(sf, node.getStart()),
+ });
+ } else if (staticModuleLinksEnabled && ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
+ const specifier = node.moduleSpecifier.text;
+ const resolvedAbs = resolveTypeScriptModule(specifier, sf.fileName, undefined, javascriptEnabled || snapshot !== undefined, snapshot);
+ imports.push({
+ fromRelPath: relPath,
+ moduleSpecifier: specifier,
+ ...(resolvedAbs !== undefined ? { resolvedRelPath: relOf(resolvedAbs) } : {}),
+ names: [],
line: lineOf(sf, node.getStart()),
});
} else if (ts.isCallExpression(node)) {
- const calleeName = nameOfCallee(node.expression);
+ if (staticModuleLinksEnabled && node.expression.kind === ts.SyntaxKind.ImportKeyword &&
+ node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0]!)) {
+ const specifier = (node.arguments[0] as ts.StringLiteral).text;
+ const resolvedAbs = resolveTypeScriptModule(specifier, sf.fileName, ts.ModuleKind.ESNext, javascriptEnabled || snapshot !== undefined, snapshot);
+ imports.push({ fromRelPath: relPath, moduleSpecifier: specifier,
+ ...(resolvedAbs !== undefined ? { resolvedRelPath: relOf(resolvedAbs) } : {}),
+ names: [], line: lineOf(sf, node.getStart()) });
+ }
+ const callee = staticModuleLinksEnabled ? unwrapStaticExpression(node.expression) : node.expression;
+ const calleeName = nameOfCallee(callee);
if (calleeName !== undefined) {
- const resolved = resolveCallTarget(checker, node.expression, relOf);
+ const resolved = resolveCallTarget(checker, callee, relOf, javascriptEnabled, snapshot);
calls.push({
callerRelPath: relPath,
+ ...(snapshot !== undefined && hasObjectLiteralMemberAncestor(node) ? { callerUnmodeledReason: "OBJECT_LITERAL_MEMBER" } : {}),
...(symbolPathStack.length > 0
? { callerSymbolPath: symbolPathStack[symbolPathStack.length - 1] }
: {}),
calleeName,
...(resolved?.relPath !== undefined ? { calleeRelPath: resolved.relPath } : {}),
...(resolved?.symbolPath !== undefined ? { calleeSymbolPath: resolved.symbolPath } : {}),
+ ...(resolved?.unmodeledReason !== undefined ? { calleeUnmodeledReason: resolved.unmodeledReason } : {}),
line: lineOf(sf, node.getStart()),
});
}
@@ -411,9 +560,19 @@ export async function extractTypeScriptParallel(
rootAbsPaths: string[],
repoRoot: string,
requested: IndexWorkerSelection = "auto",
+ snapshot?: CompilerInputSnapshot,
): Promise {
+ if (snapshot !== undefined) {
+ return { extraction: extractTypeScript(rootAbsPaths, repoRoot, snapshot), parallelism: { requested, used: 1, mode: "preflight-fallback", reason: "retained compiler input snapshot requires one semantic Program" } };
+ }
assertConfinedTypeScriptSources(rootAbsPaths, repoRoot);
const workerLimit = resolveWorkerCount(requested, rootAbsPaths.length);
+ if (workerLimit > 1 && rootAbsPaths.some(path => /\.(mjs|cjs|js|jsx)$/.test(path))) {
+ return {
+ extraction: extractTypeScript(rootAbsPaths, repoRoot),
+ parallelism: { requested, used: 1, mode: "preflight-fallback", reason: "mixed JavaScript/TypeScript extraction requires one semantic Program" },
+ };
+ }
if (workerLimit <= 1 || rootAbsPaths.length <= 1) {
return {
extraction: extractTypeScript(rootAbsPaths, repoRoot),
@@ -789,6 +948,11 @@ function isExtractionWorkerResponse(
&& typeof item["moduleSpecifier"] === "string"
&& (item["resolvedRelPath"] === undefined || inRepository(item["resolvedRelPath"]))
&& stringArray(item["names"])
+ && (item["bindingTargets"] === undefined || (Array.isArray(item["bindingTargets"])
+ && item["bindingTargets"].every(target => isRecord(target) && inRepository(target["relPath"])
+ && typeof target["symbolPath"] === "string"
+ && (target["runtimeUsed"] === undefined || typeof target["runtimeUsed"] === "boolean")
+ && optionalString(target["unmodeledReason"]))))
&& line(item["line"]))
&& dto["calls"].every((item) => isRecord(item)
&& owned(item["callerRelPath"])
@@ -796,6 +960,8 @@ function isExtractionWorkerResponse(
&& typeof item["calleeName"] === "string"
&& (item["calleeRelPath"] === undefined || inRepository(item["calleeRelPath"]))
&& optionalString(item["calleeSymbolPath"])
+ && optionalString(item["calleeUnmodeledReason"])
+ && optionalString(item["callerUnmodeledReason"])
&& line(item["line"]));
}
@@ -836,12 +1002,12 @@ function isFunctionLike(node: ts.Node): boolean {
* `import { type X }`) execute nothing, so they must NOT create tested_by coverage.
* Structural `imports` edges do not use these names, so they are unaffected.
*/
-function importedNames(node: ts.ImportDeclaration): string[] {
+function importedNames(node: ts.ImportDeclaration, canonical = false): string[] {
const clause = node.importClause;
if (clause === undefined) return [];
if (clause.isTypeOnly) return [];
const names: string[] = [];
- if (clause.name) names.push(clause.name.text);
+ if (clause.name) names.push(canonical ? "default" : clause.name.text);
const bindings = clause.namedBindings;
if (bindings) {
if (ts.isNamespaceImport(bindings)) {
@@ -849,13 +1015,82 @@ function importedNames(node: ts.ImportDeclaration): string[] {
} else {
for (const element of bindings.elements) {
if (element.isTypeOnly) continue;
- names.push(element.name.text);
+ names.push(canonical ? (element.propertyName ?? element.name).text : element.name.text);
}
}
}
return names;
}
+function valueImportBindings(node: ts.ImportDeclaration): ts.Identifier[] {
+ const clause = node.importClause;
+ if (clause === undefined || clause.isTypeOnly) return [];
+ const bindings: ts.Identifier[] = clause.name === undefined ? [] : [clause.name];
+ if (clause.namedBindings !== undefined && ts.isNamedImports(clause.namedBindings)) {
+ for (const element of clause.namedBindings.elements) if (!element.isTypeOnly) bindings.push(element.name);
+ }
+ return bindings;
+}
+
+/** Runtime lexical uses; declaration/type/shadow occurrences never prove a value read. */
+function runtimeReferenceSymbols(source: ts.SourceFile, checker: ts.TypeChecker): Set {
+ const symbols = new Set();
+ const visit = (node: ts.Node): void => {
+ if (ts.isImportDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)
+ || (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword))) return;
+ if (ts.isTypeNode(node)) {
+ if (ts.isExpressionWithTypeArguments(node) && ts.isHeritageClause(node.parent) && node.parent.token === ts.SyntaxKind.ExtendsKeyword
+ && (ts.isClassDeclaration(node.parent.parent) || ts.isClassExpression(node.parent.parent))) visit(node.expression);
+ return;
+ }
+ if (ts.isExportDeclaration(node) && node.isTypeOnly || ts.isExportSpecifier(node) && node.isTypeOnly) return;
+ if (ts.isIdentifier(node)) {
+ const symbol = ts.isShorthandPropertyAssignment(node.parent) ? checker.getShorthandAssignmentValueSymbol(node.parent)
+ : ts.isExportSpecifier(node.parent) ? checker.getExportSpecifierLocalTargetSymbol(node.parent) : checker.getSymbolAtLocation(node);
+ if (symbol !== undefined) symbols.add(symbol);
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(source); return symbols;
+}
+
+/** Static member references keyed by their actual lexical receiver binding, never its spelling. */
+function namespaceMemberReferences(source: ts.SourceFile, checker: ts.TypeChecker, retainUnmodeled = false): Map {
+ const references = new Map();
+ const unwrap = unwrapStaticExpression;
+ const add = (binding: ts.Symbol | undefined, expression: ts.Expression, target: ts.Symbol | undefined): void => {
+ if (target !== undefined && (target.flags & ts.SymbolFlags.Alias) !== 0) target = checker.getAliasedSymbol(target);
+ const declaration = target?.getDeclarations()?.[0];
+ const modeled = declaration !== undefined && (ts.isFunctionDeclaration(declaration) || ts.isClassDeclaration(declaration)
+ || (ts.isVariableDeclaration(declaration) && declaration.initializer !== undefined && isFunctionLike(declaration.initializer)));
+ const unmodeledValue = retainUnmodeled && declaration !== undefined
+ && (ts.isVariableDeclaration(declaration) || ts.isEnumDeclaration(declaration) || ts.isEnumMember(declaration));
+ if (binding === undefined || (!modeled && !unmodeledValue)) return;
+ const targets = references.get(binding) ?? [];
+ targets.push({ expression, targetSymbol: target }); references.set(binding, targets);
+ };
+ const visit = (node: ts.Node): void => {
+ if (ts.isTypeNode(node)) return;
+ if (ts.isPropertyAccessExpression(node) || (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression))) {
+ const member = node;
+ let receiver = unwrap(member.expression);
+ while (ts.isPropertyAccessExpression(receiver) || ts.isElementAccessExpression(receiver)) receiver = unwrap(receiver.expression);
+ add(checker.getSymbolAtLocation(receiver), member, checker.getSymbolAtLocation(member));
+ } else if (ts.isBindingElement(node) && node.dotDotDotToken === undefined && ts.isIdentifier(node.name)
+ && ts.isObjectBindingPattern(node.parent) && ts.isVariableDeclaration(node.parent.parent) && node.parent.parent.initializer !== undefined) {
+ const receiver = unwrap(node.parent.parent.initializer);
+ const binding = checker.getSymbolAtLocation(receiver);
+ if (binding?.getDeclarations()?.some(declaration => ts.isNamespaceImport(declaration))) {
+ const property = node.propertyName ?? node.name;
+ if (ts.isIdentifier(property) || ts.isStringLiteralLike(property)) add(binding, node.name, checker.getPropertyOfType(checker.getTypeAtLocation(receiver), property.text));
+ }
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ return references;
+}
+
/**
* The name a declaration contributes to the scope path of everything inside it.
*
@@ -864,8 +1099,9 @@ function importedNames(node: ts.ImportDeclaration): string[] {
* the two would put callers and callees in different address spaces.
*/
function scopeNameOf(node: ts.Node): string | undefined {
- if (ts.isFunctionDeclaration(node) && node.name !== undefined) return node.name.text;
- if (ts.isClassDeclaration(node) && node.name !== undefined) return node.name.text;
+ if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
+ return node.name?.text ?? ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Default) !== 0 ? "default" : undefined);
+ }
if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) return node.name.text;
if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) return node.name.text;
if (
@@ -880,21 +1116,32 @@ function scopeNameOf(node: ts.Node): string | undefined {
}
/** Enclosing scope names of a declaration, outermost first, excluding the declaration itself. */
-function enclosingScopeOf(node: ts.Node): string[] {
+function enclosingScopeOf(node: ts.Node, retainInterfaceOwners = false): string[] {
const scope: string[] = [];
for (let current: ts.Node | undefined = node.parent; current !== undefined; current = current.parent) {
- const name = scopeNameOf(current);
+ const name = retainInterfaceOwners && ts.isInterfaceDeclaration(current) ? current.name.text : scopeNameOf(current);
if (name !== undefined) scope.unshift(name);
}
return scope;
}
+/** Private proof coordinate: the same declaration domain and scope used by extraction. */
+export function retainedFunctionOwnerCoordinate(declaration: ts.FunctionDeclaration, root: string): { relPath: string; symbolPath: string } | undefined {
+ const name = scopeNameOf(declaration);
+ if (declaration.body === undefined || name === undefined) return undefined;
+ return { relPath: relative(root, declaration.getSourceFile().fileName).replaceAll("\\", "/"),
+ symbolPath: symbolScopePath(enclosingScopeOf(declaration, true), name) };
+}
+
function resolveCallTarget(
checker: ts.TypeChecker,
expr: ts.Expression,
relOf: (abs: string) => string,
-): { relPath?: string; symbolPath?: string } | undefined {
- let symbol = checker.getSymbolAtLocation(expr);
+ javascriptEnabled = false,
+ snapshot?: CompilerInputSnapshot,
+ targetSymbol?: ts.Symbol,
+): { relPath?: string; symbolPath?: string; unmodeledReason?: string } | undefined {
+ let symbol = targetSymbol ?? checker.getSymbolAtLocation(expr);
if (symbol === undefined) return undefined;
// Follow import aliases to the real declaration (imported functions call across files).
if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
@@ -905,9 +1152,49 @@ function resolveCallTarget(
const decl = declarations[0];
if (decl === undefined) return undefined;
const sf = decl.getSourceFile();
- if (sf.isDeclarationFile) return { symbolPath: symbolScopePath(enclosingScopeOf(decl), symbol.getName()) };
+ const symbolName = javascriptEnabled || snapshot !== undefined ? scopeNameOf(decl) ?? symbol.getName() : symbol.getName();
+ if (sf.isDeclarationFile) {
+ if (javascriptEnabled && /\.d\.(mts|cts|ts)$/.test(sf.fileName)) {
+ const runtimePath = sf.fileName.replace(/\.d\.(mts|cts|ts)$/, (_, extension: string) =>
+ extension === "mts" ? ".mjs" : extension === "cts" ? ".cjs" : ".js");
+ if (snapshot === undefined ? existsSync(runtimePath) : snapshotSystem(snapshot).fileExists(runtimePath)) return { relPath: relOf(runtimePath), symbolPath: symbolScopePath(enclosingScopeOf(decl, snapshot !== undefined), symbolName) };
+ }
+ return { symbolPath: symbolScopePath(enclosingScopeOf(decl, snapshot !== undefined), symbolName) };
+ }
return {
relPath: relOf(sf.fileName),
- symbolPath: symbolScopePath(enclosingScopeOf(decl), symbol.getName()),
+ symbolPath: symbolScopePath(enclosingScopeOf(decl, snapshot !== undefined), symbolName),
+ ...(snapshot !== undefined && ts.isObjectLiteralExpression(decl.parent) ? { unmodeledReason: "OBJECT_LITERAL_MEMBER" }
+ : snapshot !== undefined && (ts.isEnumDeclaration(decl) || ts.isEnumMember(decl)) ? { unmodeledReason: "ENUM_VALUE" }
+ : snapshot !== undefined && ts.isVariableDeclaration(decl) && (decl.initializer === undefined || !isFunctionLike(decl.initializer)) ? { unmodeledReason: "VALUE_DECLARATION" } : {}),
};
}
+
+/** Private semantic-policy seam: reuse the extractor's canonical declaration coordinate. */
+export function retainedCallableCoordinate(checker: ts.TypeChecker, expression: ts.Expression, root: string, snapshot: CompilerInputSnapshot): ReturnType {
+ let receiver = unwrapStaticExpression(expression);
+ while (ts.isPropertyAccessExpression(receiver) || ts.isElementAccessExpression(receiver)) receiver = unwrapStaticExpression(receiver.expression);
+ if (checker.getSymbolAtLocation(receiver)?.getDeclarations()?.some(origin => (ts.isImportSpecifier(origin) && origin.isTypeOnly)
+ || (ts.isImportClause(origin) && origin.isTypeOnly) || (ts.isNamespaceImport(origin) && origin.parent.isTypeOnly))) return undefined;
+ let symbol = checker.getSymbolAtLocation(expression);
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol);
+ if (!symbol?.getDeclarations()?.some(declaration => (ts.isFunctionDeclaration(declaration) && declaration.body !== undefined)
+ || ts.isClassDeclaration(declaration) || (ts.isMethodDeclaration(declaration) && ts.isClassLike(declaration.parent))
+ || (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.initializer !== undefined
+ && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))))) return undefined;
+ return resolveCallTarget(checker, expression, abs => normalizePath(relative(root, abs)), true, snapshot);
+}
+
+function hasObjectLiteralMemberAncestor(node: ts.Node): boolean {
+ for (let parent = node.parent; parent !== undefined; parent = parent.parent) {
+ if (parent.parent !== undefined && ts.isObjectLiteralExpression(parent.parent)
+ && (ts.isMethodDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent))) return true;
+ if (isFunctionLike(parent)) {
+ let owner = parent.parent;
+ while (owner !== undefined && (ts.isParenthesizedExpression(owner) || ts.isAsExpression(owner)
+ || ts.isTypeAssertionExpression(owner) || ts.isNonNullExpression(owner) || ts.isSatisfiesExpression(owner))) owner = owner.parent;
+ if (owner !== undefined && ts.isPropertyAssignment(owner) && ts.isObjectLiteralExpression(owner.parent)) return true;
+ }
+ }
+ return false;
+}
diff --git a/packages/ts-analyzer/test/accessors.test.ts b/packages/ts-analyzer/test/accessors.test.ts
new file mode 100644
index 00000000..a3798efa
--- /dev/null
+++ b/packages/ts-analyzer/test/accessors.test.ts
@@ -0,0 +1,20 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+const path = "/fixture/main.ts";
+for (const content of [
+ "export const settings = { get value() { return 1; } };",
+ "export class Settings { get value() { return 1; } }",
+ "export const settings = { set value(next: number) {} };",
+ "export class Settings { set value(next: number) {} }",
+]) test(`unmodeled accessor declarations are not complete: ${content}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_ACCESSOR_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_ACCESSOR_UNSUPPORTED");
+});
+test("JS producer refuses accessors while ordinary data and SDK properties remain unchanged", () => {
+ expect(inspectJavaScriptSource("/fixture/main.js", "export const settings = { get value() { return 1; } };").reasons).toContain("JAVASCRIPT_ACCESSOR_UNSUPPORTED");
+ const content = "export const settings = { value: 1 }; export class Plain { read() { return settings.value; } } export function consume() { return new Map().size; }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_ACCESSOR_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).not.toContain("JAVASCRIPT_ACCESSOR_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/array-coercion-mutation.test.ts b/packages/ts-analyzer/test/array-coercion-mutation.test.ts
new file mode 100644
index 00000000..cc0f0180
--- /dev/null
+++ b/packages/ts-analyzer/test/array-coercion-mutation.test.ts
@@ -0,0 +1,52 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const method of ["join", "toLocaleString"]) {
+ test(`mutated literal array ${method} refuses ${extension}`, () => {
+ const hook = method === "join" ? "toString" : "toLocaleString";
+ const assertion = extension === "ts" ? " as any" : "";
+ const source = `function hidden() { return 1; } const values = [1]; values[0] = { ${hook}: () => hidden() }${assertion}; export function run() { return values.${method}(); }`;
+ const path = `/fixture/main.${extension}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ extension, method, reasons }));
+ expect(reasons).toContain("SOURCE_COERCION_UNSUPPORTED");
+ });
+}
+
+for (const method of ["join", "toLocaleString"]) {
+ test(`${method} does not trust const array aliases or mutator calls`, () => {
+ for (const source of [
+ `const values = [1]; export function run() { return values.${method}(); }`,
+ `function hidden() { return 1; } const values = [1]; const alias = values; alias[0] = { toString: hidden, toLocaleString: hidden } as any; export function run() { return values.${method}(); }`,
+ `function hidden() { return 1; } const values = [1]; values.push({ toString: hidden, toLocaleString: hidden } as any); export function run() { return values.${method}(); }`,
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+ }
+ });
+ test(`fresh ${method} primitive literals and primitive aliases stay eligible`, () => {
+ for (const source of [
+ `export function run() { return [1, 'one', true, null, undefined].${method}(); }`,
+ `export function run() { return [].${method}(); }`,
+ `const value = 1; const alias = value; export function run() { return [alias].${method}(); }`,
+ method === "join"
+ ? "const separator = ','; const alias = separator; export function run() { return [1, 2].join(alias); }"
+ : "const locale = 'en-US'; const digits = 0; export function run() { return [1].toLocaleString(locale, { maximumFractionDigits: digits }); }",
+ `const own = { ${method}() { return 1; } }; export function run() { return own.${method}(); }`,
+ `const member = [1].${method}; const alias = member; export function run() { return alias(); }`,
+ `export function run() { return ([1] as number[]).${method}(); }`,
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+ });
+}
+
+test("native array coercion actually invokes the replaced element hooks", () => {
+ for (const method of ["join", "toLocaleString"]) {
+ const hook = method === "join" ? "toString" : "toLocaleString";
+ const source = `function hidden() { return 'HIDDEN_CALLED'; } const values = [1]; values[0] = { ${hook}: () => hidden() }; if (values.${method}() !== 'HIDDEN_CALLED') throw Error('missing implicit invocation');`;
+ const result = Bun.spawnSync([process.execPath, "-e", source], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0);
+ }
+});
diff --git a/packages/ts-analyzer/test/await-global-mutation.test.ts b/packages/ts-analyzer/test/await-global-mutation.test.ts
new file mode 100644
index 00000000..a3199597
--- /dev/null
+++ b/packages/ts-analyzer/test/await-global-mutation.test.ts
@@ -0,0 +1,88 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+const cases = [
+ [
+ "reassigned async",
+ "SOURCE_AWAIT_UNSUPPORTED",
+ "async function load(){return 1;}load=()=>({then:hidden});export async function run(){await load();return 1;}"
+ ],
+ [
+ "for await thenable",
+ "SOURCE_AWAIT_UNSUPPORTED",
+ "export async function run(){for await(const item of [{then:hidden}]){}return 1;}"
+ ],
+ [
+ "plural global descriptor",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Object.defineProperties(globalThis,{Math:{value:{random:hidden}}});export function run(){return Math.random();}"
+ ],
+ [
+ "reflect global set",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Reflect.set(globalThis,'Math',{random:hidden});export function run(){return Math.random();}"
+ ],
+ [
+ "assign global namespace",
+ "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED",
+ "Object.assign(globalThis,{Math:{random:hidden}});export function run(){return Math.random();}"
+ ]
+] as const;
+
+for (const extension of ["ts", "mjs"]) for (const [name, reason, body] of cases) {
+ test(`${name} ${extension} refuses hidden invocation with its specific reason`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = `function hidden(){return 1;}${body}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain(reason);
+ });
+}
+
+for (const extension of ["ts", "mjs"]) test(`unaltered async, primitive for-await and local/data mutation ${extension} stay supported`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const source of [
+ "async function load(){return 1;}export async function run(){await load();return 1;}",
+ "export async function run(){for await(const item of [1,2]){}return 1;}",
+ "export async function run(){for await(const item of [1,,2]){}return 1;}",
+ "export async function run(){for await(const item of 'text'){}return 1;}",
+ "export async function run(){for await(const item of [Promise.resolve(1)]){}return 1;}",
+ "const load=async()=>1;export async function run(){await load();return 1;}",
+ "const obj={async load(){return 1;}};export async function run(){await obj.load();return 1;}",
+ "export async function run(){for(const item of [{then:()=>1}]){}return 1;}",
+ "const globalThis={Math:{random:()=>1}};Object.defineProperties(globalThis,{Math:{value:{random:()=>2}}});export function run(){return 1;}",
+ "const globalThis={Math:{random:()=>1}};Reflect.set(globalThis,'Math',{random:()=>2});export function run(){return 1;}",
+ "const globalThis={Math:{random:()=>1}};Object.assign(globalThis,{Math:{random:()=>2}});export function run(){return 1;}",
+ "const data={value:1};Object.assign(data,{value:2});Reflect.set(data,'value',3);Object.defineProperties(data,{value:{value:4}});export function run(){return 1;}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+});
+
+for (const [name, source, expected] of [
+ ["reassigned async", "function hidden(resolve){console.log('REASSIGNED_CALLED');resolve(1);} async function load(){return 1;}load=()=>({then:hidden});await load();", "REASSIGNED_CALLED"],
+ ["for-await thenable", "function hidden(resolve){console.log('FOR_AWAIT_CALLED');resolve(1);}for await(const item of [{then:hidden}]){}", "FOR_AWAIT_CALLED"],
+ ["plural descriptor", "function hidden(){console.log('DEFINE_PROPERTIES_CALLED');return 7;}Object.defineProperties(globalThis,{Math:{value:{random:hidden}}});if(Math.random()!==7)throw Error('replacement failed');", "DEFINE_PROPERTIES_CALLED"],
+ ["reflect set", "function hidden(){console.log('REFLECT_SET_CALLED');return 7;}Reflect.set(globalThis,'Math',{random:hidden});if(Math.random()!==7)throw Error('replacement failed');", "REFLECT_SET_CALLED"],
+ ["assign", "function hidden(){console.log('ASSIGN_CALLED');return 7;}Object.assign(globalThis,{Math:{random:hidden}});if(Math.random()!==7)throw Error('replacement failed');", "ASSIGN_CALLED"],
+] as const) test(`native ${name} invokes the retained helper`, () => {
+ const child = Bun.spawnSync([process.execPath, "-e", source], { stdout: "pipe", stderr: "pipe" });
+ expect(child.exitCode).toBe(0);
+ expect(new TextDecoder().decode(child.stdout)).toContain(expected);
+});
+
+for (const extension of ["ts", "mjs"]) test(`async provenance follows replacement through aliases and destructuring ${extension}`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const body of [
+ "async function load(){return 1;}load=()=>({then:hidden});const alias=load;export async function run(){await alias();return 1;}",
+ "async function load(){return 1;}[load]=[()=>({then:hidden})];export async function run(){await load();return 1;}",
+ "const obj={async load(){return 1;}};obj.load=()=>({then:hidden});export async function run(){await obj.load();return 1;}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, `function hidden(){return 1;}${body}`]]))).toContain("SOURCE_AWAIT_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) test(`for-await mutable container and spread origins do not prove element assimilation ${extension}`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const source of [
+ "const values=[{then:()=>1}];export async function run(){for await(const item of values){}return 1;}",
+ "const values=[1];values[0]={then:()=>1};export async function run(){for await(const item of values){}return 1;}",
+ "const values=[{then:()=>1}];export async function run(){for await(const item of [...values]){}return 1;}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_AWAIT_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/builtin-execution-reexport.test.ts b/packages/ts-analyzer/test/builtin-execution-reexport.test.ts
new file mode 100644
index 00000000..4bcb5060
--- /dev/null
+++ b/packages/ts-analyzer/test/builtin-execution-reexport.test.ts
@@ -0,0 +1,52 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "js"]) test(`runtime launcher re-export is unsupported ${extension}`, () => {
+ const path = `/fixture/launcher.${extension}`;
+ const content = "export { fork } from 'node:child_process';";
+ const snapshot = new Map([[path, content], [`/fixture/main.${extension}`, "import { fork } from './launcher'; export function run() { return fork('./child.js'); }"]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_PROCESS_LAUNCH_UNSUPPORTED`);
+});
+
+for (const extension of ["ts", "js"]) for (const module of ["cluster", "node:cluster"]) for (const [content, consumer] of [
+ [`export { fork } from '${module}';`, "import { fork } from './launcher'; export function run() { return fork(); }"],
+ [`export { setupPrimary as launch } from '${module}';`, "import { launch } from './launcher'; export function run() { return launch(); }"],
+] as const) test(`runtime cluster re-export ${module} ${extension} ${content} retains process launch refusal`, () => {
+ const path = `/fixture/launcher.${extension}`;
+ const snapshot = new Map([[path, content], [`/fixture/main.${extension}`, consumer]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ extension, module, content, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_PROCESS_LAUNCH_UNSUPPORTED`);
+});
+
+test("cluster type-only and ordinary data re-exports preserve their domain", () => {
+ for (const module of ["cluster", "node:cluster"]) for (const content of [
+ `export type { fork, setupPrimary } from '${module}';`,
+ `export { type fork, type setupPrimary } from '${module}';`,
+ `export { isPrimary } from '${module}';`,
+ `export { isPrimary as primary } from '${module}';`,
+ ]) {
+ const path = "/fixture/launcher.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+
+test("type-only, local and ordinary Node data re-exports are distinct", () => {
+ for (const content of [
+ "export type { fork } from 'node:child_process';",
+ "export { type fork } from 'node:child_process';",
+ "export { basename } from 'node:path';",
+ "function fork() { return 1; } export { fork };",
+ ]) {
+ const path = "/fixture/launcher.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+
+test("the same explicit builtin execution boundary covers VM re-exports", () => {
+ const path = "/fixture/evaluation.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, "export { runInThisContext } from 'node:vm';"]]))).toContain("SOURCE_RUNTIME_EVALUATION_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, "export type { runInThisContext } from 'node:vm';"]]))).not.toContain("SOURCE_RUNTIME_EVALUATION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/callable-runtime.test.ts b/packages/ts-analyzer/test/callable-runtime.test.ts
new file mode 100644
index 00000000..54c9aa78
--- /dev/null
+++ b/packages/ts-analyzer/test/callable-runtime.test.ts
@@ -0,0 +1,46 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration, inspectNativeModuleBindings, extractTypeScript } from "../src";
+
+const path = "/fixture/main.ts";
+const inspect = (content: string) => inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+for (const initializer of ["class {}", "true ? (() => 1) : (() => 2)", "(() => 1)", "original", "make()"])
+ test(`callable identifier initializer lacks extraction coordinate: ${initializer}`, () => {
+ const content = `const original = () => 1; function make() { return original; } export const helper = ${initializer};`;
+ expect(extractTypeScript([path], "/fixture", new Map([[path, content]])).symbols.some(symbol => symbol.name === "helper")).toBe(false);
+ expect(inspect(content)).toContain("SOURCE_CALLABLE_EXPORT_UNSUPPORTED");
+ });
+for (const access of ["(() => {}).constructor", "helper.constructor", "alias.constructor", "(() => {}).constructor.call(null, 'return 1').call(null)"])
+ test(`intrinsic callable constructor route refuses ${access}`, () => {
+ const content = `function helper() {} const alias = helper; export function read() { return ${access}; }`;
+ expect(inspect(content)).toContain("SOURCE_DYNAMIC_EVALUATION_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_DYNAMIC_EVALUATION_UNSUPPORTED");
+ });
+for (const body of ["import.meta.require('./hidden.js')", "(() => { const meta = import.meta; return meta.require('./hidden.js'); })()", "(() => { const { require: loader } = import.meta; return loader('./hidden.js'); })()"])
+ test(`Bun metadata loader is not a complete static module link: ${body}`, () => {
+ const content = `export function read() { return ${body}; }`;
+ expect(inspectNativeModuleBindings(ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(true);
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+ });
+test("modeled identifier exports, own properties and ordinary metadata remain eligible", () => {
+ const content = "export const helper = () => 1; export const fn = function () { return 2; }; export const value = 1; const own = { constructor() { return 1; }, call() { return 2; } }; export function read() { return own.constructor() + own.call() + import.meta.url.length; }";
+ const reasons = inspect(content);
+ expect(reasons).not.toContain("SOURCE_CALLABLE_EXPORT_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_DYNAMIC_EVALUATION_UNSUPPORTED");
+ expect(inspectNativeModuleBindings(ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(false);
+ const object = "export function read() { return ({}).constructor; }";
+ expect(inspect(object)).not.toContain("SOURCE_DYNAMIC_EVALUATION_UNSUPPORTED");
+ const metadata = "const meta = import.meta; const { url } = meta; export function read() { return meta.url + url; }";
+ expect(inspectNativeModuleBindings(ts.createSourceFile(path, metadata, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(false);
+ expect(inspect(metadata)).not.toContain("SOURCE_COMMONJS_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, "export const C = class {};", ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_CALLABLE_EXPORT_UNSUPPORTED");
+});
+
+for (const content of [
+ "const key = 'require'; export function read() { return import.meta[key].call(null, './hidden.js'); }",
+ "export function read() { return ({ meta: import.meta }).meta.require('./hidden.js'); }",
+]) test(`Bun metadata computed/container escape is explicitly refused: ${content}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(true);
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+ expect(inspect(content)).toContain("SOURCE_COMMONJS_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/classic-jsx-factory.test.ts b/packages/ts-analyzer/test/classic-jsx-factory.test.ts
new file mode 100644
index 00000000..f0c63229
--- /dev/null
+++ b/packages/ts-analyzer/test/classic-jsx-factory.test.ts
@@ -0,0 +1,52 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+test("actual retained classic JSX factory emits a call missing from extraction", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-classic-factory-"));
+ try {
+ const path = join(root, "render.tsx"); const factory = join(root, "factory.ts"); const config = join(root, "tsconfig.json");
+ const content = "import {h} from './factory'; export function render(){return ;}";
+ const inputs = new Map([[path, content], [factory, "export function h(){return 1;}"], [config, '{"compilerOptions":{"jsx":"react","jsxFactory":"h","module":"ESNext","moduleResolution":"Bundler"}}']]);
+ for (const [file, text] of inputs) writeFileSync(file, text);
+ const facts = extractTypeScript([path, factory], root, inputs);
+ const emittedCalls: { originalKind: string; pos: number; end: number; synthesized: boolean }[] = [];
+ const output = ts.transpileModule(content, { fileName: path, compilerOptions: { jsx: ts.JsxEmit.React, jsxFactory: "h", module: ts.ModuleKind.ESNext }, transformers: { after: [() => node => {
+ const visit = (value: ts.Node): void => { if (ts.isCallExpression(value)) emittedCalls.push({ originalKind: ts.SyntaxKind[ts.getOriginalNode(value).kind], pos: value.pos, end: value.end, synthesized: (value.flags & ts.NodeFlags.Synthesized) !== 0 }); ts.forEachChild(value, visit); };
+ visit(node); return node;
+ }] } });
+ const reasons = inspectModuleConfiguration(path, root, inputs);
+ console.info(JSON.stringify({ output: output.outputText, emittedCalls, calls: facts.calls, reasons }));
+ expect(output.outputText).toContain('h("div", null)');
+ expect(facts.calls.some(call => call.callerSymbolPath === "render" && call.calleeSymbolPath === "h")).toBe(false);
+ expect(reasons).toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("classic fragment emission is refused while preserve, JSX-free and ordinary paths retain their boundary", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-classic-controls-"));
+ try {
+ const path = join(root, "render.tsx"); const factory = join(root, "factory.ts"); const config = join(root, "tsconfig.json");
+ const content = "import {h, Fragment} from './factory'; export function render(){return <>>;}";
+ const options = { jsx: "react", jsxFactory: "h", jsxFragmentFactory: "Fragment", module: "ESNext", moduleResolution: "Bundler" };
+ const inputs = new Map([[path, content], [factory, "export function h(){return 1;} export const Fragment=1;"], [config, JSON.stringify({ compilerOptions: options })]]);
+ for (const [file, text] of inputs) writeFileSync(file, text);
+ expect(inspectModuleConfiguration(path, root, inputs)).toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const preserve = new Map(inputs); preserve.set(config, JSON.stringify({ compilerOptions: { ...options, jsx: "preserve" } }));
+ expect(inspectModuleConfiguration(path, root, preserve)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const plain = new Map(inputs); plain.set(path, "export function render(){return 1;}");
+ expect(inspectModuleConfiguration(path, root, plain)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, root)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const jsPath = join(root, "render.jsx"); writeFileSync(jsPath, content);
+ const ordinaryJs = inspectJavaScriptSource(jsPath, content, root).reasons;
+ expect(ordinaryJs).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const retainedJs = new Map(inputs); retainedJs.set(jsPath, content);
+ const retainedJsReasons = inspectJavaScriptSource(jsPath, content, root, retainedJs).reasons;
+ console.info(JSON.stringify({ ordinaryJs, retainedJsReasons }));
+ expect(retainedJsReasons).toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ expect(extractTypeScript([path, factory], root).symbols.some(symbol => symbol.name === "render")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/classic-jsx-positive.test.ts b/packages/ts-analyzer/test/classic-jsx-positive.test.ts
new file mode 100644
index 00000000..563b396e
--- /dev/null
+++ b/packages/ts-analyzer/test/classic-jsx-positive.test.ts
@@ -0,0 +1,25 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+test("preserve, JSX-free and ordinary classic JSX controls retain their boundary", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-classic-controls-"));
+ try {
+ const path = join(root, "render.tsx"); const factory = join(root, "factory.ts"); const config = join(root, "tsconfig.json");
+ const content = "import {h, Fragment} from './factory'; export function render(){return <>>;}";
+ const options = { jsx: "react", jsxFactory: "h", jsxFragmentFactory: "Fragment", module: "ESNext", moduleResolution: "Bundler" };
+ const inputs = new Map([[path, content], [factory, "export function h(){return 1;} export const Fragment=1;"], [config, JSON.stringify({ compilerOptions: options })]]);
+ for (const [file, text] of inputs) writeFileSync(file, text);
+ const preserve = new Map(inputs); preserve.set(config, JSON.stringify({ compilerOptions: { ...options, jsx: "preserve" } }));
+ expect(inspectModuleConfiguration(path, root, preserve)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const plain = new Map(inputs); plain.set(path, "export function render(){return 1;}");
+ expect(inspectModuleConfiguration(path, root, plain)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, root)).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ const jsPath = join(root, "render.jsx"); writeFileSync(jsPath, content);
+ const ordinaryJs = inspectJavaScriptSource(jsPath, content, root).reasons;
+ expect(ordinaryJs).not.toContain("SOURCE_CLASSIC_JSX_RUNTIME_UNSUPPORTED");
+ expect(extractTypeScript([path, factory], root).symbols.some(symbol => symbol.name === "render")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/coercion-assimilation.test.ts b/packages/ts-analyzer/test/coercion-assimilation.test.ts
new file mode 100644
index 00000000..f93ba15f
--- /dev/null
+++ b/packages/ts-analyzer/test/coercion-assimilation.test.ts
@@ -0,0 +1,77 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("quoted SDK receiver register helper cannot hide loading behind any", () => {
+ const content = "export function start() { return (navigator.serviceWorker as any)['register'].call(navigator.serviceWorker, './sw.js'); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_SERVICE_WORKER_LOADER_UNSUPPORTED");
+});
+test("quoted SDK receiver revocable helper cannot hide traps behind any", () => {
+ const content = "export function start() { return (Proxy as any)['revocable'].call(Proxy, {}, { get: () => 1 }); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_PROXY_UNSUPPORTED");
+});
+test("follow-up return await preserves a proven native promise", () => {
+ const content = "export async function start() { return await Promise.resolve(1); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).not.toContain("SOURCE_ASYNC_RETURN_UNSUPPORTED");
+});
+test("follow-up SDK revocable proxy cannot hide a trap invocation", () => {
+ const content = "const trap = () => 1; export function start() { return Proxy.revocable({}, { get: trap }).proxy.key; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_PROXY_UNSUPPORTED");
+});
+test("follow-up assertion cannot hide a real SDK service worker receiver", () => {
+ const content = "export function start() { return (navigator.serviceWorker as any).register('./sw.js'); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_SERVICE_WORKER_LOADER_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["const value = { [Symbol.toPrimitive]() { return 1; } }; export function start() { return '' + value; }", "COERCION_UNSUPPORTED"],
+ ["const value = { then() { return 1; } }; export async function start() { return value; }", "ASYNC_RETURN_UNSUPPORTED"],
+ ["const trap = () => 1; const value = new Proxy({}, { get: trap }); export function start() { return value.key; }", "PROXY_UNSUPPORTED"],
+ ["export function start() { return navigator.serviceWorker.register('./sw.js', { type: 'module' }); }", "SERVICE_WORKER_LOADER_UNSUPPORTED"],
+] as const) test(`direct ${reason} refuses hidden invocation`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+for (const [content, reason] of [
+ ["const value = { [Symbol.toPrimitive]() { return 1; } }; export function start() { return `${value}`; }", "COERCION_UNSUPPORTED"],
+ ["export function start(input: number) { return input + 1; }", "COERCION_UNSUPPORTED"],
+ ["import { base } from './dep.mjs'; export function bridge() { return base() + 1; }", "COERCION_UNSUPPORTED"],
+ ["const value = { then() { return 1; } }; export const start = async () => value;", "ASYNC_RETURN_UNSUPPORTED"],
+ ["const Factory = Proxy; export function start() { return new Factory({}, {}); }", "PROXY_UNSUPPORTED"],
+ ["const register = navigator.serviceWorker.register; export function start() { return register.call(navigator.serviceWorker, './sw.js'); }", "SERVICE_WORKER_LOADER_UNSUPPORTED"],
+ ["const { register } = navigator.serviceWorker; export function start() { return register('./sw.js'); }", "SERVICE_WORKER_LOADER_UNSUPPORTED"],
+] as const) test(`unmodeled family refuses ${content}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain(`SOURCE_${reason}`);
+});
+test("proven primitives, native promises, plain returns and own names preserve their domain", () => {
+ for (const content of [
+ "const value = 1; export function start() { return value + value; }",
+ "export function start(value) { return `${typeof value}:${!!value}`; }",
+ "export async function start() { return 1 + 1; }",
+ "export async function start() { return Promise.resolve(1); }",
+ "export async function start() { return await Promise.resolve(1); }",
+ "export async function start() { return await 1; }",
+ "export async function start() { return import('./dep.mjs'); }",
+ "const value = { then() { return 1; } }; export function start() { return value; }",
+ "const value = { then() { return 1; } }; export async function start() { function inner() { return value; } return 1; }",
+ "const Proxy = Map; export function start() { return new Proxy(); }",
+ "const Proxy = { revocable() { return {proxy: {key: 1}}; } }; export function start() { return Proxy.revocable().proxy.key; }",
+ "const Proxy = { revocable() { return {proxy: {key: 1}}; } }; export function start() { return (Proxy as any)['revocable'].call(Proxy).proxy.key; }",
+ "const navigator = { serviceWorker: { register(value) { return value; } } }; export function start() { return (navigator.serviceWorker as any)['register'].call(navigator.serviceWorker, 'ordinary'); }",
+ "const navigator = { serviceWorker: { register(value) { return value; } } }; export function start() { return navigator.serviceWorker.register('ordinary'); }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content], ["/fixture/dep.mjs", "export function dep() { return 1; }"]]));
+ expect(reasons.filter(reason => /SOURCE_(COERCION|ASYNC_RETURN|PROXY|SERVICE_WORKER_LOADER)_UNSUPPORTED/.test(reason))).toEqual([]);
+ }
+});
diff --git a/packages/ts-analyzer/test/compiler-snapshot.test.ts b/packages/ts-analyzer/test/compiler-snapshot.test.ts
new file mode 100644
index 00000000..b429dcc7
--- /dev/null
+++ b/packages/ts-analyzer/test/compiler-snapshot.test.ts
@@ -0,0 +1,80 @@
+import { afterEach, expect, it, spyOn } from "bun:test";
+import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractionContext, extractTypeScript, extractTypeScriptParallel } from "../src/ts-symbols";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { analyzeRepository, discoverRepository } from "../src";
+import { inspectJavaScriptSource } from "../src/javascript-diagnostics";
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+it("retains source and configuration bytes across an ABA compiler read", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-compiler-aba-")); roots.push(root);
+ const source = join(root, "entry.mjs"); const config = join(root, "tsconfig.json");
+ const leaf = join(root, "leaf.mjs"); const other = join(root, "other.mjs");
+ const a = "import { leaf } from '@fixture/leaf'; export function retainedA() { return leaf(); }";
+ const configA = JSON.stringify({ compilerOptions: { baseUrl: ".", paths: { "@fixture/leaf": ["./leaf.mjs"] } } });
+ const snapshot = new Map([[source, a], [config, configA], [leaf, "export function leaf() { return 1; }"], [other, "export function leaf() { return 2; }"]]);
+ for (const [path, content] of snapshot) writeFileSync(path, content);
+ const original = extractionContext.createProgram;
+ const hook = spyOn(extractionContext, "createProgram").mockImplementation((paths, supplied) => {
+ writeFileSync(source, "import { leaf } from '@fixture/leaf'; export function transientB() { return leaf(); }");
+ writeFileSync(config, JSON.stringify({ compilerOptions: { baseUrl: ".", paths: { "@fixture/leaf": ["./other.mjs"] } } }));
+ const program = original(paths, supplied);
+ writeFileSync(source, a); writeFileSync(config, configA);
+ return program;
+ });
+ try {
+ const result = extractTypeScript([source, leaf, other], root, snapshot);
+ expect(result.symbols.some(symbol => symbol.name === "retainedA")).toBe(true);
+ expect(result.symbols.some(symbol => symbol.name === "transientB")).toBe(false);
+ expect(result.imports.find(item => item.fromRelPath === "entry.mjs")?.resolvedRelPath).toBe("leaf.mjs");
+ expect(result.calls.find(item => item.callerSymbolPath === "retainedA")?.calleeRelPath).toBe("leaf.mjs");
+ } finally { hook.mockRestore(); }
+});
+it("forces retained TS-only inputs through a single Program on the worker entrypoint", async () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ts-worker-snapshot-")); roots.push(root);
+ const path = join(root, "entry.ts");
+ writeFileSync(path, "export function transientB() { return 2; }");
+ const snapshot = new Map([[path, "export function retainedA() { return 1; }"]]);
+ const result = await extractTypeScriptParallel([path], root, 2, snapshot);
+ expect(result.extraction.symbols[0]!.name).toBe("retainedA");
+ expect(result.parallelism).toMatchObject({ requested: 2, used: 1, mode: "preflight-fallback", reason: "retained compiler input snapshot requires one semantic Program" });
+});
+it("reports declared third-party dependency boundaries while retaining unresolved workspace aliases", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-external-snapshot-")); roots.push(root);
+ const path = join(root, "test.mjs"); const content = "import { test } from 'vitest'; import { leaf } from '@fixture/local';";
+ const snapshot = new Map([[path, content], [join(root, "package.json"), JSON.stringify({ devDependencies: { vitest: "4.0.0", "@fixture/local": "workspace:*" } })]]);
+ const inspected = inspectJavaScriptSource(path, content, root, snapshot);
+ expect(inspected.staticModuleLinks).toContainEqual({ specifier: "vitest", resolution: "external" });
+ expect(inspected.reasons).not.toContain("JAVASCRIPT_IMPORT_UNRESOLVED:vitest");
+ expect(inspected.reasons).toContain("JAVASCRIPT_IMPORT_UNRESOLVED:@fixture/local");
+});
+it("refuses discovery metadata inconsistent with the retained raw source map", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-discovery-snapshot-")); roots.push(root);
+ const path = join(root, "script.mjs");
+ writeFileSync(path, "export function retainedA() { return 1; }");
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*.mjs"], exclude: [], languages: { javascript: "on" as const } };
+ const files = discoverRepository(config).files;
+ const snapshot = new Map([[path, files[0]!.content]]);
+ expect(() => analyzeRepository(config, [{ ...files[0]!, content: "export function transientB() { return 2; }" }], snapshot)).toThrow("SOURCE_SNAPSHOT_MISMATCH");
+});
+it("assembles a retained source graph instead of sealing transient compiler input", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-bound-snapshot-")); roots.push(root);
+ const path = join(root, "script.mjs"); const retained = "export function retainedA() { return 1; }";
+ writeFileSync(path, retained);
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*.mjs"], exclude: [], languages: { javascript: "on" as const } };
+ const discovery = discoverRepository(config);
+ const original = extractionContext.createProgram;
+ const hook = spyOn(extractionContext, "createProgram").mockImplementation((paths, snapshot) => {
+ writeFileSync(path, "export function transientB() { return 2; }");
+ const program = original(paths, snapshot);
+ writeFileSync(path, retained);
+ return program;
+ });
+ try {
+ const result = analyzeRepository(config, discovery.files, new Map([[path, retained]]));
+ expect(result.graph.nodes.some(node => node.name === "retainedA")).toBe(true);
+ expect(result.graph.nodes.some(node => node.name === "transientB")).toBe(false);
+ } finally { hook.mockRestore(); }
+});
diff --git a/packages/ts-analyzer/test/construction-reference.test.ts b/packages/ts-analyzer/test/construction-reference.test.ts
new file mode 100644
index 00000000..03815ccd
--- /dev/null
+++ b/packages/ts-analyzer/test/construction-reference.test.ts
@@ -0,0 +1,91 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { extractTypeScript, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+const path = "/fixture/main.ts";
+const inspect = (content: string) => inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+test("constructor-body coordinates do not create the missing make-to-Helper invocation", () => {
+ const content = "function touch() { return 1; } export class Helper { constructor() { touch(); } } export function make() { return new Helper(); }";
+ const extraction = extractTypeScript([path], "/fixture", new Map([[path, content]]));
+ expect(extraction.calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "Helper", calleeSymbolPath: "touch" }));
+ expect(extraction.calls.some(call => call.callerSymbolPath === "make" && call.calleeSymbolPath === "Helper")).toBe(false);
+ expect(inspect(content)).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+});
+for (const content of [
+ "export class Helper {} export function make() { return new Helper(); }",
+ "declare const Constructor: MapConstructor; export function make() { return new Constructor(); }",
+ "function choose(): MapConstructor { return Map; } export function make() { return new (choose())(); }",
+ "export class Helper {} export function make() { return new (Helper as unknown as typeof Error)(); }",
+ "let Constructor = Map; export function make() { return new Constructor(); }",
+]) test(`unmodeled or uncertain constructor origin refuses ${content}`, () => {
+ expect(inspect(content)).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+});
+test("known SDK constructors, plain class declarations and ordinary facts retain their domain", () => {
+ expect(inspect("const Alias = Map; export class Plain {} export function make() { return new Alias(); }")).not.toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ expect(inspect("export function make() { return new Map(); }")).not.toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ const content = "export class Helper {} export function make() { return new Helper(); }";
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ expect(extractTypeScript([path], "/fixture", new Map([[path, content]])).symbols.some(symbol => symbol.kind === "class" && symbol.name === "Helper")).toBe(true);
+});
+test("retained module-to-module path reference has no extracted runtime import and must refuse", () => {
+ const reference = "/fixture/reader.ts"; const core = "/fixture/core.ts";
+ const content = "/// \nexport {};";
+ const snapshot = new Map([[reference, content], [core, "export function core() { return 1; }"]]);
+ expect(extractTypeScript([reference, core], "/fixture", snapshot).imports).toEqual([]);
+ expect(inspectModuleConfiguration(reference, "/fixture", snapshot)).toContain("SOURCE_REFERENCE_PATH_UNSUPPORTED");
+ expect(inspectModuleConfiguration(reference, "/fixture", undefined, ts.createSourceFile(reference, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_REFERENCE_PATH_UNSUPPORTED");
+ const javascript = "/fixture/reader.js";
+ expect(inspectJavaScriptSource(javascript, content, "/fixture", new Map([[javascript, content], [core, "export function core() { return 1; }"]])).reasons).toContain("JAVASCRIPT_REFERENCE_PATH_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) for (const route of ["property", "element", "alias"] as const) test(`SDK-shaped local receiver cannot qualify ${route} construction in ${extension}`, () => {
+ const path = `/fixture/main.${extension}`;
+ const receiver = extension === "ts"
+ ? "const env:typeof globalThis={Date:LocalDate} as unknown as typeof globalThis;"
+ : "/** @type {typeof globalThis} */\nconst env=/** @type {any} */ ({Date:LocalDate});";
+ const callee = route === "property" ? "env.Date" : route === "element" ? "env['Date']" : "C";
+ const content = `class LocalDate { constructor(value) {} }\n${receiver}\n${route === "alias" ? "const C=env.Date;" : ""}export function make() { return new ${callee}(1); }`;
+ const inputs = new Map([[path, content]]);
+ expect(inspectModuleConfiguration(path, "/fixture", inputs)).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ if (extension === "mjs") expect(inspectJavaScriptSource(path, content, "/fixture", inputs).reasons).toContain("JAVASCRIPT_CONSTRUCTION_UNSUPPORTED");
+});
+test("SDK-shaped caller receiver cannot qualify construction", () => {
+ expect(inspect("export function make(env:typeof globalThis) { return new env.Date(1); }")).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+});
+test("true SDK constructor aliases and namespace destructuring retain their boundary", () => {
+ for (const content of [
+ "const C=Date;export function make() { return new C(1); }",
+ "const env=globalThis;export function make() { return new env.Date(1); }",
+ "const C=globalThis.Date;export function make() { return new C(1); }",
+ "const {Module:C}=WebAssembly;export function make() { return new C(new Uint8Array()); }",
+ ]) expect(inspect(content)).toEqual([]);
+});
+
+test("an SDK-shaped imported-helper constructor is still an internal invocation", () => {
+ const content = "import { hidden } from './hidden';const env:typeof globalThis={Date:class {constructor(){hidden();}}} as unknown as typeof globalThis;export function make(){return new env.Date(1);}";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content], ["/fixture/hidden.ts", "export function hidden(){return 1;}"]]))).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+});
+test("intrinsic instance data cannot become an SDK constructor namespace through a cast", () => {
+ for (const value of ["[1]", "'native data'", "new Date(1)"]) {
+ expect(inspect(`const env=${value} as unknown as typeof globalThis;export function make(){return new env.Date(1);}`)).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ }
+});
+test("SDK-shaped constructor receiver cannot qualify instanceof or native instance origins", () => {
+ const content = "class LocalMap {} const env:typeof globalThis={Map:LocalMap} as unknown as typeof globalThis;export function make(){const value=new env.Map();return value instanceof env.Map;}";
+ const reasons = inspect(content);
+ expect(reasons).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ expect(reasons).toContain("SOURCE_INSTANCEOF_UNSUPPORTED");
+});
+
+test("SDK-shaped construction does not confer native promise or collection origin", () => {
+ const promise = "const env:typeof globalThis={Promise:class {}} as unknown as typeof globalThis;export async function make(){await new env.Promise(undefined as any);}";
+ expect(inspect(promise)).toContain("SOURCE_AWAIT_UNSUPPORTED");
+ const collection = "const env:typeof globalThis={Map:class {}} as unknown as typeof globalThis;export function make(){for(const item of new env.Map()){} }";
+ expect(inspect(collection)).toContain("SOURCE_ITERATION_UNSUPPORTED");
+});
+
+test("proven SDK namespace constructor aliases still require Date argument provenance", () => {
+ expect(inspect("const {Date:C}=window;export function make(){return new C(1);}")).toEqual([]);
+ const content = "const {Date:C}=window;function helper(){return 1;}const value={valueOf:helper};export function make(){return new C(value as any);}";
+ expect(inspect(content)).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/create-require.test.ts b/packages/ts-analyzer/test/create-require.test.ts
new file mode 100644
index 00000000..78ba19a4
--- /dev/null
+++ b/packages/ts-analyzer/test/create-require.test.ts
@@ -0,0 +1,213 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectNativeModuleBindings, hasNodeCreateRequireUse } from "../src/javascript-diagnostics";
+for (const source of ["module.exports = require('./src/main.ts');", "module.exports = 1;", "exports.value = 1;"]) test(`round4 CommonJS synthetic symbols remain ambient: ${source}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile("hidden.mjs", source, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(true);
+});
+for (const source of ["const module = { exports: 0 }; module.exports = 1; export {};", "const require = value => value; const module = { exports: 0 }; module.exports = require(1); export {};"]) test(`round4 actual module and require bindings remain ordinary: ${source}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile("local.mjs", source, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(false);
+});
+for (const source of ["const require = value => value; require('x'); export {};", "function use(require, path) { return require(path); } export {};", "function require(...values) { return values; } require(1, 2); export {};"]) test(`round3 local require calls remain ordinary JavaScript: ${source}`, () => {
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).not.toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+});
+for (const source of ["exports.legacy = 1;", "Object.assign(exports, { value: 1 });", "const target = globalThis.exports; target.value = 1;"]) test(`round2 ambient exports have CommonJS origin: ${source}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile("/fixture/main.ts", source, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(true);
+});
+for (const source of ["const exports = { legacy: 0 }; exports.legacy = 1;", "function update(exports) { Object.assign(exports, { value: 1 }); }"]) test(`round2 local exports keep ordinary origin: ${source}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile("/fixture/main.ts", source, ts.ScriptTarget.Latest, true)).commonJsUnsupported).toBe(false);
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).not.toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+});
+for (const source of [
+ "globalThis.eval('code');", "window.eval('code');", "const load = self.Function('code'); load();",
+ "const run = eval; run('code');", "const Build = globalThis.Function; new Build('code');",
+ "const { eval: run } = globalThis; run('code');", "const host = globalThis; host['Function']('code');",
+ "const name = 'eval'; window[name]('code');", "const browser = window; browser['ev' + 'al']('code');",
+ "const name = 'Function'; self[name]('code');", "const box = { browser: window }; box.browser.eval('code');",
+ "(0, eval)('code');", "eval.call(null, 'code');", "Function('code');",
+]) test(`round2 JavaScript intrinsic references remain unsupported: ${source}`, () => {
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).toContain("JAVASCRIPT_DYNAMIC_EVALUATION_UNSUPPORTED");
+});
+for (const source of [
+ "function eval(value) { return value; } eval('code');",
+ "function Function(value) { return value; } new Function('code');",
+ "function run(globalThis) { return globalThis.eval('code'); }",
+ "function run(window) { return window.eval('code'); }", "const self = { Function: value => () => value }; self.Function('code')();",
+ "const globalThis = { eval: value => value }; const run = globalThis.eval; run('code');",
+ "const tools = { eval: value => value }; tools.eval('code');",
+ "const window = { eval: value => value }; const name = 'eval'; window[name]('code');",
+ "import { eval as run } from 'ordinary'; run('code');",
+]) test(`round2 local evaluation-like bindings keep ordinary origin: ${source}`, () => {
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).not.toContain("JAVASCRIPT_DYNAMIC_EVALUATION_UNSUPPORTED");
+});
+
+for (const source of [
+ "const load = globalThis.require; const hidden = load('./src/main.mjs');",
+ "const load = require; const hidden = load('./src/main.mjs');",
+ "export const nativeModule = globalThis.module;",
+ "export const nativeModule = module;",
+ "import { createRequire as make } from 'node:module'; const load = make(import.meta.url);",
+ "import * as nodeModule from 'module'; const factory = nodeModule.createRequire; const load = factory(import.meta.url);",
+ "import nodeModule from 'node:module'; const { createRequire: make } = nodeModule; const load = make(import.meta.url);",
+ "const { createRequire: make } = await import('node:module'); const load = make(import.meta.url);",
+ "const load = (await import('node:module')).createRequire(import.meta.url);",
+ "export { createRequire as make } from 'node:module';",
+ "export * from 'node:module';",
+ "export { default as Module } from 'node:module';",
+ "import { Module } from 'node:module'; const load = Module.createRequire(import.meta.url);",
+ "import nodeModule from 'node:module'; export default nodeModule;",
+ "import nodeModule from 'node:module'; export { nodeModule as Module };",
+ "import nodeModule from 'node:module'; export const Module = nodeModule;",
+ "import * as m from 'node:module'; let n; n = m; const r = n.createRequire(import.meta.url); r('./src/main.mjs');",
+ "import * as m from 'node:module'; const box = { m }; const r = box.m.createRequire(import.meta.url);",
+ "import * as m from 'node:module'; function forward(value) { return value; } const n = forward(m); const r = n.createRequire(import.meta.url);",
+ "import { default as M } from 'node:module'; const r = M.createRequire(import.meta.url);",
+ "import { 'default' as M } from 'node:module'; const r = M.createRequire(import.meta.url);",
+ "import * as m from 'node:module'; const { default: M } = m; const r = M.createRequire(import.meta.url);",
+ "import * as m from 'node:module'; const { 'Module': M } = m; const r = M.createRequire(import.meta.url);",
+ "import * as m from 'node:module'; const { 'createRequire': make } = m; const r = make(import.meta.url);",
+ "import M from 'node:module'; const value = M._load('./src/main.mjs', undefined, false);",
+ "import { _load as nativeLoad } from 'module'; const value = nativeLoad('./src/main.mjs', undefined, false);",
+]) test("selected JavaScript diagnoses Node CommonJS loader bindings", () => {
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+});
+for (const [source, member] of [
+ ["export const native = process.binding('contextify');", "process.binding"],
+ ["const get = Reflect.get(process, 'getBuiltinModule'); const native = get('module');", "ambient-process"],
+ ["export const nativeProcess = process;", "ambient-process"],
+ ["const key = 'getBuiltinModule'; const native = process[key]('module');", "process."],
+ ["export const hidden = process.mainModule.require('./src/main.mjs');", "process.mainModule"],
+ ["export const globals = globalThis;", "ambient-global"],
+ ["const p = Reflect.get(globalThis, 'process'); const M = p.getBuiltinModule('module');", "ambient-global"],
+ ["const M = globalThis.globalThis.process.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const M = globalThis.global.process.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const M = global.globalThis.process.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const M = global.global.process.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const g = globalThis['global']['globalThis']; const { getBuiltinModule: get } = g['process']; const M = get('module');", "process.getBuiltinModule"],
+ ["const p = global['process']; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const g = globalThis; const p = g['process']; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const M = globalThis.process.getBuiltinModule('module'); const r = M.createRequire(import.meta.url);", "process.getBuiltinModule"],
+ ["const p = globalThis['process']; const get = p['getBuiltinModule']; const M = get('module');", "process.getBuiltinModule"],
+ ["const { getBuiltinModule: get } = globalThis.process; const M = get('module');", "process.getBuiltinModule"],
+ ["const { process: p } = globalThis; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const { default: p } = globalThis.process; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["import { default as p } from 'node:process'; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["import { 'default' as p } from 'process'; const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["const { 'getBuiltinModule': get } = process; const M = get('module');", "process.getBuiltinModule"],
+ ["export { getBuiltinModule as get } from 'node:process';", "process.getBuiltinModule"],
+ ["const { getBuiltinModule: get } = await import('node:process'); const M = get('module');", "process.getBuiltinModule"],
+ ["const p = await import('process'); const M = p.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["import M from 'node:module'; const loader = M.prototype.require;", "prototype"],
+ ["import { _resolveFilename as resolveName } from 'node:module'; const value = resolveName('./file');", "_resolveFilename"],
+ ["const get = process.getBuiltinModule; const M = get('module'); const r = M.createRequire(import.meta.url);", "process.getBuiltinModule"],
+ ["import P from 'node:process'; const M = P.getBuiltinModule('module');", "process.getBuiltinModule"],
+ ["import { getBuiltinModule as get } from 'process'; const M = get('module');", "process.getBuiltinModule"],
+] as const) test("unknown used native members remain explicitly unmodeled", () => {
+ const reasons = inspectJavaScriptSource("/fixture/main.mjs", source).reasons;
+ expect(reasons).toContain(`JAVASCRIPT_NATIVE_MODULE_MEMBER_UNSUPPORTED:${member}`);
+ expect(reasons).not.toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+});
+const unprovedValueExports = [
+ "import { builtinModules } from 'node:module'; export const known = builtinModules;",
+ "import { default as p } from 'node:process'; export const ordinary = p.platform;",
+ "const { platform } = process; export const ordinary = platform;",
+ "const p = await import('node:process'); export const ordinary = p.platform;",
+ "const process = { getBuiltinModule(value) { return value; } }; const { getBuiltinModule: get } = process; export const ordinary = get(1);",
+ "const globalThis = { process: { getBuiltinModule(value) { return value; } } }; export const ordinary = globalThis.process.getBuiltinModule(1);",
+ "const globalThis = { process: { getBuiltinModule(value) { return value; } } }; const { process: p } = globalThis; export const ordinary = p.getBuiltinModule(1);",
+ "export const ordinary = globalThis.process.platform;",
+ "const global = { process: { getBuiltinModule(value) { return value; } } }; export const ordinary = global.process.getBuiltinModule(1);",
+ "const globalThis = { global: { process: { getBuiltinModule(value) { return value; } } } }; export const ordinary = globalThis.global.process.getBuiltinModule(1);",
+ "export const ordinary = process.env; export function cwd() { return process.cwd(); } export const args = process.argv;",
+ "const process = { mainModule: { require(value) { return value; } } }; export const ordinary = process.mainModule.require(1);",
+ "const process = { getBuiltinModule(value) { return value; } }; const get = Reflect.get(process, 'getBuiltinModule'); export const ordinary = get(1);",
+ "export const streams = [process.stdout, process.stderr]; export const runtime = process.versions; export const executable = process.execPath; process.exitCode = 0; export function exit() { process.exit(0); }",
+ "const require = (value) => value; const load = require; export const ordinary = load(1);",
+ "const module = { isBuiltin(value) { return value; } }; export const ordinary = module.isBuiltin(1);",
+ "const globalThis = { require(value) { return value; } }; const load = globalThis.require; export const ordinary = load(1);",
+ "const process = { binding(value) { return value; } }; export const ordinary = process.binding(1);",
+] as const;
+for (const source of unprovedValueExports) test(`ordinary native bindings do not prove value-export completeness: ${source}`, () => {
+ const ast = ts.createSourceFile("/fixture/main.mjs", source, ts.ScriptTarget.Latest, true);
+ expect(inspectNativeModuleBindings(ast)).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ const reasons = inspectJavaScriptSource("/fixture/main.mjs", source).reasons;
+ expect(reasons).toContain("JAVASCRIPT_CALLABLE_EXPORT_UNSUPPORTED");
+ expect(reasons).not.toContain("JAVASCRIPT_COMMONJS_UNSUPPORTED");
+ expect(reasons.some(reason => reason.startsWith("JAVASCRIPT_NATIVE_MODULE_MEMBER_UNSUPPORTED"))).toBe(false);
+});
+
+
+for (const source of [
+ "import * as nodeModule from 'node:module'; export function known(name) { return nodeModule.isBuiltin(name); }",
+ "import * as m from 'node:module'; const n = m; export function known(name) { return n.isBuiltin(name); }",
+ "import * as m from 'node:module'; const { isBuiltin } = m; export function known(name) { return isBuiltin(name); }",
+ "import { default as M } from 'node:module'; export function known(name) { return M.isBuiltin(name); }",
+ "import * as m from 'node:module'; const { default: M } = m; export function known(name) { return M.isBuiltin(name); }",
+ "import * as m from 'node:module'; const { 'isBuiltin': known } = m; export function query(name) { return known(name); }"
+]) test(`opaque Node data remains an explicit unsupported control: ${source}`, () => {
+ expect(inspectNativeModuleBindings(ts.createSourceFile("/fixture/main.mjs", source, ts.ScriptTarget.Latest, true))).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).toContain("JAVASCRIPT_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+
+test("ordinary Node APIs and unrelated factories retain modeled function or known-literal boundaries", () => {
+ for (const source of [
+ "import * as nodeModule from 'node:module'; export function known(name) { return nodeModule.isBuiltin('node:fs'); }",
+ "function createRequire(value) { return value; } export function known() { return createRequire(1); }",
+ "const opaque = { createRequire(value) { return value; } }; export function known() { return opaque.createRequire(1); }",
+ "import { createRequire } from 'node:module'; export const unusedFactoryImport = 1;",
+ "import { createRequire as make } from 'node:module'; export function known(make) { return make(1); }",
+ "import { createRequire } from 'node:module'; const opaque = { createRequire(value) { return value; } }; export function known() { return opaque.createRequire(1); }",
+ "export { isBuiltin } from 'node:module';",
+ "import * as m from 'node:module'; const n = m; export function known(name) { return n.isBuiltin('node:fs'); }",
+ "import * as m from 'node:module'; const { isBuiltin } = m; export function known(name) { return isBuiltin('node:fs'); }",
+ "import * as m from 'node:module'; export function known(m) { return m.isBuiltin(1); }",
+ "import { default as M } from 'node:module'; export function known(name) { return M.isBuiltin('node:fs'); }",
+ "import { 'default' as M } from 'node:module'; export const unused = 1;",
+ "import * as m from 'node:module'; const { default: M } = m; export function known(name) { return M.isBuiltin('node:fs'); }",
+ "import * as m from 'node:module'; const { 'isBuiltin': known } = m; export function query(name) { return known('node:fs'); }",
+ "import { default as M } from 'node:module'; export function known(M) { return M.isBuiltin(1); }",
+ "import { builtinModules } from 'node:module'; export function known() { return builtinModules; }",
+ "import { _resolveFilename } from 'node:module'; export const unused = 1;",
+ "import { _resolveFilename as resolveName } from 'node:module'; export function known(resolveName) { return resolveName(1); }",
+ "const process = { getBuiltinModule(value) { return value; } }; export function known() { return process.getBuiltinModule(1); }",
+ "export function known(process) { return process.getBuiltinModule(1); }",
+ "import P from 'node:process'; export function known() { return P.platform; }",
+ "import { getBuiltinModule as get } from 'node:process'; export const unused = 1;",
+ "import { default as p } from 'node:process'; export function ordinary() { return p.platform; }",
+ "const { platform } = process; export function ordinary() { return platform; }",
+ "export { platform } from 'node:process';",
+ "const p = await import('node:process'); export function ordinary() { return p.platform; }",
+ "const process = { getBuiltinModule(value) { return value; } }; const { getBuiltinModule: get } = process; export function ordinary() { return get(1); }",
+ "import { default as p } from 'node:process'; export function ordinary(p) { return p.getBuiltinModule(1); }",
+ "const globalThis = { process: { getBuiltinModule(value) { return value; } } }; export function ordinary() { return globalThis.process.getBuiltinModule(1); }",
+ "export function ordinary(globalThis) { return globalThis.process.getBuiltinModule(1); }",
+ "const globalThis = { process: { getBuiltinModule(value) { return value; } } }; const { process: p } = globalThis; export function ordinary() { return p.getBuiltinModule(1); }",
+ "export function ordinary() { return globalThis.process.platform; }",
+ "const global = { process: { getBuiltinModule(value) { return value; } } }; export function ordinary() { return global.process.getBuiltinModule(1); }",
+ "export function ordinary(global) { return global.process.getBuiltinModule(1); }",
+ "const globalThis = { global: { process: { getBuiltinModule(value) { return value; } } } }; export function ordinary() { return globalThis.global.process.getBuiltinModule(1); }",
+ "export function ordinary(global) { return global.globalThis.process.getBuiltinModule(1); }",
+ "export const ordinary = globalThis.console;",
+ "export function environment() { return process.env; } export function cwd() { return process.cwd(); } export function args() { return process.argv; }",
+ "const process = { mainModule: { require(value) { return value; } } }; export function ordinary() { return process.mainModule.require(1); }",
+ "const process = { getBuiltinModule(value) { return value; } }; const get = Reflect.get(process, 'getBuiltinModule'); export function ordinary() { return get(1); }",
+ "export function streams() { return [process.stdout, process.stderr]; } export function runtime() { return process.versions; } export function executable() { return process.execPath; } process.exitCode = 0; export function exit() { process.exit(0); }",
+ "import { binding } from 'node:process'; export const unused = 1;",
+ "const require = (value) => value; const load = require; export function ordinary() { return load(1); }",
+ "const module = { isBuiltin(value) { return value; } }; export function ordinary() { return module.isBuiltin(1); }",
+ "const globalThis = { require(value) { return value; } }; const load = globalThis.require; export function ordinary() { return load(1); }",
+ "const process = { binding(value) { return value; } }; export function ordinary() { return process.binding(1); }",
+ ]) expect(inspectJavaScriptSource("/fixture/main.mjs", source).reasons).toEqual([]);
+});
+test("type-only native bindings are inert and the compatibility predicate makes no opaque-member execution claim", () => {
+ const typeOnly = ts.createSourceFile("/fixture/main.ts", "import type { _resolveFilename as Native } from 'node:module'; export type Alias = Native;", ts.ScriptTarget.Latest, true);
+ expect(inspectNativeModuleBindings(typeOnly)).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ const processTypeOnly = ts.createSourceFile("/fixture/main.ts", "import type { default as p } from 'node:process'; export type Alias = typeof p.getBuiltinModule; export type { getBuiltinModule } from 'node:process';", ts.ScriptTarget.Latest, true);
+ expect(inspectNativeModuleBindings(processTypeOnly)).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ const opaqueProcessTypeOnly = ts.createSourceFile("/fixture/main.ts", "import type { binding } from 'node:process'; export type Alias = typeof binding;", ts.ScriptTarget.Latest, true);
+ expect(inspectNativeModuleBindings(opaqueProcessTypeOnly)).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ const globalProcessTypeOnly = ts.createSourceFile("/fixture/main.ts", "export type Getter = typeof globalThis.process.getBuiltinModule;", ts.ScriptTarget.Latest, true);
+ expect(inspectNativeModuleBindings(globalProcessTypeOnly)).toEqual({ commonJsUnsupported: false, unmodeledMembers: [] });
+ const unknown = ts.createSourceFile("/fixture/main.mjs", "import M from 'node:module'; const loader = M.prototype.require;", ts.ScriptTarget.Latest, true);
+ expect(hasNodeCreateRequireUse(unknown)).toBe(false);
+ expect(inspectNativeModuleBindings(unknown)).toEqual({ commonJsUnsupported: false, unmodeledMembers: ["prototype"] });
+});
diff --git a/packages/ts-analyzer/test/date-cluster-document-write.test.ts b/packages/ts-analyzer/test/date-cluster-document-write.test.ts
new file mode 100644
index 00000000..42a7eb9e
--- /dev/null
+++ b/packages/ts-analyzer/test/date-cluster-document-write.test.ts
@@ -0,0 +1,122 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const [name, source, reason] of [
+ ["Date valueOf", "function helper(){return 1;}const value={valueOf:helper};export function run(){return new Date(value as any);}", "SOURCE_COERCION_UNSUPPORTED"],
+ ["cluster executable", "import cluster from 'node:cluster';export function run(){cluster.setupPrimary({exec:'./src/hidden.mjs'});return cluster.fork();}", "SOURCE_PROCESS_LAUNCH_UNSUPPORTED"],
+ ["document.write", "export function run(){document.write('');}", "SOURCE_DOCUMENT_WRITE_UNSUPPORTED"],
+ ["document.writeln", "export function run(){document.writeln('');}", "SOURCE_DOCUMENT_WRITE_UNSUPPORTED"],
+] as const) test(`${name} ${extension} refuses hidden runtime dependency`, () => {
+ const path = `/fixture/main.${extension}`;
+ const content = extension === "mjs" ? source.replace(" as any", "") : source;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain(reason);
+});
+
+test("primitive Date arguments, intrinsic copies, ordinary calls and local names remain distinct", () => {
+ for (const source of [
+ "export function run(){return new Date();}",
+ "export function run(){return new Date(1);}",
+ "const value=1;const C=Date;export function run(){return new C(value);}",
+ "export function run(){return new Date('2020-01-01');}",
+ "export function run(){return new Date(2020,1,2,3,4,5,6);}",
+ "const value=new Date(1);export function run(){return new Date(value);}",
+ "function helper(){return 1;}const value={valueOf:helper};export function run(){return Date(value as any);}",
+ "function helper(){return 1;}const value={valueOf:helper};export function run(){return new Date(2020,1,2,3,4,5,6,value as any);}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+ const path = "/fixture/main.ts";
+ const local = "class Date {constructor(_value:unknown){}}function helper(){return 1;}const value={valueOf:helper};export function run(){return new Date(value);}";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, local]]))).toEqual(["SOURCE_CONSTRUCTION_UNSUPPORTED"]);
+});
+
+test("ordinary cluster bindings, unused imports and SDK data reads remain distinct", () => {
+ for (const source of [
+ "const cluster={setupPrimary(_options:unknown){},fork(){return 1;}};export function run(){cluster.setupPrimary({exec:'plain-data'});return cluster.fork();}",
+ "import cluster from 'node:cluster';export function run(){return 1;}",
+ "import cluster from 'node:cluster';export function run(){return cluster.isPrimary;}",
+ "import type cluster from 'node:cluster';export type Cluster=typeof cluster;export function run(){return 1;}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).not.toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+
+test("ordinary local Document lookalikes and unrelated SDK data calls remain distinct", () => {
+ for (const source of [
+ "const document={write(_value:string){},writeln(_value:string){}};export function run(){document.write('');document.writeln('text');}",
+ "export function run(){document.createTextNode('');}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+test("a typed local constructor does not acquire SDK Date runtime origin", () => {
+ const path = "/fixture/main.ts";
+ const source = "const env:typeof globalThis={Date:class{constructor(_value:unknown){}}} as unknown as typeof globalThis;function helper(){return 1;}const value={valueOf:helper};export function run(){return new env.Date(value as any);}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ expect(reasons).not.toContain("SOURCE_COERCION_UNSUPPORTED");
+ expect(reasons).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+});
+
+test("a typed local Date lookalike cannot confer intrinsic copy slots", () => {
+ const path = "/fixture/main.ts";
+ const source = "function helper(){return 1;}const env:typeof globalThis={Date:class{valueOf(){return helper();}}} as unknown as typeof globalThis;const value=new env.Date();export function run(){return new Date(value as any);}";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+
+for (let index = 0; index < 7; index++) test(`Date numeric argument ${index} requires primitive runtime provenance`, () => {
+ const path = "/fixture/main.ts";
+ const args = ["2020", "0", "1", "0", "0", "0", "0"];
+ args[index] = "value as any";
+ const source = `function helper(){return 1;}const value={valueOf:helper};export function run(){return new Date(${args.join(",")});}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+
+test("captured Date and Document aliases preserve their actual SDK boundary", () => {
+ for (const [source, reason] of [
+ ["const C=Date;function helper(){return 1;}const value={valueOf:helper};export function run(){return new C(value as any);}", "SOURCE_COERCION_UNSUPPORTED"],
+ ["const doc:Document=document;export function run(){doc.write('ordinary HTML/text');}", "SOURCE_DOCUMENT_WRITE_UNSUPPORTED"],
+ ] as const) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain(reason);
+ }
+ const path = "/fixture/main.ts";
+ const source = "const doc:Document={write(_value:string){}} as unknown as Document;export function run(){doc.write('ordinary HTML/text');}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ expect(reasons).toEqual(["SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ expect(reasons).not.toContain("SOURCE_DOCUMENT_WRITE_UNSUPPORTED");
+});
+
+test("cluster module origin survives only the existing captured binding aliases", () => {
+ for (const source of [
+ "import {fork as launch} from 'node:cluster';const start=launch;export function run(){return start();}",
+ "import * as cluster from 'cluster';const worker=cluster;export function run(){worker.setupPrimary({exec:'hidden.mjs'});return worker.fork();}",
+ "import cluster from 'node:cluster';const {fork:start}=cluster;export function run(){return start();}",
+ "import cluster from 'node:cluster';export function run(){cluster.setupPrimary({exec:'hidden.mjs'});return 1;}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+
+for (const extension of ["ts", "mjs"]) for (const source of [
+ "const value=new Date(1);export function run(){return new Date(value);}",
+ "const Native=Date;const value=new Native(1);const alias=value;export function run(){return new Native(alias);}",
+ "const value=new globalThis.Date(1);export function run(){return new globalThis.Date(value);}",
+]) test(`intrinsic Date copy ${extension} does not invoke an SDK callback dictionary: ${source}`, () => {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+});
+
+for (const [name, source] of [
+ ["typed local Date constructor", "function helper(){return 1;}const env:typeof globalThis={Date:class{valueOf(){return helper();}}} as unknown as typeof globalThis;const value=new env.Date();export function run(){return new Date(value as any);}"],
+ ["typed local Date value", "function helper(){return 1;}const value:Date={getVarDate:helper,valueOf:helper} as unknown as Date;export function run(){return new Date(value);}"],
+ ["coercing Date callback object", "function helper(){return 1;}const value={getVarDate:helper,valueOf:helper};export function run(){return new Date(value as any);}"],
+ ["numeric Date argument with Date object", "const value=new Date(1);export function run(){return new Date(value as any,1);}"],
+] as const) test(`${name} cannot use intrinsic Date copy semantics`, () => {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/declared-external-positive.test.ts b/packages/ts-analyzer/test/declared-external-positive.test.ts
new file mode 100644
index 00000000..2f01d360
--- /dev/null
+++ b/packages/ts-analyzer/test/declared-external-positive.test.ts
@@ -0,0 +1,16 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { inspectJavaScriptSource } from "../src/javascript-diagnostics";
+
+test("declared semver third-party dependency retains its external boundary", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-external-snapshot-"));
+ try {
+ const path = join(root, "test.mjs"); const content = "import { test } from 'vitest';";
+ const snapshot = new Map([[path, content], [join(root, "package.json"), JSON.stringify({ devDependencies: { vitest: "4.0.0" } })]]);
+ const inspected = inspectJavaScriptSource(path, content, root, snapshot);
+ expect(inspected.staticModuleLinks).toContainEqual({ specifier: "vitest", resolution: "external" });
+ expect(inspected.reasons).not.toContain("JAVASCRIPT_IMPORT_UNRESOLVED:vitest");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/decorated-destructured.test.ts b/packages/ts-analyzer/test/decorated-destructured.test.ts
new file mode 100644
index 00000000..325de155
--- /dev/null
+++ b/packages/ts-analyzer/test/decorated-destructured.test.ts
@@ -0,0 +1,64 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import ts from "typescript";
+import { analyzeRepository, discoverRepository, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+import { extractTypeScript } from "../src/ts-symbols";
+
+const path = "/fixture/main.ts";
+const inspect = (content: string) => inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+
+for (const decorator of ["@logged", "@decorators.logged", "@(logged)", "@factory()"])
+ test(`runtime decorator invocation is unsupported: ${decorator}`, () => {
+ expect(inspect(`function logged(value: unknown) { return value; } const decorators = { logged }; function factory() { return logged; } ${decorator} export class View {}`)).toContain("SOURCE_DECORATOR_UNSUPPORTED");
+ });
+
+for (const content of [
+ "const [helper] = [() => 1]; export { helper };",
+ "const { helper } = { helper: () => 1 }; export { helper as publicHelper };",
+ "export const [helper] = [() => 1];",
+ "const [helper] = [() => 1]; const alias = helper; export { alias };",
+]) test(`destructured callable export has no modeled symbol: ${content}`, () => {
+ const snapshot = new Map([[path, content]]);
+ const extraction = extractTypeScript([path], "/fixture", snapshot);
+ expect(extraction.symbols.some(symbol => symbol.name === "helper" || symbol.name === "alias")).toBe(false);
+ expect(inspect(content)).toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+});
+
+for (const content of [
+ "declare const source: { helper: any }; const { helper } = source; export { helper };",
+ "declare const source: { helper: unknown }; const { helper } = source; export { helper };",
+ "declare const source: { helper: (() => number) | number }; const { helper } = source; export { helper };",
+ "const { Constructor } = { Constructor: class {} }; export { Constructor };",
+]) test(`uncertain or constructible destructured exports stay unsupported: ${content}`, () => {
+ expect(inspect(content)).toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+});
+
+test("JS producer refuses destructured local callable exports and decorators", () => {
+ expect(inspectJavaScriptSource("/fixture/main.js", "const [helper] = [() => 1]; export { helper };").reasons).toContain("JAVASCRIPT_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", "function logged(value) { return value; } @logged export class View {}").reasons).toContain("JAVASCRIPT_DECORATOR_UNSUPPORTED");
+});
+
+test("import-only tests cannot acquire invented coverage for destructured callable coordinates", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-destructured-coverage-"));
+ try {
+ writeFileSync(join(root, "lib.mjs"), "const [helper] = [() => 1]; export { helper };");
+ writeFileSync(join(root, "lib.test.js"), "import { helper } from './lib.mjs';");
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*"], exclude: [], languages: { typescript: "on" as const, javascript: "on" as const } };
+ const result = analyzeRepository(config, discoverRepository(config).files).graph;
+ expect(result.nodes.some(node => node.name === "helper")).toBe(false);
+ expect(result.edges.some(edge => edge.kind === "tested_by" || edge.kind === "covers")).toBe(false);
+ expect(inspectJavaScriptSource(join(root, "lib.mjs"), "const [helper] = [() => 1]; export { helper };").reasons).toContain("JAVASCRIPT_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("modeled identifier exports, noncallable destructuring and ordinary TS legacy remain unchanged", () => {
+ expect(inspect("const helper = () => 1; export { helper as publicHelper };")).not.toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ expect(inspect("const [value] = [1]; export { value };")).not.toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ expect(inspect("const [helper] = [() => 1]; export function own() { const helper = () => 2; return helper(); }")).not.toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+ const content = "function logged(value: unknown) { return value; } @logged export class View {} const [helper] = [() => 1]; export { helper };";
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_DECORATOR_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_DESTRUCTURED_CALLABLE_EXPORT_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/default-expression-eligibility.test.ts b/packages/ts-analyzer/test/default-expression-eligibility.test.ts
new file mode 100644
index 00000000..e9405de6
--- /dev/null
+++ b/packages/ts-analyzer/test/default-expression-eligibility.test.ts
@@ -0,0 +1,30 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src/javascript-diagnostics";
+
+for (const expression of ["() => 1", "(function () { return 1; })", "((() => 1))", "(function named() { return 1; })", "true ? (() => 1) : (() => 2)", "(class {})", "({ run() {} })", "1", "build()"]) {
+ test(`JavaScript unmodeled function default expression stays partial: ${expression}`, () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", `export default ${expression};`).reasons).toContain("JAVASCRIPT_DEFAULT_EXPRESSION_UNSUPPORTED");
+ });
+}
+for (const content of ["const impl = () => 1; const alias = impl; export default alias;", "const run = () => 1; export default run;"]) test("default identifier assignments remain explicitly outside the closed profile", () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", content).reasons).toContain("JAVASCRIPT_DEFAULT_EXPRESSION_UNSUPPORTED");
+});
+for (const expression of ["(() => 1) as () => number", "(() => 1) satisfies () => number", "(() => 1)!"]) {
+ test(`TypeScript transparent wrappers cannot admit an unmodeled function default: ${expression}`, () => {
+ const path = "/fixture/module.ts";
+ const content = `export default ${expression};`;
+ const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]), source)).toContain("SOURCE_DEFAULT_EXPRESSION_UNSUPPORTED");
+ });
+}
+for (const expression of ["(impl)", "impl as () => number", "impl satisfies () => number", "impl!"]) test(`wrapped bound identifier remains an unsupported assignment: ${expression}`, () => {
+ const path = "/fixture/module.ts";
+ const content = `const impl = () => 1; export default ${expression};`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]), ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).toContain("SOURCE_DEFAULT_EXPRESSION_UNSUPPORTED");
+});
+for (const content of ["export default function () { return 1; }", "export default class {}"]) {
+ test(`modeled declaration or ordinary default value remains eligible: ${content}`, () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", content).reasons).not.toContain("JAVASCRIPT_DEFAULT_EXPRESSION_UNSUPPORTED");
+ });
+}
diff --git a/packages/ts-analyzer/test/discovery-missing-import-alias.test.ts b/packages/ts-analyzer/test/discovery-missing-import-alias.test.ts
new file mode 100644
index 00000000..fd580cfb
--- /dev/null
+++ b/packages/ts-analyzer/test/discovery-missing-import-alias.test.ts
@@ -0,0 +1,40 @@
+import { afterEach, expect, it } from "bun:test";
+import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { discoverRepository, inspectJavaScriptSource } from "../src";
+
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+
+function fixture(content: string) {
+ const parent = mkdtempSync(join(tmpdir(), "semctx-missing-import-alias-")); roots.push(parent);
+ const physical = join(parent, "physical"); mkdirSync(physical);
+ const alias = join(parent, "alias");
+ symlinkSync(physical, alias, process.platform === "win32" ? "junction" : "dir");
+ writeFileSync(join(physical, "script.mjs"), content);
+ const config = { ...createGlobSelectionConfig(alias), include: ["**/*.mjs"], exclude: [], languages: { javascript: "on" as const } };
+ return { parent, physical, alias, config };
+}
+
+it("keeps missing in-repository imports selected when the repository root has a directory alias", () => {
+ const { config } = fixture("export { missing } from './not-created/deeper/missing.mjs';");
+ const result = discoverRepository(config);
+ expect(result.candidates.find(candidate => candidate.relPath === "script.mjs")).toMatchObject({ selectionDecision: "selected", reason: "SELECTED" });
+ expect(result.files).toHaveLength(1);
+ const source = result.files[0]!;
+ expect(inspectJavaScriptSource(source.absPath, source.content, config.repositoryRoot).reasons).toContain("JAVASCRIPT_IMPORT_UNRESOLVED:./not-created/deeper/missing.mjs");
+});
+
+it("still rejects genuinely escaping missing paths under a directory alias", () => {
+ const { config } = fixture("export { missing } from '../outside/not-created/missing.mjs';");
+ expect(discoverRepository(config).candidates.find(candidate => candidate.relPath === "script.mjs")).toMatchObject({ analysisOutcome: "failed", reason: "IMPORT_OUTSIDE_REPOSITORY" });
+});
+
+it("still rejects missing targets below an existing external directory symlink", () => {
+ const { parent, physical, config } = fixture("export { missing } from './external/not-created/missing.mjs';");
+ const outside = join(parent, "outside"); mkdirSync(outside);
+ symlinkSync(outside, join(physical, "external"), process.platform === "win32" ? "junction" : "dir");
+ expect(discoverRepository(config).candidates.find(candidate => candidate.relPath === "script.mjs")).toMatchObject({ analysisOutcome: "failed", reason: "IMPORT_OUTSIDE_REPOSITORY" });
+});
diff --git a/packages/ts-analyzer/test/guarded-string.test.ts b/packages/ts-analyzer/test/guarded-string.test.ts
new file mode 100644
index 00000000..9e1877da
--- /dev/null
+++ b/packages/ts-analyzer/test/guarded-string.test.ts
@@ -0,0 +1,53 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "mjs"]) test(`guarded opaque string ${extension} calls its actual SDK member`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = "import {read} from 'opaque';const source=read();if(typeof source!=='string')throw new TypeError();source.includes('needle');";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]));
+ console.info(JSON.stringify({ name: "guarded string positive", extension, reasons }));
+ expect(reasons).toEqual([]);
+});
+
+for (const extension of ["ts", "mjs"]) test(`string callee ${extension} requires matching runtime kind and binding dominance`, () => {
+ const typed = extension === "ts" ? "const source=read() as string;" : "/** @type {string} */const source=read();";
+ const global = extension === "ts" ? "const source=read() as unknown as typeof globalThis;" : "/** @type {typeof globalThis} */const source=read();";
+ const castGuard = extension === "ts" ? "(source as unknown)" : "/** @type {unknown} */(source)";
+ const cases = [
+ ["annotation without guard", `${typed}source.includes('needle');`],
+ ["late guard", `${typed}source.includes('needle');if(typeof source!=='string')throw new TypeError();`],
+ ["shadowed guard", `${typed}{const source='text';if(typeof source!=='string')throw new TypeError();}source.includes('needle');`],
+ ["different binding", `${typed}const other=read();if(typeof other!=='string')throw new TypeError();source.includes('needle');`],
+ ["outer closure guard", `${typed}if(typeof source!=='string')throw new TypeError();export function run(){return source.includes('needle');}`],
+ ["loop assignment", `${typed}if(typeof source!=='string')throw new TypeError();for(source of ['text']){}source.includes('needle');`],
+ ["global SDK shape", `${global}if(typeof source!=='string')throw new TypeError();source.parseInt('1');`],
+ ["wrong runtime kind behind cast", `${typed}if(typeof ${castGuard}!=='number')throw new TypeError();source.includes('needle');`],
+ ];
+ for (const [name, body] of cases) {
+ const path = `/fixture/main.${extension}`;
+ const source = `import {read} from 'opaque';${body}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]));
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toEqual(["SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ }
+});
+
+test("guarded primitive member does not erase callback refusal", () => {
+ for (const extension of ["ts", "mjs"]) {
+ const path = `/fixture/main.${extension}`;
+ const source = "import {read} from 'opaque';function hidden(){return 'text';}const source=read();if(typeof source!=='string')throw new TypeError();source.replace('needle',hidden);";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]));
+ console.info(JSON.stringify({ name: "guarded string callback", extension, reasons }));
+ expect(reasons).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ }
+});
+
+test("guarded primitive member does not erase captured prototype mutation refusal", () => {
+ for (const extension of ["ts", "mjs"]) {
+ const path = `/fixture/main.${extension}`;
+ const source = "import {read} from 'opaque';function hidden(){return true;}String.prototype.includes=hidden;const source=read();if(typeof source!=='string')throw new TypeError();source.includes('needle');";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]));
+ console.info(JSON.stringify({ name: "guarded string prototype override", extension, reasons }));
+ expect(reasons).toContain("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/implicit-callback-descriptor.test.ts b/packages/ts-analyzer/test/implicit-callback-descriptor.test.ts
new file mode 100644
index 00000000..759f096b
--- /dev/null
+++ b/packages/ts-analyzer/test/implicit-callback-descriptor.test.ts
@@ -0,0 +1,45 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+import ts from "typescript";
+const path = "/fixture/main.ts";
+for (const content of [
+ "const { defineProperty } = Object; function helper() { return 1; } const box = {}; export function start() { defineProperty.call(Object, box, 'value', { get: helper }); return box.value; }",
+ "function helper() { return 1; } const box = {}; Object.defineProperty(box, 'value', { __proto__: { get: helper } }); export function read() { return box.value; }",
+]) test(`descriptor follow-up refuses ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["function helper() { return 1; } export function start() { return setTimeout(helper, 0); }", "TIMER_CALLBACK_UNSUPPORTED"],
+ ["function helper() { return 1; } const box = {}; Object.defineProperty(box, 'value', { get: helper }); export function read() { return box.value; }", "DESCRIPTOR_ACCESSOR_UNSUPPORTED"],
+ ["declare function eval(source: string): unknown; export function run() { return eval(\"import('./hidden.js')\"); }", "AMBIENT_FUNCTION_UNSUPPORTED"],
+] as const) test(`unmodeled runtime ${reason} refuses`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+});
+for (const content of [
+ "function helper() { return 1; } const make = Object.defineProperty; const box = {}; make(box, 'value', { set: helper }); export function read() { return box.value; }",
+ "function helper() { return 1; } const { defineProperty: make } = Object; const box = {}; make(box, 'value', { get: helper }); export function read() { return box.value; }",
+ "function helper() { return 1; } const box = {}; Object.defineProperty.call(null, box, 'value', { get: helper }); export function read() { return box.value; }",
+ "function helper() { return 1; } const descriptor = { value: 1 }; descriptor.get = helper; const box = {}; Object.defineProperty(box, 'value', descriptor); export function read() { return box.value; }",
+]) test(`actual descriptor SDK origin refuses accessor or unproved escape: ${content}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+});
+test("descriptor data literals, own homonyms and type queries preserve their boundary", () => {
+ for (const content of [
+ "const box = {}; Object.defineProperty(box, 'value', { value: 1 }); export function read() { return box.value; }",
+ "const own = { defineProperty(box, key, descriptor) { return box; } }; export function read() { return own.defineProperty({}, 'value', { get: () => 1 }); }",
+ "const Object = { defineProperty(box, key, descriptor) { return box; } }; const { defineProperty } = Object; export function read() { return defineProperty({}, 'value', { get: () => 1 }); }",
+ "export type Factory = typeof Object.defineProperty;",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_DESCRIPTOR_ACCESSOR_UNSUPPORTED");
+});
+test("real overload implementation and no-snapshot declaration facts remain distinct", () => {
+ const implemented = "function eval(source: string): unknown; function eval(source: string) { return 1; } export function run() { return eval('ordinary'); }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, implemented]]))).not.toContain("SOURCE_AMBIENT_FUNCTION_UNSUPPORTED");
+ const bodyless = "declare function eval(source: string): unknown; export function run() { return eval('ordinary'); }";
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, ts.createSourceFile(path, bodyless, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_AMBIENT_FUNCTION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/implicit-protocols.test.ts b/packages/ts-analyzer/test/implicit-protocols.test.ts
new file mode 100644
index 00000000..e042a9b8
--- /dev/null
+++ b/packages/ts-analyzer/test/implicit-protocols.test.ts
@@ -0,0 +1,85 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("outer literal array does not prove a nested iterable item", () => {
+ const content = "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { const [[value]] = [values]; return value; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain("SOURCE_ITERATION_UNSUPPORTED");
+ expect(js).toContain("JAVASCRIPT_ITERATION_UNSUPPORTED");
+});
+test("asserted async signature does not prove the actual callee returns a native promise", () => {
+ const content = "const thenable = { then() { return 1; } }; function load() { return thenable; } async function native() { return 1; } export async function start() { return await (load as typeof native)(); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_AWAIT_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { const [value] = values; return value; }", "ITERATION_UNSUPPORTED"],
+ ["const value = { then(resolve) { resolve(1); } }; export async function start() { return await value; }", "AWAIT_UNSUPPORTED"],
+ ["const matcher = { [Symbol.hasInstance](value) { return !!value; } }; export function start(value) { return value instanceof matcher; }", "INSTANCEOF_UNSUPPORTED"],
+] as const) test(`direct ${reason} refuses implicit invocation`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+for (const content of [
+ "const values = { *[Symbol.iterator]() { yield 1; } }; export function* start() { yield* values; }",
+ "export function start([value]: number[]) { return value; }",
+ "const values = { *[Symbol.iterator]() { yield 1; } }; export function start() { let value; [value] = values; return value; }",
+ "export async function start(value: unknown) { return await value; }",
+ "const value = { then(resolve) { resolve(1); } }; const promise: Promise = value as unknown as Promise; export async function start() { return await promise; }",
+ "const matcher = { [Symbol.hasInstance](value) { return !!value; } }; const typed: typeof Error = matcher as unknown as typeof Error; export function start(value) { return value instanceof typed; }",
+]) test(`unproven protocol origin refuses ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons.some(reason => /SOURCE_(ITERATION|AWAIT|INSTANCEOF)_UNSUPPORTED/.test(reason))).toBe(true);
+});
+test("real intrinsic values and native promise origins preserve their boundaries", () => {
+ for (const content of [
+ "export function start() { const [value] = [1]; return value; }",
+ "const values = [[1]]; export function start() { const [[value]] = values; return value; }",
+ "export async function start() { return await 1; }",
+ "const ready = Promise.resolve(1); export async function start() { return await ready; }",
+ "export async function start() { return await new Promise(resolve => resolve(1)); }",
+ "async function load() { return 1; } export async function start() { return await load(); }",
+ "export async function start() { return await import('./dep.mjs'); }",
+ "const Kind = Error; export function start(value) { return value instanceof Kind; }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content], ["/fixture/dep.mjs", "export function dep() { return 1; }"]]));
+ expect(reasons).not.toContain("SOURCE_ITERATION_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_AWAIT_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_INSTANCEOF_UNSUPPORTED");
+ }
+});
+
+test("the formerly eligible intrinsic delegated generator is explicitly outside resumption qualification", () => {
+ const content = "export function* start() { yield* 'abc'; }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_ITERATION_UNSUPPORTED");
+});
+
+test("native promise value eligibility does not model its executor invocation", () => {
+ const content = "export async function start() { return await new Promise(resolve => resolve(1)); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_AWAIT_UNSUPPORTED");
+ expect(reasons).toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+});
+test("named extends is consumed globally but project references do not read compiler options", () => {
+ const content = "export function main() { return 1; }";
+ const snapshot = new Map([[path, content], ["/fixture/other/main.ts", content],
+ ["/fixture/tsconfig.json", '{"extends":"./tsconfig.base.json"}'], ["/fixture/tsconfig.base.json", '{"compilerOptions":{"module":"ESNext","moduleResolution":"Bundler"}}'],
+ ["/fixture/other/tsconfig.json", '{"compilerOptions":{"module":"ESNext"}}']]);
+ expect(inspectModuleConfiguration("/fixture/other/main.ts", "/fixture", snapshot)).not.toContain("SOURCE_NAMED_CONFIGURATION_UNSUPPORTED");
+ const referenced = new Map([[path, content], ["/fixture/tsconfig.json", '{"references":[{"path":"./tsconfig.app.json"}]}'], ["/fixture/tsconfig.app.json", '{"compilerOptions":{"jsx":"react-jsx"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", referenced)).toContain("SOURCE_NAMED_CONFIGURATION_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content], ["/fixture/tsconfig.json", '{}']]))).not.toContain("SOURCE_NAMED_CONFIGURATION_UNSUPPORTED");
+});
+test("retained unconsumed named config cannot silently select default semantics", () => {
+ const view = "/fixture/view.tsx"; const content = "export function view() { return ; }";
+ const snapshot = new Map([[view, content], ["/fixture/tsconfig.app.json", '{"compilerOptions":{"jsx":"react-jsx"}}']]);
+ const reasons = inspectModuleConfiguration(view, "/fixture", snapshot);
+ console.info(JSON.stringify({ reasons }));
+ expect(reasons).toContain("SOURCE_NAMED_CONFIGURATION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/in-global-descriptor.test.ts b/packages/ts-analyzer/test/in-global-descriptor.test.ts
new file mode 100644
index 00000000..b80af6fc
--- /dev/null
+++ b/packages/ts-analyzer/test/in-global-descriptor.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+const cases = [
+ ["in key", "function helper(){return 'x';} const key={toString:helper}; const target={x:1}; export function run(){return (key as any) in target;}", "SOURCE_COERCION_UNSUPPORTED"],
+ ["global descriptor", "function helper(){return 1;} Object.defineProperty(globalThis,'Math',{value:{random:helper}}); export function run(){return Math.random();}", "SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED"],
+] as const;
+for (const [name, source, reason] of cases) for (const extension of ["ts", "mjs"]) test(`${name} ${extension} refuses hidden invocation`, () => {
+ const path=`/fixture/main.${extension}`;
+ const content=extension === "mjs" ? source.replace(" as any", "") : source;
+ const reasons=inspectModuleConfiguration(path,"/fixture",new Map([[path,content]]));
+ console.info(JSON.stringify({name,extension,reasons})); expect(reasons).toContain(reason);
+});
+test("native in coercion and descriptor replacement invoke the hidden helper", () => {
+ for (const source of [
+ "function helper(){console.log('IN_CALLED');return 'x'};const key={toString:helper};if(!(key in {x:1}))throw Error('key mismatch');",
+ "function helper(){console.log('GLOBAL_CALLED');return 7};Object.defineProperty(globalThis,'Math',{value:{random:helper}});if(Math.random()!==7)throw Error('global mismatch');",
+ ]) {
+ const result=Bun.spawnSync([process.execPath,"-e",source],{stdout:"pipe",stderr:"pipe"});
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("_CALLED");
+ }
+});
+test("primitive keys and ordinary local descriptor targets stay distinct", () => {
+ for (const source of [
+ "export function run(){return 'x' in {x:1};}",
+ "const key='x';export function run(){return key in {x:1};}",
+ "const globalThis={Math:{random(){return 1;}}};Object.defineProperty(globalThis,'Math',{value:{random(){return 2;}}});export function run(){return globalThis.Math.random();}",
+ "const target={};Object.defineProperty(target,'value',{value:1});export function run(){return 1;}",
+ ]) for (const extension of ["ts","mjs"]) {
+ const path=`/fixture/main.${extension}`; expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);
+ }
+});
diff --git a/packages/ts-analyzer/test/internal-member-target.test.ts b/packages/ts-analyzer/test/internal-member-target.test.ts
new file mode 100644
index 00000000..912e5cc7
--- /dev/null
+++ b/packages/ts-analyzer/test/internal-member-target.test.ts
@@ -0,0 +1,21 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts","mjs"]) test(`internal erased member ${extension} cannot prove a call target`, () => {
+ const path=`/fixture/main.${extension}`;
+ const source="function helper(){return 1;}const holder:any={};holder.invoke=helper;export function run(){return holder.invoke();}";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,extension==="mjs"?source.replace(":any",""):source]])).length).toBeGreaterThan(0);
+});
+
+test("actual internal methods, SDK primitive calls and explicit external bindings remain distinct",()=>{
+ for(const source of [
+ "function helper(){return 1;}const holder={invoke:helper};export function run(){return holder.invoke();}",
+ "const holder={invoke(){return 1;}};export function run(){return holder.invoke();}",
+ "class Holder{invoke(){return 1;}};export function run(holder:Holder){return holder.invoke();}",
+ "export function run(){return JSON.stringify({value:1});}",
+ "import {receiver} from 'opaque';export function run(){return receiver.invoke('data');}",
+ ]) {
+ const path="/fixture/main.ts";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source],["/fixture/package.json",'{"dependencies":{"opaque":"1.0.0"}}']]))).toEqual([]);
+ }
+});
diff --git a/packages/ts-analyzer/test/iterator-mutation.test.ts b/packages/ts-analyzer/test/iterator-mutation.test.ts
new file mode 100644
index 00000000..859c066e
--- /dev/null
+++ b/packages/ts-analyzer/test/iterator-mutation.test.ts
@@ -0,0 +1,35 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+for (const content of [
+ "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; values[Symbol.iterator] = helper; export function start() { return [...values]; }",
+ "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; const key = Symbol.iterator; const alias = key; values[alias] = helper; export function start() { for (const value of values) { return value; } }",
+ "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; values[Symbol.asyncIterator] = helper; export async function start() { for await (const value of values) { break; } }",
+ "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; const { iterator: key } = Symbol; values[key] = helper; export function start() { const [value] = values; return value; }",
+ "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; values[Symbol.iterator] = helper; export function* start() { yield* values; }",
+]) test(`iterator protocol mutation refuses ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain("SOURCE_ITERATOR_MUTATION_UNSUPPORTED");
+ expect(js).toContain("JAVASCRIPT_ITERATOR_MUTATION_UNSUPPORTED");
+});
+test("unmodified array/string origins and own string iterator properties remain distinct", () => {
+ for (const content of [
+ "const values = [1]; export function start() { return [...values]; }",
+ "const values = [1]; export function start() { const [value] = values; return value; }",
+ "const values = [1]; export function* start() { yield* values; }",
+ "const text = 'abc'; export function start() { return [...text]; }",
+ "const values = new Set([1]); export function start() { return [...values]; }",
+ "function helper() { return 1; } const values = [1]; values['iterator'] = helper; export function start() { return [...values]; }",
+ "function helper() { return 1; } const Symbol = { iterator: 'iterator' }; const values = [1]; values[Symbol.iterator] = helper; export function start() { return 1; }",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_ITERATOR_MUTATION_UNSUPPORTED");
+});
+test("type-only references and ordinary TypeScript without a snapshot retain their boundary", () => {
+ const typeOnly = "export type Key = typeof Symbol.iterator;";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, typeOnly]]))).not.toContain("SOURCE_ITERATOR_MUTATION_UNSUPPORTED");
+ const content = "function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; values[Symbol.iterator] = helper; export function start() { return [...values]; }";
+ const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
+ expect(inspectModuleConfiguration(path, "/fixture", undefined, source)).not.toContain("SOURCE_ITERATOR_MUTATION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/javascript.test.ts b/packages/ts-analyzer/test/javascript.test.ts
new file mode 100644
index 00000000..7edc9445
--- /dev/null
+++ b/packages/ts-analyzer/test/javascript.test.ts
@@ -0,0 +1,81 @@
+import { afterEach, expect, it } from "bun:test";
+import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createDefaultConfig, type SemctxConfig } from "@semantic-context/core";
+import { analyzeRepository, analyzeRepositoryAsync, discoverRepository, extractTypeScript } from "../src";
+
+const roots: string[] = [];
+for (const extension of ["mjs", "ts"]) it(`direct bound default identifier retains its actual ${extension} call coordinate`, () => {
+ const { root } = fixture();
+ const leaf = `alias.${extension}`;
+ writeFileSync(join(root, leaf), "const implementation = value => value + 1; export default implementation;");
+ writeFileSync(join(root, "caller.mjs"), `import run from './${leaf}'; export function caller() { return run(1); }`);
+ const extraction = extractTypeScript([join(root, leaf), join(root, "caller.mjs")], root);
+ expect(extraction.symbols.find(symbol => symbol.relPath === leaf && symbol.name === "implementation")).toMatchObject({ exported: true });
+ expect(extraction.calls.find(call => call.callerSymbolPath === "caller")).toMatchObject({ calleeRelPath: leaf, calleeSymbolPath: "implementation" });
+});
+for (const kind of ["function", "class"] as const) it(`represents anonymous default ${kind} declarations and their call coordinates`, () => {
+ const { root } = fixture();
+ writeFileSync(join(root, "default.mjs"), kind === "function"
+ ? "export default function () { function nested() { return 1; } return nested(); }"
+ : "export default class { method() { function nested() { return 1; } return nested(); } }");
+ writeFileSync(join(root, "caller.mjs"), kind === "function"
+ ? "import value from './default.mjs'; export function caller() { return value(); }"
+ : "import Value from './default.mjs'; export function caller() { return new Value().method(); }");
+ const extraction = extractTypeScript([join(root, "default.mjs"), join(root, "caller.mjs")], root);
+ expect(extraction.symbols.find(symbol => symbol.relPath === "default.mjs" && symbol.name === "default")).toMatchObject({ kind, exported: true });
+ expect(extraction.symbols.find(symbol => symbol.name === "nested")?.scope).toEqual(kind === "function" ? ["default"] : ["default", "method"]);
+ if (kind === "function") expect(extraction.calls.find(call => call.callerSymbolPath === "caller")).toMatchObject({ calleeRelPath: "default.mjs", calleeSymbolPath: "default" });
+});
+it("uses configured NodeNext require conditions for CommonJS module resolution", () => {
+ const { root } = fixture();
+ mkdirSync(join(root, "node_modules/conditional"), { recursive: true });
+ writeFileSync(join(root, "node_modules/conditional/package.json"), JSON.stringify({ name: "conditional", exports: { import: "./esm.js", require: "./cjs.js" } }));
+ writeFileSync(join(root, "node_modules/conditional/esm.js"), "export function value() { return 1; }");
+ writeFileSync(join(root, "node_modules/conditional/cjs.js"), "export function value() { return 2; }");
+ writeFileSync(join(root, "tsconfig.json"), JSON.stringify({ compilerOptions: { module: "NodeNext", moduleResolution: "NodeNext" } }));
+ writeFileSync(join(root, "consumer.cjs"), "import { value } from 'conditional'; export function consumer() { return value(); } export function load() { return import('conditional'); }");
+ const extraction = extractTypeScript([join(root, "consumer.cjs")], root);
+ expect(extraction.imports[0]?.resolvedRelPath).toBe("node_modules/conditional/cjs.js");
+ expect(extraction.imports[1]?.resolvedRelPath).toBe("node_modules/conditional/esm.js");
+});
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+function fixture() {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-")); roots.push(root);
+ const sources = {
+ "leaf.mjs": "export function leaf() { return 1; }",
+ "bridge.js": "export { leaf } from './leaf.mjs';",
+ "main.ts": "import { leaf } from './bridge.js'; export function main() { return leaf(); }",
+ "view.jsx": "export function View() { return ; }",
+ "legacy.cjs": "module.exports = require('./leaf.mjs');",
+ };
+ for (const [path, content] of Object.entries(sources)) writeFileSync(join(root, path), content);
+ const config: SemctxConfig = { ...createDefaultConfig(root), version: 2, selectionMode: "globs-v1", include: ["**/*"], exclude: [], languages: { typescript: "on", javascript: "on" } };
+ return { root, config };
+}
+it("extracts opt-in JS symbols and TS to JS reexport dependency paths", () => {
+ const { root, config } = fixture();
+ const discovery = discoverRepository(config);
+ expect(discovery.files.map(f => f.relPath)).toEqual(["bridge.js", "leaf.mjs", "legacy.cjs", "main.ts", "view.jsx"]);
+ const extraction = extractTypeScript(discovery.files.map(f => f.absPath), root);
+ expect(extraction.symbols.find(s => s.name === "leaf")).toMatchObject({ relPath: "leaf.mjs", exported: true });
+ expect(extraction.symbols.find(s => s.name === "View")).toMatchObject({ relPath: "view.jsx", exported: true });
+ expect(extraction.calls.find(c => c.callerSymbolPath === "main")).toMatchObject({ calleeRelPath: "leaf.mjs", calleeSymbolPath: "leaf" });
+ const graph = analyzeRepository(config, discovery.files).graph;
+ const dependencies = graph.edges.filter(e => e.kind === "imports");
+ expect(dependencies).toHaveLength(2);
+ expect(dependencies.some(e => e.from.includes("bridge.js") && e.to.includes("leaf.mjs"))).toBe(true);
+});
+it("keeps JS outside legacy v1 selected files", () => {
+ const { root } = fixture();
+ expect(discoverRepository({ ...createDefaultConfig(root), exclude: [] }).files.map(f => f.relPath)).toEqual(["main.ts"]);
+});
+it("keeps mixed extraction in one semantic Program when workers are requested", async () => {
+ const { config } = fixture();
+ const discovery = discoverRepository(config);
+ const synchronous = analyzeRepository(config, discovery.files);
+ const asynchronous = await analyzeRepositoryAsync(config, discovery.files, 2);
+ expect(asynchronous.analysis).toEqual(synchronous);
+ expect(asynchronous.parallelism).toMatchObject({ requested: 2, used: 1, mode: "preflight-fallback" });
+});
diff --git a/packages/ts-analyzer/test/location-navigation.test.ts b/packages/ts-analyzer/test/location-navigation.test.ts
new file mode 100644
index 00000000..4c3c22af
--- /dev/null
+++ b/packages/ts-analyzer/test/location-navigation.test.ts
@@ -0,0 +1,66 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "mjs"]) test(`SDK Location href ${extension} cannot hide JavaScript module execution`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = "export function run(){location.href=\"javascript:import('./hidden.js')\";}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ extension, reasons }));
+ expect(reasons).toContain("SOURCE_SCRIPT_NAVIGATION_UNSUPPORTED");
+});
+
+test("SDK navigation follows URL scheme case and control normalization", () => {
+ for (const source of [
+ "export function run(){location.href=\"JaVaScRiPt:import('./hidden.js')\";}",
+ "export function run(){location.href=\" \\u0000 javascript:import('./hidden.js')\";}",
+ "export function run(){location.href=\"java\\tscri\\npt:import('./hidden.js')\";}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_SCRIPT_NAVIGATION_UNSUPPORTED");
+ }
+});
+
+test("actual SDK navigation aliases and unknown targets require an inert proof", () => {
+ for (const source of [
+ "const target=location;export function run(){target.href='javascript:1';}",
+ "const target=window.location;export function run(){target['href']='javascript:1';}",
+ "const url='javascript:1';export function run(){location.href=url;}",
+ "export function run(url:string){location.href=url;}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_SCRIPT_NAVIGATION_UNSUPPORTED");
+ }
+});
+
+test("inert SDK href strings and real local location homonyms remain distinct", () => {
+ for (const source of [
+ "export function run(){location.href='https://example.invalid/path';}",
+ "export function run(){location.href='HTTP://example.invalid/path';}",
+ "export function run(){location.href='//example.invalid/path';}",
+ "export function run(){location.href='next.html?value=data:opaque';}",
+ "export function run(){location.href='#section';}",
+ "export function run(){location.href='./next.html';}",
+ "const target=window.location;export function run(){target.href='/next.html';}",
+ "const location={href:''};export function run(){location.href='javascript:1';}",
+ "const location={href:''};export function run(url:string){location.href=url;}",
+ "const inert='javascript:1';export function run(){return inert;}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+for (const extension of ["ts", "mjs"]) test(`SDK Location href ${extension} refuses executable and unsupported URL schemes`, () => {
+ for (const source of [
+ "export function run(){location.href=\"data:text/html,\";}",
+ "export function run(){location.href=\"vbscript:Execute(\\\"hidden\\\")\";}",
+ "export function run(){location.href=\"DaTa:text/html,hidden\";}",
+ "export function run(){location.href=\" \\u0000 da\\tta:text/html,hidden\";}",
+ "export function run(){location.href=\"vbscri\\npt:hidden\";}",
+ "export function run(){location.href=\"custom:opaque\";}",
+ "export function run(){location.href=\"blob:https://example.invalid/opaque\";}",
+ ]) {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_SCRIPT_NAVIGATION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/math-coercion.test.ts b/packages/ts-analyzer/test/math-coercion.test.ts
new file mode 100644
index 00000000..fb130c3e
--- /dev/null
+++ b/packages/ts-analyzer/test/math-coercion.test.ts
@@ -0,0 +1,20 @@
+import {expect,test} from "bun:test";
+import {inspectModuleConfiguration} from "../src";
+for(const extension of ["ts","mjs"])test(`SDK Math.max ${extension} requires primitive consumed values`,()=>{
+ const path=`/fixture/main.${extension}`;const source="function helper(){return 1;}const value={valueOf:helper};export function run(){return Math.max(value as any,0);}";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,extension==="mjs"?source.replace(" as any",""):source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+test("SDK numeric arguments, zero arity, ignored extras and local Math stay distinct",()=>{
+ for(const source of [
+ "export function run(){return Math.max(1,2,3);}","export function run(){return Math.abs(1);}","export function run(){return Math.random();}",
+ "function helper(){return 1;}const value={valueOf:helper};export function run(){return Math.random(value as any);}",
+ "function helper(){return 1;}const value={valueOf:helper};export function run(){return Math.abs(1,value as any);}",
+ "const Math={max(_value:unknown,_other:number){return 1;}};function helper(){return 1;}export function run(){return Math.max({valueOf:helper},0);}",
+ ]){const path="/fixture/main.ts";expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);}
+});
+test("fixed and rest Math signatures consume the actual numeric coordinates",()=>{
+ for(const call of ["Math.max(0,value as any)","Math.abs(value as any)"]){
+ const path="/fixture/main.ts";const source=`function helper(){return 1;}const value={valueOf:helper};export function run(){return ${call};}`;
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/number-locale-coercion.test.ts b/packages/ts-analyzer/test/number-locale-coercion.test.ts
new file mode 100644
index 00000000..ceb9aea7
--- /dev/null
+++ b/packages/ts-analyzer/test/number-locale-coercion.test.ts
@@ -0,0 +1,24 @@
+import {expect,test} from "bun:test";
+import {inspectModuleConfiguration} from "../src";
+for(const extension of ["ts","mjs"])for(const [name,source] of [
+ ["Number","function helper(){return 1;}const value={valueOf:helper};export function run(){return Number(value);}"],
+ ["array locale","function helper(){return 'one';}const value={toLocaleString:helper};export function run(){return [value].toLocaleString();}"],
+]as const)test(`${name} ${extension} requires primitive coercion provenance`,()=>{
+ const path=`/fixture/main.${extension}`;expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+test("SDK primitives and ordinary local homonyms stay distinct",()=>{
+ for(const source of [
+ "export function run(){return Number();}","export function run(){return Number(1);}","export function run(){return Number(true);}","export function run(){return Number('2');}","export function run(){return Number(2n);}",
+ "function helper(){return 1;}export function run(){return Boolean({valueOf:helper});}",
+ "export function run(){return [1,'one',true,null,undefined].toLocaleString();}","export function run(){return [].toLocaleString();}",
+ "export function run(){return [1].toLocaleString('en-US',{maximumFractionDigits:0});}",
+ "function Number(_value:unknown){return 1;}function helper(){return 1;}export function run(){return Number({valueOf:helper});}",
+ "const value={toLocaleString(){return 'local';}};export function run(){return value.toLocaleString();}",
+ ]){const path="/fixture/main.ts";expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);}
+});
+test("locale arguments with hooks cannot prove primitive option values",()=>{
+ for(const source of [
+ "function helper(){return 'en-US';}export function run(){return [1].toLocaleString([{toString:helper}] as any);}",
+ "function helper(){return 1;}export function run(){return [1].toLocaleString('en-US',{minimumFractionDigits:{valueOf:helper}} as any);}",
+ ]){const path="/fixture/main.ts";expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");}
+});
diff --git a/packages/ts-analyzer/test/opaque-data-serialization.test.ts b/packages/ts-analyzer/test/opaque-data-serialization.test.ts
new file mode 100644
index 00000000..32d8df30
--- /dev/null
+++ b/packages/ts-analyzer/test/opaque-data-serialization.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "js"]) for (const [name, content, reason] of [
+ ["nested opaque callback", "import { register } from 'opaque'; function helper() { return 1; } export function run() { return register({ handler: helper }); }", "OPAQUE_CALLBACK_UNSUPPORTED"],
+ ["SDK serialization hook", "import { value } from './value'; export function run() { return JSON.stringify(value); }", "SERIALIZATION_UNSUPPORTED"],
+] as const) test(`${name} is unsupported for ${extension}`, () => {
+ const path = `/fixture/main.${extension}`;
+ const snapshot = new Map([[path, content], [`/fixture/value.${extension}`, "export const value = { toJSON() { return 1; } };"], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_${reason}`);
+});
+
+test("direct recursive literal data, primitive serialization and actual own APIs remain distinct", () => {
+ for (const content of [
+ "import { register } from 'opaque'; register({ nested: { count: 1, values: ['x', 2] } }); export function run() { return 1; }",
+ "export function run() { return JSON.stringify('data'); }",
+ "const JSON = { stringify(value) { return value; } }; export function run() { return JSON.stringify(1); }",
+ "const register = value => value; export function run() { return register({ value: 1 }); }",
+ ]) {
+ const path = "/fixture/main.ts"; const snapshot = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", snapshot)).toEqual([]);
+ }
+});
+test("container aliases and serializer dispatch cannot supply absence-of-callback proof", () => {
+ for (const content of [
+ "import { register } from 'opaque'; function helper() { return 1; } const value = { handler: helper }; register(value); export function run() { return 1; }",
+ "import { register } from 'opaque'; const value = { count: 1 }; register(value); export function run() { return 1; }",
+ "import { value } from './value'; const serialize = JSON.stringify; export function run() { return serialize.call(JSON, value); }",
+ ]) {
+ const path = "/fixture/main.ts";
+ const snapshot = new Map([[path, content], ["/fixture/value.ts", "export const value = { toJSON() { return 1; } };"], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", snapshot).some(reason => reason.includes("UNSUPPORTED"))).toBe(true);
+ }
+});
diff --git a/packages/ts-analyzer/test/opaque-inline-ownership.test.ts b/packages/ts-analyzer/test/opaque-inline-ownership.test.ts
new file mode 100644
index 00000000..16ee631a
--- /dev/null
+++ b/packages/ts-analyzer/test/opaque-inline-ownership.test.ts
@@ -0,0 +1,60 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { analyzeRepository, inspectModuleConfiguration, registerOpaqueCallbackOwnership } from "../src";
+
+for (const [body, allowed] of [["register(() => helper())", true], ["register(() => helper)", false],
+ ["helper(); register(() => helper)", false], ["register(() => unknown)", false], ["register(() => 1)", false]] as const) test(`opaque inline ownership ${body}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inline-facts-"));
+ try {
+ const path = join(root, "main.ts"); const content = `import { register } from 'opaque'; function helper() { return 1; } export function main() { return ${body}; }`;
+ const snapshot = new Map([[path, content], [join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}']]);
+ for (const [file, text] of snapshot) writeFileSync(file, text);
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const };
+ expect(inspectModuleConfiguration(path, root, snapshot)).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ const analysis = analyzeRepository(config, [{ absPath: path, relPath: "main.ts", role: "source", language: "typescript", content }], snapshot);
+ expect(inspectModuleConfiguration(path, root, snapshot).includes("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED")).toBe(!allowed);
+ if (allowed) {
+ const caller = analysis.graph.nodes.find(node => node.name === "main")!; const helper = analysis.graph.nodes.find(node => node.name === "helper")!;
+ expect(analysis.graph.edges).toContainEqual(expect.objectContaining({ kind: "calls", from: caller.id, to: helper.id }));
+ registerOpaqueCallbackOwnership(snapshot, { ...analysis.graph, edges: [] }, root);
+ expect(inspectModuleConfiguration(path, root, snapshot)).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("actual test binding ownership preserves the unchanged public Vitest callback", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inline-test-binding-"));
+ try {
+ const path = join(root, "value.test.ts"); const leaf = join(root, "value.mjs");
+ const content = 'import { test, expect } from "vitest";\nimport { value } from "./value.mjs";\ntest("returns the primitive fixture value", () => { expect(value(1)).toBe(2); });\n';
+ const target = "export function value(input) { return 1 + 1; }\n";
+ const snapshot = new Map([[path, content], [leaf, target], [join(root, "package.json"), '{"devDependencies":{"vitest":"4.0.0"}}']]);
+ for (const [file, text] of snapshot) writeFileSync(file, text);
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const, languages: { typescript: "on" as const, javascript: "on" as const } };
+ const analysis = analyzeRepository(config, [{ absPath: path, relPath: "value.test.ts", role: "test", language: "typescript", content },
+ { absPath: leaf, relPath: "value.mjs", role: "source", language: "javascript", content: target }], snapshot);
+ const testNode = analysis.graph.nodes.find(node => node.kind === "test")!; const value = analysis.graph.nodes.find(node => node.name === "value")!;
+ expect(analysis.graph.edges).toContainEqual(expect.objectContaining({ kind: "covers", from: testNode.id, to: value.id }));
+ expect(analysis.graph.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: value.id, to: testNode.id }));
+ expect(inspectModuleConfiguration(path, root, snapshot)).not.toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+test("anonymous default declaration owns its already modeled inline call", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-inline-default-"));
+ try {
+ const path = join(root, "main.ts");
+ const content = "import { register } from 'opaque'; function helper() { return 1; } export default function() { return register(() => helper()); }";
+ const snapshot = new Map([[path, content], [join(root, "package.json"), '{"dependencies":{"opaque":"1.0.0"}}']]);
+ for (const [file, text] of snapshot) writeFileSync(file, text);
+ const config = { ...createGlobSelectionConfig(root), selectionMode: "qualified-static-v1" as const, analysisProfile: "modelo-suite-static-v1" as const };
+ const analysis = analyzeRepository(config, [{ absPath: path, relPath: "main.ts", role: "source", language: "typescript", content }], snapshot);
+ const caller = analysis.graph.nodes.find(node => node.name === "default")!; const helper = analysis.graph.nodes.find(node => node.name === "helper")!;
+ expect(analysis.graph.edges).toContainEqual(expect.objectContaining({ kind: "calls", from: caller.id, to: helper.id }));
+ const reasons = inspectModuleConfiguration(path, root, snapshot);
+ console.info(JSON.stringify({ defaultInlineReasons: reasons, caller: caller.name }));
+ expect(reasons).not.toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/parallel-extraction.test.ts b/packages/ts-analyzer/test/parallel-extraction.test.ts
index be78542a..e5668d3b 100644
--- a/packages/ts-analyzer/test/parallel-extraction.test.ts
+++ b/packages/ts-analyzer/test/parallel-extraction.test.ts
@@ -32,6 +32,55 @@ afterEach(() => {
});
describe("parallel TypeScript extraction", () => {
+ test("private runtime-use and origin metadata survive validated worker transport and merge", async () => {
+ const { root, paths } = fixture({
+ "a/dep.ts": "export const FLAG=true;", "a/run.ts": "import {FLAG} from './dep';export function run(){return FLAG;}",
+ "b/dep.ts": "export const FLAG=true;", "b/run.ts": "import {FLAG} from './dep';export function run(){return 1;}",
+ });
+ const snapshot = new Map(paths.map(path => [path, Bun.file(path)]));
+ const inputs = new Map(await Promise.all([...snapshot].map(async ([path, file]) => [path, await file.text()] as const)));
+ __setExtractionWorkerFactoryForTesting(() => {
+ let onmessage: ((event: { data: unknown }) => void) | null = null;
+ return {
+ onerror: null,
+ get onmessage() { return onmessage; }, set onmessage(value: ((event: { data: unknown }) => void) | null) { onmessage = value; },
+ postMessage(request: { jobId: string; emitAbsPaths: string[] }) {
+ const extraction = extractTypeScript(request.emitAbsPaths, root, inputs);
+ queueMicrotask(() => onmessage?.({ data: structuredClone({ schemaVersion: 1, jobId: request.jobId, ok: true, extraction }) }));
+ },
+ terminate() {},
+ } as unknown as Worker;
+ });
+ const result = await extractTypeScriptParallel(paths, root, 2);
+ expect(result.parallelism.used).toBe(2);
+ expect(result.extraction).toEqual(extractTypeScript(paths, root, inputs));
+ expect(result.extraction.imports.find(imp => imp.fromRelPath === "a/run.ts")?.bindingTargets?.[0]?.unmodeledReason).toBe("VALUE_DECLARATION");
+ expect(result.extraction.imports.find(imp => imp.fromRelPath === "b/run.ts")?.bindingTargets?.[0]?.runtimeUsed).toBe(false);
+ });
+
+ for (const field of ["runtimeUsed", "unmodeledReason"] as const) test(`worker rejects malformed private import-target ${field}`, async () => {
+ const { root, paths } = fixture({ "a.ts": "export function a(){return 1;}", "b.ts": "export function b(){return 1;}" });
+ __setExtractionWorkerFactoryForTesting(() => {
+ let onmessage: ((event: { data: unknown }) => void) | null = null;
+ return {
+ onerror: null,
+ get onmessage() { return onmessage; }, set onmessage(value: ((event: { data: unknown }) => void) | null) { onmessage = value; },
+ postMessage(request: { jobId: string; emitAbsPaths: string[] }) {
+ const owned = relative(root, request.emitAbsPaths[0]!).replaceAll("\\", "/");
+ const target = { relPath: owned, symbolPath: "a", [field]: field === "runtimeUsed" ? "yes" : 42 };
+ queueMicrotask(() => onmessage?.({ data: { schemaVersion: 1, jobId: request.jobId, ok: true, extraction: {
+ modules: [owned], symbols: [], calls: [], imports: [{ fromRelPath: owned, moduleSpecifier: "./a", names: ["a"], bindingTargets: [target], line: 1 }],
+ } } }));
+ }, terminate() {},
+ } as unknown as Worker;
+ });
+ let thrown: unknown;
+ try { await extractTypeScriptParallel(paths, root, 2); }
+ catch (error) { thrown = error; }
+ expect(thrown).toBeInstanceOf(Error);
+ expect((thrown as Error).message).toMatch(/malformed extraction DTO/);
+ });
+
test("workers 1 and 2 return byte-identical structured extraction with shared declarations", async () => {
const { root, paths } = fixture({
"globals.d.ts": "declare interface SharedShape { value: string }\n",
diff --git a/packages/ts-analyzer/test/plain-template-positive.test.ts b/packages/ts-analyzer/test/plain-template-positive.test.ts
new file mode 100644
index 00000000..356147db
--- /dev/null
+++ b/packages/ts-analyzer/test/plain-template-positive.test.ts
@@ -0,0 +1,18 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript, inspectModuleConfiguration } from "../src";
+
+test("retained TS and MJS plain templates retain their actual helper call", () => {
+ for (const extension of ["ts", "mjs"]) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-tagged-template-"));
+ try {
+ const path = join(root, `main.${extension}`);
+ const content = "export function helper() { return 1; } export function main() { return `value ${helper()}`; }";
+ const inputs = new Map([[path, content]]);
+ expect(inspectModuleConfiguration(path, root, inputs)).not.toContain("SOURCE_TAGGED_TEMPLATE_UNSUPPORTED");
+ expect(extractTypeScript([path], root, inputs).calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "main", calleeSymbolPath: "helper" }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+ }
+});
diff --git a/packages/ts-analyzer/test/plural-descriptor-regexp.test.ts b/packages/ts-analyzer/test/plural-descriptor-regexp.test.ts
new file mode 100644
index 00000000..728392f2
--- /dev/null
+++ b/packages/ts-analyzer/test/plural-descriptor-regexp.test.ts
@@ -0,0 +1,50 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+const descriptorCases = [
+ "function* helper() { yield 7; } const values = [1]; Object.defineProperties(values, { [Symbol.iterator]: { value: helper } }); export function run() { return [...values]; }",
+ "function helper() { return 7; } const box = Object.create({}, { value: { get: helper } }); export function run() { return box.value; }",
+] as const;
+for (const [index, content] of descriptorCases.entries()) for (const extension of ["ts", "js"]) test(`descriptor route ${index} refuses ${extension}`, () => {
+ const path = `/fixture/main.${extension}`; const snapshot = new Map([[path, content]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ index, extension, reasons }));
+ expect(reasons.some(reason => reason.includes("DESCRIPTOR_UNSUPPORTED"))).toBe(true);
+});
+for (const [key, operation] of [["match", "'x'.match(matcher)"], ["matchAll", "'x'.matchAll(matcher)"], ["replace", "'x'.replace(matcher, 'y')"], ["search", "'x'.search(matcher)"], ["split", "'x'.split(matcher)"]] as const) test(`actual SDK Symbol.${key} mutation refuses`, () => {
+ const path = "/fixture/main.ts";
+ const content = `function helper() { return 7; } const matcher = {}; matcher[Symbol.${key}] = helper; export function run() { return ${operation}; }`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ key, reasons }));
+ expect(reasons).toContain("SOURCE_PROTOCOL_MUTATION_UNSUPPORTED");
+});
+test("native descriptor getter and match hook invoke the actual retained helper", () => {
+ for (const content of [
+ "function helper() { console.log('HOOK_CALLED'); return 7; } const box = Object.create({}, { value: { get: helper } }); if(box.value!==7)throw Error('getter mismatch');",
+ "function helper() { console.log('HOOK_CALLED'); return 7; } const matcher={}; matcher[Symbol.match]=helper; if('x'.match(matcher)!==7)throw Error('match mismatch');",
+ "function* helper() { console.log('HOOK_CALLED'); yield 7; } const values=[1]; Object.defineProperties(values,{[Symbol.iterator]:{value:helper}}); if([...values][0]!==7)throw Error('iterator mismatch');",
+ ]) {
+ const result = Bun.spawnSync([process.execPath, "-e", content], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("HOOK_CALLED");
+ }
+});
+test("ordinary SDK data descriptors and string properties are separate from protocol hooks", () => {
+ for (const content of [
+ "const box = {}; Object.defineProperties(box, { value: { value: 1 } }); export function run() { return box.value; }",
+ "const box = Object.create({}, { value: { value: 1 } }); export function run() { return box.value; }",
+ "function helper() { return 1; } const matcher = {}; matcher['match'] = helper; export function run() { return 1; }",
+ ]) {
+ const path = "/fixture/main.ts"; const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons.some(reason => reason.includes("DESCRIPTOR_UNSUPPORTED") || reason.includes("PROTOCOL_MUTATION_UNSUPPORTED"))).toBe(false);
+ }
+});
+test("known SDK aliases and helper escapes retain the same mutation obligations", () => {
+ for (const [content, reason] of [
+ ["function* helper() { yield 1; } const values = [1]; const change = Object.setPrototypeOf; change.call(Object, values, { [Symbol.iterator]: helper }); export function run() { return [...values]; }", "PROTOTYPE_MUTATION_UNSUPPORTED"],
+ ["function* helper() { yield 1; } const values = [1]; const { defineProperties: define } = Object; define.call(Object, values, { [Symbol.iterator]: { value: helper } }); export function run() { return [...values]; }", "DESCRIPTOR_UNSUPPORTED"],
+ ["function helper() { return 1; } const matcher = {}; const { match: key } = Symbol; matcher[key] = helper; export function run() { return 'x'.match(matcher); }", "PROTOCOL_MUTATION_UNSUPPORTED"],
+ ] as const) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain(`SOURCE_${reason}`);
+ }
+});
diff --git a/packages/ts-analyzer/test/process-launcher.test.ts b/packages/ts-analyzer/test/process-launcher.test.ts
new file mode 100644
index 00000000..b77b48d8
--- /dev/null
+++ b/packages/ts-analyzer/test/process-launcher.test.ts
@@ -0,0 +1,39 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "js"]) test(`builtin fork is an unmodeled code launcher ${extension}`, () => {
+ const content = "import { fork } from 'node:child_process'; export function run() { return fork('./hidden.js'); }";
+ const path = `/fixture/main.${extension}`; const snapshot = new Map([[path, content]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_PROCESS_LAUNCH_UNSUPPORTED`);
+});
+test("ordinary local fork and unrelated primitive Node helpers are distinct", () => {
+ for (const content of [
+ "function fork(path) { return path; } export function run() { return fork('value'); }",
+ "import { basename } from 'node:path'; export function run() { return basename('src/value.ts'); }",
+ "import { fork } from 'node:child_process'; export function run(fork) { return fork('value'); }",
+ "import { fork } from 'node:child_process'; export function run() { return 1; }",
+ "import type { fork } from 'node:child_process'; export type Launch = typeof fork; export function run() { return 1; }",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+test("builtin namespace aliases and helper dispatch preserve the launcher boundary", () => {
+ for (const content of [
+ "import * as cp from 'node:child_process'; const processes = cp; export function run() { return processes.fork('./hidden.js'); }",
+ "import { fork as launch } from 'child_process'; const start = launch; export function run() { return start.call(null, './hidden.js'); }",
+ "import * as cp from 'node:child_process'; const { fork: start } = cp; export function run() { return start('./hidden.js'); }",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+ }
+});
+test("return-only builtin launcher escape cannot skip semantic inspection", () => {
+ const path = "/fixture/main.ts";
+ const content = "import { fork } from 'node:child_process'; export function run() { return fork; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ returnOnlyEscape: reasons }));
+ expect(reasons).toContain("SOURCE_PROCESS_LAUNCH_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/property-disposal.test.ts b/packages/ts-analyzer/test/property-disposal.test.ts
new file mode 100644
index 00000000..e120ab12
--- /dev/null
+++ b/packages/ts-analyzer/test/property-disposal.test.ts
@@ -0,0 +1,70 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("erased interface computed names do not imply runtime property coercion", () => {
+ for (const content of [
+ "declare const key: unique symbol; export interface Box { [key]: number; }",
+ "declare const key: unique symbol; export type Box = { [key]: number; };",
+ "declare const key: unique symbol; export declare class Box { [key]: number; }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_PROPERTY_KEY_UNSUPPORTED");
+ }
+ const content = "const key = { toString() { return 'value'; } }; export class Box { [key] = 1; }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_PROPERTY_KEY_UNSUPPORTED");
+});
+test("native promise then override through a constant key cannot preserve async return admission", () => {
+ const content = "function helper() {} const p = Promise.resolve(1); const key = 'then'; (p as any)[key] = helper; export async function start() { return p; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_THENABLE_MUTATION_UNSUPPORTED");
+});
+for (const content of [
+ "const resource = { [Symbol.asyncDispose]() {} }; export async function start() { await using owned = resource; return 1; }",
+ "const key = { toString() { return 'value'; } }; export function start() { return { [key]: 1 }; }",
+ "const key = { [Symbol.toPrimitive]() { return 'value'; } }; const target = { value: 1 }; export function start() { const { [key]: value } = target; return value; }",
+ "function helper() {} const p = Promise.resolve(1); p.then = helper; export async function start() { return p; }",
+]) test(`unmodeled property or disposal protocol refuses ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons.some(reason => /SOURCE_(PROPERTY_KEY|DISPOSAL|THENABLE_MUTATION)_UNSUPPORTED/.test(reason))).toBe(true);
+});
+test("proven primitive keys, actual SDK symbols and unmodified native promises preserve their boundaries", () => {
+ for (const content of [
+ "const key = 'value'; const target = { value: 1 }; export function start() { return target[key]; }",
+ "const target = [1]; export function start() { return target[0]; }",
+ "const key = Symbol.iterator; const target = { [key]() { return 1; } }; export function start() { return target[key]; }",
+ "export async function start() { return await Promise.resolve(1); }",
+ "export async function start() { return import('./dep.mjs'); }",
+ "export function start() { const using = 1; return using; }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content], ["/fixture/dep.mjs", "export function dep() { return 1; }"]]));
+ expect(reasons.filter(reason => /SOURCE_(PROPERTY_KEY|DISPOSAL|THENABLE_MUTATION|AWAIT|ASYNC_RETURN)_UNSUPPORTED/.test(reason))).toEqual([]);
+ }
+});
+for (const member of ["iterator", "hasInstance", "toPrimitive"]) test(`actual SDK Symbol.${member} is a proven property key`, () => {
+ const content = `const key = Symbol.${member}; const target = { [key]: 1 }; export function start() { return target[key]; }`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_PROPERTY_KEY_UNSUPPORTED");
+});
+for (const member of ["dispose", "asyncDispose"]) test(`Symbol.${member} outside the loaded SDK declarations is an unknown property key`, () => {
+ const content = `const key = Symbol.${member}; const target = { [key]: 1 }; export function start() { return target[key]; }`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).toContain("SOURCE_PROPERTY_KEY_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["function helper() {} const value: any = []; value.then = helper; export async function start() { await value; }", "AWAIT_UNSUPPORTED"],
+ ["const key = { [Symbol.toPrimitive]() { return 'value'; } }; const target = { value: 1 }; export function start() { return target[key]; }", "PROPERTY_KEY_UNSUPPORTED"],
+ ["const disposable = { [Symbol.dispose]() {} }; export function start() { using resource = disposable; return 1; }", "DISPOSAL_UNSUPPORTED"],
+] as const) test(`direct ${reason} refuses omitted implicit invocation`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+});
+test("retained CommonJS JavaScript format is unsupported even with ESM syntax", () => {
+ const file = "/fixture/main.cjs"; const content = "export function run() { return 1; }";
+ const source = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
+ const reasons = inspectModuleConfiguration(file, "/fixture", new Map([[file, content]]), source);
+ console.info(JSON.stringify({ reasons, js: inspectJavaScriptSource(file, content).reasons }));
+ expect(reasons).toContain("SOURCE_COMMONJS_EXTENSION_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/protocol-definition.test.ts b/packages/ts-analyzer/test/protocol-definition.test.ts
new file mode 100644
index 00000000..5cccdf3d
--- /dev/null
+++ b/packages/ts-analyzer/test/protocol-definition.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+for (const [name, definition] of [
+ ["object", "const matcher = { [Symbol.match]: helper };"],
+ ["class", "class Matcher { static [Symbol.match] = helper; } const matcher = Matcher;"],
+] as const) for (const extension of ["ts", "js"]) test(`callable protocol ${name} definition ${extension}`, () => {
+ const content = `function helper() { return 1; } ${definition} export function run() { return 'x'.match(matcher${extension === "ts" ? " as any" : ""}); }`;
+ const path = `/fixture/main.${extension}`; const inputs = new Map([[path, content]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", inputs).reasons : inspectModuleConfiguration(path, "/fixture", inputs);
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_PROTOCOL_DEFINITION_UNSUPPORTED`);
+});
+
+test("native literal protocol definition invokes the owned helper", () => {
+ const result = Bun.spawnSync([process.execPath, "-e", "function helper(){console.log('PROTOCOL_DEFINED_CALLED');return 7}const matcher={[Symbol.match]:helper};if('x'.match(matcher)!==7)throw Error('hook result');"], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("PROTOCOL_DEFINED_CALLED");
+});
+
+test("erased protocol members and actual ordinary data keys remain distinct", () => {
+ for (const content of [
+ "export interface Matcher { [Symbol.match](value: string): number; }",
+ "declare class Matcher { static [Symbol.match](value: string): number; } export {};",
+ "const matcher = { [Symbol.match]: 1 }; export function run() { return 1; }",
+ "function helper(){return 1;} const matcher={match:helper}; export function run(){return matcher.match();}",
+ "const Symbol={match:'match'}; function helper(){return 1;} const matcher={[Symbol.match]:helper}; export function run(){return matcher.match();}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_PROTOCOL_DEFINITION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/protocol-promise-opaque.test.ts b/packages/ts-analyzer/test/protocol-promise-opaque.test.ts
new file mode 100644
index 00000000..72865acf
--- /dev/null
+++ b/packages/ts-analyzer/test/protocol-promise-opaque.test.ts
@@ -0,0 +1,80 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("opaque data uses an actual dominating runtime primitive guard", () => {
+ const content = "import { read, write } from 'opaque'; const source = read(); if (typeof source !== 'string') throw new TypeError('string required'); write(source); export function start() { return 1; }";
+ const inputs = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", inputs)).not.toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+test("a guard outside the callback execution scope is not dominance proof", () => {
+ const content = "import { read, write } from 'opaque'; const source = read(); start(); if (typeof source !== 'string') throw new TypeError('string required'); export function start() { write(source); }";
+ const inputs = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", inputs)).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+test("runtime primitive guards do not prove reassigned or differently bound data", () => {
+ for (const body of [
+ "export function start(source) { if (typeof source !== 'string') throw new TypeError(); source = read(); write(source); }",
+ "export function start(source) { if (typeof source !== 'string') throw new TypeError(); [source] = [read()]; write(source); }",
+ "function helper() {} export function start(source) { if (typeof source !== 'string') throw new TypeError(); ({ source } = { source: helper }); write(source); }",
+ "const source = read(); { const source = 'text'; if (typeof source !== 'string') throw new TypeError(); } write(source); export function start() { return 1; }",
+ ]) {
+ const content = "import { read, write } from 'opaque'; " + body;
+ const inputs = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", inputs)).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ }
+});
+for (const [name, body] of [
+ ["annotation", "export function start(source: string) { return write(source); }"],
+ ["assertion", "const source = read() as string; write(source); export function start() { return 1; }"],
+] as const) test(`opaque primitive ${name} alone is not value provenance`, () => {
+ const content = "import { read, write } from 'opaque'; " + body;
+ const inputs = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ const reasons = inspectModuleConfiguration(path, "/fixture", inputs);
+ console.info(JSON.stringify({ primitiveProof: name, reasons }));
+ expect(reasons).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+for (const [body, allowed] of [
+ ["function value() { return 1 + 1; }", true],
+ ["function helper() {} function value() { return helper; }", false],
+ ["function value(input: number) { return input; }", false],
+ ["async function value() { return 1; }", false],
+ ["function* value() { yield 1; return 1; }", false],
+] as const) test(`opaque result proof inspects the actual return: ${body}`, () => {
+ const content = "import { write } from 'opaque'; " + body + " export function start() { return write(value(1)); }";
+ const inputs = new Map([[path, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", inputs).includes("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED")).toBe(!allowed);
+});
+test("a reassigned retained function is not primitive-return provenance", () => {
+ const jsPath = "/fixture/main.js";
+ const content = "import { expect } from 'opaque'; function helper() {} function value() { return 1; } value = () => helper; export function run() { return expect(value()); }";
+ const inputs = new Map([[jsPath, content], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]);
+ const reasons = inspectModuleConfiguration(jsPath, "/fixture", inputs);
+ console.info(JSON.stringify({ reassignedCalleeReasons: reasons }));
+ expect(reasons).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+test("Promise resolve helper dispatch retains the assimilation obligation", () => {
+ const content = "const value = { then() { return 1; } }; export function start() { return Promise.resolve.call(Promise, value); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ promiseHelperReasons: reasons }));
+ expect(reasons).toContain("SOURCE_PROMISE_ASSIMILATION_UNSUPPORTED");
+});
+test("ordinary SDK symbol-key data is not an iterable API input", () => {
+ const content = "const box = {}; Object.defineProperty(box, Symbol.toStringTag, { value: 'box' }); export function run() { return box; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ ordinarySymbolData: reasons }));
+ expect(reasons).not.toContain("SOURCE_SDK_ITERATION_UNSUPPORTED");
+});
+for (const [content, expected] of [
+ ["function helper() { return true; } Object.defineProperty(Array, Symbol.hasInstance, { value: helper }); export function run(value) { return value instanceof Array; }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["function* helper() { yield 1; } const values = [1]; Reflect.set(values, Symbol.iterator, helper); export function run() { return [...values]; }", "PROTOCOL_MUTATION_UNSUPPORTED"],
+ ["function* helper() { yield 1; } const values = [1]; Reflect.defineProperty(values, Symbol.iterator, { value: helper }); export function run() { return [...values]; }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["const value = { then() { return 1; } }; export function start() { return Promise.resolve(value); }", "PROMISE_ASSIMILATION_UNSUPPORTED"],
+ ["import { register } from 'opaque'; function helper() { return 1; } export function start() { return register(helper); }", "OPAQUE_CALLBACK_UNSUPPORTED"],
+] as const) test(`qualified implicit operation reports ${expected}`, () => {
+ const metadata: [string,string] = ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}'];
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content], metadata]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content, "/fixture", new Map([["/fixture/main.js", content], metadata])).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${expected}`);
+ expect(js).toContain(`JAVASCRIPT_${expected}`);
+});
diff --git a/packages/ts-analyzer/test/prototype-mutation.test.ts b/packages/ts-analyzer/test/prototype-mutation.test.ts
new file mode 100644
index 00000000..dc45f5d8
--- /dev/null
+++ b/packages/ts-analyzer/test/prototype-mutation.test.ts
@@ -0,0 +1,27 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+const source = (api: string): string => `function* helper() { console.log('ITERATOR_CALLED'); yield 7; } const values = [1]; ${api}.setPrototypeOf(values, { [Symbol.iterator]: helper }); export function run() { return [...values]; }`;
+for (const [api, extension] of [["Object", "ts"], ["Object", "js"], ["Reflect", "ts"]] as const) test(`actual ${api} prototype replacement refuses ${extension}`, () => {
+ const path = `/fixture/main.${extension}`; const content = source(api); const snapshot = new Map([[path, content]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ api, extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_PROTOTYPE_MUTATION_UNSUPPORTED`);
+});
+test("native prototype replacement actually invokes the retained iterator", () => {
+ for (const api of ["Object", "Reflect"]) {
+ const result = Bun.spawnSync([process.execPath, "-e", source(api) + "if (run()[0] !== 7) throw new Error('iterator output mismatch');"], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("ITERATOR_CALLED");
+ }
+});
+test("ordinary own mutators, type-only SDK use and unmodified arrays remain distinct", () => {
+ for (const content of [
+ "const Object = { setPrototypeOf(value, prototype) { return value; } }; export function run() { return Object.setPrototypeOf(1, null); }",
+ "const Reflect = { setPrototypeOf(value, prototype) { return value; } }; export function run() { return Reflect.setPrototypeOf(1, null); }",
+ "export type Mutator = typeof Object.setPrototypeOf; export function run() { return 1; }",
+ "const values = [1]; export function run() { return [...values]; }",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_PROTOTYPE_MUTATION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/qualified-export-jsx.test.ts b/packages/ts-analyzer/test/qualified-export-jsx.test.ts
new file mode 100644
index 00000000..fe303645
--- /dev/null
+++ b/packages/ts-analyzer/test/qualified-export-jsx.test.ts
@@ -0,0 +1,47 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript, inspectModuleConfiguration } from "../src";
+
+test("retained TS-only export clauses mark the actual imported declaration exported, preserving legacy extraction", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-export-clause-"));
+ try {
+ const leaf = join(root, "leaf.ts"); const caller = join(root, "caller.ts");
+ const inputs = new Map([[leaf, "function helper() { return 1; } export { helper }"], [caller, "import { helper } from './leaf'; export function main() { return helper(); }"]]);
+ for (const [path, content] of inputs) writeFileSync(path, content);
+ expect(extractTypeScript([leaf, caller], root).symbols.find(symbol => symbol.name === "helper")?.exported).toBe(false);
+ const result = extractTypeScript([leaf, caller], root, inputs);
+ expect(result.symbols.find(symbol => symbol.name === "helper")?.exported).toBe(true);
+ expect(result.calls).toContainEqual(expect.objectContaining({ callerRelPath: "caller.ts", calleeRelPath: "leaf.ts", calleeSymbolPath: "helper" }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+for (const [mode, content, expected] of [
+ ["react-jsx", "export const view = ;", true],
+ ["react-jsxdev", "export const view = <>>;", true],
+ ["preserve", "/** @jsxRuntime automatic */\nexport const view = ;", true],
+ ["react-jsx", "export function plain() { return 1; }", false],
+ ["preserve", "export const view = ;", false],
+ ["react-jsx", "/** @jsxRuntime classic */\nexport const view = ;", false],
+ ["preserve", "/** @jsxImportSource @fixture/runtime */\nexport const view = ;", true],
+ ["preserve", "/** @jsxRuntime classic */\n/** @jsxRuntime automatic */\nexport const view = ;", true],
+] as const) test(`qualified JSX runtime mode ${mode} refuses only unmodeled automatic JSX: ${content}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-jsx-mode-"));
+ try {
+ const path = join(root, "view.tsx"); const config = join(root, "tsconfig.json");
+ const inputs = new Map([[path, content], [config, JSON.stringify({ compilerOptions: { jsx: mode } })]]);
+ expect(inspectModuleConfiguration(path, root, inputs).includes("SOURCE_AUTOMATIC_JSX_RUNTIME_UNSUPPORTED")).toBe(expected);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("retained TS-only named default declarations retain canonical callee and import coordinates", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ts-default-"));
+ try {
+ const leaf = join(root, "leaf.ts"); const caller = join(root, "caller.ts");
+ const inputs = new Map([[leaf, "export default function namedFn() { return 1; }"], [caller, "import value from './leaf'; export function main() { return value(); }"]]);
+ const result = extractTypeScript([leaf, caller], root, inputs);
+ expect(result.calls).toContainEqual(expect.objectContaining({ calleeRelPath: "leaf.ts", calleeSymbolPath: "namedFn" }));
+ expect(result.imports[0]?.bindingTargets).toEqual([{ relPath: "leaf.ts", symbolPath: "namedFn" }]);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/qualified-module-mode.test.ts b/packages/ts-analyzer/test/qualified-module-mode.test.ts
new file mode 100644
index 00000000..58c9827c
--- /dev/null
+++ b/packages/ts-analyzer/test/qualified-module-mode.test.ts
@@ -0,0 +1,35 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { inspectModuleConfiguration } from "../src/javascript-diagnostics";
+import { extractionContext } from "../src/ts-symbols";
+
+const root = join(tmpdir(), "semctx-module-snapshot").replaceAll("\\", "/");
+for (const file of ["module.cts", "module.d.cts"]) test(`round4 explicit exports cannot qualify ${file} as ESM`, () => {
+ const path = `${root}/${file}`;
+ const content = "export interface Marker { value: number }";
+ const inputs = new Map([[path, content]]);
+ const source = extractionContext.createProgram([path], inputs).getSourceFile(path)!;
+ expect(inspectModuleConfiguration(path, root, inputs, source)).toContain("SOURCE_COMMONJS_EXTENSION_UNSUPPORTED");
+});
+for (const [file, content, metadata, rejected] of [
+ ["global.ts", "const FLAG = 1;", {}, true],
+ ["global.d.ts", "declare const FLAG: number;", {}, true],
+ ["augment.ts", "export {}; declare global { const FLAG: number; }", {}, true],
+ ["module-augment.ts", "export {}; declare module './main' { interface Registry { extra: number } }", {}, true],
+ ["explicit.ts", "export const FLAG = 1;", {}, false],
+ ["implicit.mjs", "const FLAG = 1;", {}, false],
+ ["implicit.mts", "const FLAG = 1;", {}, false],
+ ["packaged.ts", "const FLAG = 1;", { "package.json": '{"type":"module"}' }, true],
+ ["nested/file.js", "const FLAG = 1;", { "package.json": '{"type":"module"}', "nested/package.json": '{"type":"commonjs"}' }, true],
+ ["forced.ts", "const FLAG = 1;", { "tsconfig.json": '{"extends":"./base.json"}', "base.json": '{"compilerOptions":{"moduleDetection":"force"}}' }, true],
+ ["forced.d.ts", "declare const FLAG: number;", { "tsconfig.json": '{"compilerOptions":{"moduleDetection":"force"}}' }, true],
+] as const) test(`round3 retained module mode respects ${file}`, () => {
+ const path = `${root}/${file}`;
+ const inputs = new Map([[path, content], ...Object.entries(metadata).map(([name, value]) => [`${root}/${name}`, value] as [string, string])]);
+ const source = extractionContext.createProgram([path], inputs).getSourceFile(path)!;
+ expect(ts.isExternalModule(source)).toBe(file.includes("augment") || !rejected);
+ const reasons = inspectModuleConfiguration(path, root, inputs, source);
+ expect(reasons.some(reason => /SOURCE_(?:GLOBAL_SCRIPT|GLOBAL_AUGMENTATION|MODULE_AUGMENTATION)_UNSUPPORTED/.test(reason))).toBe(rejected);
+});
diff --git a/packages/ts-analyzer/test/reflect-iteration.test.ts b/packages/ts-analyzer/test/reflect-iteration.test.ts
new file mode 100644
index 00000000..7167edbc
--- /dev/null
+++ b/packages/ts-analyzer/test/reflect-iteration.test.ts
@@ -0,0 +1,51 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("typed string annotation cannot hide a real internal iterator origin", () => {
+ const content = "const iterable = { *[Symbol.iterator]() { yield 1; } }; const text: string = iterable as unknown as string; export function start() { return [...text]; }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_ITERATION_UNSUPPORTED");
+});
+test("type-only Reflect query is not a reflective runtime invocation", () => {
+ const content = "export type Invocation = typeof Reflect.apply;";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).not.toContain("SOURCE_REFLECT_INVOCATION_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["function helper() { return 1; } export function start() { return Reflect.apply(helper, null, []); }", "REFLECT_INVOCATION_UNSUPPORTED"],
+ ["const iterable = { *[Symbol.iterator]() { yield 1; } }; export function start() { for (const value of iterable) { return value; } }", "ITERATION_UNSUPPORTED"],
+] as const) test(`direct ${reason} refuses unmodeled invocation`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+for (const content of [
+ "function helper() {} const invoke = Reflect.apply; export function start() { return invoke(helper, null, []); }",
+ "function Helper() {} const { construct: create } = Reflect; const make = create; export function start() { return make(Helper, []); }",
+ "function helper() {} export function start() { return Reflect.apply.call(null, helper, null, []); }",
+ "export function start(value: unknown) { return [...value]; }",
+ "export function start(values: number[]) { for (const value of values) return value; }",
+ "export async function start(values: unknown) { for await (const value of values) return value; }",
+ "const iterable = { *[Symbol.iterator]() { yield 1; } }; export function start() { return Math.max(...iterable); }",
+]) test(`unproven reflection or iteration refuses ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons.some(reason => /SOURCE_(REFLECT_INVOCATION|ITERATION)_UNSUPPORTED/.test(reason))).toBe(true);
+});
+test("known intrinsic values, object spread and ordinary Reflect homonyms preserve their domain", () => {
+ for (const content of [
+ "const values = [1, 2]; export function start() { return [...values]; }",
+ "export function start() { for (const value of 'abc') return value; }",
+ "export function start() { return [...new Map()]; }",
+ "export function start() { return {...{value: 1}}; }",
+ "const Reflect = { apply(value: number) { return value; } }; export function start() { return Reflect.apply(1); }",
+ "export function start() { return Reflect.get({value: 1}, 'value'); }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_REFLECT_INVOCATION_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_ITERATION_UNSUPPORTED");
+ }
+});
diff --git a/packages/ts-analyzer/test/resumption-borrowed-regexp.test.ts b/packages/ts-analyzer/test/resumption-borrowed-regexp.test.ts
new file mode 100644
index 00000000..473969c7
--- /dev/null
+++ b/packages/ts-analyzer/test/resumption-borrowed-regexp.test.ts
@@ -0,0 +1,92 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { analyzeRepository, extractTypeScript, inspectModuleConfiguration } from "../src";
+import { getQualifiedCallIntegrityReasons } from "../src/analyze";
+
+const initializeGit = (root: string): void => {
+ const result = Bun.spawnSync(["git", "init", "-q"], { cwd: root, stdout: "pipe", stderr: "pipe" });
+ if (result.exitCode !== 0) throw new Error(new TextDecoder().decode(result.stderr));
+};
+
+test("generator extraction keeps initialization but has no consumer resumption edge", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-generator-facts-"));
+ try {
+ const dep = join(root, "dep.ts"); const consumer = join(root, "consumer.ts");
+ const inputs = new Map([[dep, "export function* values(){yield 1;}"], [consumer, "import {values} from './dep';const iterator=values();export function run(){return iterator.next().value;}"]]);
+ for (const [path, content] of inputs) writeFileSync(path, content);
+ const facts = extractTypeScript([...inputs.keys()], root, inputs);
+ console.info(JSON.stringify({ calls: facts.calls }));
+ expect(facts.calls.some(call => call.calleeSymbolPath === "values" && call.callerSymbolPath === undefined)).toBe(true);
+ expect(facts.calls.some(call => call.callerSymbolPath === "run" && call.calleeSymbolPath === "values")).toBe(false);
+ expect(inspectModuleConfiguration(dep, root, inputs)).toContain("SOURCE_ITERATION_UNSUPPORTED");
+ expect(extractTypeScript([...inputs.keys()], root).symbols.some(symbol => symbol.name === "values" && symbol.exported)).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("known ordinary functions and direct intrinsic RegExp calls remain distinct", () => {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, "export function values(){return 1;} export function run(){return /x/.test('x');}"]]))).toEqual([]);
+});
+
+test("the original intrinsic delegated generator retains ordinary extraction facts", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-ordinary-delegation-"));
+ try {
+ const path = join(root, "main.ts");
+ writeFileSync(path, "export function* start() { yield* 'abc'; }");
+ expect(extractTypeScript([path], root).symbols).toContainEqual(expect.objectContaining({ name: "start", exported: true }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("borrowed SDK RegExp protocol helper has no modeled invocation of the ordinary exec member", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-borrowed-regexp-"));
+ try {
+ const dep = join(root, "dep.ts"); const consumer = join(root, "consumer.ts");
+ const inputs = new Map([[dep, "export const matcher={exec(){return null;}};"], [consumer, "import {matcher} from './dep';export function run(){return RegExp.prototype[Symbol.match].call(matcher as RegExp,'x');}"]]);
+ for (const [path, content] of inputs) writeFileSync(path, content);
+ const facts = extractTypeScript([...inputs.keys()], root, inputs);
+ console.info(JSON.stringify({ calls: facts.calls, reasons: inspectModuleConfiguration(consumer, root, inputs) }));
+ expect(facts.calls.some(call => call.callerSymbolPath === "run" && call.calleeRelPath === "dep.ts")).toBe(false);
+ expect(inspectModuleConfiguration(consumer, root, inputs)).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("native borrowed RegExp hook invokes ordinary exec without a Symbol.match definition", () => {
+ const result = Bun.spawnSync([process.execPath, "-e", "const matcher={exec(){console.log('ORDINARY_EXEC_CALLED');return null;}};RegExp.prototype[Symbol.match].call(matcher,'x');"], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("ORDINARY_EXEC_CALLED");
+});
+
+test("test binding coordinate for an exported primitive exists while its symbol is deliberately unmodeled", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-constant-binding-"));
+ try {
+ const dep = join(root, "dep.ts"); const testFile = join(root, "dep.test.ts");
+ const inputs = new Map([[dep, "export const SECURITY_MODE=true;"], [testFile, "import {SECURITY_MODE} from './dep';export const observed=SECURITY_MODE;"]]);
+ for (const [path, content] of inputs) writeFileSync(path, content);
+ const facts = extractTypeScript([...inputs.keys()], root, inputs);
+ console.info(JSON.stringify({ imports: facts.imports, symbols: facts.symbols }));
+ expect(facts.imports[0]?.bindingTargets).toContainEqual({ relPath: "dep.ts", symbolPath: "SECURITY_MODE", unmodeledReason: "VALUE_DECLARATION" });
+ expect(facts.symbols.some(symbol => symbol.name === "SECURITY_MODE")).toBe(false);
+ initializeGit(root);
+ const base = { ...createGlobSelectionConfig(root), include: ["**/*.ts"] };
+ const ordinary = analyzeRepository(base, undefined, inputs);
+ expect(getQualifiedCallIntegrityReasons(ordinary).size).toBe(0);
+ const qualified = analyzeRepository({ ...base, selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1" }, undefined, inputs);
+ expect(getQualifiedCallIntegrityReasons(qualified).get("dep.test.ts")).toContain("QUALIFIED_TEST_BINDING_MISSING:dep.ts:SECURITY_MODE");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("modeled function test targets preserve their canonical association", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-modeled-binding-"));
+ try {
+ const dep = join(root, "dep.ts"); const file = join(root, "dep.test.ts");
+ const inputs = new Map([[dep, "export function value(){return 1;}"], [file, "import {value} from './dep'; export function check(){return value();}"]]);
+ for (const [path, content] of inputs) writeFileSync(path, content);
+ initializeGit(root);
+ const result = analyzeRepository({ ...createGlobSelectionConfig(root), include: ["**/*.ts"], selectionMode: "qualified-static-v1", analysisProfile: "modelo-suite-static-v1" }, undefined, inputs);
+ expect(getQualifiedCallIntegrityReasons(result).size).toBe(0);
+ expect(result.graph.edges.some(edge => edge.kind === "tested_by")).toBe(true);
+ expect(result.graph.edges.some(edge => edge.kind === "covers")).toBe(true);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/retained-directories.test.ts b/packages/ts-analyzer/test/retained-directories.test.ts
new file mode 100644
index 00000000..0ae25030
--- /dev/null
+++ b/packages/ts-analyzer/test/retained-directories.test.ts
@@ -0,0 +1,14 @@
+import { expect, spyOn, test } from "bun:test";
+import { retainedCompilerSystem } from "../src/ts-symbols";
+test("retained directory probes reuse an ancestor inventory without allocating key scans", () => {
+ const inputs = new Map(Array.from({ length: 2000 }, (_, index) => [`/fixture/package-${index}/src/main.ts`, "export {}; "]));
+ const system = retainedCompilerSystem(inputs);
+ const keys = spyOn(Map.prototype, "keys");
+ try {
+ for (let index = 0; index < 100; index++) {
+ expect(system.directoryExists?.(`/fixture/package-${index}/src`)).toBe(true);
+ expect(system.directoryExists?.(`/fixture/missing-${index}`)).toBe(false);
+ }
+ expect(keys).not.toHaveBeenCalled();
+ } finally { keys.mockRestore(); }
+});
diff --git a/packages/ts-analyzer/test/retained-directory-alias.test.ts b/packages/ts-analyzer/test/retained-directory-alias.test.ts
new file mode 100644
index 00000000..f962e4ee
--- /dev/null
+++ b/packages/ts-analyzer/test/retained-directory-alias.test.ts
@@ -0,0 +1,23 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, symlinkSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript, inspectModuleConfiguration } from "../src";
+import { retainedCompilerSystem } from "../src/ts-symbols";
+
+for (const alias of [false, true]) test(`retained nonexistent sources resolve through ${alias ? "directory alias" : "real directory"}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-directory-origin-"));
+ const link = `${root}-alias`;
+ try {
+ if (alias) symlinkSync(root, link, process.platform === "win32" ? "junction" : "dir");
+ const directory = alias ? link : root;
+ const leaf = join(directory, "leaf.ts"); const caller = join(directory, "caller.ts");
+ const inputs = new Map([[leaf, "export default function namedFn() { return 1; } export class Base {}"], [caller, "import value, { Base } from './leaf'; export class Derived extends Base {} export function main() { return value(); }"]]);
+ expect(retainedCompilerSystem(inputs).directoryExists?.(directory)).toBe(true);
+ expect(extractTypeScript([leaf, caller], directory, inputs).calls).toContainEqual(expect.objectContaining({ calleeRelPath: "leaf.ts", calleeSymbolPath: "namedFn" }));
+ expect(inspectModuleConfiguration(caller, directory, inputs)).toContain("SOURCE_INTERNAL_HERITAGE_UNSUPPORTED");
+ } finally {
+ if (alias) rmSync(link);
+ rmSync(root, { recursive: true, force: true });
+ }
+});
diff --git a/packages/ts-analyzer/test/runtime-code-boundaries.test.ts b/packages/ts-analyzer/test/runtime-code-boundaries.test.ts
new file mode 100644
index 00000000..a08e96b0
--- /dev/null
+++ b/packages/ts-analyzer/test/runtime-code-boundaries.test.ts
@@ -0,0 +1,71 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+const wasmBytes = "new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,12,1,3,101,110,118,4,104,111,111,107,0,0,8,1,0])";
+for (const [name, source, reason] of [
+ ["wasm start", `function helper(){} const bytes=${wasmBytes};export function run(){return WebAssembly.instantiate(bytes,{env:{hook:helper}});}`, "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["script insertion", "export function run(){const script=document.createElement('script');script.src='./hidden.js';document.head.append(script);}", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+] as const) for (const extension of ["ts", "mjs"]) test(`${name} ${extension} refuses runtime code`, () => {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain(reason);
+});
+
+for (const extension of ["ts", "mjs"]) for (const tag of ["SCRIPT", "ScRiPt"]) test(`HTML script tag ${tag} ${extension} retains script insertion refusal`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = `export function run(){const script=document.createElement('${tag}');script.src='./hidden.js';document.head.append(script);}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ extension, tag, reasons }));
+ expect(reasons).toContain("SOURCE_SCRIPT_INSERTION_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) test(`HTML tag case controls ${extension} preserve inert and local origins`, () => {
+ for (const source of [
+ "export function run(){const script=document.createElement('SCRIPT');script.src='./hidden.js';return 1;}",
+ "export function run(){const div=document.createElement('div');document.head.append(div);}",
+ "export function run(){const div=document.createElement('DIV');document.head.append(div);}",
+ "const document={createElement(tag){return {src:tag};},head:{append(value){return value;}}};export function run(){const script=document.createElement('SCRIPT');script.src='./hidden.js';document.head.append(script);}",
+ ]) {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+test("native wasm instantiation invokes an imported helper through its start function", async () => {
+ let calls = 0;
+ const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,12,1,3,101,110,118,4,104,111,111,107,0,0,8,1,0]);
+ await WebAssembly.instantiate(bytes, { env: { hook() { calls++; } } });
+ expect(calls).toBe(1);
+});
+
+test("inert SDK operations and ordinary local lookalikes retain admission", () => {
+ for (const source of [
+ "export function run(){const script=document.createElement('script');script.src='./hidden.js';return 1;}",
+ "export function run(){const div=document.createElement('div');document.head.append(div);}",
+ "export function run(){document.head.append('plain data');}",
+ `const bytes=${wasmBytes};export function run(){return WebAssembly.validate(bytes);}`,
+ `const bytes=${wasmBytes};export function run(){return WebAssembly.compile(bytes);}`,
+ "const WebAssembly={instantiate(_bytes:unknown,_imports:unknown){return 1;}};export function run(){return WebAssembly.instantiate(1,{env:{hook(){}}});}",
+ "const document={createElement(_tag:string){return {src:''};},head:{append(_value:unknown){}}};export function run(){const script=document.createElement('script');script.src='./hidden.js';document.head.append(script);}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+test("a local value annotated as the WASM SDK does not acquire runtime provenance", () => {
+ const path = "/fixture/main.ts";
+ const source = "const wasm:typeof WebAssembly={instantiate(){return 1;}} as unknown as typeof WebAssembly;export function run(){return wasm.instantiate(null as unknown as BufferSource,{});}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ expect(reasons).toEqual(["SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ expect(reasons).not.toContain("SOURCE_WASM_INSTANTIATION_UNSUPPORTED");
+});
+
+test("typed SDK aliases preserve the actual runtime boundary", () => {
+ for (const [source, reason] of [
+ [`const wasm:typeof WebAssembly=WebAssembly;const bytes=${wasmBytes};export function run(){return wasm.instantiate(bytes,{});}`, "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["const dom:Document=document;export function run(){const script=dom.createElement('script');dom.head.append(script);}", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+ ] as const) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain(reason);
+ }
+});
diff --git a/packages/ts-analyzer/test/runtime-code-family.test.ts b/packages/ts-analyzer/test/runtime-code-family.test.ts
new file mode 100644
index 00000000..04dbfd34
--- /dev/null
+++ b/packages/ts-analyzer/test/runtime-code-family.test.ts
@@ -0,0 +1,83 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+const insertions = [
+ "document.head.appendChild(script)",
+ "document.head.insertBefore(script,null)",
+ "document.head.replaceChild(script,document.head.firstChild)",
+ "document.head.append(script)",
+ "document.head.prepend(script)",
+ "document.head.replaceChildren(script)",
+ "document.body.before(script)",
+ "document.body.after(script)",
+ "document.body.replaceWith(script)",
+ "document.body.insertAdjacentElement('beforeend',script)",
+ "new Range().insertNode(script)",
+] as const;
+
+for (const extension of ["ts", "mjs"]) for (const [name, source, reason] of [
+ ["streaming wasm start", "function helper(){}export function run(){return WebAssembly.instantiateStreaming(fetch('module.wasm'),{env:{hook:helper}});}", "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["nullable SDK callback", "function helper(){return 1;}const button=document.createElement('button');button.onclick=helper;export function run(){button.click();}", "SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED"],
+ ...insertions.map(insertion => [insertion, `export function run(){const script=document.createElement('script');script.src='./hidden.js';${insertion};}`, "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"] as const),
+] as const) test(`${name} ${extension} retains runtime family refusal`, () => {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain(reason);
+});
+
+test("SDK aliases retain actual instantiation and insertion provenance", () => {
+ for (const [source, reason] of [
+ ["const wasm:typeof WebAssembly=WebAssembly;export function run(){return wasm.instantiateStreaming(fetch('module.wasm'),{});}", "SOURCE_WASM_INSTANTIATION_UNSUPPORTED"],
+ ["const dom:Document=document;export function run(){const script=dom.createElement('script');const alias=script;dom.head.appendChild(alias);}", "SOURCE_SCRIPT_INSERTION_UNSUPPORTED"],
+ ] as const) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain(reason);
+ }
+});
+
+test("an SDK-shaped local streaming method lacks a retained callee coordinate, not WASM runtime provenance", () => {
+ const path = "/fixture/main.ts";
+ const source = "const wasm:typeof WebAssembly={instantiateStreaming(){return 1;}} as unknown as typeof WebAssembly;export function run(){return wasm.instantiateStreaming(null as any,{});}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ expect(reasons).toEqual(["SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ expect(reasons).not.toContain("SOURCE_WASM_INSTANTIATION_UNSUPPORTED");
+});
+
+test("inert SDK data and actual local values preserve their domain", () => {
+ for (const source of [
+ "const button=document.createElement('button');button.onclick=null;export function run(){return 1;}",
+ "const button=document.createElement('button');button.onclick=1 as any;export function run(){return 1;}",
+ "function helper(){return 1;}const button:{onclick:null|(()=>number)}={onclick:null};button.onclick=helper;export function run(){return button.onclick!();}",
+ "export function run(){const script=document.createElement('script');script.src='./hidden.js';return 1;}",
+ "export function run(){const div=document.createElement('div');document.head.appendChild(div);}",
+ "export function run(){document.head.prepend('plain data');}",
+ "export function run(){const script=document.createElement('script');const div=document.createElement('div');document.head.replaceChild(div,script);}",
+ "export function run(){return WebAssembly.compileStreaming(fetch('module.wasm'));}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+test("a typed local DOM lookalike does not acquire SDK script insertion origin", () => {
+ const path = "/fixture/main.ts";
+ const source = "const dom:Document={createElement(){return {src:''};},head:{appendChild(){return 1;}}} as unknown as Document;export function run(){const script=dom.createElement('script');dom.head.appendChild(script);}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ expect(reasons).toEqual(["SOURCE_OPAQUE_CALLBACK_UNSUPPORTED", "SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ expect(reasons).not.toContain("SOURCE_SCRIPT_INSERTION_UNSUPPORTED");
+});
+
+test("insertion coordinates distinguish inserted arguments from reference nodes", () => {
+ for (const insertion of [
+ "document.head.append(div,script)", "document.head.prepend(div,script)", "document.head.replaceChildren(div,script)",
+ "document.body.before(div,script)", "document.body.after(div,script)", "document.body.replaceWith(div,script)",
+ ]) {
+ const path = "/fixture/main.ts";
+ const source = `export function run(){const script=document.createElement('script');const div=document.createElement('div');${insertion};}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toContain("SOURCE_SCRIPT_INSERTION_UNSUPPORTED");
+ }
+ for (const insertion of ["document.head.insertBefore(div,script)", "document.head.replaceChild(div,script)"]) {
+ const path = "/fixture/main.ts";
+ const source = `export function run(){const script=document.createElement('script');const div=document.createElement('div');${insertion};}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
diff --git a/packages/ts-analyzer/test/runtime-value-imports.test.ts b/packages/ts-analyzer/test/runtime-value-imports.test.ts
new file mode 100644
index 00000000..39eb5c9f
--- /dev/null
+++ b/packages/ts-analyzer/test/runtime-value-imports.test.ts
@@ -0,0 +1,51 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { extractTypeScript } from "../src";
+import { analyzeRepository } from "../src";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+
+test("namespace constant runtime reference must retain its actual missing-target coordinate", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-namespace-value-"));
+ try {
+ const dep = join(root, "dep.ts"); const file = join(root, "dep.test.ts");
+ const inputs = new Map([[dep, "export const FLAG=true;"], [file, "import * as dep from './dep';export const observed=dep.FLAG;"]]);
+ for (const [path, text] of inputs) writeFileSync(path, text);
+ const result = extractTypeScript([...inputs.keys()], root, inputs);
+ console.info(JSON.stringify({ imports: result.imports, symbols: result.symbols }));
+ expect(result.imports[0]?.bindingTargets?.some(target => target.relPath === "dep.ts" && target.symbolPath === "FLAG")).toBe(true);
+ expect(result.symbols.some(symbol => symbol.name === "FLAG")).toBe(false);
+ expect(result.imports[0]?.bindingTargets?.[0]?.unmodeledReason).toBe("VALUE_DECLARATION");
+ expect(extractTypeScript([...inputs.keys()], root).imports[0]?.bindingTargets).toBeUndefined();
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("unused, erased and shadowed import occurrences do not prove runtime use", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-import-use-"));
+ try {
+ const dep = join(root, "dep.ts"); const file = join(root, "consumer.ts");
+ for (const content of [
+ "import {FLAG} from './dep';export function run(){return 1;}",
+ "import {FLAG} from './dep';export type Mode=typeof FLAG;export function run(){return 1;}",
+ "import {FLAG} from './dep';export function run(FLAG:boolean){return FLAG;}",
+ ]) {
+ const inputs = new Map([[dep, "export const FLAG=true;"], [file, content]]);
+ for (const [path, text] of inputs) writeFileSync(path, text);
+ expect(extractTypeScript([...inputs.keys()], root, inputs).imports[0]?.bindingTargets?.[0]?.runtimeUsed).toBe(false);
+ }
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+
+test("enum namespace value cannot reuse a global-function homonym as test coverage", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-enum-origin-"));
+ try {
+ const dep = join(root, "dep.ts"); const file = join(root, "dep.test.ts");
+ const inputs = new Map([[dep, "export enum Mode { FLAG } export function FLAG(){return 1;}"], [file, "import * as dep from './dep';export const observed=dep.Mode.FLAG;"]]);
+ for (const [path, text] of inputs) writeFileSync(path, text);
+ const result = analyzeRepository({ ...createGlobSelectionConfig(root), include: ["**/*.ts"] }, undefined, inputs);
+ const target = result.graph.nodes.find(node => node.name === "FLAG" && node.kind === "function");
+ expect(target).toBeDefined();
+ expect(result.graph.edges.some(edge => edge.kind === "tested_by" && edge.from === target?.id)).toBe(false);
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/sdk-callback-dictionary.test.ts b/packages/ts-analyzer/test/sdk-callback-dictionary.test.ts
new file mode 100644
index 00000000..dbed8227
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-callback-dictionary.test.ts
@@ -0,0 +1,19 @@
+import {expect,test} from "bun:test";
+import {inspectModuleConfiguration} from "../src";
+for(const extension of ["ts","mjs"])test(`SDK callback dictionary ${extension} refuses hidden invocation`,()=>{
+ const path=`/fixture/main.${extension}`;const source="function helper(){return 1;}export function run(){return new ReadableStream({start:helper});}";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+});
+test("actual SDK closed data and local function homonyms stay distinct",()=>{
+ for(const source of [
+ "export function run(){return new ReadableStream();}","export function run(){return new ReadableStream({});}",
+ "export function run(){return new ReadableStream({}, {highWaterMark:1});}",
+ "export function run(){return new ReadableStream({start:undefined});}",
+ "function ReadableStream(_source:unknown){return 1;}function helper(){return 1;}export function run(){return ReadableStream({start:helper});}",
+ "const api={create(_source:unknown){return 1;}};function helper(){return 1;}export function run(){return api.create({start:helper});}",
+ ]){const path="/fixture/main.ts";expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);}
+});
+test("ordinary SDK primitive and object methods are not callback dictionary slots",()=>{
+ const path="/fixture/main.ts";const source="export function run(){return new URL('./x',import.meta.url);}";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);
+});
diff --git a/packages/ts-analyzer/test/sdk-callback.test.ts b/packages/ts-analyzer/test/sdk-callback.test.ts
new file mode 100644
index 00000000..017fd029
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-callback.test.ts
@@ -0,0 +1,36 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+test("actual SDK callback helper route cannot erase its callback signature", () => {
+ const content = "function helper(value) { return 1; } export function run() { return [1].map.call([1], helper); }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain("JAVASCRIPT_SDK_CALLBACK_UNSUPPORTED");
+});
+for (const content of [
+ "function helper(value) { return value; } export function run() { return [1].map(helper); }",
+ "function executor() {} export async function run() { await new Promise(executor); }",
+]) test(`actual SDK callback cannot hide an invocation: ${content}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ expect(js).toContain("JAVASCRIPT_SDK_CALLBACK_UNSUPPORTED");
+});
+
+test("actual SDK opaque data and own callback APIs keep their boundaries", () => {
+ for (const content of [
+ "export function run() { return Math.max(1, 2); }",
+ "export function run() { return Math.max.call(null, 1, 2); }",
+ "const own = { call(callback) { return 1; } }; function helper() { return 1; } export function run() { return own.call(helper); }",
+ "export function run() { return new Map(); }",
+ "export async function run() { return await Promise.resolve(1); }",
+ "function helper() { return 1; } const own = { map(callback) { return callback(); } }; export function run() { return own.map(helper); }",
+ "function helper() { return 1; } const own = { map(callback) { return callback(); } }; export function run() { return (own.map as typeof Array.prototype.map)(helper); }",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+});
+
+test("transparent SDK aliases retain callback refusal and ordinary no-snapshot diagnostics remain unchanged", () => {
+ const content = "function executor() {} const Native = Promise; export function run() { return new Native(executor); }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture")).not.toContain("SOURCE_SDK_CALLBACK_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/sdk-coercion-worklet-helpers.test.ts b/packages/ts-analyzer/test/sdk-coercion-worklet-helpers.test.ts
new file mode 100644
index 00000000..33b4c5d8
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-coercion-worklet-helpers.test.ts
@@ -0,0 +1,49 @@
+import { expect, test } from "bun:test";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+for (const [content, reason] of [
+ ["const input = { valueOf() { return 1; } }; export function run() { return +input; }", "COERCION_UNSUPPORTED"],
+ ["const audio = new AudioContext(); export function run() { return audio.audioWorklet.addModule('./processor.js'); }", "WORKLET_LOADER_UNSUPPORTED"],
+] as const) test(`new implicit SDK operation refuses ${reason}`, () => {
+ const inputs = new Map([[path, content]]);
+ const reasons = inspectModuleConfiguration(path, "/fixture", inputs);
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+test("actual retained ES5 helper emission has an unmodeled tslib dependency", () => {
+ const content = "export async function run() { return 1; }";
+ const options = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.ESNext, importHelpers: true };
+ const emitted = ts.transpileModule(content, { compilerOptions: options }).outputText;
+ expect(emitted).toContain('from "tslib"');
+ expect(emitted).toContain("__awaiter");
+ expect(emitted).toContain("__generator");
+ const inputs = new Map([[path, content], ["/fixture/tsconfig.json", '{"compilerOptions":{"target":"ES5","module":"ESNext","importHelpers":true}}']]);
+ const reasons = inspectModuleConfiguration(path, "/fixture", inputs);
+ console.info(JSON.stringify({ emitted, reasons }));
+ expect(reasons).toContain("SOURCE_CONFIGURATION_HELPERS_UNSUPPORTED");
+});
+
+test("known primitive operators, ordinary loader methods and non-emitting helper configurations remain distinct", () => {
+ for (const content of [
+ "export function run() { return +1; }",
+ "export function run() { return 4 - 1; }",
+ "export function run(value) { return !value; }",
+ "export function run(value) { return typeof value; }",
+ "export function run(value) { return value === null; }",
+ "const own = { addModule(path) { return 1; } }; export function run() { return own.addModule('./file.js'); }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_COERCION_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_WORKLET_LOADER_UNSUPPORTED");
+ }
+ const content = "export async function run() { return 1; }";
+ const modern = new Map([[path, content], ["/fixture/tsconfig.json", '{"compilerOptions":{"target":"ES2022","module":"ESNext","importHelpers":true}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", modern)).not.toContain("SOURCE_CONFIGURATION_HELPERS_UNSUPPORTED");
+ const inherited = new Map([[path, content], ["/fixture/tsconfig.json", '{"extends":"./tsconfig.base.json"}'],
+ ["/fixture/tsconfig.base.json", '{"compilerOptions":{"target":"ES5","module":"ESNext","importHelpers":true}}']]);
+ expect(inspectModuleConfiguration(path, "/fixture", inherited)).toContain("SOURCE_CONFIGURATION_HELPERS_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, "/fixture")).not.toContain("SOURCE_CONFIGURATION_HELPERS_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/sdk-global-combinator.test.ts b/packages/ts-analyzer/test/sdk-global-combinator.test.ts
new file mode 100644
index 00000000..1d2de2ae
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-global-combinator.test.ts
@@ -0,0 +1,51 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const [name, content, reason] of [
+ ["global replacement", "function helper() { return 1; } Math = { ...Math, random: helper }; export function run() { return Math.random(); }", "SDK_GLOBAL_MUTATION_UNSUPPORTED"],
+ ["explicit primitive hook", "function helper() { return 'value'; } const value = {}; value[Symbol.toPrimitive] = helper; export function run() { return String(value); }", "PROTOCOL_MUTATION_UNSUPPORTED"],
+ ["combinator thenable", "function helper() {} export function run() { return Promise.all([{ then: helper }]); }", "PROMISE_ASSIMILATION_UNSUPPORTED"],
+] as const) test(name, () => {
+ const path = "/fixture/main.ts"; const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ name, reasons }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+});
+test("native global replacement and nested thenable invoke retained code", () => {
+ for (const content of [
+ "function helper(){console.log('GLOBAL_CALLED');return 7} Math={...Math,random:helper};if(Math.random()!==7)throw Error('math mismatch');",
+ "function helper(){console.log('THEN_CALLED')} Promise.all([{then:helper}]);",
+ ]) {
+ const result = Bun.spawnSync([process.execPath, "-e", content], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("_CALLED");
+ }
+});
+test("ordinary local bindings, primitive Promise values and unmodified SDK values remain distinct", () => {
+ for (const content of [
+ "let Math = { random() { return 1; } }; Math = { random() { return 2; } }; export function run() { return Math.random(); }",
+ "export function run() { return Promise.all([1, Promise.resolve(2)]); }",
+ "export function run() { return Math.random(); }",
+ ]) {
+ const path = "/fixture/main.ts"; expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toEqual([]);
+ }
+});
+test("combinator aliases and mutable container inputs retain assimilation refusal", () => {
+ for (const content of [
+ "const values = [1]; export function run() { return Promise.all(values); }",
+ "function helper() {} const { all: gather } = Promise; export function run() { return gather.call(Promise, [{ then: helper }]); }",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_PROMISE_ASSIMILATION_UNSUPPORTED");
+ }
+});
+
+test("SDK binding writes follow assignment targets while local homonyms stay ordinary", () => {
+ for (const content of [
+ "function helper(){return 1;} [Math]=[{random:helper}]; export function run(){return Math.random();}",
+ "function helper(){return 1;} ({Math}={Math:{random:helper}}); export function run(){return Math.random();}",
+ ]) {
+ const path="/fixture/main.ts";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,content]]))).toContain("SOURCE_SDK_GLOBAL_MUTATION_UNSUPPORTED");
+ }
+ const path="/fixture/main.ts";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,"let Math={random(){return 1;}}; [Math]=[{random(){return 2;}}]; export function run(){return Math.random();}"]]))).toEqual([]);
+});
diff --git a/packages/ts-analyzer/test/sdk-helper-coercion.test.ts b/packages/ts-analyzer/test/sdk-helper-coercion.test.ts
new file mode 100644
index 00000000..0fee46b9
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-helper-coercion.test.ts
@@ -0,0 +1,88 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+const cases = [
+ [
+ "Number.call",
+ "const value={valueOf:hidden};",
+ "Number.call(null,value)"
+ ],
+ [
+ "String.call",
+ "const value={toString:hidden};",
+ "String.call(null,value)"
+ ],
+ [
+ "Math.max.call",
+ "const value={valueOf:hidden};",
+ "Math.max.call(null,value,0)"
+ ],
+ [
+ "Array.join.call",
+ "const value={toString:hidden};",
+ "Array.prototype.join.call([value])"
+ ],
+ [
+ "Number.apply",
+ "const value={valueOf:hidden};",
+ "Number.apply(null,[value])"
+ ],
+ [
+ "String.bind",
+ "const value={toString:hidden};const bound=String.bind(null,value);",
+ "bound()"
+ ],
+ [
+ "Math.max.apply",
+ "const value={valueOf:hidden};",
+ "Math.max.apply(null,[value,0])"
+ ],
+ [
+ "Array.join.bind",
+ "const value={toString:hidden};const bound=Array.prototype.join.bind([value]);",
+ "bound()"
+ ]
+] as const;
+for (const extension of ["ts", "mjs"]) for (const [name, setup, call] of cases) test(`${name} ${extension} refuses helper-dispatched coercion`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = `function hidden(){return 1;}${setup}export function run(){return ${call};}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) test(`pristine SDK helper primitive controls ${extension} remain supported`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const call of [
+ "Number.call(null,1)", "String.call(null,'value')", "Math.max.call(null,1,0)", "Array.prototype.join.call([1,2])",
+ "Number.apply(null,[1])", "Math.max.apply(null,[1,0])",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, `export function run(){return ${call};}`]]))).toEqual([]);
+});
+
+for (const extension of ["ts", "mjs"]) test(`pristine bound SDK primitive controls ${extension} remain supported`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const source of [
+ "const bound=String.bind(null,'value');export function run(){return bound();}",
+ "const bound=Array.prototype.join.bind([1,2]);export function run(){return bound();}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+});
+
+test("native SDK call, apply and bound invocation reach hidden conversion hooks", () => {
+ for (const [name, setup, call] of cases) {
+ const source = `function hidden(){console.log('COERCION_CALLED');return 1;}${setup}const result=${call};if(result===undefined)throw Error('missing result');`;
+ const child = Bun.spawnSync([process.execPath, "-e", source], { stdout: "pipe", stderr: "pipe" });
+ console.info(JSON.stringify({ name, exitCode: child.exitCode }));
+ expect(child.exitCode).toBe(0); expect(new TextDecoder().decode(child.stdout)).toContain("COERCION_CALLED");
+ }
+});
+
+for (const extension of ["ts", "mjs"]) test(`SDK helper coercion only consumes actual conversion coordinates ${extension}`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const source of [
+ "function hidden(){return 1;}const value={valueOf:hidden};export function run(){return Math.random.call(null,value);}",
+ "function hidden(){return 1;}const value={valueOf:hidden};export function run(){return Math.abs.call(null,1,value);}",
+ "function hidden(){return 1;}const value={valueOf:hidden};export function run(){return Number.call(null,1,value);}",
+ "function hidden(){return 1;}const value={toString:hidden};const bound=String.bind(null,value);export function run(){return 1;}",
+ "const own={call(_receiver,_value){return 1;}};function hidden(){return 1;}export function run(){return own.call(null,{valueOf:hidden});}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+});
diff --git a/packages/ts-analyzer/test/sdk-iteration-mutation.test.ts b/packages/ts-analyzer/test/sdk-iteration-mutation.test.ts
new file mode 100644
index 00000000..f2cd3620
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-iteration-mutation.test.ts
@@ -0,0 +1,37 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+for (const [content, reason] of [
+ ["function* iterator() { yield 1; } const value = { [Symbol.iterator]: iterator }; export function run() { return Array.from(value); }", "SDK_ITERATION_UNSUPPORTED"],
+ ["function helper() { return 1; } Object.assign(Math, { random: helper }); export function run() { return Math.random(); }", "SDK_MUTATION_UNSUPPORTED"],
+] as const) test(`actual SDK protocol refuses ${reason}`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+
+test("SDK iterable literals and ordinary data APIs preserve their boundaries", () => {
+ for (const content of [
+ "export function run() { return Array.from([1]); }",
+ "export function run() { return Array.from('abc'); }",
+ "const values = new Set([1]); export function run() { return Array.from(values); }",
+ "const own = { from(value) { return 1; }, assign(target, value) { return 2; } }; export function run() { return own.from({}) === own.assign({}, {}); }",
+ "export function run() { return Object.assign({}, { value: 1 }); }",
+ "export function run() { return Object.assign(Math, { extra: 1 }); }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_SDK_ITERATION_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_SDK_MUTATION_UNSUPPORTED");
+ }
+});
+
+test("SDK aliases and helper dispatch cannot drop protocol obligations", () => {
+ for (const content of [
+ "function* iterator() { yield 1; } const value = { [Symbol.iterator]: iterator }; const from = Array.from; export function run() { return from(value); }",
+ "function* iterator() { yield 1; } const value = { [Symbol.iterator]: iterator }; export function run() { return Array.from.call(Array, value); }",
+ "function helper() { return 1; } const { assign } = Object; assign.call(Object, Math, { random: helper }); export function run() { return Math.random(); }",
+ "function helper() { return 1; } const patch = { random: helper }; Object.assign(Math, patch); export function run() { return Math.random(); }",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]])).some(reason => /SOURCE_SDK_(ITERATION|MUTATION)_UNSUPPORTED/.test(reason))).toBe(true);
+});
diff --git a/packages/ts-analyzer/test/sdk-protocol-loader.test.ts b/packages/ts-analyzer/test/sdk-protocol-loader.test.ts
new file mode 100644
index 00000000..eb675bfa
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-protocol-loader.test.ts
@@ -0,0 +1,53 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+import { extractionContext } from "../src/ts-symbols";
+import ts from "typescript";
+const path = "/fixture/main.ts";
+test("SDK callable logical assignment reaches the same mutation guard", () => {
+ const content = "function helper() { return 1; } Math.random &&= helper; export function run() { return Math.random(); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+});
+test("SDK boundaries preserve ordinary data, owned methods, executed shadows and type queries", () => {
+ for (const content of [
+ "const box = {}; Object.defineProperty(box, 'value', { value: 1 }); export function start() { return box.value; }",
+ "function helper() { return 1; } const Math = { random() { return 2; } }; Math.random = helper; export function start() { return Math.random(); }",
+ "function importScripts(value) { return value; } export function start() { return importScripts('ordinary'); }",
+ "const own = { importScripts(value) { return value; } }; export function start() { return own.importScripts('ordinary'); }",
+ "export type Loader = typeof importScripts; export type Method = typeof Math.random;",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]])).filter(reason => /SOURCE_(PROTOCOL_DESCRIPTOR|SDK_CALLABLE_MUTATION|WORKER_LOADER)_UNSUPPORTED/.test(reason))).toEqual([]);
+});
+for (const [content, reason] of [
+ ["function helper() { return { next() { return { done: true }; } }; } const key = Symbol.iterator; const values = [1]; Object.defineProperty(values, key, { value: helper }); export function start() { const [value] = values; return value; }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["const load = importScripts; export function start() { load('./hidden.js'); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["function helper() { return 1; } const math = Math; const key = 'random'; math[key] = helper; export function start() { return math.random(); }", "SDK_CALLABLE_MUTATION_UNSUPPORTED"],
+] as const) test(`same SDK alias origin refuses ${reason}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain(`SOURCE_${reason}`);
+ expect(inspectJavaScriptSource("/fixture/main.js", content).reasons).toContain(`JAVASCRIPT_${reason}`);
+});
+test("SDK callable descriptor route cannot preserve native call authority", () => {
+ const content = "function helper() { return 1; } Object.defineProperty(Math, 'random', { value: helper }); export function run() { return Math.random(); }";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain("SOURCE_SDK_CALLABLE_MUTATION_UNSUPPORTED");
+});
+for (const [content, reason] of [
+ ["function helper() { return { next() { return { done: true, value: 1 }; } }; } const values = [1]; Object.defineProperty(values, Symbol.iterator, { value: helper }); export function start() { return [...values]; }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["export function load() { importScripts('./hidden.js'); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["function helper() { return 1; } Math.random = helper; export function run() { return Math.random(); }", "SDK_CALLABLE_MUTATION_UNSUPPORTED"],
+] as const) test(`unmodeled SDK boundary ${reason} refuses`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ console.info(JSON.stringify({ content, reasons }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+});
+test("actual loaded Program proves importScripts through its partial worker SDK library", () => {
+ const program = extractionContext.createProgram([path], new Map([[path, "export function load() { importScripts('./hidden.js'); }"]]));
+ const source = program.getSourceFile(path)!; const checker = program.getTypeChecker();
+ let expression: ts.Identifier | undefined;
+ const visit = (node: ts.Node): void => { if (ts.isIdentifier(node) && node.text === "importScripts") expression = node; ts.forEachChild(node, visit); };
+ visit(source);
+ const declarations = checker.getSymbolAtLocation(expression!)?.getDeclarations() ?? [];
+ console.info(JSON.stringify({ declarations: declarations.map(node => node.getSourceFile().fileName), libs: program.getSourceFiles().filter(file => program.isSourceFileDefaultLibrary(file)).map(file => file.fileName) }));
+ expect(declarations.some(node => program.isSourceFileDefaultLibrary(node.getSourceFile()) && node.getSourceFile().fileName.endsWith("/lib.webworker.importscripts.d.ts"))).toBe(true);
+});
diff --git a/packages/ts-analyzer/test/sdk-receiver-loop-bindings.test.ts b/packages/ts-analyzer/test/sdk-receiver-loop-bindings.test.ts
new file mode 100644
index 00000000..e331f0c5
--- /dev/null
+++ b/packages/ts-analyzer/test/sdk-receiver-loop-bindings.test.ts
@@ -0,0 +1,74 @@
+import { expect, test } from "bun:test";
+import { extractTypeScript, inspectModuleConfiguration } from "../src";
+
+test("an SDK-shaped annotation cannot prove a locally initialized callable member", () => {
+ const path = "/fixture/main.ts";
+ const source = "function hidden(){return 1;}const env:typeof globalThis={parseInt:hidden} as unknown as typeof globalThis;export function run(){return env.parseInt('1');}";
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ name: "SDK-shaped local member", reasons }));
+ expect(reasons).toContain("SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED");
+});
+
+test("actual SDK and retained own callable members keep distinct origins", () => {
+ const path = "/fixture/main.ts";
+ for (const source of [
+ "export function run(){return globalThis.parseInt('1');}",
+ "class Env{static parseInt(_value){return 1;}}export function run(){return Env.parseInt('1');}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+});
+
+const loopWrites = [
+ ["for-of identifier", "for(load of [()=>({then:hidden})]){}"],
+ ["for-of array target", "for([load] of [[()=>({then:hidden})]]){}"],
+ ["for-of object target", "for({load} of [{load:()=>({then:hidden})}]){}"],
+ ["for-in identifier", "for(load in {replacement:1}){}"],
+] as const;
+for (const extension of ["ts", "mjs"]) for (const [name, loop] of loopWrites) test(`${name} ${extension} invalidates async callee authority`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = `function hidden(){return 1;}async function load(){return 1;}export async function run(){${loop}await load();return 1;}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]));
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain("SOURCE_AWAIT_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) for (const [name, loop] of [
+ ["identifier for-of", "for(value of [()=>hidden]){}"],
+ ["array for-of", "for([value] of [[()=>hidden]]){}"],
+ ["identifier for-in", "for(value in {replacement:1}){}"],
+] as const) test(`${name} ${extension} invalidates primitive-return authority for opaque data`, () => {
+ const path = `/fixture/main.${extension}`;
+ const source = `import {write} from 'opaque';function hidden(){return 1;}function value(){return 1;}export function run(){${loop}return write(value());}`;
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]));
+ console.info(JSON.stringify({ name: "loop primitive-return", extension, loop, reasons }));
+ expect(reasons).toContain("SOURCE_OPAQUE_CALLBACK_UNSUPPORTED");
+});
+
+for (const extension of ["ts", "mjs"]) test(`ordinary iteration and separate loop bindings ${extension} do not revoke unwritten callees`, () => {
+ const path = `/fixture/main.${extension}`;
+ for (const source of [
+ "async function load(){return 1;}export async function run(){for(const load of [1]){}await load();return 1;}",
+ "async function load(){return 1;}export async function run(){for(const key in {replacement:1}){}await load();return 1;}",
+ "async function load(){return 1;}export async function run(){for(const item of [1]){}await load();return 1;}",
+ "import {write} from 'opaque';function value(){return 1;}export function run(){for(const value of [1]){}return write(value());}",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source], ["/fixture/package.json", '{"dependencies":{"opaque":"1.0.0"}}']]))).toEqual([]);
+});
+
+test("SDK-shaped local member refusal does not invent a run-to-hidden call endpoint", () => {
+ const path = "/fixture/main.ts"; const hidden = "/fixture/hidden.ts";
+ const source = "import {hidden} from './hidden';const env:typeof globalThis={parseInt:hidden} as unknown as typeof globalThis;export function run(){return env.parseInt('1');}";
+ const snapshot = new Map([[path, source], [hidden, "export function hidden(){return 1;}"]]);
+ const facts = extractTypeScript([path, hidden], "/fixture", snapshot);
+ console.info(JSON.stringify({ name: "no invented local member edge", calls: facts.calls }));
+ expect(facts.calls.some(call => call.callerSymbolPath === "run" && call.calleeRelPath === "hidden.ts" && call.calleeSymbolPath === "hidden")).toBe(false);
+});
+
+test("SDK-shaped hidden method and property implementations cannot disappear behind library coordinates", () => {
+ const path = "/fixture/main.ts"; const hidden = "/fixture/hidden.ts";
+ for (const member of ["parseInt(){return hidden();}", "parseInt:hidden"]) {
+ const source = `import {hidden} from './hidden';const env:typeof globalThis={${member}} as unknown as typeof globalThis;export function run(){return env.parseInt('1');}`;
+ const snapshot = new Map([[path, source], [hidden, "export function hidden(){return 1;}"]]);
+ expect(inspectModuleConfiguration(path, "/fixture", snapshot)).toEqual(["SOURCE_UNRESOLVED_CALLEE_UNSUPPORTED"]);
+ const facts = extractTypeScript([path, hidden], "/fixture", snapshot);
+ expect(facts.calls.some(call => call.callerSymbolPath === "run" && call.calleeRelPath === "hidden.ts" && call.calleeSymbolPath === "hidden")).toBe(false);
+ }
+});
diff --git a/packages/ts-analyzer/test/semantic-dependencies.test.ts b/packages/ts-analyzer/test/semantic-dependencies.test.ts
new file mode 100644
index 00000000..efd68d76
--- /dev/null
+++ b/packages/ts-analyzer/test/semantic-dependencies.test.ts
@@ -0,0 +1,42 @@
+import { expect, spyOn, test } from "bun:test";
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import ts from "typescript";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+import { extractionContext } from "../src/ts-symbols";
+
+for (const invocation of ["helper.call(null)", "helper.apply(null, [])", "helper.bind(null)", "(helper as any).call(null)", "alias.call(null)"]) test(`SDK invocation helper on retained function refuses ${invocation}`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-helper-origin-"));
+ try {
+ const path = join(root, "main.ts"); const content = `export function helper() { return 1; } const alias = helper; export function caller() { return ${invocation}; }`;
+ expect(inspectModuleConfiguration(path, root, new Map([[path, content]]))).toContain("SOURCE_FUNCTION_HELPER_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+test("real JS producer refuses a bind/alias helper without treating ordinary own methods as prototypes", () => {
+ expect(inspectJavaScriptSource("/fixture/main.js", "export function helper() { return 1; } const invoke = helper.call; export function caller() { return invoke(null); }").reasons).toContain("JAVASCRIPT_FUNCTION_HELPER_UNSUPPORTED");
+ const own = inspectJavaScriptSource("/fixture/main.js", "export class Own { call() { return 1; } apply() { return 2; } } export function caller() { const own = new Own(); return own.call() + own.apply(); }");
+ expect(own.reasons.some(reason => reason.includes("FUNCTION_HELPER_UNSUPPORTED"))).toBe(false);
+ expect(own.reasons).toContain("JAVASCRIPT_CONSTRUCTION_UNSUPPORTED");
+});
+test("intrinsic JSX, plain classes and SDK base aliases preserve their existing semantic domain", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-semantic-positive-"));
+ try {
+ const path = join(root, "main.tsx"); const content = "const Alias = Error; export class ExternalBoundary extends Alias {} export class Plain {} export const View = () => <>>;";
+ const reasons = inspectModuleConfiguration(path, root, new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_INTERNAL_HERITAGE_UNSUPPORTED"); expect(reasons).not.toContain("SOURCE_JSX_COMPONENT_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, root, undefined, ts.createSourceFile(path, "export class Base {} export class Derived extends Base {}", ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_INTERNAL_HERITAGE_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+test("semantic Program is shared within the exact retained snapshot and rebuilt for a new boundary", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-context-cache-"));
+ const first = join(root, "first.ts"); const second = join(root, "second.ts");
+ const inputs = new Map([[first, "export class Base {} export class Own { call() {} } export function own() { return new Own().call(); }"], [second, "import { Base } from './first'; export class Derived extends Base {}"]]);
+ const creation = spyOn(extractionContext, "createProgram");
+ try {
+ expect(inspectModuleConfiguration(first, root, inputs)).toContain("SOURCE_CONSTRUCTION_UNSUPPORTED");
+ expect(inspectModuleConfiguration(second, root, inputs)).toContain("SOURCE_INTERNAL_HERITAGE_UNSUPPORTED");
+ expect(creation).toHaveBeenCalledTimes(1);
+ inspectModuleConfiguration(first, root, new Map(inputs)); expect(creation).toHaveBeenCalledTimes(2);
+ } finally { creation.mockRestore(); rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/semantic-js-contracts.test.ts b/packages/ts-analyzer/test/semantic-js-contracts.test.ts
new file mode 100644
index 00000000..99458c6f
--- /dev/null
+++ b/packages/ts-analyzer/test/semantic-js-contracts.test.ts
@@ -0,0 +1,137 @@
+import { afterEach, expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { createGlobSelectionConfig } from "@semantic-context/core";
+import { analyzeRepository, discoverRepository, extractTypeScript, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+import ts from "typescript";
+
+const roots: string[] = [];
+afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
+function graph(files: Record) {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-contract-")); roots.push(root);
+ for (const [path, content] of Object.entries(files)) writeFileSync(join(root, path), content);
+ const config = { ...createGlobSelectionConfig(root), include: ["**/*"], exclude: [], languages: { typescript: "on" as const, javascript: "on" as const } };
+ return analyzeRepository(config, discoverRepository(config).files).graph;
+}
+test("semantic parenthesized call retains its actual owner edge", () => {
+ const result = graph({ "lib.mjs": "export function helper() { return 1; } export function caller() { return (helper)(); }" });
+ const helper = result.nodes.find(node => node.name === "helper")!;
+ const caller = result.nodes.find(node => node.name === "caller")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "calls", from: caller.id, to: helper.id }));
+});
+for (const extension of ["js", "ts"]) test(`namespace member calls create canonical test coverage in ${extension}`, () => {
+ const result = graph({ "lib.mjs": "export function helper() { return 1; } export function unused() { return 2; }", [`lib.test.${extension}`]: "import * as lib from './lib.mjs'; lib.helper();" });
+ const helper = result.nodes.find(node => node.name === "helper")!;
+ const unused = result.nodes.find(node => node.name === "unused")!;
+ const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "covers", from: tester.id, to: helper.id }));
+ expect(result.edges.some(edge => edge.kind === "tested_by" && edge.from === unused.id)).toBe(false);
+});
+for (const extension of ["js", "ts"]) test(`namespace member values create static canonical test association in ${extension}`, () => {
+ const result = graph({ "lib.mjs": "export function helper() { return 1; } export function unused() { return 2; }", [`lib.test.${extension}`]: "import * as api from './lib.mjs'; expect(api.helper).toBeDefined();" });
+ const helper = result.nodes.find(node => node.name === "helper")!;
+ const unused = result.nodes.find(node => node.name === "unused")!;
+ const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "covers", from: tester.id, to: helper.id }));
+ expect(result.edges.some(edge => edge.kind === "tested_by" && edge.from === unused.id)).toBe(false);
+});
+test("non-call namespace members retain barrel/default owners and reject shadows or unmodeled homonyms", () => {
+ const result = graph({ "leaf.mjs": "export default function namedFn() { return 1; }", "barrel.mjs": "export { default as facade } from './leaf.mjs';", "leaf.test.js": "import * as api from './barrel.mjs'; expect(api.facade).toBeDefined();" });
+ const helper = result.nodes.find(node => node.name === "namedFn")!; const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ for (const testSource of [
+ "import * as api from './lib.mjs'; function ordinary(api) { expect(api.helper).toBeDefined(); }",
+ "import * as api from './lib.mjs'; expect(api.obj.helper).toBeDefined();",
+ "import type * as api from './lib.mjs'; export type Fn = typeof api.helper;",
+ ]) {
+ const negative = graph({ "lib.mjs": "export function helper() { return 1; } export const obj = { helper() { return 2; } };", "lib.test.ts": testSource });
+ expect(negative.edges.some(edge => edge.kind === "tested_by" || edge.kind === "covers")).toBe(false);
+ }
+});
+for (const extension of ["js", "ts"]) test(`namespace destructured values associate the real canonical owner in ${extension}`, () => {
+ const result = graph({ "lib.mjs": "export function helper() { return 1; } export function unused() { return 2; }", [`lib.test.${extension}`]: "import * as api from './lib.mjs'; const { helper: renamed } = api; expect(renamed).toBeDefined();" });
+ const helper = result.nodes.find(node => node.name === "helper")!;
+ const unused = result.nodes.find(node => node.name === "unused")!;
+ const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "covers", from: tester.id, to: helper.id }));
+ expect(result.edges.some(edge => edge.kind === "tested_by" && edge.from === unused.id)).toBe(false);
+});
+test("namespace destructuring preserves leaf/default owners and rejects a local shadow or unmodeled homonym", () => {
+ const result = graph({ "leaf.mjs": "export default function namedFn() { return 1; }", "barrel.mjs": "export { default as facade } from './leaf.mjs';", "leaf.test.js": "import * as api from './barrel.mjs'; const { facade: renamed } = api; expect(renamed).toBeDefined();" });
+ const helper = result.nodes.find(node => node.name === "namedFn")!; const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ for (const content of [
+ "import * as api from './lib.mjs'; function ordinary(api) { const { helper } = api; expect(helper).toBeDefined(); }",
+ "import * as api from './lib.mjs'; const { helper } = api.obj; expect(helper).toBeDefined();",
+ "import type * as api from './lib.mjs'; type Fn = typeof api.helper;",
+ ]) expect(graph({ "lib.mjs": "export function helper() { return 1; } export const obj = { helper() { return 2; } };", "lib.test.ts": content }).edges.some(edge => edge.kind === "tested_by" || edge.kind === "covers")).toBe(false);
+});
+test("namespace test coverage follows actual leaf aliases and excludes a shadowed namespace", () => {
+ const result = graph({ "leaf.mjs": "export function helper() { return 1; }", "barrel.mjs": "export { helper as facade } from './leaf.mjs';", "leaf.test.js": "import * as lib from './barrel.mjs'; lib.facade();" });
+ const helper = result.nodes.find(node => node.name === "helper")!; const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ const shadow = graph({ "lib.mjs": "export function helper() { return 1; }", "lib.test.js": "import * as lib from './lib.mjs'; function ordinary(lib) { lib.helper(); } ordinary({ helper() {} });" });
+ expect(shadow.edges.some(edge => edge.kind === "tested_by")).toBe(false);
+});
+test("namespace default calls retain declaration coordinates while dynamic whole reads do not claim coverage", () => {
+ const result = graph({ "lib.mjs": "export default function namedFn() { return 1; }", "lib.test.js": "import * as lib from './lib.mjs'; lib.default();" });
+ const helper = result.nodes.find(node => node.name === "namedFn")!; const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ const dynamic = graph({ "lib.mjs": "export function helper() { return 1; }", "lib.test.js": "import * as lib from './lib.mjs'; Object.keys(lib);" });
+ expect(dynamic.edges.some(edge => edge.kind === "tested_by")).toBe(false);
+});
+test("unmodeled object members cannot claim namespace test coverage of a global homonym", () => {
+ const result = graph({ "lib.mjs": "export function helper() { return 1; } export const obj = { helper() { return 2; } };", "lib.test.js": "import * as lib from './lib.mjs'; lib.obj.helper();" });
+ expect(result.edges.some(edge => edge.kind === "tested_by" || edge.kind === "covers")).toBe(false);
+});
+test("namespace construction associates the actual modeled class declaration", () => {
+ const result = graph({ "lib.mjs": "export class Model {}", "lib.test.js": "import * as lib from './lib.mjs'; new lib.Model();" });
+ const model = result.nodes.find(node => node.name === "Model")!; const tester = result.nodes.find(node => node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: model.id, to: tester.id }));
+});
+test("retained TypeScript transparent calls use the owner while v1 no-snapshot stays unchanged", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-js-contract-")); roots.push(root);
+ const path = join(root, "lib.ts");
+ const content = "export function helper() { return 1; } export function caller() { return ((helper as () => number))(); }";
+ writeFileSync(path, content);
+ expect(extractTypeScript([path], root).calls).toEqual([]);
+ expect(extractTypeScript([path], root, new Map([[path, content]])).calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "caller", calleeSymbolPath: "helper" }));
+});
+test("parentheses cannot conceal an unmodeled conditional callee", () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", "export function caller(flag, a, b) { return (flag ? a : b)(); }").reasons).toContain("JAVASCRIPT_DYNAMIC_CALL_UNSUPPORTED");
+});
+for (const [source, name, imported] of [
+ ["export function helper() { return 1; }", "helper", "{ helper as renamed }"],
+ ["export default function namedFn() { return 1; }", "namedFn", "renamed"],
+ ["export default function () { return 1; }", "default", "renamed"],
+ ["export default class NamedClass {}", "NamedClass", "renamed"],
+] as const) test(`semantic test import associates canonical ${name}`, () => {
+ const result = graph({ "lib.mjs": source, "lib.test.js": `import ${imported} from './lib.mjs';` });
+ const helper = result.nodes.find(node => node.filePath === "lib.mjs" && node.name === name)!;
+ const tester = result.nodes.find(node => node.filePath === "lib.test.js" && node.kind === "test")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "covers", from: tester.id, to: helper.id }));
+});
+test("reexport test bindings retain leaf coverage and the actual barrel import", () => {
+ const result = graph({ "leaf.mjs": "export function helper() { return 1; }", "barrel.js": "export { helper as facade } from './leaf.mjs';", "leaf.test.js": "import { facade as renamed } from './barrel.js';" });
+ const helper = result.nodes.find(node => node.name === "helper")!;
+ const tester = result.nodes.find(node => node.kind === "test")!;
+ const barrel = result.nodes.find(node => node.filePath === "barrel.js" && node.kind === "module")!;
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "tested_by", from: helper.id, to: tester.id }));
+ expect(result.edges).toContainEqual(expect.objectContaining({ kind: "imports", from: tester.id, to: barrel.id }));
+});
+for (const doc of ["/** @type {import('./types.mjs').Foo} */", "/** @typedef {import('./types.mjs').Foo} Foo */", "/** @import { Foo } from './types.mjs' */"]) test(`semantic JSDoc imports stay unsupported: ${doc}`, () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", `${doc}\nexport const value = 1;`).reasons).toContain("JAVASCRIPT_JSDOC_IMPORT_UNSUPPORTED");
+});
+test("ordinary JSDoc prose does not fabricate a JavaScript import dependency", () => {
+ expect(inspectJavaScriptSource("/fixture/module.mjs", "/** Documentation discusses import('./types.mjs'). */ export const value = 1;").reasons).not.toContain("JAVASCRIPT_JSDOC_IMPORT_UNSUPPORTED");
+});
+test("nonsemantic TypeScript JSDoc types remain documentation", () => {
+ const path = "/fixture/module.ts";
+ const content = "/** @type {import('./types.mjs').Foo} */ export const value = 1;";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]), ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_JSDOC_IMPORT_UNSUPPORTED");
+});
diff --git a/packages/ts-analyzer/test/species-sort-vm.test.ts b/packages/ts-analyzer/test/species-sort-vm.test.ts
new file mode 100644
index 00000000..0e3ae54d
--- /dev/null
+++ b/packages/ts-analyzer/test/species-sort-vm.test.ts
@@ -0,0 +1,51 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+export const cases = [
+ ["species", "function helper() {} Object.defineProperty(Array, Symbol.species, { value: helper }); export function run() { return [1, 2].slice(); }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ["sort", "const values = [{ toString() { return 'b'; } }, { toString() { return 'a'; } }]; export function run() { return values.sort(); }", "SORT_COERCION_UNSUPPORTED"],
+ ["vm", "import { runInThisContext } from 'node:vm'; export function run() { return runInThisContext('7'); }", "RUNTIME_EVALUATION_UNSUPPORTED"],
+] as const;
+for (const [name, content, reason] of cases) for (const extension of ["ts", "js"]) test(`${name} implicit invocation refuses ${extension}`, () => {
+ const path = `/fixture/main.${extension}`; const snapshot = new Map([[path, content]]);
+ const reasons = extension === "js" ? inspectJavaScriptSource(path, content, "/fixture", snapshot).reasons : inspectModuleConfiguration(path, "/fixture", snapshot);
+ console.info(JSON.stringify({ name, extension, reasons }));
+ expect(reasons).toContain(`${extension === "js" ? "JAVASCRIPT" : "SOURCE"}_${reason}`);
+});
+test("native isolated cases observe the species, coercion and evaluated-code hooks", () => {
+ for (const content of [
+ "function helper() { console.log('SPECIES_CALLED'); } Object.defineProperty(Array,Symbol.species,{value:helper}); const result=[1,2].slice();if(result[0]!==1)throw Error('slice mismatch');",
+ "const values=[{toString(){console.log('COERCION_CALLED');return 'b'}},{toString(){return 'a'}}];values.sort();",
+ "import { runInThisContext } from 'node:vm';const result=runInThisContext('console.log(\"EVAL_CALLED\");7');if(result!==7)throw Error('eval mismatch');",
+ ]) {
+ const result = Bun.spawnSync([process.execPath, "-e", content], { stdout: "pipe", stderr: "pipe" });
+ expect(result.exitCode).toBe(0); expect(new TextDecoder().decode(result.stdout)).toContain("_CALLED");
+ }
+});
+test("fresh primitive sort, own methods and ordinary Node data keep their distinct boundary", () => {
+ for (const content of [
+ "export function run() { return [2, 1].sort(); }",
+ "const own = { sort() { return 1; } }; export function run() { return own.sort(); }",
+ "function runInThisContext(value) { return value; } export function run() { return runInThisContext(1); }",
+ "import { basename } from 'node:path'; export function run() { return basename('src/value.ts'); }",
+ ]) {
+ const path = "/fixture/main.ts"; const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).toEqual([]);
+ }
+});
+test("aliases do not fabricate immutable sort elements or erase VM/species origins", () => {
+ for (const [content, reason] of [
+ ["const values = [2, 1]; export function run() { return values.sort(); }", "SORT_COERCION_UNSUPPORTED"],
+ ["import * as vm from 'node:vm'; const { runInThisContext: evaluate } = vm; export function run() { return evaluate.call(vm, '7'); }", "RUNTIME_EVALUATION_UNSUPPORTED"],
+ ["function helper() {} const { species: key } = Symbol; Object.defineProperty(Array, key, { value: helper }); export function run() { return [1].slice(); }", "PROTOCOL_DESCRIPTOR_UNSUPPORTED"],
+ ] as const) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain(`SOURCE_${reason}`);
+ }
+});
+
+test("default comparator value proof unwraps assertions without trusting their type", () => {
+ const path="/fixture/main.ts";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,"const values=[{toString(){return 'b'}},{toString(){return 'a'}}]; export function run(){return values.sort(undefined as any);}"]]))).toContain("SOURCE_SORT_COERCION_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,"export function run(){return [2,1].sort(undefined as any);}"]]))).toEqual([]);
+});
diff --git a/packages/ts-analyzer/test/static-call-shapes.test.ts b/packages/ts-analyzer/test/static-call-shapes.test.ts
new file mode 100644
index 00000000..b9dcff9e
--- /dev/null
+++ b/packages/ts-analyzer/test/static-call-shapes.test.ts
@@ -0,0 +1,38 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import ts from "typescript";
+import { extractTypeScript, inspectModuleConfiguration } from "../src";
+
+test("retained unmodeled TS call has no extracted callee and a refusal; ordinary no-snapshot diagnostics stay unchanged", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-static-call-"));
+ try {
+ const path = join(root, "main.ts"); const content = "export function helper() { return 1; } export function fallback() { return 2; } export function main(flag: boolean) { return (flag ? helper : fallback)(); }";
+ writeFileSync(path, content); const inputs = new Map([[path, content]]);
+ expect(extractTypeScript([path], root, inputs).calls).toEqual([]);
+ expect(inspectModuleConfiguration(path, root, inputs)).toContain("SOURCE_DYNAMIC_CALL_UNSUPPORTED");
+ expect(inspectModuleConfiguration(path, root, undefined, ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_DYNAMIC_CALL_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+for (const expression of ["(helper)", "(helper as () => number)", "(<() => number>helper)", "helper!", "(helper satisfies () => number)"]) test(`retained transparent identifier call ${expression} has its actual coordinate`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-static-call-"));
+ try {
+ const path = join(root, "main.ts"); const content = `export function helper() { return 1; } export function main() { return ${expression}(); }`;
+ const inputs = new Map([[path, content]]);
+ expect(inspectModuleConfiguration(path, root, inputs)).not.toContain("SOURCE_DYNAMIC_CALL_UNSUPPORTED");
+ expect(extractTypeScript([path], root, inputs).calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "main", calleeSymbolPath: "helper" }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
+test("retained transparent namespace property call and existing class construction retain their domain", () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-static-call-"));
+ try {
+ const path = join(root, "main.ts"); const leaf = join(root, "leaf.ts");
+ const content = "import * as leaf from './leaf'; export class Model {} export function main() { const model = new Model(); return ((leaf.helper) as () => number)(); }";
+ const inputs = new Map([[path, content], [leaf, "export function helper() { return 1; }"]]);
+ expect(inspectModuleConfiguration(path, root, inputs)).not.toContain("SOURCE_DYNAMIC_CALL_UNSUPPORTED");
+ const result = extractTypeScript([path, leaf], root, inputs);
+ expect(result.calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "main", calleeRelPath: "leaf.ts", calleeSymbolPath: "helper" }));
+ expect(result.symbols).toContainEqual(expect.objectContaining({ name: "Model", kind: "class" }));
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/string-join-coercion.test.ts b/packages/ts-analyzer/test/string-join-coercion.test.ts
new file mode 100644
index 00000000..12d26b7d
--- /dev/null
+++ b/packages/ts-analyzer/test/string-join-coercion.test.ts
@@ -0,0 +1,16 @@
+import {expect,test} from "bun:test";
+import {inspectModuleConfiguration} from "../src";
+for(const extension of ["ts","mjs"])for(const [name,source] of [
+ ["SDK String hook","function helper(){return 'one';}const value={toString:helper};export function run(){return String(value);}"],
+ ["SDK join hook","function helper(){return 'one';}const value={toString:helper};export function run(){return [value].join();}"],
+]as const)test(`${name} ${extension} requires primitive coercion provenance`,()=>{
+ const path=`/fixture/main.${extension}`;expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toContain("SOURCE_COERCION_UNSUPPORTED");
+});
+test("primitive values and real local homonyms remain distinct",()=>{
+ for(const source of [
+ "export function run(){return String(1);}","export function run(){return String(true);}","export function run(){return Number(1);}","export function run(){return Boolean(true);}",
+ "const value='one';export function run(){return [value,2,true,null,undefined].join();}",
+ "function String(_value:unknown){return 'local';}function helper(){return 'one';}export function run(){return String({toString:helper});}",
+ "const value={join(){return 'local';}};export function run(){return value.join();}",
+ ]){const path="/fixture/main.ts";expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]]))).toEqual([]);}
+});
diff --git a/packages/ts-analyzer/test/tagged-template.test.ts b/packages/ts-analyzer/test/tagged-template.test.ts
new file mode 100644
index 00000000..1eecefb8
--- /dev/null
+++ b/packages/ts-analyzer/test/tagged-template.test.ts
@@ -0,0 +1,25 @@
+import { expect, test } from "bun:test";
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import ts from "typescript";
+import { extractTypeScript, inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+
+test("JavaScript producer rejects tagged invocation independently of qualified mode", () => {
+ const result = inspectJavaScriptSource("/fixture/main.mjs", "export function tag(parts) { return parts[0]; } export function main() { return tag`value`; }");
+ expect(result.reasons).toContain("JAVASCRIPT_TAGGED_TEMPLATE_UNSUPPORTED");
+});
+for (const extension of ["ts", "mjs"]) test(`retained ${extension} distinguishes plain templates from tagged invocations`, () => {
+ const root = mkdtempSync(join(tmpdir(), "semctx-tagged-template-"));
+ try {
+ const path = join(root, `main.${extension}`);
+ const content = "export function helper() { return 1; } export function main() { return `value ${helper()}`; }";
+ const inputs = new Map([[path, content]]);
+ expect(inspectModuleConfiguration(path, root, inputs)).not.toContain("SOURCE_TAGGED_TEMPLATE_UNSUPPORTED");
+ expect(extractTypeScript([path], root, inputs).calls).toContainEqual(expect.objectContaining({ callerSymbolPath: "main", calleeSymbolPath: "helper" }));
+ const tagged = "export function tag(parts) { return parts[0]; } export function main() { return tag`value`; }";
+ expect(inspectModuleConfiguration(path, root, new Map([[path, tagged]]))).toContain("SOURCE_TAGGED_TEMPLATE_UNSUPPORTED");
+ expect(extractTypeScript([path], root, new Map([[path, tagged]])).calls).toEqual([]);
+ if (extension === "ts") expect(inspectModuleConfiguration(path, root, undefined, ts.createSourceFile(path, tagged, ts.ScriptTarget.Latest, true))).not.toContain("SOURCE_TAGGED_TEMPLATE_UNSUPPORTED");
+ } finally { rmSync(root, { recursive: true, force: true }); }
+});
diff --git a/packages/ts-analyzer/test/unresolved-meta-prototype.test.ts b/packages/ts-analyzer/test/unresolved-meta-prototype.test.ts
new file mode 100644
index 00000000..8364c94d
--- /dev/null
+++ b/packages/ts-analyzer/test/unresolved-meta-prototype.test.ts
@@ -0,0 +1,57 @@
+import { expect, test } from "bun:test";
+import { inspectModuleConfiguration } from "../src";
+
+for (const extension of ["ts", "mjs"]) for (const [name, source] of [
+ ["unresolved host launcher", "export function run(){return Bun.spawnSync(['bun','./src/hidden.ts']);}"],
+ ["prototype setter", "function helper(){return 1;}const value={__proto__:{toString:helper}};export function run(){return value.toString();}"],
+ ["import meta callable", "const pages=import.meta.glob('./pages/*.mjs',{eager:true});export function run(){return pages['./pages/hidden.mjs'];}"],
+] as const) test(`${name} ${extension} cannot establish admission`, () => {
+ const path = `/fixture/main.${extension}`;
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]])).length).toBeGreaterThan(0);
+});
+
+test("captured lexical and SDK callees and metadata reads remain distinct", () => {
+ for (const source of [
+ "const Bun={spawnSync(_args:string[]){return 1;}};export function run(){return Bun.spawnSync(['plain','data']);}",
+ "function helper(){return 1;}const value={['__proto__']:{toString:helper}};export function run(){return 1;}",
+ "export function run(){return import.meta.url;}",
+ "export function run(){return Math.abs(1);}",
+ "export function run(){return JSON.stringify({value:1});}",
+ "export function run(){return console.log('ordinary SDK data');}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, source]]))).toEqual([]);
+ }
+});
+
+test("ordinary native process members, metadata aliases and closed prototypes retain their domain", () => {
+ for (const source of [
+ "export function run(){return process.cwd();}",
+ "export function run(){process.stdout.write('ordinary data');return 1;}",
+ "export function run(){return globalThis.process.cwd();}",
+ "export function run(){return global.process.cwd();}",
+ "const url=import.meta.url;export function run(){return url;}",
+ "const meta=import.meta;const {url}=meta;export function run(){return url;}",
+ "const value={__proto__:null};export function run(){return 1;}",
+ "const value={__proto__:{value:1}};export function run(){return 1;}",
+ "const value={__proto__(){return 1;}};export function run(){return value.__proto__();}",
+ "const __proto__=1;const value={__proto__};export function run(){return 1;}",
+ "import { receiver } from 'opaque';export function run(){return receiver.operation('ordinary data');}",
+ ]) {
+ const path = "/fixture/main.ts";
+ const snapshot = new Map([[path,source],["/fixture/package.json",'{"dependencies":{"opaque":"1.0.0"}}']]);
+ expect(inspectModuleConfiguration(path,"/fixture",snapshot)).toEqual([]);
+ }
+});
+
+test("unproven ambient member roots and escaped metadata do not gain data proof", () => {
+ for (const source of [
+ "const host=Bun;export function run(){return host.spawnSync(['plain','data']);}",
+ "const {spawnSync}=Bun;export function run(){return spawnSync.call(null,['plain','data']);}",
+ "const load=import.meta.glob;export function run(){return load;}",
+ "const meta=import.meta;const {glob:load}=meta;export function run(){return load('literal data');}",
+ ]) {
+ const path = "/fixture/main.ts";
+ expect(inspectModuleConfiguration(path,"/fixture",new Map([[path,source]])).length).toBeGreaterThan(0);
+ }
+});
diff --git a/packages/ts-analyzer/test/worker-timer.test.ts b/packages/ts-analyzer/test/worker-timer.test.ts
new file mode 100644
index 00000000..e2bb6c78
--- /dev/null
+++ b/packages/ts-analyzer/test/worker-timer.test.ts
@@ -0,0 +1,58 @@
+import { expect, test } from "bun:test";
+import { inspectJavaScriptSource, inspectModuleConfiguration } from "../src";
+const path = "/fixture/main.ts";
+for (const [content, reason] of [
+ ["export function start() { return new Worker('./worker.js', { type: 'module' }); }", "WORKER_LOADER_UNSUPPORTED"],
+ ["export function start() { return setTimeout(\"import('./hidden.js')\", 0); }", "TIMER_EVALUATION_UNSUPPORTED"],
+] as const) test(`direct SDK ${reason} refuses hidden dependency`, () => {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ const js = inspectJavaScriptSource("/fixture/main.js", content).reasons;
+ console.info(JSON.stringify({ content, reasons, js }));
+ expect(reasons).toContain(`SOURCE_${reason}`);
+ expect(js).toContain(`JAVASCRIPT_${reason}`);
+});
+
+for (const content of [
+ "const Background = Worker; export function start() { return new Background('./worker.js'); }",
+ "const Background = globalThis.SharedWorker; export function start() { return new Background(new URL('./worker.js', import.meta.url)); }",
+]) test(`SDK worker transparent alias refuses ${content}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_WORKER_LOADER_UNSUPPORTED");
+});
+
+for (const content of [
+ "const later = setTimeout; export function start() { return later('hidden()', 0); }",
+ "const { setTimeout: later } = globalThis; export function start() { return later('hidden()', 0); }",
+ "export function start(handler: string | (() => void)) { return window.setTimeout(handler, 0); }",
+ "export function start(handler: unknown) { return setInterval(handler, 0); }",
+ "export function start(handler: T) { return setTimeout(handler, 0); }",
+ "export function start() { return setTimeout.call(null, 'hidden()', 0); }",
+ "export function start() { return globalThis.setInterval.apply(null, ['hidden()', 0]); }",
+ "const later = setTimeout.bind(globalThis); export function start() { return later('hidden()', 0); }",
+ "export function start() { return ({ later: setTimeout }).later('hidden()', 0); }",
+ "const { setTimeout: schedule } = globalThis; const later = schedule; export function start() { return later.call(null, 'hidden()', 0); }",
+]) test(`SDK timer evaluator or unproved escape refuses ${content}`, () => {
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_TIMER_EVALUATION_UNSUPPORTED");
+});
+
+test("known native timer callbacks are explicitly unsupported until their invocation is modeled", () => {
+ for (const content of [
+ "export function start() { return setTimeout(() => 1, 0); }",
+ "export function start() { return (setTimeout)(() => 1, 0); }",
+ "const later = (setTimeout); export function start() { return later(() => 1, 0); }",
+ "const later = globalThis.setTimeout; export function start(callback: () => void) { return later(callback, 0); }",
+ ]) expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).toContain("SOURCE_TIMER_CALLBACK_UNSUPPORTED");
+ expect(inspectJavaScriptSource("/fixture/main.js", "export function start() { return setTimeout(() => 1, 0); }").reasons).toContain("JAVASCRIPT_TIMER_CALLBACK_UNSUPPORTED");
+});
+test("ordinary names, Node timers and nonworker SDK constructors preserve their domain", () => {
+ for (const content of [
+ "const setTimeout = (value: string) => value; export function start() { return setTimeout('ordinary'); }",
+ "const own = { setTimeout(value: string) { return value; } }; export function start() { return own.setTimeout('ordinary'); }",
+ "import { setTimeout } from 'node:timers'; export function start() { return setTimeout('ordinary', 0); }",
+ ]) {
+ const reasons = inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]));
+ expect(reasons).not.toContain("SOURCE_TIMER_EVALUATION_UNSUPPORTED");
+ expect(reasons).not.toContain("SOURCE_TIMER_CALLBACK_UNSUPPORTED");
+ }
+ const content = "const Worker = Map; export function start() { return [new Worker(), new Error('x'), new URL('https://example.invalid')]; }";
+ expect(inspectModuleConfiguration(path, "/fixture", new Map([[path, content]]))).not.toContain("SOURCE_WORKER_LOADER_UNSUPPORTED");
+});
diff --git a/packages/workspace-analyzer-internal/src/index.ts b/packages/workspace-analyzer-internal/src/index.ts
index 78afe260..92e6fa92 100644
--- a/packages/workspace-analyzer-internal/src/index.ts
+++ b/packages/workspace-analyzer-internal/src/index.ts
@@ -68,6 +68,8 @@ export interface AnalyzeWorkspaceInput {
readonly repositoryRoot: string;
readonly repositoryId?: string;
readonly artifacts?: readonly WorkspaceArtifact[];
+ /** Optional retained manifest bytes keyed by resolved absolute path; never fall back to live bytes. */
+ readonly manifestContents?: ReadonlyMap;
}
export interface ProjectWorkspaceCandidatesInput {
@@ -111,7 +113,7 @@ export async function analyzeWorkspace(input: AnalyzeWorkspaceInput): Promise();
@@ -183,7 +185,7 @@ export function analyzeWorkspaceSync(input: AnalyzeWorkspaceInput): WorkspacePro
}
const { directories, layoutCandidates, manifests, malformedRoots } =
- inspectRepositorySync(repositoryRoot, diagnostics);
+ inspectRepositorySync(repositoryRoot, diagnostics, input.manifestContents);
const candidates: WorkspaceCandidate[] = [];
const identitiesByRoot = new Map();
@@ -413,6 +415,7 @@ export function projectWorkspaceCandidates(
async function inspectRepository(
repositoryRoot: string,
diagnostics: WorkspaceDiagnostic[],
+ manifestContents?: ReadonlyMap,
): Promise<{
directories: readonly DirectoryRecord[];
layoutCandidates: readonly WorkspaceLayoutCandidate[];
@@ -431,7 +434,7 @@ async function inspectRepository(
const entries = (await readdir(absolute, { withFileTypes: true }))
.sort((left, right) => compareText(left.name, right.name));
for (const entry of entries) {
- if (entry.name === ".git" || entry.name === "node_modules") continue;
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === ".semctx") continue;
const child = current === ROOT ? entry.name : `${current}/${entry.name}`;
if (entry.isSymbolicLink()) {
directories.push({ root: child, safe: false });
@@ -455,6 +458,7 @@ async function inspectRepository(
for (const manifestName of [PACKAGE_MANIFEST, PYTHON_MANIFEST] as const) {
const manifestPath = directory.root === ROOT ? manifestName : `${directory.root}/${manifestName}`;
const manifestAbsolute = resolve(absolute, manifestName);
+ if (manifestContents !== undefined && !manifestContents.has(manifestAbsolute)) continue;
let source: string;
try {
const stat = await lstat(manifestAbsolute);
@@ -472,7 +476,7 @@ async function inspectRepository(
});
continue;
}
- source = await readFile(manifestAbsolute, "utf8");
+ source = manifestContents === undefined ? await readFile(manifestAbsolute, "utf8") : manifestContents.get(manifestAbsolute)!;
} catch (error) {
if (isMissing(error)) continue;
throw error;
@@ -499,6 +503,7 @@ async function inspectRepository(
function inspectRepositorySync(
repositoryRoot: string,
diagnostics: WorkspaceDiagnostic[],
+ manifestContents?: ReadonlyMap,
): {
directories: readonly DirectoryRecord[];
layoutCandidates: readonly WorkspaceLayoutCandidate[];
@@ -517,7 +522,7 @@ function inspectRepositorySync(
const entries = readdirSync(absolute, { withFileTypes: true })
.sort((left, right) => compareText(left.name, right.name));
for (const entry of entries) {
- if (entry.name === ".git" || entry.name === "node_modules") continue;
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === ".semctx") continue;
const child = current === ROOT ? entry.name : `${current}/${entry.name}`;
if (entry.isSymbolicLink()) {
directories.push({ root: child, safe: false });
@@ -541,6 +546,7 @@ function inspectRepositorySync(
for (const manifestName of [PACKAGE_MANIFEST, PYTHON_MANIFEST] as const) {
const manifestPath = directory.root === ROOT ? manifestName : `${directory.root}/${manifestName}`;
const manifestAbsolute = resolve(absolute, manifestName);
+ if (manifestContents !== undefined && !manifestContents.has(manifestAbsolute)) continue;
let source: string;
try {
const stat = lstatSync(manifestAbsolute);
@@ -558,7 +564,7 @@ function inspectRepositorySync(
});
continue;
}
- source = readFileSync(manifestAbsolute, "utf8");
+ source = manifestContents === undefined ? readFileSync(manifestAbsolute, "utf8") : manifestContents.get(manifestAbsolute)!;
} catch (error) {
if (isMissing(error)) continue;
throw error;
diff --git a/packages/workspace-analyzer-internal/test/workspace-analyzer.test.ts b/packages/workspace-analyzer-internal/test/workspace-analyzer.test.ts
index 9befa9ab..3aabcb54 100644
--- a/packages/workspace-analyzer-internal/test/workspace-analyzer.test.ts
+++ b/packages/workspace-analyzer-internal/test/workspace-analyzer.test.ts
@@ -12,6 +12,23 @@ import {
} from "../src/index";
const fixtureRoots: string[] = [];
+it("round3 reserved semctx manifests do not affect sync or async workspace projection", async () => {
+ const root = await fixture();
+ const before = analyzeWorkspaceSync({ repositoryRoot: root });
+ await json(root, ".semctx/package.json", { name: "internal-artifact" });
+ await text(root, ".semctx/pyproject.toml", '[project]\nname = "internal-python"\n');
+ expect(analyzeWorkspaceSync({ repositoryRoot: root })).toEqual(before);
+ expect(await analyzeWorkspace({ repositoryRoot: root })).toEqual(before);
+});
+it("round3 retained workspace manifest bytes survive live ABA input changes", async () => {
+ const root = await fixture();
+ await json(root, "package.json", { name: "original" });
+ const before = analyzeWorkspaceSync({ repositoryRoot: root });
+ const manifestContents = new Map([[join(root, "package.json"), '{"name":"original"}']]);
+ await json(root, "package.json", { name: "transient" });
+ expect(analyzeWorkspaceSync({ repositoryRoot: root, manifestContents })).toEqual(before);
+ expect(await analyzeWorkspace({ repositoryRoot: root, manifestContents })).toEqual(before);
+});
const itOnPosix = process.platform === "win32" ? it.skip : it;
const itOnWindows = process.platform === "win32" ? it : it.skip;
diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json
index f9498bfd..45bc2545 100644
--- a/plugins/claude-code/.claude-plugin/plugin.json
+++ b/plugins/claude-code/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "semctx",
"description": "Trace repository impact, authored intent and fail-closed migration plans, then verify changes with proof-honest verdicts. Local-first and deterministic.",
- "version": "0.4.2",
+ "version": "0.4.3",
"author": {
"name": "hoklims",
"email": "hoklims@gmail.com",
diff --git a/plugins/claude-code/dist/semctx-index-worker.js b/plugins/claude-code/dist/semctx-index-worker.js
index d379992b..3f80276e 100644
--- a/plugins/claude-code/dist/semctx-index-worker.js
+++ b/plugins/claude-code/dist/semctx-index-worker.js
@@ -1,5 +1,5 @@
// @bun
-var Kdt=Object.create;var{getPrototypeOf:Zdt,defineProperty:Ahe,getOwnPropertyNames:emt}=Object;var tmt=Object.prototype.hasOwnProperty;function rmt(B){return this[B]}var nmt,imt,smt=(B,Pe,Je)=>{var Nt=B!=null&&typeof B==="object";if(Nt){var Pt=Pe?nmt??=new WeakMap:imt??=new WeakMap,hr=Pt.get(B);if(hr)return hr}Je=B!=null?Kdt(Zdt(B)):{};let bn=Pe||!B||!B.__esModule?Ahe(Je,"default",{value:B,enumerable:!0}):Je;if(B&&typeof B==="object"||typeof B==="function"){for(let li of emt(B))if(!tmt.call(bn,li))Ahe(bn,li,{get:rmt.bind(B,li),enumerable:!0})}if(Nt)Pt.set(B,bn);return bn};var amt=(B,Pe)=>()=>(Pe||B((Pe={exports:{}}).exports,Pe),Pe.exports);var omt=(B)=>B;function cmt(B,Pe){this[B]=omt.bind(null,Pe)}var lmt=(B,Pe)=>{for(var Je in Pe)Ahe(B,Je,{get:Pe[Je],enumerable:!0,configurable:!0,set:cmt.bind(Pe,Je)})};var Cx=import.meta.require;var pOe=amt(function(Sht,uY){var __dirname=import.meta.dir+"/typescript-lib",__filename=__dirname+"/typescript.js";/*! *****************************************************************************
+var dmt=Object.create;var{getPrototypeOf:mmt,defineProperty:Bhe,getOwnPropertyNames:gmt}=Object;var hmt=Object.prototype.hasOwnProperty;function ymt(M){return this[M]}var vmt,bmt,Smt=(M,Se,Fe)=>{var dt=M!=null&&typeof M==="object";if(dt){var nt=Se?vmt??=new WeakMap:bmt??=new WeakMap,sr=nt.get(M);if(sr)return sr}Fe=M!=null?dmt(mmt(M)):{};let Hr=Se||!M||!M.__esModule?Bhe(Fe,"default",{value:M,enumerable:!0}):Fe;if(M&&typeof M==="object"||typeof M==="function"){for(let Kr of gmt(M))if(!hmt.call(Hr,Kr))Bhe(Hr,Kr,{get:ymt.bind(M,Kr),enumerable:!0})}if(dt)nt.set(M,Hr);return Hr};var xmt=(M,Se)=>()=>(Se||M((Se={exports:{}}).exports,Se),Se.exports);var Tmt=(M)=>M;function kmt(M,Se){this[M]=Tmt.bind(null,Se)}var Cmt=(M,Se)=>{for(var Fe in Se)Bhe(M,Fe,{get:Se[Fe],enumerable:!0,configurable:!0,set:kmt.bind(Se,Fe)})};var DT=import.meta.require;var bOe=xmt(function(jht,bY){var __dirname=import.meta.dir+"/typescript-lib",__filename=__dirname+"/typescript.js";/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
@@ -12,42 +12,42 @@ MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
-***************************************************************************** */var fOe={};((B)=>{var{defineProperty:Pe,getOwnPropertyDescriptor:Je,getOwnPropertyNames:Nt}=Object,Pt=Object.prototype.hasOwnProperty,hr=(e,t)=>{for(var n in t)Pe(e,n,{get:t[n],enumerable:!0})},bn=(e,t,n,i)=>{if(t&&typeof t==="object"||typeof t==="function"){for(let s of Nt(t))if(!Pt.call(e,s)&&s!==n)Pe(e,s,{get:()=>t[s],enumerable:!(i=Je(t,s))||i.enumerable})}return e},li=(e)=>e,cs={};hr(cs,{ANONYMOUS:()=>TH,AccessFlags:()=>kK,AssertionLevel:()=>OY,AssignmentDeclarationKind:()=>FK,AssignmentKind:()=>ste,Associativity:()=>pte,BreakpointResolver:()=>bG,BuilderFileEmit:()=>cae,BuilderProgramKind:()=>gae,BuilderState:()=>sm,CallHierarchy:()=>Zk,CharacterCodes:()=>UK,CheckFlags:()=>bK,CheckMode:()=>qV,ClassificationType:()=>Fq,ClassificationTypeNames:()=>yoe,CommentDirectiveType:()=>sK,Comparison:()=>Vu,CompletionInfoFlags:()=>_oe,CompletionTriggerKind:()=>Aq,Completions:()=>iC,ContainerFlags:()=>Wie,ContextFlags:()=>fK,Debug:()=>E,DiagnosticCategory:()=>m5,Diagnostics:()=>p,DocumentHighlights:()=>lM,ElementFlags:()=>xK,EmitFlags:()=>PB,EmitHint:()=>$K,EmitOnly:()=>oK,EndOfLineState:()=>doe,ExitStatus:()=>cK,ExportKind:()=>cce,Extension:()=>qK,ExternalEmitHelpers:()=>GK,FileIncludeKind:()=>yB,FilePreprocessingDiagnosticsKind:()=>aK,FileSystemEntryKind:()=>nZ,FileWatcherEventKind:()=>eZ,FindAllReferences:()=>Ro,FlattenLevel:()=>use,FlowFlags:()=>d5,ForegroundColorEscapeSequences:()=>Kse,FunctionFlags:()=>_te,GeneratedIdentifierFlags:()=>hB,GetLiteralTextFlags:()=>Tee,GoToDefinition:()=>q6,HighlightSpanKind:()=>loe,IdentifierNameMap:()=>S6,ImportKind:()=>oce,ImportsNotUsedAsValues:()=>BK,IndentStyle:()=>uoe,IndexFlags:()=>CK,IndexKind:()=>PK,InferenceFlags:()=>AK,InferencePriority:()=>NK,InlayHintKind:()=>coe,InlayHints:()=>_$,InternalEmitFlags:()=>HK,InternalNodeBuilderFlags:()=>dK,InternalSymbolName:()=>SK,IntersectionFlags:()=>_K,InvalidatedProjectKind:()=>Bae,JSDocParsingMode:()=>ZK,JsDoc:()=>rb,JsTyping:()=>N1,JsxEmit:()=>jK,JsxFlags:()=>tK,JsxReferenceKind:()=>EK,LanguageFeatureMinimumTarget:()=>Ul,LanguageServiceMode:()=>aoe,LanguageVariant:()=>WK,LexicalEnvironmentFlags:()=>QK,ListFormat:()=>YK,LogLevel:()=>UY,MapCode:()=>f$,MemberOverrideStatus:()=>lK,ModifierFlags:()=>mB,ModuleDetectionKind:()=>OK,ModuleInstanceState:()=>Jie,ModuleKind:()=>cE,ModuleResolutionKind:()=>oE,ModuleSpecifierEnding:()=>lre,NavigateTo:()=>Ice,NavigationBar:()=>Oce,NewLineKind:()=>JK,NodeBuilderFlags:()=>pK,NodeCheckFlags:()=>SB,NodeFactoryFlags:()=>Jre,NodeFlags:()=>dB,NodeResolutionFeatures:()=>Nie,ObjectFlags:()=>xB,OperationCanceledException:()=>ZD,OperatorPrecedence:()=>dte,OrganizeImports:()=>nb,OrganizeImportsMode:()=>Nq,OuterExpressionKinds:()=>XK,OutliningElementsCollector:()=>d$,OutliningSpanKind:()=>foe,OutputFileType:()=>poe,PackageJsonAutoImportPreference:()=>soe,PackageJsonDependencyGroup:()=>ioe,PatternMatchKind:()=>zH,PollingInterval:()=>wB,PollingWatchKind:()=>RK,PragmaKindFlags:()=>KK,PredicateSemantics:()=>rK,PreparePasteEdits:()=>w$,PrivateIdentifierKind:()=>Qre,ProcessLevel:()=>dse,ProgramUpdateLevel:()=>Gse,QuotePreference:()=>Joe,RegularExpressionFlags:()=>nK,RelationComparisonResult:()=>gB,Rename:()=>HM,ScriptElementKind:()=>goe,ScriptElementKindModifier:()=>hoe,ScriptKind:()=>CB,ScriptSnapshot:()=>SL,ScriptTarget:()=>zK,SemanticClassificationFormat:()=>ooe,SemanticMeaning:()=>voe,SemicolonPreference:()=>Iq,SignatureCheckMode:()=>HV,SignatureFlags:()=>kB,SignatureHelp:()=>T8,SignatureInfo:()=>oae,SignatureKind:()=>DK,SmartSelectionRange:()=>h$,SnippetKind:()=>DB,StatisticType:()=>$ae,StructureIsReused:()=>vB,SymbolAccessibility:()=>hK,SymbolDisplay:()=>o0,SymbolDisplayPartKind:()=>xL,SymbolFlags:()=>bB,SymbolFormatFlags:()=>gK,SyntaxKind:()=>pB,Ternary:()=>IK,ThrottledCancellationToken:()=>Wle,TokenClass:()=>moe,TokenFlags:()=>iK,TransformFlags:()=>EB,TypeFacts:()=>UV,TypeFlags:()=>TB,TypeFormatFlags:()=>mK,TypeMapKind:()=>wK,TypePredicateKind:()=>yK,TypeReferenceSerializationKind:()=>vK,UnionReduction:()=>uK,UpToDateStatusType:()=>Iae,VarianceFlags:()=>TK,Version:()=>Hd,VersionRange:()=>p5,WatchDirectoryFlags:()=>VK,WatchDirectoryKind:()=>MK,WatchFileKind:()=>LK,WatchLogLevel:()=>Xse,WatchType:()=>Yl,accessPrivateIdentifier:()=>lse,addEmitFlags:()=>jm,addEmitHelper:()=>bk,addEmitHelpers:()=>fh,addInternalEmitFlags:()=>ZS,addNodeFactoryPatcher:()=>z0e,addObjectAllocatorPatcher:()=>C0e,addRange:()=>Wn,addRelatedInfo:()=>Zs,addSyntheticLeadingComment:()=>y1,addSyntheticTrailingComment:()=>mw,addToSeen:()=>ld,advancedAsyncSuperHelper:()=>IO,affectsDeclarationPathOptionDeclarations:()=>tie,affectsEmitOptionDeclarations:()=>eie,allKeysStartWithDot:()=>E9,altDirectorySeparator:()=>v5,and:()=>u5,append:()=>Lr,appendIfUnique:()=>Bh,arrayFrom:()=>ds,arrayIsEqualTo:()=>Vf,arrayIsHomogeneous:()=>hre,arrayOf:()=>NY,arrayReverseIterator:()=>Hj,arrayToMap:()=>nE,arrayToMultiMap:()=>FN,arrayToNumericMap:()=>IY,assertType:()=>dv,assign:()=>CS,asyncSuperHelper:()=>AO,attachFileToDiagnostics:()=>_k,base64decode:()=>Lte,base64encode:()=>Ote,binarySearch:()=>W0,binarySearchKey:()=>Nx,bindSourceFile:()=>Vie,breakIntoCharacterSpans:()=>kce,breakIntoWordSpans:()=>Cce,buildLinkParts:()=>$oe,buildOpts:()=>XI,buildOverload:()=>DNe,bundlerModuleNameResolver:()=>Aie,canBeConvertedToAsync:()=>GH,canHaveDecorators:()=>w2,canHaveExportModifier:()=>vI,canHaveFlowNode:()=>IE,canHaveIllegalDecorators:()=>eV,canHaveIllegalModifiers:()=>Lne,canHaveIllegalType:()=>p1e,canHaveIllegalTypeParameters:()=>One,canHaveJSDoc:()=>JA,canHaveLocals:()=>mg,canHaveModifiers:()=>pd,canHaveModuleSpecifier:()=>rte,canHaveSymbol:()=>Xd,canIncludeBindAndCheckDiagnostics:()=>sw,canJsonReportNoInputFiles:()=>t3,canProduceDiagnostics:()=>_3,canUsePropertyAccess:()=>iW,canWatchAffectingLocation:()=>kae,canWatchAtTypes:()=>xae,canWatchDirectoryOrFile:()=>GU,canWatchDirectoryOrFilePath:()=>E3,cartesianProduct:()=>WY,cast:()=>ra,chainBundle:()=>im,chainDiagnosticMessages:()=>Fs,changeAnyExtension:()=>JN,changeCompilerHostLikeToUseCache:()=>Yw,changeExtension:()=>$y,changeFullExtension:()=>T5,changesAffectModuleResolution:()=>X5,changesAffectingProgramStructure:()=>dee,characterCodeToRegularExpressionFlag:()=>JB,childIsDecorated:()=>DP,classElementOrClassElementParameterIsDecorated:()=>BJ,classHasClassThisAssignment:()=>oU,classHasDeclaredOrExplicitlyAssignedName:()=>cU,classHasExplicitlyAssignedName:()=>B9,classOrConstructorParameterIsDecorated:()=>Vy,classicNameResolver:()=>jie,classifier:()=>Hle,cleanExtendedConfigCache:()=>q9,clear:()=>ed,clearMap:()=>af,clearSharedExtendedConfigFileWatcher:()=>TU,climbPastPropertyAccess:()=>EL,clone:()=>FY,cloneCompilerOptions:()=>Kq,closeFileWatcher:()=>Nd,closeFileWatcherOf:()=>Bp,codefix:()=>Zu,collapseTextChangeRangesAcrossMultipleVersions:()=>EZ,collectExternalModuleInfo:()=>nU,combine:()=>wx,combinePaths:()=>Vn,commandLineOptionOfCustomType:()=>iie,commentPragmas:()=>g5,commonOptionsWithBuild:()=>s9,compact:()=>AN,compareBooleans:()=>pv,compareDataObjects:()=>Iz,compareDiagnostics:()=>KP,compareEmitHelpers:()=>Kre,compareNumberOfDirectorySeparators:()=>mI,comparePaths:()=>zh,comparePathsCaseInsensitive:()=>Cye,comparePathsCaseSensitive:()=>kye,comparePatternKeys:()=>RV,compareProperties:()=>jY,compareStringsCaseInsensitive:()=>LN,compareStringsCaseInsensitiveEslintCompatible:()=>LY,compareStringsCaseSensitive:()=>wu,compareStringsCaseSensitiveUI:()=>MN,compareTextSpans:()=>c5,compareValues:()=>No,compilerOptionsAffectDeclarationPath:()=>nre,compilerOptionsAffectEmit:()=>rre,compilerOptionsAffectSemanticDiagnostics:()=>tre,compilerOptionsDidYouMeanDiagnostics:()=>l9,compilerOptionsIndicateEsModules:()=>iH,computeCommonSourceDirectoryOfFilenames:()=>Qse,computeLineAndCharacterOfPosition:()=>_E,computeLineOfPosition:()=>iP,computeLineStarts:()=>Bx,computePositionOfLineAndCharacter:()=>E5,computeSignatureWithDiagnostics:()=>zU,computeSuggestionDiagnostics:()=>UH,computedOptions:()=>ZP,concatenate:()=>$i,concatenateDiagnosticMessageChains:()=>$te,consumesNodeCoreModules:()=>eM,contains:()=>Ds,containsIgnoredPath:()=>cw,containsObjectRestOrSpread:()=>qI,containsParseError:()=>$x,containsPath:()=>Ep,convertCompilerOptionsForTelemetry:()=>vie,convertCompilerOptionsFromJson:()=>Tve,convertJsonOption:()=>dT,convertToBase64:()=>Fte,convertToJson:()=>KI,convertToObject:()=>pie,convertToOptionsWithAbsolutePaths:()=>p9,convertToRelativePath:()=>rP,convertToTSConfig:()=>bV,convertTypeAcquisitionFromJson:()=>xve,copyComments:()=>xT,copyEntries:()=>Q5,copyLeadingComments:()=>R6,copyProperties:()=>Zj,copyTrailingAsLeadingComments:()=>G3,copyTrailingComments:()=>p4,couldStartTrivia:()=>fZ,countWhere:()=>My,createAbstractBuilder:()=>w2e,createAccessorPropertyBackingField:()=>nV,createAccessorPropertyGetRedirector:()=>Vne,createAccessorPropertySetRedirector:()=>Une,createBaseNodeFactory:()=>Lre,createBinaryExpressionTrampoline:()=>e9,createBuilderProgram:()=>WU,createBuilderProgramUsingIncrementalBuildInfo:()=>bae,createBuilderStatusReporter:()=>pL,createCacheableExportInfoMap:()=>FH,createCachedDirectoryStructureHost:()=>V9,createClassifier:()=>sTe,createCommentDirectivesMap:()=>bee,createCompilerDiagnostic:()=>dc,createCompilerDiagnosticForInvalidCustomType:()=>sie,createCompilerDiagnosticFromMessageChain:()=>ZF,createCompilerHost:()=>Yse,createCompilerHostFromProgramHost:()=>cq,createCompilerHostWorker:()=>H9,createDetachedDiagnostic:()=>uk,createDiagnosticCollection:()=>WP,createDiagnosticForFileFromMessageChain:()=>OJ,createDiagnosticForNode:()=>fn,createDiagnosticForNodeArray:()=>kE,createDiagnosticForNodeArrayFromMessageChain:()=>yA,createDiagnosticForNodeFromMessageChain:()=>nh,createDiagnosticForNodeInSourceFile:()=>Pp,createDiagnosticForRange:()=>Oee,createDiagnosticMessageChainFromDiagnostic:()=>Fee,createDiagnosticReporter:()=>qk,createDocumentPositionMapper:()=>ise,createDocumentRegistry:()=>pce,createDocumentRegistryInternal:()=>jH,createEmitAndSemanticDiagnosticsBuilderProgram:()=>HU,createEmitHelperFactory:()=>Yre,createEmptyExports:()=>RI,createEvaluator:()=>Dre,createExpressionForJsxElement:()=>Pne,createExpressionForJsxFragment:()=>wne,createExpressionForObjectLiteralElementLike:()=>Nne,createExpressionForPropertyName:()=>$W,createExpressionFromEntityName:()=>jI,createExternalHelpersImportDeclarationIfNeeded:()=>YW,createFileDiagnostic:()=>Dl,createFileDiagnosticFromMessageChain:()=>aF,createFlowNode:()=>Cg,createForOfBindingStatement:()=>GW,createFutureSourceFile:()=>aM,createGetCanonicalFileName:()=>gu,createGetIsolatedDeclarationErrors:()=>Rse,createGetSourceFile:()=>DU,createGetSymbolAccessibilityDiagnosticForNode:()=>Hv,createGetSymbolAccessibilityDiagnosticForNodeName:()=>Mse,createGetSymbolWalker:()=>Uie,createIncrementalCompilerHost:()=>fL,createIncrementalProgram:()=>Aae,createJsxFactoryExpression:()=>HW,createLanguageService:()=>Vle,createLanguageServiceSourceFile:()=>CM,createMemberAccessForPropertyName:()=>fT,createModeAwareCache:()=>g6,createModeAwareCacheKey:()=>Mw,createModeMismatchDetails:()=>yJ,createModuleNotFoundChain:()=>K5,createModuleResolutionCache:()=>h6,createModuleResolutionLoader:()=>FU,createModuleResolutionLoaderUsingGlobalCache:()=>Pae,createModuleSpecifierResolutionHost:()=>Qv,createMultiMap:()=>rd,createNameResolver:()=>lW,createNodeConverters:()=>jre,createNodeFactory:()=>kI,createOptionNameMap:()=>o9,createOverload:()=>A$,createPackageJsonImportFilter:()=>j6,createPackageJsonInfo:()=>CH,createParenthesizerRules:()=>Mre,createPatternMatcher:()=>yce,createPrinter:()=>P1,createPrinterWithDefaults:()=>qse,createPrinterWithRemoveComments:()=>F2,createPrinterWithRemoveCommentsNeverAsciiEscape:()=>Hse,createPrinterWithRemoveCommentsOmitTrailingSemicolon:()=>SU,createProgram:()=>x3,createProgramDiagnostics:()=>sae,createProgramHost:()=>lq,createPropertyNameNodeForIdentifierOrLiteral:()=>yI,createQueue:()=>ON,createRange:()=>Ap,createRedirectedBuilderProgram:()=>qU,createResolutionCache:()=>XU,createRuntimeTypeSerializer:()=>vse,createScanner:()=>Yg,createSemanticDiagnosticsBuilderProgram:()=>P2e,createSet:()=>eB,createSolutionBuilder:()=>Mae,createSolutionBuilderHost:()=>Oae,createSolutionBuilderWithWatch:()=>Rae,createSolutionBuilderWithWatchHost:()=>Lae,createSortedArray:()=>qj,createSourceFile:()=>Mk,createSourceMapGenerator:()=>Zie,createSourceMapSource:()=>q0e,createSuperAccessVariableStatement:()=>z9,createSymbolTable:()=>Ys,createSymlinkCache:()=>Wz,createSyntacticTypeNodeBuilder:()=>toe,createSystemWatchFunctions:()=>iZ,createTextChange:()=>J3,createTextChangeFromStartLength:()=>BL,createTextChangeRange:()=>QN,createTextRangeFromNode:()=>tH,createTextRangeFromSpan:()=>jL,createTextSpan:()=>hu,createTextSpanFromBounds:()=>$c,createTextSpanFromNode:()=>w_,createTextSpanFromRange:()=>i0,createTextSpanFromStringLiteralLikeContent:()=>eH,createTextWriter:()=>YA,createTokenRange:()=>Dz,createTypeChecker:()=>Yie,createTypeReferenceDirectiveResolutionCache:()=>x9,createTypeReferenceResolutionLoader:()=>X9,createWatchCompilerHost:()=>B2e,createWatchCompilerHostOfConfigFile:()=>uq,createWatchCompilerHostOfFilesAndCompilerOptions:()=>_q,createWatchFactory:()=>oq,createWatchHost:()=>aq,createWatchProgram:()=>fq,createWatchStatusReporter:()=>QU,createWriteFileMeasuringIO:()=>PU,declarationNameToString:()=>ko,decodeMappings:()=>eU,decodedTextSpanIntersectsWith:()=>XN,deduplicate:()=>Hb,defaultHoverMaximumTruncationLength:()=>fee,defaultInitCompilerOptions:()=>W1e,defaultMaximumTruncationLength:()=>bP,diagnosticCategoryName:()=>DS,diagnosticToString:()=>j2,diagnosticsEqualityComparer:()=>eO,directoryProbablyExists:()=>Kd,directorySeparator:()=>Oo,displayPart:()=>lf,displayPartsToString:()=>a8,disposeEmitNodes:()=>bW,documentSpansEqual:()=>_H,dumpTracingLegend:()=>eK,elementAt:()=>z0,elideNodes:()=>Wne,emitDetachedComments:()=>xte,emitFiles:()=>vU,emitFilesAndReportErrors:()=>cL,emitFilesAndReportErrorsAndGetExitStatus:()=>sq,emitModuleKindIsNonNodeESM:()=>_I,emitNewLineBeforeLeadingCommentOfPosition:()=>Tte,emitResolverSkipsTypeChecking:()=>yU,emitSkippedWithNoDiagnostics:()=>MU,emptyArray:()=>$e,emptyFileSystemEntries:()=>Qz,emptyMap:()=>j_,emptyOptions:()=>dd,endsWith:()=>zo,ensurePathIsNonModuleName:()=>PS,ensureScriptKind:()=>uO,ensureTrailingDirectorySeparator:()=>Ll,entityNameToString:()=>Sf,enumerateInsertsAndDeletes:()=>f5,equalOwnProperties:()=>AY,equateStringsCaseInsensitive:()=>H0,equateStringsCaseSensitive:()=>Xb,equateValues:()=>q0,escapeJsxAttributeString:()=>lz,escapeLeadingUnderscores:()=>hc,escapeNonAsciiString:()=>FF,escapeSnippetText:()=>v2,escapeString:()=>Sg,escapeTemplateSubstitution:()=>oz,evaluatorResult:()=>Rl,every:()=>bi,exclusivelyPrefixedNodeCoreModules:()=>CO,executeCommandLine:()=>ySe,expandPreOrPostfixIncrementOrDecrementExpression:()=>XO,explainFiles:()=>eq,explainIfFileIsRedirectAndImpliedFormat:()=>tq,exportAssignmentIsAlias:()=>UA,expressionResultIsUnused:()=>vre,extend:()=>Kj,extensionFromPath:()=>nw,extensionIsTS:()=>mO,extensionsNotSupportingExtensionlessResolution:()=>dO,externalHelpersModuleNameText:()=>i1,factory:()=>N,fileExtensionIs:()=>fc,fileExtensionIsOneOf:()=>Jc,fileIncludeReasonToDiagnostics:()=>iq,fileShouldUseJavaScriptRequire:()=>IH,filter:()=>Fn,filterMutate:()=>Jj,filterSemanticDiagnostics:()=>Z9,find:()=>Jn,findAncestor:()=>br,findBestPatternMatch:()=>aB,findChildOfKind:()=>fo,findComputedPropertyNameCacheAssignment:()=>t9,findConfigFile:()=>CU,findConstructorDeclaration:()=>TI,findContainingList:()=>AL,findDiagnosticForNode:()=>ice,findFirstNonJsxWhitespaceToken:()=>Doe,findIndex:()=>xc,findLast:()=>qb,findLastIndex:()=>s5,findListItemInfo:()=>Eoe,findModifier:()=>O6,findNextToken:()=>R2,findPackageJson:()=>nce,findPackageJsons:()=>kH,findPrecedingMatchingToken:()=>ML,findPrecedingToken:()=>Al,findSuperStatementIndexPath:()=>L9,findTokenOnLeftOfPosition:()=>a4,findUseStrictPrologue:()=>QW,first:()=>ja,firstDefined:()=>ks,firstDefinedIterator:()=>Oy,firstIterator:()=>$j,firstOrOnly:()=>PH,firstOrUndefined:()=>ml,firstOrUndefinedIterator:()=>o5,fixupCompilerOptions:()=>$H,flatMap:()=>Ra,flatMapIterator:()=>zj,flatMapToMutable:()=>VD,flatten:()=>td,flattenCommaList:()=>qne,flattenDestructuringAssignment:()=>yT,flattenDestructuringBinding:()=>I2,flattenDiagnosticMessageText:()=>Gm,forEach:()=>fr,forEachAncestor:()=>mee,forEachAncestorDirectory:()=>nP,forEachAncestorDirectoryStoppingAtGlobalCache:()=>kg,forEachChild:()=>Os,forEachChildRecursively:()=>Lk,forEachDynamicImportOrRequireCall:()=>EO,forEachEmittedFile:()=>dU,forEachEnclosingBlockScopeContainer:()=>Nee,forEachEntry:()=>Ml,forEachExternalModuleToImportFrom:()=>LH,forEachImportClauseDeclaration:()=>nte,forEachKey:()=>th,forEachLeadingCommentRange:()=>VN,forEachNameInAccessChainWalkingLeft:()=>Vte,forEachNameOfDefaultExport:()=>cM,forEachOptionsSyntaxByName:()=>mW,forEachProjectReference:()=>pw,forEachPropertyAssignment:()=>DE,forEachResolvedProjectReference:()=>pW,forEachReturnStatement:()=>c1,forEachRight:()=>rE,forEachTrailingCommentRange:()=>UN,forEachTsConfigPropArray:()=>xA,forEachUnique:()=>pH,forEachYieldExpression:()=>jee,formatColorAndReset:()=>O2,formatDiagnostic:()=>wU,formatDiagnostics:()=>r2e,formatDiagnosticsWithColorAndContext:()=>tae,formatGeneratedName:()=>zv,formatGeneratedNamePart:()=>f6,formatLocation:()=>NU,formatMessage:()=>fk,formatStringFromArgs:()=>ch,formatting:()=>Sl,generateDjb2Hash:()=>eP,generateTSConfig:()=>die,getAdjustedReferenceLocation:()=>Hq,getAdjustedRenameLocation:()=>FL,getAliasDeclarationFromName:()=>YJ,getAllAccessorDeclarations:()=>d2,getAllDecoratorsOfClass:()=>sU,getAllDecoratorsOfClassElement:()=>R9,getAllJSDocTags:()=>F5,getAllJSDocTagsOfKind:()=>$ye,getAllKeys:()=>Khe,getAllProjectOutputs:()=>W9,getAllSuperTypeNodes:()=>MP,getAllowImportingTsExtensions:()=>Qte,getAllowJSCompilerOption:()=>m1,getAllowSyntheticDefaultImports:()=>pk,getAncestor:()=>Ev,getAnyExtensionFromPath:()=>Mx,getAreDeclarationMapsEnabled:()=>tO,getAssignedExpandoInitializer:()=>Yx,getAssignedName:()=>N5,getAssignmentDeclarationKind:()=>Xc,getAssignmentDeclarationPropertyAccessKind:()=>LA,getAssignmentTargetKind:()=>l1,getAutomaticTypeDirectiveNames:()=>S9,getBaseFileName:()=>gl,getBinaryOperatorPrecedence:()=>$A,getBuildInfo:()=>bU,getBuildInfoFileVersionMap:()=>UU,getBuildInfoText:()=>Vse,getBuildOrderFromAnyBuildOrder:()=>P3,getBuilderCreationParameters:()=>nL,getBuilderFileEmit:()=>w1,getCanonicalDiagnostic:()=>Lee,getCheckFlags:()=>Ec,getClassExtendsHeritageElement:()=>u2,getClassLikeDeclarationOfSymbol:()=>Yh,getCombinedLocalAndExportSymbolFlags:()=>zE,getCombinedModifierFlags:()=>gv,getCombinedNodeFlags:()=>Vh,getCombinedNodeFlagsAlwaysIncludeJSDoc:()=>HB,getCommentRange:()=>Bm,getCommonSourceDirectory:()=>Qw,getCommonSourceDirectoryOfConfig:()=>vT,getCompilerOptionValue:()=>iO,getConditions:()=>C1,getConfigFileParsingDiagnostics:()=>L2,getConstantValue:()=>Vre,getContainerFlags:()=>BV,getContainerNode:()=>TT,getContainingClass:()=>ou,getContainingClassExcludingClassDecorators:()=>dF,getContainingClassStaticBlock:()=>Hee,getContainingFunction:()=>Kf,getContainingFunctionDeclaration:()=>qee,getContainingFunctionOrClassStaticBlock:()=>pF,getContainingNodeArray:()=>bre,getContainingObjectLiteralElement:()=>o8,getContextualTypeFromParent:()=>$L,getContextualTypeFromParentOrAncestorTypeNode:()=>IL,getDeclarationDiagnostics:()=>jse,getDeclarationEmitExtensionForPath:()=>MF,getDeclarationEmitOutputFilePath:()=>yte,getDeclarationEmitOutputFilePathWorker:()=>LF,getDeclarationFileExtension:()=>n9,getDeclarationFromName:()=>LP,getDeclarationModifierFlagsFromSymbol:()=>Fp,getDeclarationOfKind:()=>Go,getDeclarationsOfKind:()=>pee,getDeclaredExpandoInitializer:()=>AP,getDecorators:()=>Y0,getDefaultCompilerOptions:()=>kM,getDefaultFormatCodeSettings:()=>TL,getDefaultLibFileName:()=>HN,getDefaultLibFilePath:()=>Ule,getDefaultLikeExportInfo:()=>oM,getDefaultLikeExportNameFromDeclaration:()=>wH,getDefaultResolutionModeForFileWorker:()=>K9,getDiagnosticText:()=>G_,getDiagnosticsWithinSpan:()=>sce,getDirectoryPath:()=>ti,getDirectoryToWatchFailedLookupLocation:()=>$U,getDirectoryToWatchFailedLookupLocationFromTypeRoot:()=>Eae,getDocumentPositionMapper:()=>VH,getDocumentSpansEqualityComparer:()=>fH,getESModuleInterop:()=>Rm,getEditsForFileRename:()=>mce,getEffectiveBaseTypeNode:()=>Yd,getEffectiveConstraintOfTypeParameter:()=>mE,getEffectiveContainerForJSDocTemplateTag:()=>EF,getEffectiveImplementsTypeNodes:()=>OE,getEffectiveInitializer:()=>IA,getEffectiveJSDocHost:()=>Cv,getEffectiveModifierFlags:()=>Au,getEffectiveModifierFlagsAlwaysIncludeJSDoc:()=>Dte,getEffectiveModifierFlagsNoCache:()=>Pte,getEffectiveReturnTypeNode:()=>kf,getEffectiveSetAccessorTypeAnnotationNode:()=>hz,getEffectiveTypeAnnotationNode:()=>hl,getEffectiveTypeParameterDeclarations:()=>K0,getEffectiveTypeRoots:()=>Lw,getElementOrPropertyAccessArgumentExpressionOrName:()=>CF,getElementOrPropertyAccessName:()=>Hh,getElementsOfBindingOrAssignmentPattern:()=>_6,getEmitDeclarations:()=>of,getEmitFlags:()=>Ba,getEmitHelpers:()=>SW,getEmitModuleDetectionKind:()=>Yte,getEmitModuleFormatOfFileWorker:()=>e4,getEmitModuleKind:()=>__,getEmitModuleResolutionKind:()=>$u,getEmitScriptTarget:()=>Pa,getEmitStandardClassFields:()=>Jz,getEnclosingBlockScopeContainer:()=>Qd,getEnclosingContainer:()=>sF,getEncodedSemanticClassifications:()=>MH,getEncodedSyntacticClassifications:()=>RH,getEndLinePosition:()=>pA,getEntityNameFromTypeNode:()=>EA,getEntrypointsFromPackageJsonInfo:()=>OV,getErrorCountForSummary:()=>aL,getErrorSpanForNode:()=>BS,getErrorSummaryText:()=>KU,getEscapedTextOfIdentifierOrLiteral:()=>BP,getEscapedTextOfJsxAttributeName:()=>_w,getEscapedTextOfJsxNamespacedName:()=>hk,getExpandoInitializer:()=>xv,getExportAssignmentExpression:()=>KJ,getExportInfoMap:()=>Z3,getExportNeedsImportStarHelper:()=>sse,getExpressionAssociativity:()=>sz,getExpressionPrecedence:()=>zP,getExternalHelpersModuleName:()=>JI,getExternalModuleImportEqualsDeclarationExpression:()=>PP,getExternalModuleName:()=>Zx,getExternalModuleNameFromDeclaration:()=>gte,getExternalModuleNameFromPath:()=>fz,getExternalModuleNameLiteral:()=>Ok,getExternalModuleRequireArgument:()=>zJ,getFallbackOptions:()=>b3,getFileEmitOutput:()=>aae,getFileMatcherPatterns:()=>lO,getFileNamesFromConfigSpecs:()=>Fw,getFileWatcherEventKind:()=>IB,getFilesInErrorForSummary:()=>oL,getFirstConstructorWithBody:()=>ah,getFirstIdentifier:()=>x_,getFirstNonSpaceCharacterPosition:()=>Yoe,getFirstProjectOutput:()=>hU,getFixableErrorSpanExpression:()=>EH,getFormatCodeSettingsForWriting:()=>iM,getFullWidth:()=>_A,getFunctionFlags:()=>Zc,getHeritageClause:()=>qA,getHostSignatureFromJSDoc:()=>kv,getIdentifierAutoGenerate:()=>$0e,getIdentifierGeneratedImportReference:()=>Xre,getIdentifierTypeArguments:()=>eT,getImmediatelyInvokedFunctionExpression:()=>Sv,getImpliedNodeFormatForEmitWorker:()=>bT,getImpliedNodeFormatForFile:()=>T3,getImpliedNodeFormatForFileWorker:()=>Y9,getImportNeedsImportDefaultHelper:()=>rU,getImportNeedsImportStarHelper:()=>F9,getIndentString:()=>OF,getInferredLibraryNameResolveFrom:()=>Q9,getInitializedVariables:()=>XP,getInitializerOfBinaryExpression:()=>qJ,getInitializerOfBindingOrAssignmentElement:()=>WI,getInterfaceBaseTypeNodes:()=>RP,getInternalEmitFlags:()=>Pd,getInvokedExpression:()=>hF,getIsFileExcluded:()=>uce,getIsolatedModules:()=>ud,getJSDocAugmentsTag:()=>MZ,getJSDocClassTag:()=>XB,getJSDocCommentRanges:()=>MJ,getJSDocCommentsAndTags:()=>HJ,getJSDocDeprecatedTag:()=>QB,getJSDocDeprecatedTagNoCache:()=>VZ,getJSDocEnumTag:()=>YB,getJSDocHost:()=>c2,getJSDocImplementsTags:()=>RZ,getJSDocOverloadTags:()=>$J,getJSDocOverrideTagNoCache:()=>WZ,getJSDocParameterTags:()=>dE,getJSDocParameterTagsNoCache:()=>IZ,getJSDocPrivateTag:()=>Uye,getJSDocPrivateTagNoCache:()=>BZ,getJSDocProtectedTag:()=>qye,getJSDocProtectedTagNoCache:()=>JZ,getJSDocPublicTag:()=>Vye,getJSDocPublicTagNoCache:()=>jZ,getJSDocReadonlyTag:()=>Hye,getJSDocReadonlyTagNoCache:()=>zZ,getJSDocReturnTag:()=>UZ,getJSDocReturnType:()=>KN,getJSDocRoot:()=>FE,getJSDocSatisfiesExpressionType:()=>aW,getJSDocSatisfiesTag:()=>KB,getJSDocTags:()=>yv,getJSDocTemplateTag:()=>Gye,getJSDocThisTag:()=>A5,getJSDocType:()=>By,getJSDocTypeAliasName:()=>ZW,getJSDocTypeAssertionType:()=>l6,getJSDocTypeParameterDeclarations:()=>zF,getJSDocTypeParameterTags:()=>FZ,getJSDocTypeParameterTagsNoCache:()=>OZ,getJSDocTypeTag:()=>hv,getJSXImplicitImportBase:()=>fI,getJSXRuntimeImport:()=>aO,getJSXTransformEnabled:()=>sO,getKeyForCompilerOptions:()=>wV,getLanguageVariant:()=>aI,getLastChild:()=>Fz,getLeadingCommentRanges:()=>Qg,getLeadingCommentRangesOfNode:()=>LJ,getLeftmostAccessExpression:()=>WE,getLeftmostExpression:()=>VE,getLibFileNameFromLibReference:()=>fW,getLibNameFromLibReference:()=>_W,getLibraryNameFromLibFileName:()=>OU,getLineAndCharacterOfPosition:()=>Qs,getLineInfo:()=>ZV,getLineOfLocalPosition:()=>VP,getLineStartPositionForPosition:()=>md,getLineStarts:()=>$g,getLinesBetweenPositionAndNextNonWhitespaceCharacter:()=>Jte,getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter:()=>Bte,getLinesBetweenPositions:()=>sP,getLinesBetweenRangeEndAndRangeStart:()=>Pz,getLinesBetweenRangeEndPositions:()=>x0e,getLiteralText:()=>xee,getLocalNameForExternalImport:()=>u6,getLocalSymbolForExportDefault:()=>GP,getLocaleSpecificMessage:()=>Is,getLocaleTimeString:()=>D3,getMappedContextSpan:()=>dH,getMappedDocumentSpan:()=>qL,getMappedLocation:()=>u4,getMatchedFileSpec:()=>rq,getMatchedIncludeSpec:()=>nq,getMeaningFromDeclaration:()=>kL,getMeaningFromLocation:()=>ST,getMembersOfDeclaration:()=>Bee,getModeForFileReference:()=>rae,getModeForResolutionAtIndex:()=>c2e,getModeForUsageLocation:()=>IU,getModifiedTime:()=>Lx,getModifiers:()=>Yb,getModuleInstanceState:()=>ry,getModuleNameStringLiteralAt:()=>k3,getModuleSpecifierEndingPreference:()=>ure,getModuleSpecifierResolverHost:()=>sH,getNameForExportedSymbol:()=>tM,getNameFromImportAttribute:()=>TO,getNameFromIndexInfo:()=>Aee,getNameFromPropertyName:()=>W3,getNameOfAccessExpression:()=>Lz,getNameOfCompilerOptionValue:()=>f9,getNameOfDeclaration:()=>Ss,getNameOfExpando:()=>WJ,getNameOfJSDocTypedef:()=>AZ,getNameOfScriptTarget:()=>nO,getNameOrArgument:()=>OA,getNameTable:()=>vG,getNamespaceDeclarationNode:()=>AE,getNewLineCharacter:()=>Hy,getNewLineKind:()=>K3,getNewLineOrDefaultFromHost:()=>iy,getNewTargetContainer:()=>$ee,getNextJSDocCommentLocation:()=>GJ,getNodeChildren:()=>UW,getNodeForGeneratedName:()=>UI,getNodeId:()=>Xa,getNodeKind:()=>M2,getNodeModifiers:()=>o4,getNodeModulePathParts:()=>vO,getNonAssignedNameOfDeclaration:()=>w5,getNonAssignmentOperatorForCompoundAssignment:()=>Vw,getNonAugmentationDeclaration:()=>PJ,getNonDecoratorTokenPosOfNode:()=>TJ,getNonIncrementalBuildInfoRoots:()=>Sae,getNonModifierTokenPosOfNode:()=>See,getNormalizedAbsolutePath:()=>ss,getNormalizedAbsolutePathWithoutRoot:()=>MB,getNormalizedPathComponents:()=>S5,getObjectFlags:()=>Rn,getOperatorAssociativity:()=>az,getOperatorPrecedence:()=>GA,getOptionFromName:()=>mV,getOptionsForLibraryResolution:()=>NV,getOptionsNameMap:()=>d6,getOptionsSyntaxByArrayElementValue:()=>dW,getOptionsSyntaxByValue:()=>Ire,getOrCreateEmitNode:()=>Fu,getOrUpdate:()=>UD,getOriginalNode:()=>oc,getOriginalNodeId:()=>P_,getOutputDeclarationFileName:()=>Xw,getOutputDeclarationFileNameWorker:()=>mU,getOutputExtension:()=>h3,getOutputFileNames:()=>e2e,getOutputJSFileNameWorker:()=>gU,getOutputPathsFor:()=>$w,getOwnEmitOutputFilePath:()=>hte,getOwnKeys:()=>km,getOwnValues:()=>Ax,getPackageJsonTypesVersionsPaths:()=>b9,getPackageNameFromTypesPackageName:()=>Bw,getPackageScopeForPath:()=>jw,getParameterSymbolFromJSDoc:()=>zA,getParentNodeInSpan:()=>U3,getParseTreeNode:()=>As,getParsedCommandLineOfConfigFile:()=>QI,getPathComponents:()=>Nu,getPathFromPathComponents:()=>mv,getPathUpdater:()=>JH,getPathsBasePath:()=>RF,getPatternFromSpec:()=>qz,getPendingEmitKindWithSeen:()=>rL,getPositionOfLineAndCharacter:()=>zN,getPossibleGenericSignatures:()=>$q,getPossibleOriginalInputExtensionForExtension:()=>pz,getPossibleOriginalInputPathWithoutChangingExt:()=>dz,getPossibleTypeArgumentsInfo:()=>Xq,getPreEmitDiagnostics:()=>t2e,getPrecedingNonSpaceCharacterPosition:()=>HL,getPrivateIdentifier:()=>aU,getProperties:()=>iU,getProperty:()=>IN,getPropertyAssignmentAliasLikeExpression:()=>ute,getPropertyNameForPropertyNameNode:()=>GS,getPropertyNameFromType:()=>Lp,getPropertyNameOfBindingOrAssignmentElement:()=>KW,getPropertySymbolFromBindingElement:()=>UL,getPropertySymbolsFromContextualType:()=>EM,getQuoteFromPreference:()=>oH,getQuotePreference:()=>Af,getRangesWhere:()=>Uj,getRefactorContextSpan:()=>Xk,getReferencedFileLocation:()=>Zw,getRegexFromPattern:()=>Gy,getRegularExpressionForWildcard:()=>ew,getRegularExpressionsForWildcards:()=>oO,getRelativePathFromDirectory:()=>Yf,getRelativePathFromFile:()=>uE,getRelativePathToDirectoryOrUrl:()=>jx,getRenameLocation:()=>H3,getReplacementSpanForContextToken:()=>Zq,getResolutionDiagnostic:()=>jU,getResolutionModeOverride:()=>x6,getResolveJsonModule:()=>g2,getResolvePackageJsonExports:()=>cI,getResolvePackageJsonImports:()=>lI,getResolvedExternalModuleName:()=>_z,getResolvedModuleFromResolution:()=>Gx,getResolvedTypeReferenceDirectiveFromResolution:()=>Y5,getRestIndicatorOfBindingOrAssignmentElement:()=>KO,getRestParameterElementType:()=>RJ,getRightMostAssignedExpression:()=>FA,getRootDeclaration:()=>Fm,getRootDirectoryOfResolutionCache:()=>Dae,getRootLength:()=>Gd,getScriptKind:()=>yH,getScriptKindFromFileName:()=>_O,getScriptTargetFeatures:()=>xJ,getSelectedEffectiveModifierFlags:()=>sk,getSelectedSyntacticModifierFlags:()=>Cte,getSemanticClassifications:()=>_ce,getSemanticJsxChildren:()=>ME,getSetAccessorTypeAnnotationNode:()=>bte,getSetAccessorValueParameter:()=>UP,getSetExternalModuleIndicator:()=>oI,getShebang:()=>D5,getSingleVariableOfVariableStatement:()=>rk,getSnapshotText:()=>$k,getSnippetElement:()=>TW,getSourceFileOfModule:()=>fA,getSourceFileOfNode:()=>Nr,getSourceFilePathInNewDir:()=>BF,getSourceFileVersionAsHashFromText:()=>lL,getSourceFilesToEmit:()=>jF,getSourceMapRange:()=>Qy,getSourceMapper:()=>Dce,getSourceTextOfNodeFromSourceFile:()=>r2,getSpanOfTokenAtPosition:()=>wm,getSpellingSuggestion:()=>Qb,getStartPositionOfLine:()=>s1,getStartPositionOfRange:()=>$P,getStartsOnNewLine:()=>dw,getStaticPropertiesAndClassStaticBlock:()=>M9,getStrictOptionValue:()=>Iu,getStringComparer:()=>sE,getSubPatternFromSpec:()=>cO,getSuperCallFromStatement:()=>O9,getSuperContainer:()=>kA,getSupportedCodeFixes:()=>hG,getSupportedExtensions:()=>tw,getSupportedExtensionsWithJsonIfResolveJsonModule:()=>dI,getSwitchedType:()=>SH,getSymbolId:()=>ca,getSymbolNameForPrivateIdentifier:()=>HA,getSymbolTarget:()=>vH,getSyntacticClassifications:()=>fce,getSyntacticModifierFlags:()=>Uy,getSyntacticModifierFlagsNoCache:()=>bz,getSynthesizedDeepClone:()=>so,getSynthesizedDeepCloneWithReplacements:()=>xI,getSynthesizedDeepClones:()=>b2,getSynthesizedDeepClonesWithReplacements:()=>gW,getSyntheticLeadingComments:()=>XE,getSyntheticTrailingComments:()=>PI,getTargetLabel:()=>DL,getTargetOfBindingOrAssignmentElement:()=>x1,getTemporaryModuleResolutionState:()=>Rw,getTextOfConstantValue:()=>kee,getTextOfIdentifierOrLiteral:()=>Np,getTextOfJSDocComment:()=>ZN,getTextOfJsxAttributeName:()=>SI,getTextOfJsxNamespacedName:()=>fw,getTextOfNode:()=>pc,getTextOfNodeFromSourceText:()=>TP,getTextOfPropertyName:()=>Qx,getThisContainer:()=>u_,getThisParameter:()=>f2,getTokenAtPosition:()=>Pi,getTokenPosOfNode:()=>a1,getTokenSourceMapRange:()=>H0e,getTouchingPropertyName:()=>$_,getTouchingToken:()=>I6,getTrailingCommentRanges:()=>Q0,getTrailingSemicolonDeferringWriter:()=>uz,getTransformers:()=>Jse,getTsBuildInfoEmitOutputFilePath:()=>Gv,getTsConfigObjectLiteralExpression:()=>EP,getTsConfigPropArrayElementValue:()=>fF,getTypeAnnotationNode:()=>Ste,getTypeArgumentOrTypeParameterList:()=>Ooe,getTypeKeywordOfTypeOnlyImport:()=>uH,getTypeNode:()=>Gre,getTypeNodeIfAccessible:()=>m4,getTypeParameterFromJsDoc:()=>ite,getTypeParameterOwner:()=>Bye,getTypesPackageName:()=>D9,getUILocale:()=>MY,getUniqueName:()=>kT,getUniqueSymbolId:()=>Qoe,getUseDefineForClassFields:()=>uI,getWatchErrorSummaryDiagnosticMessage:()=>YU,getWatchFactory:()=>kU,group:()=>iE,groupBy:()=>Yj,guessIndentation:()=>uee,handleNoEmitOptions:()=>RU,handleWatchOptionsConfigDirTemplateSubstitution:()=>d9,hasAbstractModifier:()=>m2,hasAccessorModifier:()=>Om,hasAmbientModifier:()=>vz,hasChangesInResolutions:()=>vJ,hasContextSensitiveParameters:()=>yO,hasDecorators:()=>ep,hasDocComment:()=>Ioe,hasDynamicName:()=>Gh,hasEffectiveModifier:()=>Cf,hasEffectiveModifiers:()=>yz,hasEffectiveReadonlyModifier:()=>XS,hasExtension:()=>lE,hasImplementationTSFileExtension:()=>cre,hasIndexSignature:()=>bH,hasInferredType:()=>kO,hasInitializer:()=>zy,hasInvalidEscape:()=>cz,hasJSDocNodes:()=>qf,hasJSDocParameterTags:()=>LZ,hasJSFileExtension:()=>lh,hasJsonModuleEmitEnabled:()=>rO,hasOnlyExpressionInitializer:()=>RS,hasOverrideModifier:()=>WF,hasPossibleExternalModuleReference:()=>wee,hasProperty:()=>go,hasPropertyAccessExpressionWithName:()=>O3,hasQuestionToken:()=>ek,hasRecordedExternalHelpers:()=>Fne,hasResolutionModeOverride:()=>Cre,hasRestParameter:()=>dJ,hasScopeMarker:()=>tee,hasStaticModifier:()=>El,hasSyntacticModifier:()=>ri,hasSyntacticModifiers:()=>kte,hasTSFileExtension:()=>YS,hasTabstop:()=>Tre,hasTrailingDirectorySeparator:()=>$0,hasType:()=>H5,hasTypeArguments:()=>p0e,hasZeroOrOneAsteriskCharacter:()=>zz,hostGetCanonicalFileName:()=>$h,hostUsesCaseSensitiveFileNames:()=>$S,idText:()=>Mn,identifierIsThisKeyword:()=>gz,identifierToKeywordKind:()=>AS,identity:()=>vo,identitySourceMapConsumer:()=>tU,ignoreSourceNewlines:()=>kW,ignoredPaths:()=>y5,importFromModuleSpecifier:()=>FP,importSyntaxAffectsModuleResolution:()=>Bz,indexOfAnyCharCode:()=>kY,indexOfNode:()=>TE,indicesOf:()=>a5,inferredTypesContainingFile:()=>Kw,injectClassNamedEvaluationHelperBlockIfMissing:()=>J9,injectClassThisAssignmentIfMissing:()=>pse,insertImports:()=>lH,insertSorted:()=>J0,insertStatementAfterCustomPrologue:()=>jS,insertStatementAfterStandardPrologue:()=>a0e,insertStatementsAfterCustomPrologue:()=>bJ,insertStatementsAfterStandardPrologue:()=>rh,intersperse:()=>xp,intrinsicTagNameToString:()=>oW,introducesArgumentsExoticObject:()=>Wee,inverseJsxOptionMap:()=>$I,isAbstractConstructorSymbol:()=>zte,isAbstractModifier:()=>sne,isAccessExpression:()=>Lo,isAccessibilityModifier:()=>Yq,isAccessor:()=>r1,isAccessorModifier:()=>NW,isAliasableExpression:()=>DF,isAmbientModule:()=>l_,isAmbientPropertyDeclaration:()=>NJ,isAnyDirectorySeparator:()=>FB,isAnyImportOrBareOrAccessedRequire:()=>Dee,isAnyImportOrReExport:()=>gA,isAnyImportOrRequireStatement:()=>Pee,isAnyImportSyntax:()=>Xx,isAnySupportedFileExtension:()=>R0e,isApplicableVersionedTypesKey:()=>i3,isArgumentExpressionOfElementAccess:()=>Jq,isArray:()=>gs,isArrayBindingElement:()=>J5,isArrayBindingOrAssignmentElement:()=>sA,isArrayBindingOrAssignmentPattern:()=>cJ,isArrayBindingPattern:()=>e0,isArrayLiteralExpression:()=>Su,isArrayLiteralOrObjectLiteralDestructuringPattern:()=>n0,isArrayTypeNode:()=>II,isArrowFunction:()=>Mo,isAsExpression:()=>e6,isAssertClause:()=>pne,isAssertEntry:()=>n1e,isAssertionExpression:()=>t2,isAssertsKeyword:()=>nne,isAssignmentDeclaration:()=>NP,isAssignmentExpression:()=>$l,isAssignmentOperator:()=>Xh,isAssignmentPattern:()=>yP,isAssignmentTarget:()=>u1,isAsteriskToken:()=>NI,isAsyncFunction:()=>jP,isAsyncModifier:()=>hw,isAutoAccessorPropertyDeclaration:()=>J_,isAwaitExpression:()=>S1,isAwaitKeyword:()=>wW,isBigIntLiteral:()=>QE,isBinaryExpression:()=>yn,isBinaryLogicalOperator:()=>KA,isBinaryOperatorToken:()=>zne,isBindableObjectDefinePropertyCall:()=>VS,isBindableStaticAccessExpression:()=>o2,isBindableStaticElementAccessExpression:()=>kF,isBindableStaticNameExpression:()=>US,isBindingElement:()=>Ia,isBindingElementOfBareOrAccessedRequire:()=>Yee,isBindingName:()=>LS,isBindingOrAssignmentElement:()=>YZ,isBindingOrAssignmentPattern:()=>nA,isBindingPattern:()=>Js,isBlock:()=>Ws,isBlockLike:()=>Qk,isBlockOrCatchScoped:()=>kJ,isBlockScope:()=>AJ,isBlockScopedContainerTopLevel:()=>Eee,isBooleanLiteral:()=>hP,isBreakOrContinueStatement:()=>pP,isBreakStatement:()=>e1e,isBuildCommand:()=>Xae,isBuildInfoFile:()=>zse,isBuilderProgram:()=>ZU,isBundle:()=>hne,isCallChain:()=>IS,isCallExpression:()=>zs,isCallExpressionTarget:()=>Oq,isCallLikeExpression:()=>e2,isCallLikeOrFunctionLikeExpression:()=>lJ,isCallOrNewExpression:()=>Dm,isCallOrNewExpressionTarget:()=>Lq,isCallSignatureDeclaration:()=>Ck,isCallToHelper:()=>gw,isCaseBlock:()=>kw,isCaseClause:()=>i6,isCaseKeyword:()=>one,isCaseOrDefaultClause:()=>U5,isCatchClause:()=>D2,isCatchClauseVariableDeclaration:()=>Sre,isCatchClauseVariableDeclarationOrBindingElement:()=>CJ,isCheckJsEnabledForFile:()=>iw,isCircularBuildOrder:()=>Hk,isClassDeclaration:()=>vl,isClassElement:()=>ll,isClassExpression:()=>el,isClassInstanceProperty:()=>XZ,isClassLike:()=>ni,isClassMemberModifier:()=>sJ,isClassNamedEvaluationHelperBlock:()=>Vk,isClassOrTypeElement:()=>B5,isClassStaticBlockDeclaration:()=>zc,isClassThisAssignmentBlock:()=>qw,isColonToken:()=>tne,isCommaExpression:()=>BI,isCommaListExpression:()=>Tw,isCommaSequence:()=>ww,isCommaToken:()=>ene,isComment:()=>RL,isCommonJsExportPropertyAssignment:()=>uF,isCommonJsExportedExpression:()=>Jee,isCompoundAssignment:()=>Ww,isComputedNonLiteralName:()=>hA,isComputedPropertyName:()=>aa,isConciseBody:()=>W5,isConditionalExpression:()=>sT,isConditionalTypeNode:()=>T2,isConstAssertion:()=>cW,isConstTypeReference:()=>Ed,isConstructSignatureDeclaration:()=>bw,isConstructorDeclaration:()=>vc,isConstructorTypeNode:()=>YE,isContextualKeyword:()=>wF,isContinueStatement:()=>Z0e,isCustomPrologue:()=>TA,isDebuggerStatement:()=>t1e,isDeclaration:()=>Gl,isDeclarationBindingElement:()=>rA,isDeclarationFileName:()=>Ql,isDeclarationName:()=>bg,isDeclarationNameOfEnumOrNamespace:()=>Nz,isDeclarationReadonly:()=>vA,isDeclarationStatement:()=>see,isDeclarationWithTypeParameterChildren:()=>FJ,isDeclarationWithTypeParameters:()=>IJ,isDecorator:()=>Pl,isDecoratorTarget:()=>Soe,isDefaultClause:()=>Cw,isDefaultImport:()=>qS,isDefaultModifier:()=>MO,isDefaultedExpandoInitializer:()=>Kee,isDeleteExpression:()=>lne,isDeleteTarget:()=>QJ,isDeprecatedDeclaration:()=>rM,isDestructuringAssignment:()=>qy,isDiskPathRoot:()=>OB,isDoStatement:()=>K0e,isDocumentRegistryEntry:()=>e8,isDotDotDotToken:()=>OO,isDottedName:()=>eI,isDynamicName:()=>AF,isEffectiveExternalModule:()=>xE,isEffectiveStrictModeSourceFile:()=>wJ,isElementAccessChain:()=>ZB,isElementAccessExpression:()=>Eo,isEmittedFileOfProgram:()=>$se,isEmptyArrayLiteral:()=>Ite,isEmptyBindingElement:()=>PZ,isEmptyBindingPattern:()=>DZ,isEmptyObjectLiteral:()=>Cz,isEmptyStatement:()=>FW,isEmptyStringLiteral:()=>JJ,isEntityName:()=>S_,isEntityNameExpression:()=>ho,isEnumConst:()=>bv,isEnumDeclaration:()=>Rv,isEnumMember:()=>ey,isEqualityOperatorKind:()=>XL,isEqualsGreaterThanToken:()=>rne,isExclamationToken:()=>AI,isExcludedFile:()=>gie,isExclusivelyTypeOnlyImportOrExport:()=>AU,isExpandoPropertyDeclaration:()=>yk,isExportAssignment:()=>Xo,isExportDeclaration:()=>rl,isExportModifier:()=>xk,isExportName:()=>QO,isExportNamespaceAsDefaultDeclaration:()=>rF,isExportOrDefaultModifier:()=>VI,isExportSpecifier:()=>Xu,isExportsIdentifier:()=>WS,isExportsOrModuleExportsOrAlias:()=>N2,isExpression:()=>mt,isExpressionNode:()=>vg,isExpressionOfExternalModuleImportEqualsDeclaration:()=>koe,isExpressionOfOptionalChainRoot:()=>L5,isExpressionStatement:()=>Xl,isExpressionWithTypeArguments:()=>Kh,isExpressionWithTypeArgumentsInClassExtendsClause:()=>UF,isExternalModule:()=>Nl,isExternalModuleAugmentation:()=>i2,isExternalModuleImportEqualsDeclaration:()=>Tv,isExternalModuleIndicator:()=>oA,isExternalModuleNameRelative:()=>ql,isExternalModuleReference:()=>Zh,isExternalModuleSymbol:()=>qx,isExternalOrCommonJsModule:()=>Tf,isFileLevelReservedGeneratedIdentifier:()=>tA,isFileLevelUniqueName:()=>eF,isFileProbablyExternalModule:()=>HI,isFirstDeclarationOfSymbolParameter:()=>mH,isFixablePromiseHandler:()=>HH,isForInOrOfStatement:()=>MS,isForInStatement:()=>zO,isForInitializer:()=>Dp,isForOfStatement:()=>OI,isForStatement:()=>Mv,isFullSourceFile:()=>ih,isFunctionBlock:()=>s2,isFunctionBody:()=>_J,isFunctionDeclaration:()=>Wc,isFunctionExpression:()=>Ao,isFunctionExpressionOrArrowFunction:()=>g1,isFunctionLike:()=>js,isFunctionLikeDeclaration:()=>bo,isFunctionLikeKind:()=>zx,isFunctionLikeOrClassStaticBlockDeclaration:()=>yE,isFunctionOrConstructorTypeNode:()=>QZ,isFunctionOrModuleBlock:()=>aJ,isFunctionSymbol:()=>tte,isFunctionTypeNode:()=>Tg,isGeneratedIdentifier:()=>rc,isGeneratedPrivateIdentifier:()=>OS,isGetAccessor:()=>Zg,isGetAccessorDeclaration:()=>Mp,isGetOrSetAccessorDeclaration:()=>eA,isGlobalScopeAugmentation:()=>yg,isGlobalSourceFile:()=>Wy,isGrammarError:()=>vee,isHeritageClause:()=>Pf,isHoistedFunction:()=>cF,isHoistedVariableStatement:()=>lF,isIdentifier:()=>je,isIdentifierANonContextualKeyword:()=>tz,isIdentifierName:()=>lte,isIdentifierOrThisTypeNode:()=>Rne,isIdentifierPart:()=>Wh,isIdentifierStart:()=>dg,isIdentifierText:()=>nf,isIdentifierTypePredicate:()=>Vee,isIdentifierTypeReference:()=>gre,isIfStatement:()=>Lv,isIgnoredFileFromWildCardWatching:()=>v3,isImplicitGlob:()=>Uz,isImportAttribute:()=>dne,isImportAttributeName:()=>$Z,isImportAttributes:()=>cT,isImportCall:()=>W_,isImportClause:()=>Id,isImportDeclaration:()=>cc,isImportEqualsDeclaration:()=>wl,isImportKeyword:()=>vw,isImportMeta:()=>EE,isImportOrExportSpecifier:()=>e1,isImportOrExportSpecifierName:()=>Xoe,isImportSpecifier:()=>d_,isImportTypeAssertionContainer:()=>r1e,isImportTypeNode:()=>Jm,isImportable:()=>OH,isInComment:()=>r0,isInCompoundLikeAssignment:()=>XJ,isInExpressionContext:()=>yF,isInJSDoc:()=>wP,isInJSFile:()=>pn,isInJSXText:()=>Aoe,isInJsonFile:()=>bF,isInNonReferenceComment:()=>Roe,isInReferenceComment:()=>Moe,isInRightSideOfInternalImportEqualsDeclaration:()=>CL,isInString:()=>Gk,isInTemplateString:()=>Gq,isInTopLevelContext:()=>mF,isInTypeQuery:()=>ik,isIncrementalBuildInfo:()=>C3,isIncrementalBundleEmitBuildInfo:()=>mae,isIncrementalCompilation:()=>h2,isIndexSignatureDeclaration:()=>b1,isIndexedAccessTypeNode:()=>x2,isInferTypeNode:()=>rT,isInfinityOrNaNString:()=>lw,isInitializedProperty:()=>l3,isInitializedVariable:()=>sI,isInsideJsxElement:()=>LL,isInsideJsxElementOrAttribute:()=>Noe,isInsideNodeModules:()=>X3,isInsideTemplateLiteral:()=>B3,isInstanceOfExpression:()=>qF,isInstantiatedModule:()=>$V,isInterfaceDeclaration:()=>lu,isInternalDeclaration:()=>_ee,isInternalModuleImportEqualsDeclaration:()=>zS,isInternalName:()=>XW,isIntersectionTypeNode:()=>Pk,isIntrinsicJsxName:()=>RE,isIterationStatement:()=>n1,isJSDoc:()=>nm,isJSDocAllType:()=>bne,isJSDocAugmentsTag:()=>Fk,isJSDocAuthorTag:()=>o1e,isJSDocCallbackTag:()=>RW,isJSDocClassTag:()=>Tne,isJSDocCommentContainingNode:()=>q5,isJSDocConstructSignature:()=>tk,isJSDocDeprecatedTag:()=>WW,isJSDocEnumTag:()=>MI,isJSDocFunctionType:()=>a6,isJSDocImplementsTag:()=>GO,isJSDocImportTag:()=>qm,isJSDocIndexSignature:()=>TF,isJSDocLikeText:()=>iV,isJSDocLink:()=>yne,isJSDocLinkCode:()=>vne,isJSDocLinkLike:()=>Ux,isJSDocLinkPlain:()=>s1e,isJSDocMemberName:()=>Jv,isJSDocNameReference:()=>Ew,isJSDocNamepathType:()=>a1e,isJSDocNamespaceBody:()=>Zye,isJSDocNode:()=>vE,isJSDocNonNullableType:()=>VO,isJSDocNullableType:()=>s6,isJSDocOptionalParameter:()=>bO,isJSDocOptionalType:()=>MW,isJSDocOverloadTag:()=>o6,isJSDocOverrideTag:()=>qO,isJSDocParameterTag:()=>rp,isJSDocPrivateTag:()=>BW,isJSDocPropertyLikeTag:()=>dP,isJSDocPropertyTag:()=>xne,isJSDocProtectedTag:()=>JW,isJSDocPublicTag:()=>jW,isJSDocReadonlyTag:()=>zW,isJSDocReturnTag:()=>HO,isJSDocSatisfiesExpression:()=>sW,isJSDocSatisfiesTag:()=>$O,isJSDocSeeTag:()=>c1e,isJSDocSignature:()=>t0,isJSDocTag:()=>bE,isJSDocTemplateTag:()=>fd,isJSDocThisTag:()=>VW,isJSDocThrowsTag:()=>u1e,isJSDocTypeAlias:()=>od,isJSDocTypeAssertion:()=>P2,isJSDocTypeExpression:()=>Bv,isJSDocTypeLiteral:()=>uT,isJSDocTypeTag:()=>Dw,isJSDocTypedefTag:()=>_T,isJSDocUnknownTag:()=>l1e,isJSDocUnknownType:()=>Sne,isJSDocVariadicType:()=>UO,isJSXTagName:()=>wE,isJsonEqual:()=>gO,isJsonSourceFile:()=>wp,isJsxAttribute:()=>Um,isJsxAttributeLike:()=>V5,isJsxAttributeName:()=>kre,isJsxAttributes:()=>E2,isJsxCallLike:()=>lee,isJsxChild:()=>uA,isJsxClosingElement:()=>C2,isJsxClosingFragment:()=>gne,isJsxElement:()=>Vm,isJsxExpression:()=>n6,isJsxFragment:()=>jv,isJsxNamespacedName:()=>rm,isJsxOpeningElement:()=>tm,isJsxOpeningFragment:()=>Fd,isJsxOpeningLikeElement:()=>Gu,isJsxOpeningLikeElementTagName:()=>Toe,isJsxSelfClosingElement:()=>lT,isJsxSpreadAttribute:()=>Ik,isJsxTagNameExpression:()=>vP,isJsxText:()=>Sk,isJumpStatementTarget:()=>L3,isKeyword:()=>U_,isKeywordOrPunctuation:()=>PF,isKnownSymbol:()=>JP,isLabelName:()=>jq,isLabelOfLabeledStatement:()=>Rq,isLabeledStatement:()=>T1,isLateVisibilityPaintedStatement:()=>iF,isLeftHandSideExpression:()=>z_,isLet:()=>oF,isLineBreak:()=>Uu,isLiteralComputedPropertyDeclarationName:()=>VA,isLiteralExpression:()=>FS,isLiteralExpressionOfObject:()=>nJ,isLiteralImportTypeNode:()=>qh,isLiteralKind:()=>mP,isLiteralNameOfPropertyDeclarationOrIndexAccess:()=>PL,isLiteralTypeLiteral:()=>eee,isLiteralTypeNode:()=>Zy,isLocalName:()=>ty,isLogicalOperator:()=>wte,isLogicalOrCoalescingAssignmentExpression:()=>Sz,isLogicalOrCoalescingAssignmentOperator:()=>qP,isLogicalOrCoalescingBinaryExpression:()=>ZA,isLogicalOrCoalescingBinaryOperator:()=>VF,isMappedTypeNode:()=>iT,isMemberName:()=>Kg,isMetaProperty:()=>aT,isMethodDeclaration:()=>yc,isMethodOrAccessor:()=>Wx,isMethodSignature:()=>Ad,isMinusToken:()=>PW,isMissingDeclaration:()=>i1e,isMissingPackageJsonInfo:()=>Die,isModifier:()=>fa,isModifierKind:()=>t1,isModifierLike:()=>nc,isModuleAugmentationExternal:()=>DJ,isModuleBlock:()=>zm,isModuleBody:()=>ree,isModuleDeclaration:()=>tl,isModuleExportName:()=>WO,isModuleExportsAccessExpression:()=>sh,isModuleIdentifier:()=>VJ,isModuleName:()=>Jne,isModuleOrEnumDeclaration:()=>cA,isModuleReference:()=>oee,isModuleSpecifierLike:()=>VL,isModuleWithStringLiteralName:()=>nF,isNameOfFunctionDeclaration:()=>Wq,isNameOfModuleDeclaration:()=>zq,isNamedDeclaration:()=>Hl,isNamedEvaluation:()=>xf,isNamedEvaluationSource:()=>rz,isNamedExportBindings:()=>tJ,isNamedExports:()=>jp,isNamedImportBindings:()=>fJ,isNamedImports:()=>Wm,isNamedImportsOrExports:()=>YF,isNamedTupleMember:()=>KE,isNamespaceBody:()=>Kye,isNamespaceExport:()=>xg,isNamespaceExportDeclaration:()=>LI,isNamespaceImport:()=>ph,isNamespaceReexportDeclaration:()=>Qee,isNewExpression:()=>k2,isNewExpressionTarget:()=>i4,isNewScopeNode:()=>Are,isNoSubstitutionTemplateLiteral:()=>tT,isNodeArray:()=>Kb,isNodeArrayMultiLine:()=>jte,isNodeDescendantOf:()=>l2,isNodeKind:()=>R5,isNodeLikeSystem:()=>lB,isNodeModulesDirectory:()=>x5,isNodeWithPossibleHoistedDeclaration:()=>ote,isNonContextualKeyword:()=>ez,isNonGlobalAmbientModule:()=>EJ,isNonNullAccess:()=>xre,isNonNullChain:()=>M5,isNonNullExpression:()=>Nk,isNonStaticMethodOrAccessorWithPrivateName:()=>ase,isNotEmittedStatement:()=>mne,isNullishCoalesce:()=>eJ,isNumber:()=>V0,isNumericLiteral:()=>H_,isNumericLiteralName:()=>_h,isObjectBindingElementWithoutPropertyName:()=>V3,isObjectBindingOrAssignmentElement:()=>iA,isObjectBindingOrAssignmentPattern:()=>oJ,isObjectBindingPattern:()=>tp,isObjectLiteralElement:()=>pJ,isObjectLiteralElementLike:()=>Uh,isObjectLiteralExpression:()=>Ca,isObjectLiteralMethod:()=>ad,isObjectLiteralOrClassExpressionMethodOrAccessor:()=>_F,isObjectTypeDeclaration:()=>lk,isOmittedExpression:()=>jl,isOptionalChain:()=>qu,isOptionalChainRoot:()=>_P,isOptionalDeclaration:()=>gk,isOptionalJSDocPropertyLikeTag:()=>bI,isOptionalTypeNode:()=>RO,isOuterExpression:()=>YO,isOutermostOptionalChain:()=>fP,isOverrideModifier:()=>ane,isPackageJsonInfo:()=>T9,isPackedArrayLiteral:()=>nW,isParameter:()=>Wi,isParameterPropertyDeclaration:()=>bf,isParameterPropertyModifier:()=>gP,isParenthesizedExpression:()=>E_,isParenthesizedTypeNode:()=>nT,isParseTreeNode:()=>uP,isPartOfParameterDeclaration:()=>Dv,isPartOfTypeNode:()=>Am,isPartOfTypeOnlyImportOrExportDeclaration:()=>GZ,isPartOfTypeQuery:()=>vF,isPartiallyEmittedExpression:()=>une,isPatternMatch:()=>l5,isPinnedComment:()=>tF,isPlainJsFile:()=>SP,isPlusToken:()=>DW,isPossiblyTypeArgumentPosition:()=>j3,isPostfixUnaryExpression:()=>IW,isPrefixUnaryExpression:()=>Ov,isPrimitiveLiteralValue:()=>xO,isPrivateIdentifier:()=>zi,isPrivateIdentifierClassElementDeclaration:()=>Hu,isPrivateIdentifierPropertyAccessExpression:()=>hE,isPrivateIdentifierSymbol:()=>fte,isProgramUptoDate:()=>LU,isPrologueDirective:()=>Nm,isPropertyAccessChain:()=>O5,isPropertyAccessEntityNameExpression:()=>tI,isPropertyAccessExpression:()=>jn,isPropertyAccessOrQualifiedName:()=>aA,isPropertyAccessOrQualifiedNameOrImportTypeNode:()=>KZ,isPropertyAssignment:()=>bl,isPropertyDeclaration:()=>hs,isPropertyName:()=>cl,isPropertyNameLiteral:()=>Im,isPropertySignature:()=>p_,isPrototypeAccess:()=>p1,isPrototypePropertyAssignment:()=>MA,isPunctuation:()=>ZJ,isPushOrUnshiftIdentifier:()=>nz,isQualifiedName:()=>k_,isQuestionDotToken:()=>LO,isQuestionOrExclamationToken:()=>Mne,isQuestionOrPlusOrMinusToken:()=>Bne,isQuestionToken:()=>v1,isReadonlyKeyword:()=>ine,isReadonlyKeywordOrPlusOrMinusToken:()=>jne,isRecognizedTripleSlashComment:()=>SJ,isReferenceFileLocation:()=>k6,isReferencedFile:()=>$v,isRegularExpressionLiteral:()=>CW,isRequireCall:()=>V_,isRequireVariableStatement:()=>NA,isRestParameter:()=>gg,isRestTypeNode:()=>jO,isReturnStatement:()=>Hf,isReturnStatementWithFixablePromiseHandler:()=>uM,isRightSideOfAccessExpression:()=>kz,isRightSideOfInstanceofExpression:()=>Ate,isRightSideOfPropertyAccess:()=>w6,isRightSideOfQualifiedName:()=>xoe,isRightSideOfQualifiedNameOrPropertyAccess:()=>HP,isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName:()=>Nte,isRootedDiskPath:()=>vf,isSameEntityName:()=>NE,isSatisfiesExpression:()=>t6,isSemicolonClassElement:()=>_ne,isSetAccessor:()=>Pm,isSetAccessorDeclaration:()=>cf,isShiftOperatorOrHigher:()=>tV,isShorthandAmbientModuleSymbol:()=>mA,isShorthandPropertyAssignment:()=>Ou,isSideEffectImport:()=>uW,isSignedNumericLiteral:()=>NF,isSimpleCopiableExpression:()=>A2,isSimpleInlineableExpression:()=>Hm,isSimpleParameterList:()=>u3,isSingleOrDoubleQuote:()=>AA,isSolutionConfig:()=>kV,isSourceElement:()=>Ere,isSourceFile:()=>ji,isSourceFileFromLibrary:()=>B6,isSourceFileJS:()=>T_,isSourceFileNotJson:()=>SF,isSourceMapping:()=>nse,isSpecialPropertyDeclaration:()=>ete,isSpreadAssignment:()=>dh,isSpreadElement:()=>Rp,isStatement:()=>Ii,isStatementButNotDeclaration:()=>lA,isStatementOrBlock:()=>aee,isStatementWithLocals:()=>yee,isStatic:()=>Ks,isStaticModifier:()=>kk,isString:()=>Ps,isStringANonContextualKeyword:()=>nk,isStringAndEmptyAnonymousObjectIntersection:()=>Loe,isStringDoubleQuoted:()=>xF,isStringLiteral:()=>Ta,isStringLiteralLike:()=>Ya,isStringLiteralOrJsxExpression:()=>cee,isStringLiteralOrTemplate:()=>Zoe,isStringOrNumericLiteralLike:()=>Zf,isStringOrRegularExpressionOrTemplateLiteral:()=>Qq,isStringTextContainingNode:()=>iJ,isSuperCall:()=>JS,isSuperKeyword:()=>yw,isSuperProperty:()=>sf,isSupportedSourceFileName:()=>Xz,isSwitchStatement:()=>xw,isSyntaxList:()=>c6,isSyntheticExpression:()=>Y0e,isSyntheticReference:()=>Ak,isTagName:()=>Bq,isTaggedTemplateExpression:()=>Fv,isTaggedTemplateTag:()=>boe,isTemplateExpression:()=>JO,isTemplateHead:()=>Tk,isTemplateLiteral:()=>Vx,isTemplateLiteralKind:()=>Z0,isTemplateLiteralToken:()=>qZ,isTemplateLiteralTypeNode:()=>cne,isTemplateLiteralTypeSpan:()=>AW,isTemplateMiddle:()=>EW,isTemplateMiddleOrTemplateTail:()=>j5,isTemplateSpan:()=>r6,isTemplateTail:()=>FO,isTextWhiteSpaceLike:()=>zoe,isThis:()=>N6,isThisContainerOrFunctionBlock:()=>Gee,isThisIdentifier:()=>f1,isThisInTypeQuery:()=>p2,isThisInitializedDeclaration:()=>gF,isThisInitializedObjectBindingExpression:()=>Xee,isThisProperty:()=>CA,isThisTypeNode:()=>Sw,isThisTypeParameter:()=>uw,isThisTypePredicate:()=>Uee,isThrowStatement:()=>LW,isToken:()=>Jx,isTokenKind:()=>rJ,isTraceEnabled:()=>k1,isTransientSymbol:()=>eh,isTrivia:()=>LE,isTryStatement:()=>oT,isTupleTypeNode:()=>Dk,isTypeAlias:()=>BA,isTypeAliasDeclaration:()=>_d,isTypeAssertionExpression:()=>BO,isTypeDeclaration:()=>mk,isTypeElement:()=>Zb,isTypeKeyword:()=>c4,isTypeKeywordTokenOrIdentifier:()=>JL,isTypeLiteralNode:()=>C_,isTypeNode:()=>vi,isTypeNodeKind:()=>Oz,isTypeOfExpression:()=>ZE,isTypeOnlyExportDeclaration:()=>HZ,isTypeOnlyImportDeclaration:()=>gE,isTypeOnlyImportOrExportDeclaration:()=>Jy,isTypeOperatorNode:()=>Iv,isTypeParameterDeclaration:()=>$o,isTypePredicateNode:()=>Ek,isTypeQueryNode:()=>S2,isTypeReferenceNode:()=>Df,isTypeReferenceType:()=>G5,isTypeUsableAsPropertyName:()=>Op,isUMDExportSymbol:()=>QF,isUnaryExpression:()=>uJ,isUnaryExpressionWithWrite:()=>ZZ,isUnicodeIdentifierStart:()=>C5,isUnionTypeNode:()=>Ky,isUrl:()=>aZ,isValidBigIntString:()=>hO,isValidESSymbolDeclaration:()=>zee,isValidTypeOnlyAliasUseSite:()=>wv,isValueSignatureDeclaration:()=>HS,isVarAwaitUsing:()=>bA,isVarConst:()=>CE,isVarConstLike:()=>Ree,isVarUsing:()=>SA,isVariableDeclaration:()=>ui,isVariableDeclarationInVariableStatement:()=>CP,isVariableDeclarationInitializedToBareOrAccessedRequire:()=>a2,isVariableDeclarationInitializedToRequire:()=>wA,isVariableDeclarationList:()=>cu,isVariableLike:()=>kP,isVariableStatement:()=>Qc,isVoidExpression:()=>wk,isWatchSet:()=>Az,isWhileStatement:()=>OW,isWhiteSpaceLike:()=>Xg,isWhiteSpaceSingleLine:()=>Em,isWithStatement:()=>fne,isWriteAccess:()=>ck,isWriteOnlyAccess:()=>XF,isYieldExpression:()=>FI,jsxModeNeedsExplicitImport:()=>AH,keywordPart:()=>If,last:()=>Da,lastOrUndefined:()=>Jo,length:()=>Ar,libMap:()=>lV,libs:()=>i9,lineBreakPart:()=>M6,loadModuleFromGlobalCache:()=>Bie,loadWithModeAwareCache:()=>S3,makeIdentifierFromModuleName:()=>Cee,makeImport:()=>A1,makeStringLiteral:()=>l4,mangleScopedPackageName:()=>v6,map:()=>kr,mapAllOrFail:()=>Wj,mapDefined:()=>Vi,mapDefinedIterator:()=>B0,mapEntries:()=>DY,mapIterator:()=>WD,mapOneOrMany:()=>DH,mapToDisplayParts:()=>I1,matchFiles:()=>Hz,matchPatternOrExact:()=>Yz,matchedText:()=>zY,matchesExclude:()=>h9,matchesExcludeWorker:()=>y9,maxBy:()=>tB,maybeBind:()=>qs,maybeSetLocalizedDiagnosticMessages:()=>Gte,memoize:()=>c_,memoizeOne:()=>Cm,min:()=>rB,minAndMax:()=>pre,missingFileModifiedTime:()=>yf,modifierToFlag:()=>ak,modifiersToFlags:()=>Lm,moduleExportNameIsDefault:()=>hg,moduleExportNameTextEscaped:()=>n2,moduleExportNameTextUnescaped:()=>o1,moduleOptionDeclaration:()=>Kne,moduleResolutionIsEqualTo:()=>gee,moduleResolutionNameAndModeGetter:()=>$9,moduleResolutionOptionDeclarations:()=>_V,moduleResolutionSupportsPackageJsonExportsAndImports:()=>UE,moduleResolutionUsesNodeModules:()=>zL,moduleSpecifierToValidIdentifier:()=>Y3,moduleSpecifiers:()=>ny,moduleSupportsImportAttributes:()=>ere,moduleSymbolToValidIdentifier:()=>Q3,moveEmitHelpers:()=>qre,moveRangeEnd:()=>GF,moveRangePastDecorators:()=>Qh,moveRangePastModifiers:()=>Mm,moveRangePos:()=>Pv,moveSyntheticComments:()=>Wre,mutateMap:()=>YP,mutateMapSkippingNewValues:()=>oh,needsParentheses:()=>GL,needsScopeMarker:()=>z5,newCaseClauseTracker:()=>sM,newPrivateEnvironment:()=>cse,noEmitNotification:()=>d3,noEmitSubstitution:()=>Gw,noTransformers:()=>Bse,noTruncationMaximumTruncationLength:()=>gJ,nodeCanBeDecorated:()=>DA,nodeCoreModules:()=>$E,nodeHasName:()=>YN,nodeIsDecorated:()=>PE,nodeIsMissing:()=>Cc,nodeIsPresent:()=>sd,nodeIsSynthesized:()=>Co,nodeModuleNameResolver:()=>Iie,nodeModulesPathPart:()=>mh,nodeNextJsonConfigResolver:()=>Fie,nodeOrChildIsDecorated:()=>PA,nodeOverlapsWithStartEnd:()=>wL,nodePosToString:()=>r0e,nodeSeenTracker:()=>F6,nodeStartsNewLexicalEnvironment:()=>iz,noop:()=>ao,noopFileWatcher:()=>D6,normalizePath:()=>sa,normalizeSlashes:()=>au,normalizeSpans:()=>qB,not:()=>_5,notImplemented:()=>na,notImplementedResolver:()=>Use,nullNodeConverters:()=>Bre,nullParenthesizerRules:()=>Rre,nullTransformationContext:()=>g3,objectAllocator:()=>vu,operatorPart:()=>_4,optionDeclarations:()=>Od,optionMapToObject:()=>_9,optionsAffectingProgramStructure:()=>rie,optionsForBuild:()=>pV,optionsForWatch:()=>jk,optionsHaveChanges:()=>Hx,or:()=>hf,orderedRemoveItem:()=>HD,orderedRemoveItemAt:()=>G0,packageIdToPackageName:()=>Z5,packageIdToString:()=>vv,parameterIsThisKeyword:()=>_1,parameterNamePart:()=>Voe,parseBaseNodeFactory:()=>Hne,parseBigInt:()=>mre,parseBuildCommand:()=>uie,parseCommandLine:()=>cie,parseCommandLineWorker:()=>dV,parseConfigFileTextToJson:()=>gV,parseConfigFileWithSystem:()=>wae,parseConfigHostFromCompilerHostLike:()=>eL,parseCustomTypeOption:()=>c9,parseIsolatedEntityName:()=>Rk,parseIsolatedJSDocComment:()=>$ne,parseJSDocTypeExpressionForTests:()=>O1e,parseJsonConfigFileContent:()=>uve,parseJsonSourceFileConfigFileContent:()=>ZI,parseJsonText:()=>GI,parseListTypeOption:()=>aie,parseNodeFactory:()=>Wv,parseNodeModuleFromPath:()=>n3,parsePackageName:()=>C9,parsePseudoBigInt:()=>aw,parseValidBigInt:()=>tW,pasteEdits:()=>N$,patchWriteFileEnsuringDirectory:()=>sZ,pathContainsNodeModules:()=>E1,pathIsAbsolute:()=>tP,pathIsBareSpecifier:()=>LB,pathIsRelative:()=>Uf,patternText:()=>JY,performIncrementalCompilation:()=>Nae,performance:()=>XY,positionBelongsToNode:()=>Vq,positionIsASICandidate:()=>QL,positionIsSynthesized:()=>Zd,positionsAreOnSameLine:()=>Ip,preProcessFile:()=>vTe,probablyUsesSemicolons:()=>$3,processCommentPragmas:()=>oV,processPragmasIntoFields:()=>cV,processTaggedTemplateExpression:()=>lU,programContainsEsModules:()=>Boe,programContainsModules:()=>joe,projectReferenceIsEqualTo:()=>hJ,propertyNamePart:()=>Uoe,pseudoBigIntToString:()=>y2,punctuationPart:()=>Ku,pushIfUnique:()=>kp,quote:()=>d4,quotePreferenceFromString:()=>aH,rangeContainsPosition:()=>A6,rangeContainsPositionExclusive:()=>M3,rangeContainsRange:()=>q_,rangeContainsRangeExclusive:()=>Coe,rangeContainsStartEnd:()=>R3,rangeEndIsOnSameLineAsRangeStart:()=>iI,rangeEndPositionsAreOnSameLine:()=>Mte,rangeEquals:()=>Gj,rangeIsOnSingleLine:()=>QS,rangeOfNode:()=>Zz,rangeOfTypeParameters:()=>eW,rangeOverlapsWithStartEnd:()=>s4,rangeStartIsOnSameLineAsRangeEnd:()=>Rte,rangeStartPositionsAreOnSameLine:()=>$F,readBuilderProgram:()=>_L,readConfigFile:()=>YI,readJson:()=>JE,readJsonConfigFile:()=>_ie,readJsonOrUndefined:()=>Ez,reduceEachLeadingCommentRange:()=>dZ,reduceEachTrailingCommentRange:()=>mZ,reduceLeft:()=>o_,reduceLeftIterator:()=>j0,reducePathComponents:()=>Rx,refactor:()=>Kk,regExpEscape:()=>A0e,regularExpressionFlagToCharacterCode:()=>Nye,relativeComplement:()=>PY,removeAllComments:()=>EI,removeEmitHelper:()=>G0e,removeExtension:()=>gI,removeFileExtension:()=>f_,removeIgnoredPath:()=>iL,removeMinAndVersionNumbers:()=>sB,removePrefix:()=>GD,removeSuffix:()=>aE,removeTrailingDirectorySeparator:()=>jy,repeatString:()=>z3,replaceElement:()=>Qj,replaceFirstStar:()=>KS,resolutionExtensionIsTSOrJson:()=>rw,resolveConfigFileProjectName:()=>pq,resolveJSModule:()=>wie,resolveLibrary:()=>k9,resolveModuleName:()=>mT,resolveModuleNameFromCache:()=>Jve,resolvePackageNameToPackageJson:()=>PV,resolvePath:()=>X0,resolveProjectReferencePath:()=>Uk,resolveTripleslashReference:()=>EU,resolveTypeReferenceDirective:()=>Cie,resolvingEmptyArray:()=>mJ,returnFalse:()=>Jh,returnNoopFileWatcher:()=>t4,returnTrue:()=>Gb,returnUndefined:()=>$b,returnsPromise:()=>qH,rewriteModuleSpecifier:()=>zk,sameFlatMap:()=>CY,sameMap:()=>dl,sameMapping:()=>Nbe,scanTokenAtPosition:()=>Mee,scanner:()=>uu,semanticDiagnosticsOptionDeclarations:()=>Zne,serializeCompilerOptions:()=>SV,server:()=>bAe,servicesVersion:()=>cke,setCommentRange:()=>yl,setConfigFileInOptions:()=>TV,setConstantValue:()=>Ure,setEmitFlags:()=>hn,setGetSourceFileAsHashVersioned:()=>uL,setIdentifierAutoGenerate:()=>wI,setIdentifierGeneratedImportReference:()=>$re,setIdentifierTypeArguments:()=>Yy,setInternalEmitFlags:()=>DI,setLocalizedDiagnosticMessages:()=>Hte,setNodeChildren:()=>kne,setNodeFlags:()=>yre,setObjectAllocator:()=>qte,setOriginalNode:()=>On,setParent:()=>Za,setParentRecursive:()=>Nv,setPrivateIdentifier:()=>hT,setSnippetElement:()=>xW,setSourceMapRange:()=>Aa,setStackTraceLimit:()=>pye,setStartsOnNewLine:()=>wO,setSyntheticLeadingComments:()=>Av,setSyntheticTrailingComments:()=>vk,setSys:()=>vye,setSysLog:()=>rZ,setTextRange:()=>pt,setTextRangeEnd:()=>GE,setTextRangePos:()=>ow,setTextRangePosEnd:()=>em,setTextRangePosWidth:()=>rW,setTokenSourceMapRange:()=>zre,setTypeNode:()=>Hre,setUILocale:()=>RY,setValueDeclaration:()=>IP,shouldAllowImportingTsExtension:()=>b6,shouldPreserveConstEnums:()=>d1,shouldRewriteModuleSpecifier:()=>jA,shouldUseUriStyleNodeCoreModules:()=>nM,showModuleSpecifier:()=>Wte,signatureHasRestParameter:()=>Yu,signatureToDisplayParts:()=>hH,single:()=>Xj,singleElementArray:()=>Ox,singleIterator:()=>EY,singleOrMany:()=>fg,singleOrUndefined:()=>Gg,skipAlias:()=>yu,skipConstraint:()=>nH,skipOuterExpressions:()=>Fc,skipParentheses:()=>Ka,skipPartiallyEmittedExpressions:()=>Dd,skipTrivia:()=>Sa,skipTypeChecking:()=>HE,skipTypeCheckingIgnoringNoCheck:()=>dre,skipTypeParentheses:()=>OP,skipWhile:()=>VY,sliceAfter:()=>Kz,some:()=>_t,sortAndDeduplicate:()=>qD,sortAndDeduplicateDiagnostics:()=>pE,sourceFileAffectingCompilerOptions:()=>fV,sourceFileMayBeEmitted:()=>_2,sourceMapCommentRegExp:()=>YV,sourceMapCommentRegExpDontCareLineStart:()=>ese,spacePart:()=>Pc,spanMap:()=>Vj,startEndContainsRange:()=>wz,startEndOverlapsWithStartEnd:()=>NL,startOnNewLine:()=>Qu,startTracing:()=>ZY,startsWith:()=>Yi,startsWithDirectory:()=>jB,startsWithUnderscore:()=>NH,startsWithUseStrict:()=>Ane,stringContainsAt:()=>ace,stringToToken:()=>wS,stripQuotes:()=>cd,supportedDeclarationExtensions:()=>pO,supportedJSExtensionsFlat:()=>qE,supportedLocaleDirectories:()=>NZ,supportedTSExtensionsFlat:()=>Gz,supportedTSImplementationExtensions:()=>pI,suppressLeadingAndTrailingTrivia:()=>Ef,suppressLeadingTrivia:()=>hW,suppressTrailingTrivia:()=>Fre,symbolEscapedNameNoDefault:()=>WL,symbolName:()=>kc,symbolNameNoDefault:()=>cH,symbolToDisplayParts:()=>f4,sys:()=>Ol,sysLog:()=>BN,tagNamesAreEquivalent:()=>Uv,takeWhile:()=>cB,targetOptionDeclaration:()=>uV,targetToLibMap:()=>gZ,testFormatSettings:()=>zSe,textChangeRangeIsUnchanged:()=>CZ,textChangeRangeNewSpan:()=>lP,textChanges:()=>mn,textOrKeywordPart:()=>gH,textPart:()=>ap,textRangeContainsPositionInclusive:()=>GN,textRangeContainsTextSpan:()=>vZ,textRangeIntersectsWithTextSpan:()=>xZ,textSpanContainsPosition:()=>VB,textSpanContainsTextRange:()=>UB,textSpanContainsTextSpan:()=>yZ,textSpanEnd:()=>gc,textSpanIntersection:()=>kZ,textSpanIntersectsWith:()=>$N,textSpanIntersectsWithPosition:()=>TZ,textSpanIntersectsWithTextSpan:()=>SZ,textSpanIsEmpty:()=>hZ,textSpanOverlap:()=>bZ,textSpanOverlapsWith:()=>jye,textSpansEqual:()=>L6,textToKeywordObj:()=>k5,timestamp:()=>To,toArray:()=>Ix,toBuilderFileEmit:()=>yae,toBuilderStateFileInfoForMultiEmit:()=>hae,toEditorSettings:()=>s8,toFileNameLowerCase:()=>U0,toPath:()=>xo,toProgramEmitPending:()=>vae,toSorted:()=>gf,tokenIsIdentifierOrKeyword:()=>B_,tokenIsIdentifierOrKeywordOrGreaterThan:()=>cZ,tokenToString:()=>ia,trace:()=>ls,tracing:()=>dn,tracingEnabled:()=>jN,transferSourceFileChildren:()=>Cne,transform:()=>yke,transformClassFields:()=>yse,transformDeclarations:()=>pU,transformECMAScriptModule:()=>fU,transformES2015:()=>Ise,transformES2016:()=>Ase,transformES2017:()=>Tse,transformES2018:()=>xse,transformES2019:()=>kse,transformES2020:()=>Cse,transformES2021:()=>Ese,transformESDecorators:()=>Sse,transformESNext:()=>Dse,transformGenerators:()=>Fse,transformImpliedNodeFormatDependentModule:()=>Lse,transformJsx:()=>Nse,transformLegacyDecorators:()=>bse,transformModule:()=>_U,transformNamedEvaluation:()=>wf,transformNodes:()=>m3,transformSystemModule:()=>Ose,transformTypeScript:()=>hse,transpile:()=>PTe,transpileDeclaration:()=>ETe,transpileModule:()=>wce,transpileOptionValueCompilerOptions:()=>nie,tryAddToSet:()=>Ry,tryAndIgnoreErrors:()=>ZL,tryCast:()=>$n,tryDirectoryExists:()=>KL,tryExtractTSExtension:()=>HF,tryFileExists:()=>g4,tryGetClassExtendingExpressionWithTypeArguments:()=>Tz,tryGetClassImplementingOrExtendingExpressionWithTypeArguments:()=>xz,tryGetDirectories:()=>YL,tryGetExtensionFromPath:()=>uh,tryGetImportFromModuleSpecifier:()=>RA,tryGetJSDocSatisfiesTypeNode:()=>SO,tryGetModuleNameFromFile:()=>zI,tryGetModuleSpecifierFromDeclaration:()=>Kx,tryGetNativePerformanceHooks:()=>$Y,tryGetPropertyAccessOrIdentifierToString:()=>rI,tryGetPropertyNameOfBindingOrAssignmentElement:()=>ZO,tryGetSourceMappingURL:()=>tse,tryGetTextOfPropertyName:()=>xP,tryParseJson:()=>nI,tryParsePattern:()=>dk,tryParsePatterns:()=>hI,tryParseRawSourceMap:()=>rse,tryReadDirectory:()=>xH,tryReadFile:()=>Iw,tryRemoveDirectoryPrefix:()=>Vz,tryRemoveExtension:()=>fre,tryRemovePrefix:()=>oB,tryRemoveSuffix:()=>BY,tscBuildOption:()=>pT,typeAcquisitionDeclarations:()=>a9,typeAliasNamePart:()=>qoe,typeDirectiveIsEqualTo:()=>hee,typeKeywords:()=>rH,typeParameterNamePart:()=>Hoe,typeToDisplayParts:()=>q3,unchangedPollThresholds:()=>h5,unchangedTextChangeRange:()=>P5,unescapeLeadingUnderscores:()=>Ai,unmangleScopedPackageName:()=>s3,unorderedRemoveItem:()=>Fx,unprefixedNodeCoreModules:()=>Nre,unreachableCodeIsError:()=>Kte,unsetNodeChildren:()=>qW,unusedLabelIsError:()=>Zte,unwrapInnermostStatementOfLabel:()=>jJ,unwrapParenthesizedExpression:()=>Pre,updateErrorForNoInputFiles:()=>g9,updateLanguageServiceSourceFile:()=>yG,updateMissingFilePathsWatch:()=>xU,updateResolutionField:()=>m6,updateSharedExtendedConfigFileWatcher:()=>U9,updateSourceFile:()=>sV,updateWatchingWildcardDirectories:()=>y3,usingSingleLineStringWriter:()=>SE,utf16EncodeAsString:()=>cP,validateLocaleAndSetLanguage:()=>GB,version:()=>io,versionMajorMinor:()=>ba,visitArray:()=>Jw,visitCommaListElements:()=>c3,visitEachChild:()=>Dr,visitFunctionBody:()=>sp,visitIterationBody:()=>D_,visitLexicalEnvironment:()=>QV,visitNode:()=>et,visitNodes:()=>Br,visitParameterList:()=>Dc,walkUpBindingElementsAndPatterns:()=>NS,walkUpOuterExpressions:()=>Ine,walkUpParenthesizedExpressions:()=>wd,walkUpParenthesizedTypes:()=>WA,walkUpParenthesizedTypesAndGetParentAndChild:()=>cte,whitespaceOrMapCommentRegExp:()=>KV,writeCommentRange:()=>BE,writeFile:()=>JF,writeFileEnsuringDirectories:()=>mz,zipWith:()=>Ly}),B.exports=li(cs);var ba="5.9",io="5.9.3",Vu=((e)=>(e[e.LessThan=-1]="LessThan",e[e.EqualTo=0]="EqualTo",e[e.GreaterThan=1]="GreaterThan",e))(Vu||{}),$e=[],j_=new Map;function Ar(e){return e!==void 0?e.length:0}function fr(e,t){if(e!==void 0)for(let n=0;n=0;n--){let i=t(e[n],n);if(i)return i}return}function ks(e,t){if(e===void 0)return;for(let n=0;n=0;i--){let s=e[i];if(t(s,i))return s}return}function xc(e,t,n){if(e===void 0)return-1;for(let i=n??0;i=0;i--)if(t(e[i],i))return i;return-1}function Ds(e,t,n=q0){if(e!==void 0){for(let i=0;i{let[o,c]=t(s,i);n.set(o,c)}),n}function _t(e,t){if(e!==void 0)if(t!==void 0){for(let n=0;n0;return!1}function Uj(e,t,n){let i;for(let s=0;se[c])}function qOe(e,t){let n=[];for(let i=0;i0&&i(t,e[c-1]))return!1;if(c0)E.assertGreaterThanOrEqual(n(t[o],t[o-1]),0);t:for(let c=s;sc)E.assertGreaterThanOrEqual(n(e[s],e[s-1]),0);switch(n(t[o],e[s])){case-1:i.push(t[o]);continue e;case 0:continue e;case 1:continue t}}}return i}function Lr(e,t){if(t===void 0)return e;if(e===void 0)return[t];return e.push(t),e}function wx(e,t){if(e===void 0)return t;if(t===void 0)return e;if(gs(e))return gs(t)?$i(e,t):Lr(e,t);if(gs(t))return Lr(t,e);return[e,t]}function wY(e,t){return t<0?e.length+t:t}function Wn(e,t,n,i){if(t===void 0||t.length===0)return e;if(e===void 0)return t.slice(n,i);n=n===void 0?0:wY(t,n),i=i===void 0?t.length:wY(t,i);for(let s=n;sn(e[i],e[s])||No(i,s))}function gf(e,t){return e.length===0?$e:e.slice().sort(t)}function*Hj(e){for(let t=e.length-1;t>=0;t--)yield e[t]}function Gj(e,t,n,i){while(ne==null?void 0:e.at(t):(e,t)=>{if(e!==void 0){if(t=wY(e,t),t>1),u=n(e[_],_);switch(i(u,t)){case-1:o=_+1;break;case 0:return _;case 1:c=_-1;break}}return~o}function o_(e,t,n,i,s){if(e&&e.length>0){let o=e.length;if(o>0){let c=i===void 0||i<0?0:i,_=s===void 0||c+s>o-1?o-1:c+s,u;if(arguments.length<=2)u=e[c],c++;else u=n;while(c<=_)u=t(u,e[c],c),c++;return u}}return n}var fv=Object.prototype.hasOwnProperty;function go(e,t){return fv.call(e,t)}function IN(e,t){return fv.call(e,t)?e[t]:void 0}function km(e){let t=[];for(let n in e)if(fv.call(e,n))t.push(n);return t}function Khe(e){let t=[];do{let n=Object.getOwnPropertyNames(e);for(let i of n)kp(t,i)}while(e=Object.getPrototypeOf(e));return t}function Ax(e){let t=[];for(let n in e)if(fv.call(e,n))t.push(e[n]);return t}function NY(e,t){let n=Array(e);for(let i=0;i100&&n>t.length>>1){let _=t.length-n;t.copyWithin(0,n),t.length=_,n=0}return c}return{enqueue:s,dequeue:o,isEmpty:i}}function eB(e,t){let n=new Map,i=0;function*s(){for(let c of n.values())if(gs(c))yield*c;else yield c}let o={has(c){let _=e(c);if(!n.has(_))return!1;let u=n.get(_);if(gs(u))return Ds(u,c,t);return t(u,c)},add(c){let _=e(c);if(n.has(_)){let u=n.get(_);if(gs(u)){if(!Ds(u,c,t))u.push(c),i++}else{let g=u;if(!t(g,c))n.set(_,[g,c]),i++}}else n.set(_,c),i++;return this},delete(c){let _=e(c);if(!n.has(_))return!1;let u=n.get(_);if(gs(u)){for(let g=0;gs(),[Symbol.toStringTag]:n[Symbol.toStringTag]};return o}function gs(e){return Array.isArray(e)}function Ix(e){return gs(e)?e:[e]}function Ps(e){return typeof e==="string"}function V0(e){return typeof e==="number"}function $n(e,t){return e!==void 0&&t(e)?e:void 0}function ra(e,t){if(e!==void 0&&t(e))return e;return E.fail(`Invalid cast. The supplied value ${e} did not pass the test '${E.getFunctionName(t)}'.`)}function ao(e){}function Jh(){return!1}function Gb(){return!0}function $b(){return}function vo(e){return e}function QOe(e){return e.toLowerCase()}var Zhe=/[^\u0130\u0131\u00DFa-z0-9\\/:\-_. ]+/g;function U0(e){return Zhe.test(e)?e.replace(Zhe,QOe):e}function na(){throw Error("Not implemented")}function c_(e){let t;return()=>{if(e)t=e(),e=void 0;return t}}function Cm(e){let t=new Map;return(n)=>{let i=`${typeof n}:${n}`,s=t.get(i);if(s===void 0&&!t.has(i))s=e(n),t.set(i,s);return s}}var OY=((e)=>(e[e.None=0]="None",e[e.Normal=1]="Normal",e[e.Aggressive=2]="Aggressive",e[e.VeryAggressive=3]="VeryAggressive",e))(OY||{});function q0(e,t){return e===t}function H0(e,t){return e===t||e!==void 0&&t!==void 0&&e.toUpperCase()===t.toUpperCase()}function Xb(e,t){return q0(e,t)}function eye(e,t){return e===t?0:e===void 0?-1:t===void 0?1:et(n,i)===-1?n:i)}function LN(e,t){if(e===t)return 0;if(e===void 0)return-1;if(t===void 0)return 1;return e=e.toUpperCase(),t=t.toUpperCase(),et?1:0}function LY(e,t){if(e===t)return 0;if(e===void 0)return-1;if(t===void 0)return 1;return e=e.toLowerCase(),t=t.toLowerCase(),et?1:0}function wu(e,t){return eye(e,t)}function sE(e){return e?LN:wu}var YOe=(()=>{return t;function e(n,i,s){if(n===i)return 0;if(n===void 0)return-1;if(i===void 0)return 1;let o=s(n,i);return o<0?-1:o>0?1:0}function t(n){let i=new Intl.Collator(n,{usage:"sort",sensitivity:"variant",numeric:!0}).compare;return(s,o)=>e(s,o,i)}})(),nB,iB;function MY(){return iB}function RY(e){if(iB!==e)iB=e,nB=void 0}function MN(e,t){return nB??(nB=YOe(iB)),nB(e,t)}function jY(e,t,n,i){return e===t?0:e===void 0?-1:t===void 0?1:i(e[n],t[n])}function pv(e,t){return No(e?1:0,t?1:0)}function Qb(e,t,n){let i=Math.max(2,Math.floor(e.length*0.34)),s=Math.floor(e.length*0.4)+1,o;for(let c of t){let _=n(c);if(_!==void 0&&Math.abs(_.length-e.length)<=i){if(_===e)continue;if(_.length<3&&_.toLowerCase()!==e.toLowerCase())continue;let u=KOe(e,_,s-0.1);if(u===void 0)continue;E.assert(un?_-n:1),d=Math.floor(t.length>n+_?n+_:t.length);s[0]=_;let h=_;for(let T=1;Tn)return;let S=i;i=s,s=S}let c=i[t.length];return c>n?void 0:c}function zo(e,t,n){let i=e.length-t.length;return i>=0&&(n?H0(e.slice(i),t):e.indexOf(t,i)===i)}function aE(e,t){return zo(e,t)?e.slice(0,e.length-t.length):e}function BY(e,t){return zo(e,t)?e.slice(0,e.length-t.length):void 0}function sB(e){let t=e.length;for(let n=t-1;n>0;n--){let i=e.charCodeAt(n);if(i>=48&&i<=57)do--n,i=e.charCodeAt(n);while(n>0&&i>=48&&i<=57);else if(n>4&&(i===110||i===78)){if(--n,i=e.charCodeAt(n),i!==105&&i!==73)break;if(--n,i=e.charCodeAt(n),i!==109&&i!==77)break;--n,i=e.charCodeAt(n)}else break;if(i!==45&&i!==46)break;t=n}return t===e.length?e:e.slice(0,t)}function HD(e,t){for(let n=0;nn===t)}function ZOe(e,t){for(let n=0;n