@dzhechkov/harness-core 0.8.39 → 0.8.41

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 (161) hide show
  1. package/.dz-manifest.json +235 -135
  2. package/README.md +91 -8
  3. package/dist/__tests__/golden-baseline.test.js +1 -1
  4. package/dist/__tests__/golden-baseline.test.js.map +1 -1
  5. package/dist/agentdb-index.js +3 -3
  6. package/dist/agentdb-index.js.map +1 -1
  7. package/dist/agentdb-reindex-marker.d.ts +2 -2
  8. package/dist/agentdb-reindex-marker.js +4 -4
  9. package/dist/agentdb-reindex-marker.js.map +1 -1
  10. package/dist/agentdb-snapshot-rotation.d.ts +1 -1
  11. package/dist/agentdb-snapshot-rotation.js +1 -1
  12. package/dist/backup-freshness.d.ts +32 -0
  13. package/dist/backup-freshness.d.ts.map +1 -0
  14. package/dist/backup-freshness.js +69 -0
  15. package/dist/backup-freshness.js.map +1 -0
  16. package/dist/cmd-usage.d.ts +2 -2
  17. package/dist/cmd-usage.d.ts.map +1 -1
  18. package/dist/cmd-usage.js +56 -8
  19. package/dist/cmd-usage.js.map +1 -1
  20. package/dist/codex-hooks-assets.d.ts +2 -1
  21. package/dist/codex-hooks-assets.d.ts.map +1 -1
  22. package/dist/codex-hooks-assets.js +15 -2
  23. package/dist/codex-hooks-assets.js.map +1 -1
  24. package/dist/compounding.d.ts +109 -1
  25. package/dist/compounding.d.ts.map +1 -1
  26. package/dist/compounding.js +172 -9
  27. package/dist/compounding.js.map +1 -1
  28. package/dist/discrimination-gate.d.ts +0 -3
  29. package/dist/discrimination-gate.d.ts.map +1 -1
  30. package/dist/discrimination-gate.js +2 -2
  31. package/dist/discrimination-gate.js.map +1 -1
  32. package/dist/doctor-instrument.d.ts +65 -0
  33. package/dist/doctor-instrument.d.ts.map +1 -0
  34. package/dist/doctor-instrument.js +91 -0
  35. package/dist/doctor-instrument.js.map +1 -0
  36. package/dist/feature-tier.d.ts.map +1 -1
  37. package/dist/feature-tier.js +13 -1
  38. package/dist/feature-tier.js.map +1 -1
  39. package/dist/guard.d.ts +39 -2
  40. package/dist/guard.d.ts.map +1 -1
  41. package/dist/guard.js +117 -14
  42. package/dist/guard.js.map +1 -1
  43. package/dist/index.d.ts +20 -9
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +14 -6
  46. package/dist/index.js.map +1 -1
  47. package/dist/integration-apply.js +2 -2
  48. package/dist/integration-apply.js.map +1 -1
  49. package/dist/lesson-payoff.js +3 -3
  50. package/dist/lesson-payoff.js.map +1 -1
  51. package/dist/mutation-gate.d.ts +30 -0
  52. package/dist/mutation-gate.d.ts.map +1 -1
  53. package/dist/mutation-gate.js +45 -1
  54. package/dist/mutation-gate.js.map +1 -1
  55. package/dist/named-lock.d.ts +5 -2
  56. package/dist/named-lock.d.ts.map +1 -1
  57. package/dist/named-lock.js +30 -10
  58. package/dist/named-lock.js.map +1 -1
  59. package/dist/operations.d.ts +13 -0
  60. package/dist/operations.d.ts.map +1 -1
  61. package/dist/operations.js +112 -2
  62. package/dist/operations.js.map +1 -1
  63. package/dist/pack-inventory.d.ts +51 -0
  64. package/dist/pack-inventory.d.ts.map +1 -0
  65. package/dist/pack-inventory.js +306 -0
  66. package/dist/pack-inventory.js.map +1 -0
  67. package/dist/package-skill-layouts.d.ts +3 -2
  68. package/dist/package-skill-layouts.d.ts.map +1 -1
  69. package/dist/package-skill-layouts.js +3 -2
  70. package/dist/package-skill-layouts.js.map +1 -1
  71. package/dist/patterns.d.ts +8 -0
  72. package/dist/patterns.d.ts.map +1 -1
  73. package/dist/patterns.js +11 -1
  74. package/dist/patterns.js.map +1 -1
  75. package/dist/publish.d.ts +29 -7
  76. package/dist/publish.d.ts.map +1 -1
  77. package/dist/publish.js +92 -15
  78. package/dist/publish.js.map +1 -1
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.d.ts.map +1 -1
  81. package/dist/registry.js +86 -25
  82. package/dist/registry.js.map +1 -1
  83. package/dist/release.d.ts +18 -0
  84. package/dist/release.d.ts.map +1 -1
  85. package/dist/release.js +30 -0
  86. package/dist/release.js.map +1 -1
  87. package/dist/reqe-verdict.d.ts +55 -0
  88. package/dist/reqe-verdict.d.ts.map +1 -0
  89. package/dist/reqe-verdict.js +173 -0
  90. package/dist/reqe-verdict.js.map +1 -0
  91. package/dist/reqe.d.ts +35 -14
  92. package/dist/reqe.d.ts.map +1 -1
  93. package/dist/reqe.js +85 -32
  94. package/dist/reqe.js.map +1 -1
  95. package/dist/round-exec.d.ts +10 -0
  96. package/dist/round-exec.d.ts.map +1 -1
  97. package/dist/round-exec.js +3 -2
  98. package/dist/round-exec.js.map +1 -1
  99. package/dist/round.d.ts +29 -0
  100. package/dist/round.d.ts.map +1 -1
  101. package/dist/round.js +8 -0
  102. package/dist/round.js.map +1 -1
  103. package/dist/session-retro.d.ts +1 -1
  104. package/dist/session-retro.js +3 -3
  105. package/dist/session-retro.js.map +1 -1
  106. package/dist/skill-drift.d.ts +18 -1
  107. package/dist/skill-drift.d.ts.map +1 -1
  108. package/dist/skill-drift.js +46 -13
  109. package/dist/skill-drift.js.map +1 -1
  110. package/dist/statusline.js +2 -2
  111. package/dist/statusline.js.map +1 -1
  112. package/dist/store-guard.js +3 -3
  113. package/dist/store-guard.js.map +1 -1
  114. package/dist/store-lock.d.ts +9 -0
  115. package/dist/store-lock.d.ts.map +1 -1
  116. package/dist/store-lock.js +22 -3
  117. package/dist/store-lock.js.map +1 -1
  118. package/dist/test-receipt.d.ts +66 -0
  119. package/dist/test-receipt.d.ts.map +1 -0
  120. package/dist/test-receipt.js +73 -0
  121. package/dist/test-receipt.js.map +1 -0
  122. package/dist/workflow-run.d.ts.map +1 -1
  123. package/dist/workflow-run.js +7 -8
  124. package/dist/workflow-run.js.map +1 -1
  125. package/package.json +1 -1
  126. package/sbom.json +384 -134
  127. package/src/__tests__/golden-baseline.test.ts +1 -1
  128. package/src/agentdb-index.ts +3 -3
  129. package/src/agentdb-reindex-marker.ts +4 -4
  130. package/src/agentdb-snapshot-rotation.ts +1 -1
  131. package/src/backup-freshness.ts +96 -0
  132. package/src/cmd-usage.ts +59 -8
  133. package/src/codex-hooks-assets.ts +15 -2
  134. package/src/compounding.ts +244 -10
  135. package/src/discrimination-gate.ts +2 -5
  136. package/src/doctor-instrument.ts +153 -0
  137. package/src/feature-tier.ts +14 -1
  138. package/src/guard.ts +128 -17
  139. package/src/index.ts +28 -7
  140. package/src/integration-apply.ts +2 -2
  141. package/src/lesson-payoff.ts +3 -3
  142. package/src/mutation-gate.ts +54 -1
  143. package/src/named-lock.ts +36 -11
  144. package/src/operations.ts +109 -3
  145. package/src/pack-inventory.ts +313 -0
  146. package/src/package-skill-layouts.ts +3 -2
  147. package/src/patterns.ts +18 -1
  148. package/src/publish.ts +101 -22
  149. package/src/registry.ts +114 -18
  150. package/src/release.ts +36 -0
  151. package/src/reqe-verdict.ts +174 -0
  152. package/src/reqe.ts +111 -35
  153. package/src/round-exec.ts +13 -2
  154. package/src/round.ts +38 -0
  155. package/src/session-retro.ts +3 -3
  156. package/src/skill-drift.ts +66 -12
  157. package/src/statusline.ts +2 -2
  158. package/src/store-guard.ts +3 -3
  159. package/src/store-lock.ts +20 -3
  160. package/src/test-receipt.ts +106 -0
  161. package/src/workflow-run.ts +7 -8
