@ai-dossier/sched 0.42.1 → 0.45.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.
Files changed (52) hide show
  1. package/README.md +58 -1
  2. package/dist/batch-dispatch.d.ts +8 -3
  3. package/dist/batch-dispatch.d.ts.map +1 -1
  4. package/dist/batch-dispatch.js +23 -8
  5. package/dist/batch-dispatch.js.map +1 -1
  6. package/dist/batch-stats.d.ts +23 -0
  7. package/dist/batch-stats.d.ts.map +1 -1
  8. package/dist/batch-stats.js +81 -6
  9. package/dist/batch-stats.js.map +1 -1
  10. package/dist/dispatch-health.d.ts +1 -0
  11. package/dist/dispatch-health.d.ts.map +1 -1
  12. package/dist/dispatch-health.js +2 -1
  13. package/dist/dispatch-health.js.map +1 -1
  14. package/dist/dispatch.d.ts +30 -8
  15. package/dist/dispatch.d.ts.map +1 -1
  16. package/dist/dispatch.js +40 -8
  17. package/dist/dispatch.js.map +1 -1
  18. package/dist/engine.d.ts.map +1 -1
  19. package/dist/engine.js +50 -4
  20. package/dist/engine.js.map +1 -1
  21. package/dist/enqueue.d.ts +17 -2
  22. package/dist/enqueue.d.ts.map +1 -1
  23. package/dist/enqueue.js +45 -1
  24. package/dist/enqueue.js.map +1 -1
  25. package/dist/index.d.ts +6 -6
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +13 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/persist.d.ts.map +1 -1
  30. package/dist/persist.js +11 -1
  31. package/dist/persist.js.map +1 -1
  32. package/dist/recovery.d.ts +11 -0
  33. package/dist/recovery.d.ts.map +1 -1
  34. package/dist/recovery.js +5 -1
  35. package/dist/recovery.js.map +1 -1
  36. package/dist/scheduler.d.ts +5 -1
  37. package/dist/scheduler.d.ts.map +1 -1
  38. package/dist/scheduler.js +9 -3
  39. package/dist/scheduler.js.map +1 -1
  40. package/dist/state.d.ts +1 -0
  41. package/dist/state.d.ts.map +1 -1
  42. package/dist/state.js +27 -0
  43. package/dist/state.js.map +1 -1
  44. package/dist/status.d.ts +33 -1
  45. package/dist/status.d.ts.map +1 -1
  46. package/dist/status.js +91 -1
  47. package/dist/status.js.map +1 -1
  48. package/dist/types.d.ts +52 -2
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +3 -1
  51. package/dist/types.js.map +1 -1
  52. package/package.json +1 -1
package/README.md CHANGED
@@ -665,7 +665,35 @@ below. 1.9.0 and earlier states migrate on load, backfilling absent OR explicit
665
665
  those same defaults: nothing before this field existed was ever weighted differently, so
666
666
  the backfill is exact, not a guess.
667
667
 
668
- ## Unit priority (#565)
668
+ ## Per-member review level (#771)
669
+
670
+ A slot member carries `review: 'light' | 'full'` on its `QueueEntry` (default `light`;
671
+ pre-#771 `state.json` entries backfill to `light` on load — exact, since every earlier slot
672
+ member got the light, relevance-scoped review). `review=full` is how a risk-floor issue
673
+ (billing/security/auth/deploy/migration) joins a batch instead of being forced out to
674
+ `mode=full` (#770, operator Option A):
675
+
676
+ - **Enqueue** — set per manifest entry (`"review": "full"`) or with `sched enqueue --mode
677
+ slot --review full`. Rejected on a `mode=full` entry (a full cycle always reviews fully).
678
+ At most `MAX_FULL_REVIEW_MEMBERS` (2) `review=full` members per batch, counted over the
679
+ batch's whole membership after the call so an incremental join cannot slip a third in;
680
+ a violating call is rejected with an `EnqueueError` and writes nothing. The cap is
681
+ overridable per project via `config.json`'s `max_full_review_members` (a non-negative
682
+ integer), resolved by the CLI and passed to `enqueueEntries` as
683
+ `options.maxFullReviewMembers` — the core stays config-free.
684
+ - **Dispatch** — `memberDispatchTier` floors a `review=full` member at `strong`, whatever
685
+ tier its manifest asked for; the recorded `QueueEntry.tier` stays as written. Only the
686
+ tier moves: the batch's dispatch profile still resolves the executor for that tier, so the
687
+ batch stays one agent family. The member's `spawned` journal event carries `review: "full"`.
688
+ The floor follows the member: its bounded fix attempt dispatches at `strong` (not
689
+ `FIX_ATTEMPT_TIER`), and an eviction/abandon requeue to full-cycle rewrites its `tier` to the
690
+ floored value (and `review` back to `light`, a slot-only concept) so the full cycle keeps it.
691
+ - **Prompt** — `buildMemberPrompt` substitutes a `{review}` placeholder; a `review=full`
692
+ member whose template lacks one gets `FULL_REVIEW_MEMBER_DIRECTIVE` appended (full-cycle-
693
+ grade review before handover). A `light` member's prompt is byte-identical to before.
694
+ - **Status** — `sched status`'s Queue table has a `review` column for slot members, and the
695
+ `tier` column shows the floor as `mid→strong` when it applies.
696
+
669
697
 
