@vyuhlabs/dxkit 2.22.0 → 2.23.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/CHANGELOG.md +63 -0
- package/dist/analyzers/correctness/run.d.ts +79 -0
- package/dist/analyzers/correctness/run.d.ts.map +1 -0
- package/dist/analyzers/correctness/run.js +173 -0
- package/dist/analyzers/correctness/run.js.map +1 -0
- package/dist/analyzers/correctness/surface-run.d.ts +73 -0
- package/dist/analyzers/correctness/surface-run.d.ts.map +1 -0
- package/dist/analyzers/correctness/surface-run.js +142 -0
- package/dist/analyzers/correctness/surface-run.js.map +1 -0
- package/dist/analyzers/correctness/surface.d.ts +69 -0
- package/dist/analyzers/correctness/surface.d.ts.map +1 -0
- package/dist/analyzers/correctness/surface.js +281 -0
- package/dist/analyzers/correctness/surface.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +77 -4
- package/dist/cli.js.map +1 -1
- package/dist/languages/capabilities/correctness.d.ts +54 -0
- package/dist/languages/capabilities/correctness.d.ts.map +1 -0
- package/dist/languages/capabilities/correctness.js +20 -0
- package/dist/languages/capabilities/correctness.js.map +1 -0
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +84 -0
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +51 -0
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +12 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +12 -0
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/jvm-build.d.ts +54 -0
- package/dist/languages/jvm-build.d.ts.map +1 -0
- package/dist/languages/jvm-build.js +245 -0
- package/dist/languages/jvm-build.js.map +1 -0
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +13 -0
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +78 -0
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +64 -0
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +110 -0
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +20 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +109 -0
- package/dist/languages/typescript.js.map +1 -1
- package/dist/loop/floor-gate.d.ts +54 -0
- package/dist/loop/floor-gate.d.ts.map +1 -0
- package/dist/loop/floor-gate.js +157 -0
- package/dist/loop/floor-gate.js.map +1 -0
- package/dist/loop/floor-state.d.ts +66 -0
- package/dist/loop/floor-state.d.ts.map +1 -0
- package/dist/loop/floor-state.js +138 -0
- package/dist/loop/floor-state.js.map +1 -0
- package/dist/loop/stop-gate.d.ts +2 -1
- package/dist/loop/stop-gate.d.ts.map +1 -1
- package/dist/loop/stop-gate.js +44 -6
- package/dist/loop/stop-gate.js.map +1 -1
- package/package.json +1 -1
- package/templates/.githooks/pre-push +10 -0
- package/templates/.github/workflows/dxkit-guardrails.yml +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.23.0] - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Added — the correctness floor (a loop-safety liveness gate)
|
|
13
|
+
|
|
14
|
+
The guardrail proves "no net-new findings" (secrets, CVEs, SAST, coverage). It
|
|
15
|
+
does not prove the code still **compiles and its affected tests still pass** — so
|
|
16
|
+
an autonomous agent loop can satisfy the finding gate while shipping code that
|
|
17
|
+
does not build, and even a broken test that lifts coverage gets rewarded. The
|
|
18
|
+
correctness floor closes that gap: a liveness check that asks "does this still
|
|
19
|
+
build, and do the tests it affects still pass?" before an agent may declare
|
|
20
|
+
"done". A failing floor is a pass/fail signal, not a fingerprinted, grandfathered
|
|
21
|
+
finding (there is no "grandfather a syntax error"), so it sits outside the
|
|
22
|
+
baseline and allowlist.
|
|
23
|
+
|
|
24
|
+
- **Pack-declared, runner-executed** (CLAUDE.md Rule 15). Each language pack
|
|
25
|
+
declares two pure command builders — `syntaxCheck` (the cheap "does it
|
|
26
|
+
compile/parse" check) and `affectedTests` (run the tests the change reaches).
|
|
27
|
+
One runner owns the load-bearing policy in a single place: fail-CLOSED on a
|
|
28
|
+
real failure (a non-zero exit blocks), fail-OPEN on infrastructure (a missing
|
|
29
|
+
toolchain or a timeout is a skip, never a block — a slow or un-installed
|
|
30
|
+
toolchain is not broken code; CI is the backstop). A pack never shells out
|
|
31
|
+
itself.
|
|
32
|
+
- **All 8 packs, verified against real toolchains**, at the affected granularity
|
|
33
|
+
each ecosystem natively supports:
|
|
34
|
+
|
|
35
|
+
| Pack | Compile | Affected-test granularity |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| TypeScript / JavaScript | `tsc --noEmit` (or the project's typecheck script) | per-file (`vitest related` / `jest --findRelatedTests`) |
|
|
38
|
+
| Python | `py_compile` | per changed test file (pytest) |
|
|
39
|
+
| Go | `go build ./...` | changed package(s) |
|
|
40
|
+
| Rust | `cargo check` | changed crate(s) (`-p`) |
|
|
41
|
+
| C# / .NET | `dotnet build` | changed test project (`dotnet test <proj>`) |
|
|
42
|
+
| Java | Maven `test-compile` / Gradle `testClasses` | changed build module (Maven `-pl -am`, Gradle `:mod:test`) |
|
|
43
|
+
| Kotlin | Maven `test-compile` / Gradle `testClasses` | changed build module |
|
|
44
|
+
| Ruby | `ruby -c` per changed file | changed spec/test file (RSpec / minitest) |
|
|
45
|
+
|
|
46
|
+
A change whose dependents live in another package/module/crate is caught at
|
|
47
|
+
full/CI scope, not the fast affected surface. The two JVM packs share one
|
|
48
|
+
`jvm-build.ts` provider (Rule 2).
|
|
49
|
+
- **Three surfaces, one adaptive resolver.** The loop Stop-gate runs the floor
|
|
50
|
+
by default (an agent must not stop on broken code), scoped to what the loop
|
|
51
|
+
introduced via a testmon-style entry snapshot, so a pre-existing failure never
|
|
52
|
+
blocks. The pre-push and CI surfaces are adaptive: when the repo already runs
|
|
53
|
+
its tests in its own CI, the floor defaults to opt-in there; when no test-CI is
|
|
54
|
+
detected it runs by default; when a CI exists but its test step is opaque it
|
|
55
|
+
fails toward on. Precedence: an explicit flag, then a `DXKIT_FLOOR_<SURFACE>`
|
|
56
|
+
env, then `.dxkit/policy.json` `correctness.surfaces.<surface>`, then the
|
|
57
|
+
adaptive default.
|
|
58
|
+
- **`vyuh-dxkit floor check [--surface pre-push|ci] [--base <ref>]
|
|
59
|
+
[--correctness | --no-correctness]`** — the entry point the pre-push hook and
|
|
60
|
+
CI workflow call. It is baseline-independent, so a brand-new repo with no
|
|
61
|
+
baseline still gets push-time liveness. The installed pre-push hook runs it
|
|
62
|
+
before the baseline check; the CI guardrail workflow runs it (full scope) after
|
|
63
|
+
the finding gate.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- `LanguageSupport.correctness` is now a required field: the capability shipped
|
|
68
|
+
optional and tightened once all eight packs declared it, so a new pack that
|
|
69
|
+
omits it fails to compile (not just at test time). The `new-lang` scaffold
|
|
70
|
+
wires a dormant provider so a fresh pack still compiles, with TODOs for the
|
|
71
|
+
real commands.
|
|
72
|
+
|
|
10
73
|
## [2.22.0] - 2026-07-02
|
|
11
74
|
|
|
12
75
|
### Added — the flow feature becomes agent-operable (setup, diagnose, publish, repair)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The correctness-floor runner — executes each active pack's syntax + affected-
|
|
3
|
+
* test commands and folds them into one pass/fail signal.
|
|
4
|
+
*
|
|
5
|
+
* Policy, in one place so every surface behaves the same:
|
|
6
|
+
* - fail-CLOSED on a real failure — a non-zero exit from a check that ran is a
|
|
7
|
+
* genuine syntax error / failing test, and it BLOCKS.
|
|
8
|
+
* - fail-OPEN on infrastructure — a missing binary (the toolchain isn't
|
|
9
|
+
* installed here) skips the check rather than failing it. A hook must not
|
|
10
|
+
* block a developer who simply hasn't installed a linter locally; CI, where
|
|
11
|
+
* the toolchain is present, is the backstop.
|
|
12
|
+
*
|
|
13
|
+
* Commands come from `LanguageSupport.correctness` via the registry helper
|
|
14
|
+
* (Rule 6); this module never hardcodes a per-language command. Command
|
|
15
|
+
* execution is injected so tests exercise the policy without a real toolchain.
|
|
16
|
+
*/
|
|
17
|
+
import type { LanguageId, LanguageSupport } from '../../languages/types';
|
|
18
|
+
import type { CorrectnessCommand, CorrectnessScope } from '../../languages/capabilities/correctness';
|
|
19
|
+
export type CorrectnessStatus = 'pass' | 'fail' | 'skipped-unavailable' | 'skipped-timeout' | 'skipped-none';
|
|
20
|
+
export interface CorrectnessCheckResult {
|
|
21
|
+
readonly pack: LanguageId;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly bin: string;
|
|
24
|
+
readonly status: CorrectnessStatus;
|
|
25
|
+
/** Captured output tail — present only on `fail`, for the block message. */
|
|
26
|
+
readonly output?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CorrectnessFloorResult {
|
|
29
|
+
/** True when at least one check actually executed (not all skipped). */
|
|
30
|
+
readonly ran: boolean;
|
|
31
|
+
readonly checks: readonly CorrectnessCheckResult[];
|
|
32
|
+
/** True when any check that ran failed — the floor blocks. */
|
|
33
|
+
readonly blocks: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Outcome of running one command:
|
|
36
|
+
* - `available:false` → the binary isn't on PATH (fail-open skip);
|
|
37
|
+
* - `timedOut:true` → the command exceeded its wall-clock budget. A SLOW
|
|
38
|
+
* suite is not a BROKEN suite, so this is fail-OPEN (skipped), never a block
|
|
39
|
+
* — the fast surface stays fast and CI (unbounded) is the backstop;
|
|
40
|
+
* - otherwise `code` is the exit status and `output` its tail. */
|
|
41
|
+
export interface CommandOutcome {
|
|
42
|
+
readonly available: boolean;
|
|
43
|
+
readonly timedOut?: boolean;
|
|
44
|
+
readonly code: number;
|
|
45
|
+
readonly output: string;
|
|
46
|
+
}
|
|
47
|
+
export type CommandExec = (cmd: CorrectnessCommand, cwd: string) => CommandOutcome;
|
|
48
|
+
/**
|
|
49
|
+
* Build a command exec bounded by an optional per-command wall-clock timeout.
|
|
50
|
+
* On timeout the child is killed and the outcome is `timedOut` (fail-open),
|
|
51
|
+
* distinct from a non-zero exit (a real failure, fail-closed). `timeoutMs`
|
|
52
|
+
* undefined/0 → no timeout (CI, where the full suite is expected to run).
|
|
53
|
+
*/
|
|
54
|
+
export declare function makeCommandExec(timeoutMs?: number): CommandExec;
|
|
55
|
+
/** Default exec: resolve on PATH, run unbounded, capture combined output tail. */
|
|
56
|
+
export declare const defaultCommandExec: CommandExec;
|
|
57
|
+
export interface CorrectnessFloorOptions {
|
|
58
|
+
readonly cwd: string;
|
|
59
|
+
readonly changedFiles: readonly string[];
|
|
60
|
+
readonly scope: CorrectnessScope;
|
|
61
|
+
/** Active language packs (from `activeLanguagesFromStack` / `-Flags`). */
|
|
62
|
+
readonly packs: readonly LanguageSupport[];
|
|
63
|
+
/** Per-command wall-clock budget (ms). A command that exceeds it is a
|
|
64
|
+
* fail-OPEN skip, never a block — the fast surface stays fast, CI is the
|
|
65
|
+
* backstop. Undefined → no timeout. Ignored when `exec` is injected. */
|
|
66
|
+
readonly timeoutMs?: number;
|
|
67
|
+
/** Injected for tests; defaults to real PATH resolution + execFileSync. */
|
|
68
|
+
readonly exec?: CommandExec;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Run the correctness floor across the active packs. Never throws — an exec
|
|
72
|
+
* error surfaces as a `fail` check (fail-closed), a missing binary as
|
|
73
|
+
* `skipped-unavailable` (fail-open). `blocks` is true iff a check that ran
|
|
74
|
+
* failed.
|
|
75
|
+
*/
|
|
76
|
+
export declare function runCorrectnessFloor(opts: CorrectnessFloorOptions): CorrectnessFloorResult;
|
|
77
|
+
/** One-line human summary of a floor result (for the Stop-gate / hook block). */
|
|
78
|
+
export declare function describeCorrectnessFloor(result: CorrectnessFloorResult): string;
|
|
79
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/analyzers/correctness/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,0CAA0C,CAAC;AAqBlD,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,MAAM,GACN,qBAAqB,GACrB,iBAAiB,GACjB,cAAc,CAAC;AAEnB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;mEAKmE;AACnE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,KAAK,cAAc,CAAC;AAInF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAoC/D;AAED,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,EAAE,WAA+B,CAAC;AAOjE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C;;6EAEyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,sBAAsB,CAiCzF;AAED,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAK/E"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The correctness-floor runner — executes each active pack's syntax + affected-
|
|
4
|
+
* test commands and folds them into one pass/fail signal.
|
|
5
|
+
*
|
|
6
|
+
* Policy, in one place so every surface behaves the same:
|
|
7
|
+
* - fail-CLOSED on a real failure — a non-zero exit from a check that ran is a
|
|
8
|
+
* genuine syntax error / failing test, and it BLOCKS.
|
|
9
|
+
* - fail-OPEN on infrastructure — a missing binary (the toolchain isn't
|
|
10
|
+
* installed here) skips the check rather than failing it. A hook must not
|
|
11
|
+
* block a developer who simply hasn't installed a linter locally; CI, where
|
|
12
|
+
* the toolchain is present, is the backstop.
|
|
13
|
+
*
|
|
14
|
+
* Commands come from `LanguageSupport.correctness` via the registry helper
|
|
15
|
+
* (Rule 6); this module never hardcodes a per-language command. Command
|
|
16
|
+
* execution is injected so tests exercise the policy without a real toolchain.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.defaultCommandExec = void 0;
|
|
53
|
+
exports.makeCommandExec = makeCommandExec;
|
|
54
|
+
exports.runCorrectnessFloor = runCorrectnessFloor;
|
|
55
|
+
exports.describeCorrectnessFloor = describeCorrectnessFloor;
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const runner_1 = require("../tools/runner");
|
|
60
|
+
const languages_1 = require("../../languages");
|
|
61
|
+
/**
|
|
62
|
+
* Is a command's `bin` runnable? A bare name is resolved on PATH (`tsc`,
|
|
63
|
+
* `cargo`, `npx`); a path-like `bin` (a pack that resolved an absolute
|
|
64
|
+
* interpreter — a project venv's `python`, a `findTool` path) is accepted when
|
|
65
|
+
* the file exists. Without the latter, a resolved-path bin would be wrongly
|
|
66
|
+
* treated as missing and the check skipped (fail-open on a tool that IS
|
|
67
|
+
* present) — so this keeps the fail-open gate honest for every pack.
|
|
68
|
+
*/
|
|
69
|
+
function binaryAvailable(bin) {
|
|
70
|
+
if (bin.includes('/') || bin.includes(path.sep)) {
|
|
71
|
+
try {
|
|
72
|
+
return fs.statSync(bin).isFile();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return (0, runner_1.commandExists)(bin);
|
|
79
|
+
}
|
|
80
|
+
const OUTPUT_TAIL = 4000; // cap captured output so a block message stays readable
|
|
81
|
+
/**
|
|
82
|
+
* Build a command exec bounded by an optional per-command wall-clock timeout.
|
|
83
|
+
* On timeout the child is killed and the outcome is `timedOut` (fail-open),
|
|
84
|
+
* distinct from a non-zero exit (a real failure, fail-closed). `timeoutMs`
|
|
85
|
+
* undefined/0 → no timeout (CI, where the full suite is expected to run).
|
|
86
|
+
*/
|
|
87
|
+
function makeCommandExec(timeoutMs) {
|
|
88
|
+
return (cmd, cwd) => {
|
|
89
|
+
if (!binaryAvailable(cmd.bin))
|
|
90
|
+
return { available: false, code: -1, output: '' };
|
|
91
|
+
try {
|
|
92
|
+
const out = (0, child_process_1.execFileSync)(cmd.bin, [...cmd.args], {
|
|
93
|
+
cwd,
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
96
|
+
...(timeoutMs && timeoutMs > 0 ? { timeout: timeoutMs, killSignal: 'SIGTERM' } : {}),
|
|
97
|
+
});
|
|
98
|
+
return { available: true, code: 0, output: tail(out) };
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const err = e;
|
|
102
|
+
const combined = `${err.stdout ?? ''}${err.stderr ?? ''}`;
|
|
103
|
+
// execFileSync sets `code: 'ETIMEDOUT'` (and signal = killSignal) when it
|
|
104
|
+
// fired the timeout kill. Treat that as a fail-OPEN skip, not a failure —
|
|
105
|
+
// the run didn't finish, so it says nothing about correctness.
|
|
106
|
+
if (err.code === 'ETIMEDOUT') {
|
|
107
|
+
return { available: true, timedOut: true, code: -1, output: tail(combined) };
|
|
108
|
+
}
|
|
109
|
+
// A non-numeric status (spawn error, non-timeout signal) is treated as a
|
|
110
|
+
// failure with code 1 — the binary existed (commandExists passed) but the
|
|
111
|
+
// run broke.
|
|
112
|
+
return {
|
|
113
|
+
available: true,
|
|
114
|
+
code: typeof err.status === 'number' ? err.status : 1,
|
|
115
|
+
output: tail(combined),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Default exec: resolve on PATH, run unbounded, capture combined output tail. */
|
|
121
|
+
exports.defaultCommandExec = makeCommandExec();
|
|
122
|
+
function tail(s) {
|
|
123
|
+
const t = s.trim();
|
|
124
|
+
return t.length > OUTPUT_TAIL ? `…${t.slice(-OUTPUT_TAIL)}` : t;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Run the correctness floor across the active packs. Never throws — an exec
|
|
128
|
+
* error surfaces as a `fail` check (fail-closed), a missing binary as
|
|
129
|
+
* `skipped-unavailable` (fail-open). `blocks` is true iff a check that ran
|
|
130
|
+
* failed.
|
|
131
|
+
*/
|
|
132
|
+
function runCorrectnessFloor(opts) {
|
|
133
|
+
const exec = opts.exec ?? makeCommandExec(opts.timeoutMs);
|
|
134
|
+
const ctx = { cwd: opts.cwd, changedFiles: opts.changedFiles, scope: opts.scope };
|
|
135
|
+
const checks = [];
|
|
136
|
+
for (const { id, provider } of (0, languages_1.activeCorrectnessProviders)(opts.packs)) {
|
|
137
|
+
const commands = [provider.syntaxCheck(ctx), provider.affectedTests(ctx)];
|
|
138
|
+
for (const cmd of commands) {
|
|
139
|
+
if (cmd === null)
|
|
140
|
+
continue; // pack declined this check for this change
|
|
141
|
+
const outcome = exec(cmd, opts.cwd);
|
|
142
|
+
if (!outcome.available) {
|
|
143
|
+
checks.push({ pack: id, label: cmd.label, bin: cmd.bin, status: 'skipped-unavailable' });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (outcome.timedOut) {
|
|
147
|
+
// Exceeded the budget — fail-OPEN. The run didn't finish, so it says
|
|
148
|
+
// nothing about correctness; CI (unbounded) is the backstop.
|
|
149
|
+
checks.push({ pack: id, label: cmd.label, bin: cmd.bin, status: 'skipped-timeout' });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
checks.push({
|
|
153
|
+
pack: id,
|
|
154
|
+
label: cmd.label,
|
|
155
|
+
bin: cmd.bin,
|
|
156
|
+
status: outcome.code === 0 ? 'pass' : 'fail',
|
|
157
|
+
...(outcome.code === 0 ? {} : { output: outcome.output }),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const ran = checks.some((c) => c.status === 'pass' || c.status === 'fail');
|
|
162
|
+
const blocks = checks.some((c) => c.status === 'fail');
|
|
163
|
+
return { ran, checks, blocks };
|
|
164
|
+
}
|
|
165
|
+
/** One-line human summary of a floor result (for the Stop-gate / hook block). */
|
|
166
|
+
function describeCorrectnessFloor(result) {
|
|
167
|
+
const failed = result.checks.filter((c) => c.status === 'fail');
|
|
168
|
+
if (failed.length === 0)
|
|
169
|
+
return 'correctness floor: all checks passed';
|
|
170
|
+
const which = failed.map((c) => `${c.pack} ${c.label}`).join(', ');
|
|
171
|
+
return `correctness floor: ${failed.length} check(s) failed — ${which}`;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/analyzers/correctness/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the correctness floor for a NON-loop surface (`pre-push` / `ci`).
|
|
3
|
+
*
|
|
4
|
+
* The loop Stop-gate has its own runner (`src/loop/stop-gate.ts`) because it
|
|
5
|
+
* diffs against a cheap entry snapshot to block only on NET-NEW failures. The
|
|
6
|
+
* pre-push and CI surfaces are point-in-time LIVENESS gates instead: they run
|
|
7
|
+
* the floor at the current tree and are fail-CLOSED on the surface's scope.
|
|
8
|
+
* There is no net-new diffing here (that would cost a base-ref worktree run on
|
|
9
|
+
* every push / PR); the entry-snapshot trick is the loop's alone.
|
|
10
|
+
*
|
|
11
|
+
* - `pre-push` — AFFECTED scope, changed files computed vs the merge-base with
|
|
12
|
+
* the integration branch. Runs on the developer's machine where the
|
|
13
|
+
* toolchain is present, so the floor is meaningful; a per-command timeout
|
|
14
|
+
* keeps `git push` fast. Blocks the push when the affected tests don't pass.
|
|
15
|
+
* (It does not distinguish a pre-existing red test in a touched module from
|
|
16
|
+
* a newly-broken one — that distinction is the loop Stop-gate's job. Bypass
|
|
17
|
+
* with `--no-verify`.)
|
|
18
|
+
* - `ci` — FULL scope, no timeout (the full suite is expected to run). The
|
|
19
|
+
* backstop.
|
|
20
|
+
*
|
|
21
|
+
* Both surfaces are ADAPTIVE (`resolveCorrectnessSurface`): when the repo
|
|
22
|
+
* already runs its tests in its own CI, the floor defaults to opt-in here, so
|
|
23
|
+
* this runner returns a disabled no-op unless explicitly enabled.
|
|
24
|
+
*/
|
|
25
|
+
import type { LanguageSupport } from '../../languages/types';
|
|
26
|
+
import { type CommandExec, type CorrectnessFloorResult } from './run';
|
|
27
|
+
/** The surfaces this runner serves (the loop-stop surface has its own runner). */
|
|
28
|
+
export type RunnableSurface = 'pre-push' | 'ci';
|
|
29
|
+
export interface SurfaceFloorOutcome {
|
|
30
|
+
readonly surface: RunnableSurface;
|
|
31
|
+
/** Was the floor enabled on this surface (via the resolver)? */
|
|
32
|
+
readonly enabled: boolean;
|
|
33
|
+
/** Why it resolved the way it did (or why it was skipped). */
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
/** Did any check actually execute (false when disabled / no pack / all skipped)? */
|
|
36
|
+
readonly ran: boolean;
|
|
37
|
+
/** Does the floor block this surface (a real failure)? Always false when not `ran`. */
|
|
38
|
+
readonly blocks: boolean;
|
|
39
|
+
/** One-line human summary for CLI / hook output. */
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
readonly result?: CorrectnessFloorResult;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The merge-base of HEAD against the most likely integration branch, so the
|
|
45
|
+
* pre-push floor scopes to what this branch actually introduces. Tries, in
|
|
46
|
+
* order: an explicit ref, the tracking upstream, then the common remote/local
|
|
47
|
+
* default-branch names. Returns '' when none resolve (caller then runs full).
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolvePrePushBase(cwd: string, explicit?: string): string;
|
|
50
|
+
export interface RunFloorForSurfaceOptions {
|
|
51
|
+
readonly surface: RunnableSurface;
|
|
52
|
+
readonly cwd: string;
|
|
53
|
+
/** Explicit base ref for pre-push affected scoping (else auto-resolved). */
|
|
54
|
+
readonly base?: string;
|
|
55
|
+
/** Explicit `--correctness` / `--no-correctness` override (highest precedence). */
|
|
56
|
+
readonly flag?: boolean;
|
|
57
|
+
/** Injected for tests; defaults to the real PATH-resolving exec. */
|
|
58
|
+
readonly exec?: CommandExec;
|
|
59
|
+
/** Injected for tests; defaults to the active packs detected at `cwd`. */
|
|
60
|
+
readonly packs?: readonly LanguageSupport[];
|
|
61
|
+
/** Injected for tests; defaults to the real adaptive resolver. */
|
|
62
|
+
readonly resolveEnabled?: (surface: RunnableSurface, cwd: string) => {
|
|
63
|
+
enabled: boolean;
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve enablement + scope for a surface and run the floor. Never throws — a
|
|
69
|
+
* disabled surface, an absent pack, or an all-skipped run all return
|
|
70
|
+
* `blocks: false` (the caller exits 0); only a real check failure blocks.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runFloorForSurface(opts: RunFloorForSurfaceOptions): SurfaceFloorOutcome;
|
|
73
|
+
//# sourceMappingURL=surface-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-run.d.ts","sourceRoot":"","sources":["../../../src/analyzers/correctness/surface-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAGf,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,IAAI,CAAC;AAEhD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAC1C;AAeD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CASzE;AAUD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE,MAAM,KACR;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,mBAAmB,CAoEvF"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Run the correctness floor for a NON-loop surface (`pre-push` / `ci`).
|
|
4
|
+
*
|
|
5
|
+
* The loop Stop-gate has its own runner (`src/loop/stop-gate.ts`) because it
|
|
6
|
+
* diffs against a cheap entry snapshot to block only on NET-NEW failures. The
|
|
7
|
+
* pre-push and CI surfaces are point-in-time LIVENESS gates instead: they run
|
|
8
|
+
* the floor at the current tree and are fail-CLOSED on the surface's scope.
|
|
9
|
+
* There is no net-new diffing here (that would cost a base-ref worktree run on
|
|
10
|
+
* every push / PR); the entry-snapshot trick is the loop's alone.
|
|
11
|
+
*
|
|
12
|
+
* - `pre-push` — AFFECTED scope, changed files computed vs the merge-base with
|
|
13
|
+
* the integration branch. Runs on the developer's machine where the
|
|
14
|
+
* toolchain is present, so the floor is meaningful; a per-command timeout
|
|
15
|
+
* keeps `git push` fast. Blocks the push when the affected tests don't pass.
|
|
16
|
+
* (It does not distinguish a pre-existing red test in a touched module from
|
|
17
|
+
* a newly-broken one — that distinction is the loop Stop-gate's job. Bypass
|
|
18
|
+
* with `--no-verify`.)
|
|
19
|
+
* - `ci` — FULL scope, no timeout (the full suite is expected to run). The
|
|
20
|
+
* backstop.
|
|
21
|
+
*
|
|
22
|
+
* Both surfaces are ADAPTIVE (`resolveCorrectnessSurface`): when the repo
|
|
23
|
+
* already runs its tests in its own CI, the floor defaults to opt-in here, so
|
|
24
|
+
* this runner returns a disabled no-op unless explicitly enabled.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.resolvePrePushBase = resolvePrePushBase;
|
|
28
|
+
exports.runFloorForSurface = runFloorForSurface;
|
|
29
|
+
const child_process_1 = require("child_process");
|
|
30
|
+
const languages_1 = require("../../languages");
|
|
31
|
+
const changed_files_1 = require("../../baseline/changed-files");
|
|
32
|
+
const run_1 = require("./run");
|
|
33
|
+
const surface_1 = require("./surface");
|
|
34
|
+
/** Best-effort git stdout for a fixed arg vector; '' on any failure. */
|
|
35
|
+
function gitOut(cwd, args) {
|
|
36
|
+
try {
|
|
37
|
+
return (0, child_process_1.execFileSync)('git', [...args], {
|
|
38
|
+
cwd,
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
41
|
+
}).trim();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The merge-base of HEAD against the most likely integration branch, so the
|
|
49
|
+
* pre-push floor scopes to what this branch actually introduces. Tries, in
|
|
50
|
+
* order: an explicit ref, the tracking upstream, then the common remote/local
|
|
51
|
+
* default-branch names. Returns '' when none resolve (caller then runs full).
|
|
52
|
+
*/
|
|
53
|
+
function resolvePrePushBase(cwd, explicit) {
|
|
54
|
+
const candidates = explicit
|
|
55
|
+
? [explicit]
|
|
56
|
+
: ['@{upstream}', 'origin/HEAD', 'origin/main', 'origin/master', 'main', 'master'];
|
|
57
|
+
for (const ref of candidates) {
|
|
58
|
+
const mb = gitOut(cwd, ['merge-base', 'HEAD', ref]);
|
|
59
|
+
if (mb)
|
|
60
|
+
return mb;
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
/** Per-command wall-clock budget for the fast (pre-push) surface. Env-tunable;
|
|
65
|
+
* CI runs unbounded (the full suite is expected to run to completion). */
|
|
66
|
+
function prePushTimeoutMs() {
|
|
67
|
+
const raw = process.env.DXKIT_FLOOR_TIMEOUT_MS;
|
|
68
|
+
const n = raw ? Number(raw) : NaN;
|
|
69
|
+
return Number.isFinite(n) && n > 0 ? n : 120_000;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve enablement + scope for a surface and run the floor. Never throws — a
|
|
73
|
+
* disabled surface, an absent pack, or an all-skipped run all return
|
|
74
|
+
* `blocks: false` (the caller exits 0); only a real check failure blocks.
|
|
75
|
+
*/
|
|
76
|
+
function runFloorForSurface(opts) {
|
|
77
|
+
const { surface, cwd } = opts;
|
|
78
|
+
const res = opts.resolveEnabled
|
|
79
|
+
? opts.resolveEnabled(surface, cwd)
|
|
80
|
+
: (0, surface_1.resolveCorrectnessSurface)({ surface, cwd, flag: opts.flag });
|
|
81
|
+
if (!res.enabled) {
|
|
82
|
+
return {
|
|
83
|
+
surface,
|
|
84
|
+
enabled: false,
|
|
85
|
+
reason: res.reason,
|
|
86
|
+
ran: false,
|
|
87
|
+
blocks: false,
|
|
88
|
+
summary: `correctness floor (${surface}): disabled — ${res.reason}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const packs = (opts.packs ?? (0, languages_1.detectActiveLanguages)(cwd)).filter((p) => p.correctness);
|
|
92
|
+
if (packs.length === 0) {
|
|
93
|
+
return {
|
|
94
|
+
surface,
|
|
95
|
+
enabled: true,
|
|
96
|
+
reason: res.reason,
|
|
97
|
+
ran: false,
|
|
98
|
+
blocks: false,
|
|
99
|
+
summary: `correctness floor (${surface}): no active language pack provides a floor`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
let scope = 'full';
|
|
103
|
+
let changedFiles = [];
|
|
104
|
+
let timeoutMs;
|
|
105
|
+
if (surface === 'pre-push') {
|
|
106
|
+
scope = 'affected';
|
|
107
|
+
const base = resolvePrePushBase(cwd, opts.base);
|
|
108
|
+
// Empty changedFiles (base unresolved / diff undeterminable) → the packs
|
|
109
|
+
// treat the scope as full, per the CorrectnessContext contract.
|
|
110
|
+
changedFiles = base ? ((0, changed_files_1.computeChangedFiles)(cwd, base) ?? []) : [];
|
|
111
|
+
timeoutMs = prePushTimeoutMs();
|
|
112
|
+
}
|
|
113
|
+
// ci: full scope, no timeout — the full suite runs to completion.
|
|
114
|
+
const result = (0, run_1.runCorrectnessFloor)({
|
|
115
|
+
cwd,
|
|
116
|
+
changedFiles,
|
|
117
|
+
scope,
|
|
118
|
+
packs,
|
|
119
|
+
timeoutMs,
|
|
120
|
+
exec: opts.exec,
|
|
121
|
+
});
|
|
122
|
+
if (!result.ran) {
|
|
123
|
+
return {
|
|
124
|
+
surface,
|
|
125
|
+
enabled: true,
|
|
126
|
+
reason: res.reason,
|
|
127
|
+
ran: false,
|
|
128
|
+
blocks: false,
|
|
129
|
+
summary: `correctness floor (${surface}): all checks skipped (toolchain not present) — CI is the backstop`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
surface,
|
|
134
|
+
enabled: true,
|
|
135
|
+
reason: res.reason,
|
|
136
|
+
ran: true,
|
|
137
|
+
blocks: result.blocks,
|
|
138
|
+
summary: (0, run_1.describeCorrectnessFloor)(result),
|
|
139
|
+
result,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=surface-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-run.js","sourceRoot":"","sources":["../../../src/analyzers/correctness/surface-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The correctness-floor SURFACE resolver — decides, per surface, whether the
|
|
3
|
+
* floor runs by default. This is the canonical resolver for the correctness
|
|
4
|
+
* capability (mirror of `resolveBaselineMode` for baseline modes, Rule 11): one
|
|
5
|
+
* function, one precedence order, so no two call sites drift on the default.
|
|
6
|
+
*
|
|
7
|
+
* Three surfaces run the floor, with different postures:
|
|
8
|
+
* - `loop-stop` — an autonomous loop's Stop-gate. ALWAYS default-on: an agent
|
|
9
|
+
* must never declare "done" on code that does not compile or whose tests
|
|
10
|
+
* fail. (An explicit flag/policy can still turn it off, but the default is
|
|
11
|
+
* on regardless of what CI the repo has.)
|
|
12
|
+
* - `pre-push` / `ci` — ADAPTIVE. If the repo already runs its tests in its own
|
|
13
|
+
* CI, dxkit's floor is redundant there, so it defaults to OPT-IN. If no
|
|
14
|
+
* test-running CI is detected, the floor defaults ON so pushes/PRs are still
|
|
15
|
+
* checked. When we cannot tell (a CI exists but its test step is opaque), we
|
|
16
|
+
* FAIL TOWARD ON — a redundant floor run is cheap; a missed regression is not.
|
|
17
|
+
*
|
|
18
|
+
* Precedence (highest first), mirroring the loop-preset + baseline-mode
|
|
19
|
+
* resolvers:
|
|
20
|
+
* 1. explicit `flag` argument (a `--correctness` / `--no-correctness` CLI flag)
|
|
21
|
+
* 2. `DXKIT_FLOOR_<SURFACE>` env var (benchmark / CI override)
|
|
22
|
+
* 3. `.dxkit/policy.json` → `correctness.surfaces.<surface>`
|
|
23
|
+
* 4. the surface's default (always-on for loop-stop; adaptive for pre-push/ci)
|
|
24
|
+
*/
|
|
25
|
+
export type CorrectnessSurface = 'loop-stop' | 'pre-push' | 'ci';
|
|
26
|
+
export type SurfaceDecisionSource = 'always-on' | 'flag' | 'env' | 'policy' | 'adaptive-no-test-ci' | 'adaptive-test-ci-detected' | 'adaptive-uncertain';
|
|
27
|
+
export interface SurfaceResolution {
|
|
28
|
+
readonly surface: CorrectnessSurface;
|
|
29
|
+
readonly enabled: boolean;
|
|
30
|
+
readonly source: SurfaceDecisionSource;
|
|
31
|
+
/** One-line human-readable rationale, for `doctor` / CLI output. */
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
}
|
|
34
|
+
export type TestCiStatus = 'has-test-ci' | 'no-test-ci' | 'uncertain';
|
|
35
|
+
export interface TestCiDetection {
|
|
36
|
+
readonly status: TestCiStatus;
|
|
37
|
+
/** When `has-test-ci`, the file + command that proved it (for the reason). */
|
|
38
|
+
readonly evidence?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detect whether the repo runs its tests in its OWN CI. Reads GitHub Actions
|
|
42
|
+
* workflows (excluding dxkit-authored ones) plus the common flat CI configs,
|
|
43
|
+
* scanning for a test invocation:
|
|
44
|
+
* - a matching test command in any file → `has-test-ci`;
|
|
45
|
+
* - no CI config found at all → `no-test-ci` (dxkit's floor should default on);
|
|
46
|
+
* - CI config exists but no test command matched (opaque `make ci`, a called
|
|
47
|
+
* reusable workflow, etc.) → `uncertain` (fail toward on).
|
|
48
|
+
* Best-effort and never throws — an unreadable file is skipped.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectTestCi(cwd: string): TestCiDetection;
|
|
51
|
+
/** Read `correctness.surfaces.<surface>` from `.dxkit/policy.json`. Best-effort:
|
|
52
|
+
* a missing / malformed file or absent block yields an empty map. */
|
|
53
|
+
export declare function readSurfacePolicy(cwd: string): Partial<Record<CorrectnessSurface, boolean>>;
|
|
54
|
+
export interface ResolveSurfaceOptions {
|
|
55
|
+
readonly surface: CorrectnessSurface;
|
|
56
|
+
readonly cwd: string;
|
|
57
|
+
/** Explicit `--correctness` / `--no-correctness` CLI flag (highest precedence). */
|
|
58
|
+
readonly flag?: boolean;
|
|
59
|
+
/** Injected for tests; defaults to reading `.dxkit/policy.json`. */
|
|
60
|
+
readonly policySurfaces?: Partial<Record<CorrectnessSurface, boolean>>;
|
|
61
|
+
/** Injected for tests; defaults to the real `detectTestCi`. */
|
|
62
|
+
readonly detect?: (cwd: string) => TestCiDetection;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve whether the correctness floor is enabled on a surface. Pure aside from
|
|
66
|
+
* the default policy/env reads (both injectable); never throws.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveCorrectnessSurface(opts: ResolveSurfaceOptions): SurfaceResolution;
|
|
69
|
+
//# sourceMappingURL=surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../../src/analyzers/correctness/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;AAEjE,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,MAAM,GACN,KAAK,GACL,QAAQ,GACR,qBAAqB,GACrB,2BAA2B,GAC3B,oBAAoB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,YAAY,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AA2DD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CA4CzD;AAID;sEACsE;AACtE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAgB3F;AAoBD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,eAAe,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,qBAAqB,GAAG,iBAAiB,CAwExF"}
|