package/src/store-lock.ts CHANGED
@@ -57,7 +57,7 @@
57
57
  * one is honoured as held, same as any other lock.
58
58
  */
59
59
 
60
- import { mkdirSync, rmSync, statSync } from 'node:fs';
60
+ import { existsSync, rmSync, statSync } from 'node:fs';
61
61
  import { join, resolve } from 'node:path';
62
62
  import { lockSync } from 'proper-lockfile';
63
63
 
@@ -71,6 +71,23 @@ const MIN_STALE_MS = 2_000;
71
71
  /** Highest accepted environment override; keeps proper-lockfile's heartbeat timer in range. */
72
72
  const MAX_STALE_MS = 600_000;
73
73
 
74
+ /** A store exists exactly when the project's .dz entry exists. */
75
+ export function storeExists(projectRoot: string): boolean {
76
+ return existsSync(join(projectRoot, '.dz'));
77
+ }
78
+
79
+ /** Refusing a store-scoped lock must never seed a store. */
80
+ export class StoreAbsentError extends Error {
81
+ readonly code = 'ESTOREABSENT';
82
+ constructor(readonly root: string, readonly lock: string) {
83
+ super(
84
+ `no store at ${root} — the lock ${lock} was not taken and nothing was created; ` +
85
+ 'run `dz init` there, or pass --project <the real project root>',
86
+ );
87
+ this.name = 'StoreAbsentError';
88
+ }
89
+ }
90
+
74
91
  /** Path of the lock guarding a project's pattern store (a DIRECTORY when held). */
