@ouro.bot/cli 0.1.0-alpha.341 → 0.1.0-alpha.342

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.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.342",
6
+ "changes": [
7
+ "The nerves coverage gate now writes append-only per-test NDJSON records across Vitest workers, emits a redaction-safe heartbeat for every executed test, fails closed when per-test capture is missing or malformed, and scopes start/end pairing to explicit process lifecycle events so the full-suite audit is truthful instead of passable through empty artifacts."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.341",
6
12
  "changes": [
@@ -2,16 +2,25 @@
2
2
  /**
3
3
  * Per-test audit rules for nerves event coverage.
4
4
  *
5
- * Rule 1: every-test-emits -- every test must emit at least one event
6
- * Rule 2: start/end pairing -- _start events must have matching _end or _error
5
+ * Rule 1: every-test-emits -- every captured test must emit at least one event
6
+ * Rule 2: lifecycle start/end pairing -- process-scoped _start events must have matching _end or _error
7
7
  * Rule 3: error context -- error-level events must have non-empty meta
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.LIFECYCLE_PAIRED_STARTS = void 0;
10
11
  exports.checkEveryTestEmits = checkEveryTestEmits;
11
12
  exports.checkStartEndPairing = checkStartEndPairing;
12
13
  exports.checkErrorContext = checkErrorContext;
14
+ // Only these starts represent process-scoped lifecycle contracts. Most nerves
15
+ // `_start` events are local operation markers that can be legitimately observed
16
+ // by a narrow unit test without driving the whole operation to completion.
17
+ exports.LIFECYCLE_PAIRED_STARTS = new Set([
18
+ "daemon.server_start",
19
+ "daemon.update_checker_start",
20
+ "daemon.apply_pending_updates_start",
21
+ ]);
13
22
  /**
14
- * Rule 1: Every test must emit at least one nerves event.
23
+ * Rule 1: Every captured test must emit at least one nerves event.
15
24
  */
16
25
  function checkEveryTestEmits(data) {
17
26
  if (!data || typeof data !== "object") {
@@ -22,13 +31,13 @@ function checkEveryTestEmits(data) {
22
31
  .filter(([, events]) => !Array.isArray(events) || events.length === 0)
23
32
  .map(([name]) => name);
24
33
  return {
25
- status: silent.length === 0 ? "pass" : "fail",
34
+ status: entries.length > 0 && silent.length === 0 ? "pass" : "fail",
26
35
  total_tests: entries.length,
27
36
  silent_tests: silent,
28
37
  };
29
38
  }
30
39
  /**
31
- * Rule 2: _start events must have matching _end or _error within the same test.
40
+ * Rule 2: Process-scoped lifecycle _start events must have matching _end or _error within the same test.
32
41
  */
33
42
  function checkStartEndPairing(data) {
34
43
  if (!data || typeof data !== "object") {
@@ -39,7 +48,7 @@ function checkStartEndPairing(data) {
39
48
  if (!Array.isArray(events))
40
49
  continue;
41
50
  const eventNames = events.map((e) => e.event);
42
- const startEvents = eventNames.filter((name) => name.endsWith("_start"));
51
+ const startEvents = eventNames.filter((name) => exports.LIFECYCLE_PAIRED_STARTS.has(name));
43
52
  for (const startEvent of startEvents) {
44
53
  const prefix = startEvent.slice(0, -"_start".length);
45
54
  const hasEnd = eventNames.some((name) => name === `${prefix}_end`);
@@ -69,12 +69,38 @@ function readPerTestData(perTestPath) {
69
69
  if (!perTestPath || !(0, fs_1.existsSync)(perTestPath))
70
70
  return null;
71
71
  try {
72
- return JSON.parse((0, fs_1.readFileSync)(perTestPath, "utf8"));
72
+ const raw = (0, fs_1.readFileSync)(perTestPath, "utf8").trim();
73
+ if (!raw)
74
+ return null;
75
+ try {
76
+ const parsed = JSON.parse(raw);
77
+ if (isPerTestRecord(parsed)) {
78
+ return { [parsed.testName]: parsed.events };
79
+ }
80
+ return parsed;
81
+ }
82
+ catch {
83
+ const perTestData = {};
84
+ for (const line of raw.split("\n").map((entry) => entry.trim()).filter(Boolean)) {
85
+ const parsed = JSON.parse(line);
86
+ if (!isPerTestRecord(parsed))
87
+ return null;
88
+ const existing = perTestData[parsed.testName] ?? [];
89
+ perTestData[parsed.testName] = existing.concat(parsed.events);
90
+ }
91
+ return perTestData;
92
+ }
73
93
  }
74
94
  catch {
75
95
  return null;
76
96
  }
77
97
  }
98
+ function isPerTestRecord(value) {
99
+ if (!value || typeof value !== "object")
100
+ return false;
101
+ const record = value;
102
+ return typeof record.testName === "string" && Array.isArray(record.events);
103
+ }
78
104
  function scanSourceFiles(sourceRoot) {
79
105
  const filesWithKeys = new Map();
80
106
  const fileContents = new Map();
@@ -35,7 +35,7 @@ function runAuditCli(argv) {
35
35
  return 2;
36
36
  }
37
37
  const eventsPath = args.eventsPath ?? (0, path_1.join)(runDir, "vitest-events.ndjson");
38
- const perTestPath = args.perTestPath ?? (0, path_1.join)(runDir, "vitest-events-per-test.json");
38
+ const perTestPath = args.perTestPath ?? (0, path_1.join)(runDir, "vitest-events-per-test.ndjson");
39
39
  const sourceRoot = args.sourceRoot ?? (0, path_1.resolve)("src");
40
40
  const outputPath = args.output ?? (0, path_1.join)(runDir, "nerves-coverage.json");
41
41
  const report = (0, audit_1.auditNervesCoverage)({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.341",
3
+ "version": "0.1.0-alpha.342",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",