candor-ts 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/package.json +2 -2
- package/query.mjs +1 -1
- package/scan.mjs +31 -2
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ chains by hand.
|
|
|
12
12
|
> **Already installed? Report the version and ask before upgrading — before you scan.** If this
|
|
13
13
|
> project already has candor (a `.candor/` report directory, or `candor-ts` reachable via `npx`/a
|
|
14
14
|
> global install), do this *first*: run `npx -y candor-ts --version` (offline) and **tell the user
|
|
15
|
-
> plainly which version they're on** — e.g. *"This project is on candor-ts `<version>` (spec 0.
|
|
15
|
+
> plainly which version they're on** — e.g. *"This project is on candor-ts `<version>` (spec 0.14)."*
|
|
16
16
|
> On a build too old for the flag, read `candor.version` / `candor.spec` from an existing
|
|
17
17
|
> `.candor/report*.json`, or `npm ls -g candor-ts`.
|
|
18
18
|
>
|
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ pure-vs-Unknown ruling (PART 16) — the engines must answer identically, on eve
|
|
|
184
184
|
| A call resolving to a *type* (function-typed field/param) → `Unknown`, never silent-pure | SPEC §4 |
|
|
185
185
|
| Unmatched external calls contribute nothing (curated-classifier caveat) | SEMANTICS §8 C1 |
|
|
186
186
|
| The literal surfaces `hosts`/`cmds`/`paths`/`tables`, literal-read only | SPEC §2 |
|
|
187
|
-
| `{ candor: { version, toolchain, spec: "0.
|
|
187
|
+
| `{ candor: { version, toolchain, spec: "0.14" }, functions }` envelope; pure fns omitted | SPEC §2/§2.1 |
|
|
188
188
|
| Call-graph sidecar with **every** analyzed function a key | SPEC §2.2 |
|
|
189
189
|
| The gate: AS-EFF-006 / 008 / 009, loud on an unreadable policy | SPEC §6.2 |
|
|
190
190
|
|
|
@@ -202,7 +202,7 @@ read the Rust source".
|
|
|
202
202
|
|
|
203
203
|
## Status
|
|
204
204
|
|
|
205
|
-
0.
|
|
205
|
+
0.14.x, speaking candor-spec 0.14: the analysis core, the gate (`--policy` / `--gate-json` /
|
|
206
206
|
`.candor/config`), the full §3.1 query surface (including `containment`, `blindspots`, the
|
|
207
207
|
`--include-unknown` dispatch frontier), the MCP server, the LSP server, and the watch loop are
|
|
208
208
|
real, behaviorally tested (`npm test` — the behavioral suite across six harnesses), **soundness-fuzzed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "candor-ts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.14)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@types/node": "^25.9.2",
|
package/query.mjs
CHANGED
|
@@ -94,7 +94,7 @@ function renderPathHuman(fns, cg, fnQ, eff) {
|
|
|
94
94
|
// package.json; SPEC_VERSION is the spec contract this build speaks. Reused, never re-littered.
|
|
95
95
|
const QUERY_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
96
96
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(QUERY_DIR, "package.json"), "utf8")).version;
|
|
97
|
-
const SPEC_VERSION = "0.
|
|
97
|
+
const SPEC_VERSION = "0.14";
|
|
98
98
|
|
|
99
99
|
// ---- the §3.3.1 canonical query grammar (⟨0.10⟩, additive over 0.9) --------------------------------
|
|
100
100
|
// One shape for every verb: `<verb> <verb-args…> [--report <locator>] [--policy <file>] [--json]
|
package/scan.mjs
CHANGED
|
@@ -41,7 +41,7 @@ const ENGINE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
41
41
|
// literal stamped into the envelope's `spec` field, so the doc lines and the report can never drift.
|
|
42
42
|
// Reused, never re-littered.
|
|
43
43
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(ENGINE_DIR, "package.json"), "utf8")).version;
|
|
44
|
-
const SPEC_VERSION = "0.
|
|
44
|
+
const SPEC_VERSION = "0.14";
|
|
45
45
|
|
|
46
46
|
// --version: a print-and-exit MODE, handled before the main arg walk so it never depends on a target.
|
|
47
47
|
// Fully OFFLINE — candor never phones home. Staying current is the AGENT's job: read the installed
|
|
@@ -1091,6 +1091,29 @@ function enumerateGetters(owner, type) {
|
|
|
1091
1091
|
}
|
|
1092
1092
|
}
|
|
1093
1093
|
|
|
1094
|
+
// The synthesized `<module>` unit for a source file's TOP-LEVEL executable statements (spec §2
|
|
1095
|
+
// unitKind "initializer" — java's `<clinit>` twin). Top-level `await fetch(…)`, a bare
|
|
1096
|
+
// `readFileSync(…)`, an IIFE, `export const r = await fetch(…)` execute at MODULE-LOAD time and
|
|
1097
|
+
// belong to nothing named — without this unit their effects reached the SourceFile in `enclosing`,
|
|
1098
|
+
// resolved to `null`, and were DROPPED → a false "pure" verdict (the cardinal sin: ESM top-level
|
|
1099
|
+
// await / serverless handler files / side-effecting config modules scanned as functions: []). This
|
|
1100
|
+
// is the field-initializer `Class.constructor` synthesis (~scan.mjs:774) one level up: the module
|
|
1101
|
+
// body is the file's own initializer. Minted LAZILY — only when a top-level statement actually
|
|
1102
|
+
// attributes an effect/edge here — so a pure top-level never gains a unit (pure units are omitted).
|
|
1103
|
+
// The qual mirrors sibling top-level units (`moduleOf(sf).<module>`); the bare local is `<module>`.
|
|
1104
|
+
function moduleUnit(sf) {
|
|
1105
|
+
const mod = moduleOf(sf);
|
|
1106
|
+
const qual = `${mod}.<module>`;
|
|
1107
|
+
let rec = fns.get(qual);
|
|
1108
|
+
if (!rec) {
|
|
1109
|
+
rec = { local: "<module>", direct: new Set(), edges: new Set(), hosts: new Set(), tables: new Set(),
|
|
1110
|
+
cmds: new Set(), paths: new Set(), blind: new Set(), incomplete: new Set(), why: new Set(),
|
|
1111
|
+
entry: false, unitKind: "initializer",
|
|
1112
|
+
loc: `${path.relative(rootDir, sf.fileName)}:1:1` };
|
|
1113
|
+
fns.set(qual, rec);
|
|
1114
|
+
}
|
|
1115
|
+
return qual;
|
|
1116
|
+
}
|
|
1094
1117
|
// nearest enclosing analyzed function (closures attribute to it — SEMANTICS §2)
|
|
1095
1118
|
function enclosing(node) {
|
|
1096
1119
|
for (let p = node; p; p = p.parent) {
|
|
@@ -1104,6 +1127,9 @@ function enclosing(node) {
|
|
|
1104
1127
|
if (ts.isDecorator(p)) return null;
|
|
1105
1128
|
const n = nodeName.get(p);
|
|
1106
1129
|
if (n) return n;
|
|
1130
|
+
// Reached the SourceFile with no named unit: a TOP-LEVEL executable statement. Attribute to the
|
|
1131
|
+
// file's synthesized `<module>` initializer unit (minted lazily here) rather than dropping it.
|
|
1132
|
+
if (ts.isSourceFile(p)) return moduleUnit(p);
|
|
1107
1133
|
}
|
|
1108
1134
|
return null;
|
|
1109
1135
|
}
|
|
@@ -2236,7 +2262,10 @@ for (const [name, rec] of fns) {
|
|
|
2236
2262
|
// them are NOT in `inferred`, so it is a LOWER BOUND when this is non-empty. Omitted when none.
|
|
2237
2263
|
if (rec.blind.size) entry.invisible = [...rec.blind].sort();
|
|
2238
2264
|
if (rec.entry) entry.entryPoint = true;
|
|
2239
|
-
|
|
2265
|
+
// unitKind (spec §2, informative — per-unit, not by name): the synthesized `<module>` initializer
|
|
2266
|
+
// carries its own kind (set at mint), a CJS export is tagged "export".
|
|
2267
|
+
if (rec.unitKind) entry.unitKind = rec.unitKind;
|
|
2268
|
+
else if (rec.isCjsExport) entry.unitKind = "export"; // spec 0.5 draft, informative — per-unit, not by name
|
|
2240
2269
|
functions.push(entry);
|
|
2241
2270
|
}
|
|
2242
2271
|
// `package` names what this report COVERS — a consumer chaining it registers coverage even when
|