75
92
  export function storeLockPath(projectRoot: string): string {
76
93
  return join(projectRoot, '.dz', 'store.lock');
@@ -201,8 +218,8 @@ export async function withStoreLock<T>(
201
218
  fn: () => Promise<T>,
202
219
  opts: StoreLockOptions = {},
203
220
  ): Promise<T> {
221
+ if (!storeExists(projectRoot)) throw new StoreAbsentError(projectRoot, 'store');
204
222
  const { staleMs, timeoutMs, pollMs } = resolveOpts(opts);
205
- mkdirSync(join(projectRoot, '.dz'), { recursive: true });
206
223
  const started = Date.now();
207
224
  const deadline = started + timeoutMs;
208
225
  let compromised: Error | undefined;
@@ -240,8 +257,8 @@ export function withStoreLockSync<T>(
240
257
  fn: () => T,
241
258
  opts: StoreLockOptions = {},
242
259
  ): T {
260
+ if (!storeExists(projectRoot)) throw new StoreAbsentError(projectRoot, 'store');
243
261
  const { staleMs, timeoutMs, pollMs } = resolveOpts(opts);
244
- mkdirSync(join(projectRoot, '.dz'), { recursive: true });
245
262
  const started = Date.now();
246
263
  const deadline = started + timeoutMs;
247
264
  let compromised: Error | undefined;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Квитанция зелёного прогона: «эта область исходников была протестирована ИМЕННО в этом виде».
3
+ *
4
+ * ЗАЧЕМ ЭТО ЕСТЬ. Два процессных грабля повторялись месяцами и повторились снова 21.09 ПОСЛЕ того,
5
+ * как напоминание о них было поднято: «объявил сделанным без проверки» (пять случаев) и «коммит без
6
+ * прогона тестов» (шесть). Напоминание — слой суждения, и он молчит, когда отказывает. Владелец
7
+ * согласился поднять проверку на слой 1, но с условием: коммиты должны остаться дешёвыми. Поэтому
8
+ * проверка стоит не на коммите, а на ПУШЕ, и спрашивает не «прогони тесты», а квитанцию.
9
+ *
10
+ * ПОЧЕМУ ПО ОБЛАСТЯМ, А НЕ ЦЕЛИКОМ. ИЗМЕРЕНО 21.09: полный прогон набора harness-cli на этой машине
11
+ * не завершается — надзиратель фоновых задач убивает его по памяти при любом дроблении. Гейт,
12
+ * требующий квитанцию ПОЛНОГО прогона, заблокировал бы каждый push навсегда. Квитанция покрывает
13
+ * названные области, и отказ касается только тех, что изменились без покрытия.
14
+ *
15
+ * ЧЕГО ЭТА ПРОВЕРКА НЕ ОБЕЩАЕТ, и это сказано здесь, а не в отчёте потом:
16
+ * · она не запускает тесты и не знает, ХОРОШИ ли они — только что прогон был и был зелёным;
17
+ * · она сравнивает СОДЕРЖИМОЕ рабочего дерева, а не набор пушимых коммитов;
18
+ * · она ничего не говорит о коммитах, которые никогда не пушат;
19
+ * · зелёный прогон ДРУГОЙ области не покрывает изменённую — это и есть смысл разбиения.
20
+ */
21
+
22
+ /** Одна запись журнала квитанций. Пишется ТОЛЬКО наблюдателем, видевшим код возврата 0. */
23
+ export interface TestReceipt {
24
+ readonly v: 1;
25
+ /** Когда прогон закончился, ISO-8601. */
26
+ readonly ts: string;
27
+ /** Что именно было запущено — для сообщения об отказе, чтобы читатель мог повторить. */
28
+ readonly command: string;
29
+ /** Область → отпечаток её содержимого на момент зелёного прогона. */
30
+ readonly scopes: Readonly<Record<string, string>>;
31
+ }
32
+
33
+ /** Состояние одной области в вердикте. */
34
+ export interface TestReceiptScopeState {
35
+ readonly scope: string;
36
+ /** Отпечаток содержимого СЕЙЧАС. */
37
+ readonly digest: string;
38
+ /**
39
+ * `never` — область не встречается ни в одной квитанции: прогона не было вовсе.
40
+ * `changed` — квитанции есть, но ни одна не покрывает текущее содержимое.
41
+ */
42
+ readonly why: 'never' | 'changed';
43
+ /** Время самой свежей квитанции, покрывавшей эту область в ЛЮБОМ виде; null при `never`. */
44
+ readonly lastGreenAt: string | null;
45
+ }
46
+
47
+ export type TestReceiptVerdict =
48
+ | { readonly state: 'covered'; readonly scopes: readonly string[] }
49
+ | { readonly state: 'stale'; readonly uncovered: readonly TestReceiptScopeState[] }
50
+ | { readonly state: 'not-established'; readonly reason: string };
51
+
52
+ /**
53
+ * Чистое решение. Ничего не читает и не запускает — отпечатки и журнал подаёт вызывающий.
54
+ *
55
+ * Три исхода, а не два, намеренно: «не установлено» — это НЕ «покрыто». Гейт, выводящий пропуск из
56
+ * пустого входа, тихо ломается на каждом новом способе получить пустой вход.
57
+ */
58
+ export function decideTestReceipt(input: {
59
+ readonly current: Readonly<Record<string, string>>;
60
+ readonly receipts: readonly TestReceipt[];
61
+ }): TestReceiptVerdict {
62
+ const current = input.current ?? {};
63
+ const scopes = Object.keys(current).sort();
64
+ if (scopes.length === 0) {
65
+ return { state: 'not-established', reason: 'ни одна наблюдаемая область не разрешилась — проверка была бы вакуумной' };
66
+ }
67
+ for (const scope of scopes) {
68
+ if (typeof current[scope] !== 'string' || current[scope] === '') {
69
+ return { state: 'not-established', reason: `отпечаток области ${scope} не вычислен — вердикт не о чем выносить` };
70
+ }
71
+ }
72
+
73
+ const receipts = (Array.isArray(input.receipts) ? input.receipts : []).filter(
74
+ (r): r is TestReceipt => !!r && typeof r === 'object' && r.scopes !== null && typeof r.scopes === 'object',
75
+ );
76
+
77
+ const uncovered: TestReceiptScopeState[] = [];
78
+ for (const scope of scopes) {
79
+ const digest = current[scope]!;
80
+ let seenAt: string | null = null;
81
+ let covered = false;
82
+ for (const receipt of receipts) {
83
+ const recorded = receipt.scopes[scope];
84
+ if (typeof recorded !== 'string') continue;
85
+ if (typeof receipt.ts === 'string' && (seenAt === null || receipt.ts > seenAt)) seenAt = receipt.ts;
86
+ if (recorded === digest) { covered = true; break; }
87
+ }
88
+ if (!covered) uncovered.push({ scope, digest, why: seenAt === null ? 'never' : 'changed', lastGreenAt: seenAt });
89
+ }
90
+
91
+ return uncovered.length === 0 ? { state: 'covered', scopes } : { state: 'stale', uncovered };
92
+ }
93
+
94
+ /** Одна строка для человека. Отказ обязан говорить, ЧТО запустить, а не только что всё плохо. */
95
+ export function renderTestReceiptVerdict(verdict: TestReceiptVerdict): string {
96
+ if (verdict.state === 'covered') {
97
+ return `test-receipt: покрыто — ${verdict.scopes.length} област(и) в том же виде, в каком были зелёными`;
98
+ }
99
+ if (verdict.state === 'not-established') {
100
+ return `test-receipt: НЕ УСТАНОВЛЕНО — ${verdict.reason}. Это не пропуск и не отказ по существу`;
101
+ }
102
+ const lines = verdict.uncovered.map((item) => item.why === 'never'
103
+ ? ` · ${item.scope}: зелёного прогона не было НИ РАЗУ`
104
+ : ` · ${item.scope}: изменилась после последнего зелёного прогона (${item.lastGreenAt})`);
105
+ return [`test-receipt: НЕ ПОКРЫТО — ${verdict.uncovered.length} област(и):`, ...lines].join('\n');
106
+ }
@@ -42,6 +42,7 @@ import {
42
42
  import { classifyFailure, errSnap, gateVerdict, joinRegion, stepContractLines, type JoinOutcome } from './loop-run-semantics.js';
43
43
  import { checkpointInputHash, decideCheckpointResume, parseCheckpointRead, serializeCheckpoint } from './feature-adr-checkpoints.js';
44
44
  import { modelFamily, type BridgeFamily } from './qe-bridge.js';
45
+ import { buildReqeDebt } from './reqe.js';
45
46
  import { CODEX_EXEC_XHIGH_TIMEOUT_MS, defangGateEchoes, type DispatchResult, type Dispatcher } from './workflow-run-dispatch.js';
46
47
 
47
48
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1203,19 +1204,17 @@ export async function runWorkflow(inputs: RunnerInputs, pre: PreflightOk, deps:
1203
1204
 
1204
1205
  // a waived same-family QE step owes a machine debt, not a doc instruction (the FR-2.9 precedent)
1205
1206
  for (const w of pre.qeWaivers) {
1206
- // The FR-2.9 `reqe-due-1` shape VERBATIM (`reqe.ts` parseReqeDebt), because the debt is only
1207
- // real if `dz reqe` can read it — a record in a shape the reader rejects is a promise, not a
1208
- // debt. `qeFamily` equals `coderFamily` here: that identity IS the waiver.
1209
- store.writeReqeDebt({
1210
- schema: 'reqe-due-1',
1207
+ // Use the shared builder so the waiver stays readable by every debt consumer.
1208
+ store.writeReqeDebt(buildReqeDebt({
1211
1209
  slug: deps.slug ?? inputs.runId,
1212
- coderFamily: inputs.coderFamily,
1213
- qeFamily: inputs.coderFamily,
1210
+ coderUsed: inputs.coderFamily,
1211
+ qeReviewerUsed: inputs.coderFamily,
1214
1212
  qeGrade: null,
1213
+ cause: 'same-family-fallback',
1215
1214
  reason: `dz workflow run ${inputs.runId}: step ${w.step} (x-role: qe) resolved to the CODER family ${inputs.coderFamily} and ran under --allow-same-family-qe — the cross-family guard was consciously suspended, so an independent review is OWED`,
1216
1215
  emittedAt: startedAt,
1217
1216
  runStamp: `${inputs.runId}:${pre.execFp.slice(0, 16)}`,
1218
- });
1217
+ }));
1219
1218
  }
1220
1219
 
1221
1220
  // the PROBED ids (established above, before the resume decision) become this run's resolution