candor-ts 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scan.mjs +39 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "candor-ts",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "candor for TypeScript — per-function side effects, transitively, with a policy gate (candor-spec 0.4)",
5
5
  "type": "module",
6
6
  "dependencies": {
package/scan.mjs CHANGED
@@ -29,37 +29,37 @@ import { parsePolicy, evaluatePolicy } from "./policy.mjs";
29
29
  const ENGINE_DIR = path.dirname(fileURLToPath(import.meta.url));
30
30
 
31
31
  // ---- args ----------------------------------------------------------------------------------------
32
+ // ONE pass: the first non-flag is the target; value-taking flags consume the next arg and FAIL on a
33
+ // missing/flag-shaped value; an unknown flag fails; flags may precede the target. `--agents` is a
34
+ // flag (a print-and-exit MODE) — it must NOT fire when it is the VALUE of --out/--policy, which the
35
+ // value-consuming skip handles, nor produce a "lying unknown flag" error for a real flag given first.
36
+ const usage = "usage: candor-ts <dir | file.ts | tsconfig.json> [--out <prefix>] [--policy <file>] [--allow-js] [--agents]";
32
37
  const argv = process.argv.slice(2);
33
- if (argv.includes("--agents")) {
34
- // The agent contract for THE INSTALLED VERSION — AGENTS.md ships in the npm tarball, so doc
35
- // and engine cannot drift (the spec §2.1 version-trust rule applied to documentation).
38
+ let target = null, outPrefix = null, policyPath = process.env.CANDOR_POLICY ?? null, allowJs = false, wantAgents = false;
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ if (a === "--agents") wantAgents = true;
42
+ else if (a === "--allow-js") allowJs = true;
43
+ else if (a === "--out" || a === "--policy") {
44
+ const v = argv[i + 1];
45
+ if (v === undefined || v.startsWith("--")) { console.error(`candor-ts: ${a} requires a value (${usage})`); process.exit(2); }
46
+ if (a === "--out") outPrefix = v; else policyPath = v;
47
+ i++;
48
+ }
49
+ else if (a.startsWith("--")) { console.error(`candor-ts: unknown flag ${a} (${usage})`); process.exit(2); }
50
+ else if (target === null) target = a;
51
+ else if (outPrefix === null) outPrefix = a; // legacy positional prefix
52
+ else { console.error(`candor-ts: unexpected extra argument ${a} (${usage})`); process.exit(2); }
53
+ }
54
+ if (wantAgents) {
55
+ // The agent contract for THE INSTALLED VERSION — AGENTS.md ships in the npm tarball, so doc and
56
+ // engine cannot drift (the spec §2.1 version-trust rule applied to documentation).
36
57
  const semver = JSON.parse(fs.readFileSync(path.join(ENGINE_DIR, "package.json"), "utf8")).version;
37
58
  console.log(`<!-- candor-ts ${semver} · the agent contract for this installed version -->`);
38
59
  process.stdout.write(fs.readFileSync(path.join(ENGINE_DIR, "AGENTS.md"), "utf8"));
39
60
  process.exit(0);
40
61
  }
41
- if (argv.length === 0) {
42
- console.error("usage: node scan.mjs <dir | file.ts | tsconfig.json> [--out <prefix>] [--policy <file>] [--agents]");
43
- process.exit(2);
44
- }
45
- const target = argv[0];
46
- let outPrefix = null, policyPath = process.env.CANDOR_POLICY ?? null, allowJs = false;
47
- for (let i = 1; i < argv.length; i++) {
48
- if (argv[i] === "--out") outPrefix = argv[++i];
49
- else if (argv[i] === "--policy") policyPath = argv[++i];
50
- else if (argv[i] === "--allow-js") allowJs = true;
51
- else if (argv[i].startsWith("--")) {
52
- // An unknown flag must FAIL, not be silently ignored: a typo'd --policy drops the gate; an
53
- // agent following a newer doc against an older binary deserves a loud "upgrade me" signal.
54
- console.error(`candor-ts: unknown flag ${argv[i]} (usage: candor-ts <dir> [--out <prefix>] [--policy <file>] [--allow-js] [--agents])`);
55
- process.exit(2);
56
- }
57
- else if (!outPrefix) outPrefix = argv[i]; // legacy positional prefix
58
- }
59
- if (target.startsWith("--")) {
60
- console.error(`candor-ts: unknown flag ${target} (see usage)`);
61
- process.exit(2);
62
- }
62
+ if (target === null) { console.error(usage); process.exit(2); }
63
63
 
64
64
  // ---- project discovery (a dir, a single file, or a tsconfig) --------------------------------------
65
65
  function isTestPath(p) {
@@ -180,7 +180,10 @@ fs.mkdirSync(path.dirname(path.resolve(outPrefix)), { recursive: true });
180
180
  // scan and a .d.ts resolution). Version-aware trust (§2.1): a report from a DIFFERENT engine
181
181
  // version is downgraded to Unknown rather than silently trusted. Duplicate hashes (two same-named
182
182
  // exports in one package) UNION — a sound over-approximation, documented.
183
- const ENGINE_VERSION = "candor-ts-0.4.3";
183
+ // ONE version source: package.json. A second hardcoded literal (the envelope's, the --agents
184
+ // banner's) that drifted from this would make the engine distrust its OWN reports at the §2.1
185
+ // staleness check (`d.candor?.version !== ENGINE_VERSION`), silently downgrading every chained dep.
186
+ const ENGINE_VERSION = `candor-ts-${JSON.parse(fs.readFileSync(path.join(ENGINE_DIR, "package.json"), "utf8")).version}`;
184
187
  const crossDeps = new Map(); // hash -> {inferred:Set, hosts:[], cmds:[], paths:[], tables:[]}
185
188
  // Packages a loaded sibling report COVERS — exempt from the κ ledger even when a call joins no
186
189
  // entry (reports omit pure functions: the silence is the purity claim, SPEC §2 rule 3 — the
@@ -377,9 +380,13 @@ function moduleOf(sf) {
377
380
  const rel = path.relative(rootDir, path.resolve(sf.fileName)).replace(/\.[mc]?[tj]sx?$/, "");
378
381
  return rel.split(path.sep).join(".");
379
382
  }
380
- const cjsLocal = new Set(); // CJS export-surface units (spec 0.5 draft unitKind: "export")
381
- const markCjs = (v) => { if (v) cjsLocal.add(v); return v; };
383
+ // `_lastCjs` is set by markCjs when localName() returns a CJS export-surface name, read right after
384
+ // the call to tag THAT unit (spec 0.5 draft unitKind: "export"). Keyed to the unit, not a project-
385
+ // wide name set — a same-named ordinary TS function in another file must NOT be mislabeled.
386
+ let _lastCjs = false;
387
+ const markCjs = (v) => { if (v) _lastCjs = true; return v; };
382
388
  function localName(node) {
389
+ _lastCjs = false;
383
390
  if (ts.isFunctionDeclaration(node) && node.name) return node.name.text;
384
391
  if (ts.isMethodDeclaration(node) && ts.isClassDeclaration(node.parent) && node.parent.name)
385
392
  return `${node.parent.name.text}.${node.name.getText()}`;
@@ -477,11 +484,12 @@ for (const sf of sources) {
477
484
  nodeName.set(node, ctorQual);
478
485
  }
479
486
  const n = localName(node);
487
+ const isCjsExport = _lastCjs; // captured immediately: localName set it for THIS node only
480
488
  if (n) {
481
489
  const qual = `${mod}.${n}`;
482
490
  const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart());
483
491
  fns.set(qual, { local: n, direct: new Set(), edges: new Set(), hosts: new Set(), tables: new Set(),
484
- cmds: new Set(), paths: new Set(), why: new Set(), entry: false,
492
+ cmds: new Set(), paths: new Set(), why: new Set(), entry: false, isCjsExport,
485
493
  loc: `${path.relative(rootDir, sf.fileName)}:${line + 1}:${character + 1}` });
486
494
  nodeName.set(node, qual);
487
495
  if ((ts.isVariableDeclaration(node) || ts.isPropertyDeclaration(node)) && node.initializer)
@@ -822,12 +830,12 @@ for (const [name, rec] of fns) {
822
830
  if (inf.includes("Fs") && rec.paths.size) entry.paths = [...rec.paths].sort();
823
831
  if (rec.direct.has("Unknown") && rec.why.size) entry.unknownWhy = [...rec.why].sort();
824
832
  if (rec.entry) entry.entryPoint = true;
825
- if (cjsLocal.has(rec.local)) entry.unitKind = "export"; // spec 0.5 draft, informative
833
+ if (rec.isCjsExport) entry.unitKind = "export"; // spec 0.5 draft, informative — per-unit, not by name
826
834
  functions.push(entry);
827
835
  }
828
836
  // `package` names what this report COVERS — a consumer chaining it registers coverage even when
829
837
  // `functions` is empty (an all-pure package's report is its purity claim, SPEC §2 rule 3).
830
- const envelope = { candor: { version: "candor-ts-0.4.3", toolchain: `node-${process.versions.node}`, spec: "0.4" },
838
+ const envelope = { candor: { version: ENGINE_VERSION, toolchain: `node-${process.versions.node}`, spec: "0.4" },
831
839
  package: pkgName, functions };
832
840
  fs.writeFileSync(`${outPrefix}.json`, JSON.stringify(envelope, null, 1));
833
841
  const cg = {};