no-mistakes 0.103.0 → 0.105.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.
@@ -113,3 +113,6 @@ export function generatePostgresCatalog(
113
113
  export function analyzeTypeScriptModules(
114
114
  options: WithInvocationOptions<TypeScriptModulesOptions>,
115
115
  ): Promise<WithInvocationResult<TypeScriptModulesReport>>;
116
+
117
+ /** Read-only report of the machine-wide invocation lock; never waits and takes no lock. */
118
+ export function lockStatus(): Promise<import("./invocation-types").LockStatus>;
package/index.js CHANGED
@@ -4,6 +4,7 @@ const { resolveNativePackage } = require("./scripts/native-package");
4
4
  const addonPath = process.env.NO_MISTAKES_TEST_NAPI_ADDON_PATH || resolveNativePackage().addonPath;
5
5
  const native = require(addonPath);
6
6
  const planning = require("./planning");
7
+ const { decorateInvocationError } = require("./invocation-errors");
7
8
  const { writePlanningImpactArtifacts: writeArtifacts } = require("./planning-impact-artifacts");
8
9
  const { createPlanningArtifactLock } = require("./planning-impact-artifacts-lock");
9
10
  const { createWorkflowTopologyIndex } = require("./workflow-topology-index");
@@ -11,7 +12,11 @@ const path = require("node:path");
11
12
 
12
13
  async function callJson(fn, options) {
13
14
  const input = Buffer.from(JSON.stringify(options || {}));
14
- return JSON.parse(await fn(input));
15
+ try {
16
+ return JSON.parse(await fn(input));
17
+ } catch (error) {
18
+ throw decorateInvocationError(error);
19
+ }
15
20
  }
16
21
 
17
22
  function createJsonApis(descriptors) {
@@ -50,6 +55,7 @@ const jsonApis = createJsonApis({
50
55
  infraOutputs: "infraOutputsJson",
51
56
  infraResourceRefs: "infraResourceRefsJson",
52
57
  infraTestFor: "infraTestForJson",
58
+ lockStatus: "lockStatusJson",
53
59
  lockfileDiff: "lockfileDiffJson",
54
60
  validateMermaidMarkdown: "validateMermaidMarkdownJson",
55
61
  playwrightCheck: "playwrightCheckJson",
@@ -127,6 +133,7 @@ module.exports.writePlanningImpactArtifacts = writePlanningImpactArtifacts;
127
133
  module.exports.callSites = jsonApis.callSites;
128
134
  module.exports.check = jsonApis.check;
129
135
  module.exports.resolveConfig = jsonApis.resolveConfig;
136
+ module.exports.lockStatus = jsonApis.lockStatus;
130
137
  module.exports.ciEnv = jsonApis.ciEnv;
131
138
  module.exports.ciImpact = jsonApis.ciImpact;
132
139
  module.exports.ciTopology = ciTopology;
package/index.mjs CHANGED
@@ -31,6 +31,7 @@ export const {
31
31
  infraOutputs,
32
32
  infraResourceRefs,
33
33
  infraTestFor,
34
+ lockStatus,
34
35
  lockfileDiff,
35
36
  playwrightCheck,
36
37
  playwrightEdges,
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ const ERROR_JSON_MARKER = "\nno-mistakes-error-json: ";
4
+
5
+ // Lock and deadline failures carry a machine-readable report after a marker line.
6
+ // Expose it as `error.code` ("NO_MISTAKES_LOCK_BUSY", "NO_MISTAKES_LOCK_TIMEOUT",
7
+ // "NO_MISTAKES_DEADLINE") and `error.details` without changing the message text.
8
+ function decorateInvocationError(error) {
9
+ const message = error && typeof error.message === "string" ? error.message : "";
10
+ const at = message.indexOf(ERROR_JSON_MARKER);
11
+ if (at < 0) return error;
12
+ try {
13
+ const details = JSON.parse(message.slice(at + ERROR_JSON_MARKER.length));
14
+ error.message = message.slice(0, at);
15
+ error.code = `NO_MISTAKES_${String(details.kind).toUpperCase()}`;
16
+ error.details = details;
17
+ } catch {
18
+ // Keep the native error as-is when the report cannot be parsed.
19
+ }
20
+ return error;
21
+ }
22
+
23
+ module.exports = { decorateInvocationError };
@@ -81,3 +81,48 @@ export interface DiagnosticsProgressSnapshot {
81
81
  export function diagnosticsSnapshot(options: {
82
82
  id: string;
83
83
  }): Promise<DiagnosticsProgressSnapshot | null>;
84
+
85
+ /** Who holds the machine-wide invocation lock, as recorded by the holder. */
86
+ export interface LockHolder {
87
+ pid: number;
88
+ /** Working directory of the holder; a pid alone is ambiguous across sandboxes. */
89
+ cwd: string | null;
90
+ /** Executable basename for new records; legacy records may contain arguments. Empty for pid-only records. */
91
+ command: string;
92
+ /** Unix epoch milliseconds when the holder acquired the lock; 0 when unknown. */
93
+ startedAtMs: number;
94
+ /** Whole seconds the lock has been held; null when the start time is unknown. */
95
+ ageSeconds: number | null;
96
+ }
97
+
98
+ /** Result of `lockStatus()` / `no-mistakes lock status --format json`. */
99
+ export interface LockStatus {
100
+ state: "held" | "free";
101
+ lockFile: string;
102
+ /** Present only while the lock is held. */
103
+ holder: LockHolder | null;
104
+ }
105
+
106
+ /** Stable `kind` values for invocation failures. */
107
+ export type InvocationErrorKind = "lock_busy" | "lock_timeout" | "deadline";
108
+
109
+ /**
110
+ * The `error` object written to stdout under `--format json`, also exposed as
111
+ * `error.details` on rejected Node API calls. CLI exit codes: `lock_busy` and
112
+ * `lock_timeout` exit 75 (EX_TEMPFAIL); `deadline` exits 124.
113
+ */
114
+ export interface InvocationErrorDetails {
115
+ kind: InvocationErrorKind;
116
+ /** Phase that was running, for example `invocation.lock` or `check.prepare`. */
117
+ phase: string | null;
118
+ message: string;
119
+ exitCode: 75 | 124;
120
+ /** Present for lock failures when the holder record could be read. */
121
+ holder?: LockHolder;
122
+ }
123
+
124
+ /** Rejection from a Node API call that failed on the invocation lock or deadline. */
125
+ export interface InvocationError extends Error {
126
+ code: "NO_MISTAKES_LOCK_BUSY" | "NO_MISTAKES_LOCK_TIMEOUT" | "NO_MISTAKES_DEADLINE";
127
+ details: InvocationErrorDetails;
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "no-mistakes",
3
- "version": "0.103.0",
3
+ "version": "0.105.0",
4
4
  "description": "Deterministic impact maps, test plans, Playwright coverage, and repository checks for coding agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,6 +16,7 @@
16
16
  "*.d.ts",
17
17
  "index.js",
18
18
  "index.mjs",
19
+ "invocation-errors.js",
19
20
  "planning-impact-artifacts.js",
20
21
  "planning-impact-artifacts-errors.js",
21
22
  "planning-impact-artifacts-files.js",
@@ -65,9 +66,9 @@
65
66
  }
66
67
  },
67
68
  "optionalDependencies": {
68
- "no-mistakes-darwin-arm64": "0.103.0",
69
- "no-mistakes-linux-arm64-gnu": "0.103.0",
70
- "no-mistakes-linux-x64-gnu": "0.103.0",
71
- "no-mistakes-win32-x64-msvc": "0.103.0"
69
+ "no-mistakes-darwin-arm64": "0.105.0",
70
+ "no-mistakes-linux-arm64-gnu": "0.105.0",
71
+ "no-mistakes-linux-x64-gnu": "0.105.0",
72
+ "no-mistakes-win32-x64-msvc": "0.105.0"
72
73
  }
73
74
  }
@@ -1,4 +1,4 @@
1
- import type { Unanalyzable } from "./traversal-types";
1
+ import type { Effort, Unanalyzable } from "./traversal-types";
2
2
 
3
3
  /** Configuration for the `postgres-no-test-state-mutation` check rule. */
4
4
  export interface PostgresNoTestStateMutationOptions {
@@ -11,6 +11,8 @@ export interface PostgresNoTestStateMutationOptions {
11
11
  executorFactoryNames?: string[];
12
12
  executorTypeNames?: string[];
13
13
  trustedSqlTags?: { module: string; name: string }[];
14
+ /** `low` checks direct SQL syntax and one local const; `max` is the default. */
15
+ effort?: Effort;
14
16
  unanalyzable?: Unanalyzable;
15
17
  /** Legacy spelling; `fail` is accepted as `report`. */
16
18
  unanalyzableSql?: "fail" | "ignore" | "advise";
@@ -0,0 +1,42 @@
1
+ /** Root selectors shared with `forbidden-calls`; Playwright roots are not accepted. */
2
+ export type TestNoLiveRepositoryScanRoot =
3
+ | { vitest: true | string | string[] }
4
+ | { glob: string | string[] }
5
+ | { file: string }
6
+ | { module: string }
7
+ | { function: { file: string; symbol: string } };
8
+
9
+ /** An import whose call enumerates a directory tree, in addition to the built-in set. */
10
+ export interface TestNoLiveRepositoryScanEnumerator {
11
+ /** Module specifier; a `node:` prefix is ignored. */
12
+ module: string;
13
+ /** Export name; omit to match every export of `module`. */
14
+ export?: string;
15
+ }
16
+
17
+ /** A shipped asset tree that production code reads at runtime. */
18
+ export interface TestNoLiveRepositoryScanRuntimeAsset {
19
+ /** Repository-relative directory, for example `assets/migrations`. No `..`, no leading `/`. */
20
+ root: string;
21
+ /** Why the tree is shipped input rather than a source or policy tree. Required. */
22
+ reason: string;
23
+ }
24
+
25
+ /** Options for `test-no-live-repository-scan`. */
26
+ export interface TestNoLiveRepositoryScanOptions {
27
+ /** Traversal roots. Default: every Vitest project. */
28
+ roots?: TestNoLiveRepositoryScanRoot[];
29
+ /** Glob or globs removed from the root set. */
30
+ excludeRoots?: string | string[];
31
+ /** Path segments marking bounded fixture input; replaces `["fixtures", "__fixtures__"]`. */
32
+ fixtureDirs?: string[];
33
+ /** Extra enumerating imports. */
34
+ enumerators?: TestNoLiveRepositoryScanEnumerator[];
35
+ /** How unresolved scan roots are reported; default: `"advise"`. They are never omitted. */
36
+ unresolvedRoot?: "advise" | "report";
37
+ /**
38
+ * Shipped asset roots. A literal read at or under one is listed as an advisory with
39
+ * target `runtime asset` instead of failing; other trees are still reported.
40
+ */
41
+ runtimeAssets?: TestNoLiveRepositoryScanRuntimeAsset[];
42
+ }
package/types.d.ts CHANGED
@@ -29,3 +29,4 @@ export type * from "./coverage-assertion-owner-types";
29
29
  export type * from "./lint-suppression-types";
30
30
 
31
31
  export type * from "./production-exports-test-only-types";
32
+ export type * from "./test-no-live-repository-scan-types";