670
698
  `readiness.ts`'s `runnableUnits` ranks EVERY candidate — issues and batches together — by
671
699
  `priority` desc, then readiness age (`updated_at`) asc, then a numeric tiebreak (an
@@ -702,6 +730,17 @@ row governs assignment), so the Queue table renders `-` for it rather than a num
702
730
  looks load-bearing but is not. A batch dissolve (`recovery.ts`'s `dissolveBatch`) carries
703
731
  the parent batch's priority forward onto both split halves.
704
732
 
733
+ ## The batch gate — `gate.batch` (#777)
734
+
735
+ `runValidate` runs the injected `runSuite(worktree, ctx)` once per `validating` pass;
736
+ `ctx` is `{ batchId, baseRef: 'origin/<base_branch>' }` (`BatchSuiteContext`, also passed
737
+ on attribution/fix re-runs). The CLI's runner (`cli/src/batch-suite-runner.ts`) prefers the
738
+ repo's active `gate.batch` capability — the same CI-parity gate a normal PR pays — over
739
+ `test.full`, exporting `ctx` as `DOSSIER_BATCH_ID` / `DOSSIER_BATCH_BASE`; outcomes map
740
+ identically either way (`ok` / red-and-attributable / `suite-unreadable` block).
741
+ `sched enqueue` refuses to form a batch whose only full gate is a `test.full` marked
742
+ `timeout_prone: true`. Details: `docs/reference/capabilities.md`.
743
+
705
744
  ## Batch dispatch (#523)
706
745
 
707
746
  `batch-dispatch.ts`'s `runBatchTick` — called from `tick()` after the issue-level pass,
@@ -1188,6 +1227,24 @@ after-the-fact recovery, not a missing-data bug.
1188
1227
  can be manual (`sched pause`) or automatic (dispatch-health, #505/#629 above); `sched
1189
1228
  resume` clears the flag and both dispatch-health streaks.
1190
1229
 
1230
+ ## Status health warnings (#776)
1231
+
1232
+ `sched status` (and `warnings[]` in `--json`, built by `buildStatusWarnings`) flags state
1233
+ nobody came back to, each with its exact remedy:
1234
+
1235
+ - **`long-pause`** — paused for more than 24 h (`STATUS_HEALTH_WARNING_AGE_MS`), measured
1236
+ from `paused_at`, which `setPaused` stamps on the running → paused edge and clears on
1237
+ resume. A pause recorded before `paused_at` existed is flagged as "unknown age".
1238
+ - **`stale-engine-lease`** — the engine lease holder's pid is dead while queue entries are
1239
+ unfinished or slots live: nothing is ticking (`sched start`).
1240
+ - **`stuck-slot`** — a live slot (`assigned`/`running`/`recovering`) with no progress for
1241
+ more than 24 h (`sched stop --issue N` / `--batch B` once the work is confirmed done or
1242
+ abandoned).
1243
+ - **`stale-closed`** — each tick polls `issueClosed` for every `recovering` cycle slot; a
1244
+ closed issue sets the entry's sticky `stale_closed_at`, journals `stale-closed` once, and
1245
+ the recovery rail never respawns it (even after `sched resume`). Report slots are exempt:
1246
+ their issue is closed at merge by design. Remedy: `sched stop --issue N`.
1247
+
1191
1248
  ## Hard-block labels are re-read every tick (#544)
1192
1249
 
1193
1250
  #507's enqueue pre-screen resolves an issue's GitHub labels in the CLI and lands the
@@ -65,7 +65,7 @@ import { type GroundTruth } from './groundtruth';
65
65
  import { type Journal } from './journal';
66
66
  import type { SchedStore } from './persist';
67
67
  import type { ExecFn } from './project';
68
- import { type SuiteResult } from './recovery';
68
+ import { type BatchSuiteContext, type SuiteResult } from './recovery';
69
69
  import { type FsExists } from './teardown';
70
70
  import type { BatchEntry, CapabilityGateResult, SchedConfig } from './types';
71
71
  export type { CapOutcome } from './types';
@@ -88,8 +88,13 @@ export interface BatchDispatchDeps {
88
88
  * timeout, e.g. `@ai-dossier/worktree-pool`'s `WARM_COMMAND_TIMEOUT_MS`).
89
89
  */
90
90
  warmExec?: ExecFn;
91
- /** Runs the aggregate suite inside a batch worktree; batches never leave `validating` without one. */
92
- runSuite: (worktree: string) => SuiteResult;
91
+ /**
92
+ * Runs the aggregate suite inside a batch worktree; batches never leave
93
+ * `validating` without one. `ctx` (#777) names the batch and its base ref so
94
+ * the runner can hand them to the repo's `gate.batch` capability; optional
95
+ * so runners written before it keep working unchanged.
96
+ */
97
+ runSuite: (worktree: string, ctx?: BatchSuiteContext) => SuiteResult;
93
98
  /**
94
99
  * Runs one `ai-dossier cap run <capabilityId>` in a batch worktree. Two call
95
100
  * sites, different degrade contracts:
@@ -1 +1 @@
1
- {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"batch-dispatch.d.ts","sourceRoot":"","sources":["../src/batch-dispatch.ts"],"names":[],"mappings":"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"}
@@ -356,9 +356,10 @@ function applyBatchAndIssues(fresh, computed, batchId, issues, membersBeforeReco
356
356
  * into `tick()`'s own catch — a bare `tick-failed` with no unit id, repeating
357
357
  * every reconcile interval forever since nothing about the batch changed.
358
358
  */
359
- function safeSuite(deps, batchId, worktree) {
359
+ function safeSuite(deps, batch, worktree) {
360
+ const batchId = batch.id;
360
361
  try {
361
- return deps.runSuite(worktree);
362
+ return deps.runSuite(worktree, batchSuiteContext(batch));
362
363
  }
363
364
  catch (err) {
364
365
  const detail = `suite runner threw: ${err.message}`;
@@ -368,13 +369,19 @@ function safeSuite(deps, batchId, worktree) {
368
369
  return { ok: false, failing: [], readable: false, detail };
369
370
  }
370
371
  }
372
+ /** What the aggregate-suite runner is told about the batch it gates (#777). */
373
+ function batchSuiteContext(batch) {
374
+ return { batchId: batch.id, baseRef: `origin/${batch.base_branch}` };
375
+ }
371
376
  function recoveryDeps(deps, config, batch, now) {
372
377
  return {
373
378
  exec: deps.exec,
374
379
  repoDir: batch.worktree ?? deps.repoDir,
375
380
  journal: deps.journal,
376
381
  postMilestone: (0, recovery_1.createExecMilestonePoster)(deps.exec, { repoDir: deps.repoDir }),
377
- runSuite: batch.worktree !== null ? () => deps.runSuite(batch.worktree) : undefined,
382
+ runSuite: batch.worktree !== null
383
+ ? () => deps.runSuite(batch.worktree, batchSuiteContext(batch))
384
+ : undefined,
378
385
  dissolvePolicy: (0, types_1.resolveDissolvePolicy)(config.dissolve_policy),
379
386
  now: () => now,
380
387
  };
@@ -914,13 +921,17 @@ function spawnMember(deps, dispatch, state, slot, batchId, now, result, member)
914
921
  return releaseSlot(state, batchId, now);
915
922
  }
916
923
  const withStatus = advanceMemberToInWork(state, memberIssue, now);
917
- const tier = (0, state_1.findEntry)(withStatus, memberIssue)?.tier ?? 'mid';
924
+ const memberEntry = (0, state_1.findEntry)(withStatus, memberIssue);
925
+ // #771: a `review=full` member dispatches at `strong` minimum (within this
926
+ // batch's profile — `dispatch` is already the batch-profiled resolution).
927
+ const tier = memberEntry ? (0, dispatch_1.memberDispatchTier)(memberEntry) : 'mid';
928
+ const review = memberEntry?.review ?? 'light';
918
929
  const spawnSpec = (0, dispatch_1.resolveTierSpawn)(dispatch, tier, memberIssue);
919
930
  const cmd = spawnSpec.cmd;
920
931
  // #677: the prompt carries THIS member's own worktree and the integration
921
932
  // branch it was cut from — `member-cycle`'s `worktree`/`integration_branch`
922
933
  // inputs. The shared batch worktree is no longer a member surface at all.
923
- const prompt = (0, dispatch_1.buildMemberPrompt)(dispatch.memberPrompt, memberIssue, batchId, member.worktree, batch.branch);
934
+ const prompt = (0, dispatch_1.buildMemberPrompt)(dispatch.memberPrompt, memberIssue, batchId, member.worktree, batch.branch, review);
924
935
  const logFile = (0, dispatch_1.batchMemberLogPath)(deps.store.runsDir, batchId, batch.executing_member, memberIssue);
925
936
  // #629: captured BEFORE spawning, mirroring `engine.ts`'s own
926
937
  // `spawnAndRecord` — the log is per-role and append-mode, so the size at
@@ -966,6 +977,7 @@ function spawnMember(deps, dispatch, state, slot, batchId, now, result, member)
966
977
  deps.journal.append((0, journal_1.unitEvent)('spawned', unit(batchId), {
967
978
  pid,
968
979
  tier,
980
+ ...(review === 'full' ? { review } : {}),
969
981
  slot: slot.id,
970
982
  issue: memberIssue,
971
983
  ...(0, dispatch_1.journalCmdModelFields)(spawnSpec),
@@ -1342,7 +1354,7 @@ function runValidate(deps, config, dispatch, batchId, now, result) {
1342
1354
  const batch = (0, state_1.findBatch)(state, batchId);
1343
1355
  if (!batch || batch.worktree === null)
1344
1356
  return;
1345
- const suite = safeSuite(deps, batchId, batch.worktree);
1357
+ const suite = safeSuite(deps, batch, batch.worktree);
1346
1358
  const rDeps = recoveryDeps(deps, config, batch, now);
1347
1359
  const poster = (0, recovery_1.createExecMilestonePoster)(deps.exec, { repoDir: deps.repoDir });
1348
1360
  if (suite.ok) {
@@ -1984,7 +1996,10 @@ function recordMemberRunLog(deps, dispatch, state, batchId, batch, memberIssue,
1984
1996
  });
1985
1997
  return null;
1986
1998
  }
1987
- const tier = (0, state_1.findEntry)(state, memberIssue)?.tier ?? 'mid';
1999
+ // Same effective tier `spawnMember` dispatched at (#771), so the run log
2000
+ // records the model that actually ran.
2001
+ const memberEntry = (0, state_1.findEntry)(state, memberIssue);
2002
+ const tier = memberEntry ? (0, dispatch_1.memberDispatchTier)(memberEntry) : 'mid';
1988
2003
  const { cmd, model } = (0, dispatch_1.resolveTierSpawn)(dispatch, tier, memberIssue);
1989
2004
  const logFile = (0, dispatch_1.batchMemberLogPath)(deps.store.runsDir, batchId, batch.executing_member, memberIssue);
1990
2005
  // #629: fenced to THIS dispatch's slice (`log_offset_at_spawn`, stamped by
@@ -2333,7 +2348,7 @@ function reconcileFixSlot(deps, config, batchId, slot, now, result) {
2333
2348
  // packages/sched/README.md's dispatch-health section.
2334
2349
  handleDeadDispatchApiError(deps, batchId, (0, dispatch_1.batchFixLogPath)(deps.store.runsDir, batchId, offenderRecord.issue), slot.log_offset_at_spawn ?? 0, now, { release: false });
2335
2350
  deps.store.withLock((s) => ({ state: releaseSlot(s, batchId, now), result: undefined }));
2336
- const suite = safeSuite(deps, batchId, batch.worktree);
2351
+ const suite = safeSuite(deps, batch, batch.worktree);
2337
2352
  const rDeps = recoveryDeps(deps, config, batch, now);
2338
2353
  const { state: resolved } = (0, recovery_1.resolveFixAttempt)(deps.store.load(), batchId, offenderRecord.issue, suite.ok ? 'green' : 'red', rDeps);
2339
2354
  deps.store.withLock((s) => ({