@ai-dossier/sched 0.2.0 → 0.2.1

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 (53) hide show
  1. package/dist/dispatch.d.ts +103 -0
  2. package/dist/dispatch.d.ts.map +1 -0
  3. package/dist/dispatch.js +262 -0
  4. package/dist/dispatch.js.map +1 -0
  5. package/dist/engine.d.ts +71 -0
  6. package/dist/engine.d.ts.map +1 -0
  7. package/dist/engine.js +590 -0
  8. package/dist/engine.js.map +1 -0
  9. package/dist/enqueue.d.ts +38 -0
  10. package/dist/enqueue.d.ts.map +1 -0
  11. package/dist/enqueue.js +220 -0
  12. package/dist/enqueue.js.map +1 -0
  13. package/dist/groundtruth.d.ts +74 -0
  14. package/dist/groundtruth.d.ts.map +1 -0
  15. package/dist/groundtruth.js +114 -0
  16. package/dist/groundtruth.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +86 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/journal.d.ts +32 -0
  22. package/dist/journal.d.ts.map +1 -0
  23. package/dist/journal.js +113 -0
  24. package/dist/journal.js.map +1 -0
  25. package/dist/persist.d.ts +54 -0
  26. package/dist/persist.d.ts.map +1 -0
  27. package/dist/persist.js +334 -0
  28. package/dist/persist.js.map +1 -0
  29. package/dist/project.d.ts +41 -0
  30. package/dist/project.d.ts.map +1 -0
  31. package/dist/project.js +124 -0
  32. package/dist/project.js.map +1 -0
  33. package/dist/readiness.d.ts +43 -0
  34. package/dist/readiness.d.ts.map +1 -0
  35. package/dist/readiness.js +102 -0
  36. package/dist/readiness.js.map +1 -0
  37. package/dist/scheduler.d.ts +66 -0
  38. package/dist/scheduler.d.ts.map +1 -0
  39. package/dist/scheduler.js +174 -0
  40. package/dist/scheduler.js.map +1 -0
  41. package/dist/state.d.ts +39 -0
  42. package/dist/state.d.ts.map +1 -0
  43. package/dist/state.js +360 -0
  44. package/dist/state.js.map +1 -0
  45. package/dist/status.d.ts +32 -0
  46. package/dist/status.d.ts.map +1 -0
  47. package/dist/status.js +76 -0
  48. package/dist/status.js.map +1 -0
  49. package/dist/types.d.ts +214 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +87 -0
  52. package/dist/types.js.map +1 -0
  53. package/package.json +2 -2
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ /**
3
+ * Project identity for the scheduler state directory
4
+ * (`~/.dossier/sched/<project>/`).
5
+ *
6
+ * `<project>` follows fleet-cycle's convention exactly: the repo slug
7
+ * `<owner>-<repo>` from `gh repo view --json owner,name`; when that fails (no
8
+ * remote, no `gh`), the basename of `git rev-parse --show-toplevel`.
9
+ *
10
+ * The exec function is injectable so tests (and any non-CLI consumer) never
11
+ * spawn processes. Since #464 these are no longer the package's only
12
+ * subprocesses (dispatch.ts spawns the configured agent; groundtruth.ts execs
13
+ * runstate/gh/git), but all process I/O in the package is injectable, and
14
+ * none of it is an LLM call the scheduler makes itself (AC7).
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.defaultExec = void 0;
51
+ exports.createExecFn = createExecFn;
52
+ exports.sanitizeSlug = sanitizeSlug;
53
+ exports.resolveProjectSlug = resolveProjectSlug;
54
+ exports.schedStateDir = schedStateDir;
55
+ const node_child_process_1 = require("node:child_process");
56
+ const os = __importStar(require("node:os"));
57
+ const path = __importStar(require("node:path"));
58
+ /** Build an `ExecFn` via `execFileSync` (never throws), optionally with a hard timeout and a failure observer. */
59
+ function createExecFn(timeoutMs, opts) {
60
+ return (file, args, cwd) => {
61
+ try {
62
+ return String((0, node_child_process_1.execFileSync)(file, args, {
63
+ encoding: 'utf-8',
64
+ stdio: ['ignore', 'pipe', 'pipe'],
65
+ ...(timeoutMs !== undefined ? { timeout: timeoutMs } : {}),
66
+ ...(cwd ? { cwd } : {}),
67
+ })).trim();
68
+ }
69
+ catch (err) {
70
+ opts?.onError?.(file, args, err);
71
+ return null;
72
+ }
73
+ };
74
+ }
75
+ /** Default exec: no timeout. */
76
+ exports.defaultExec = createExecFn();
77
+ /** Characters permitted in a project directory name. */
78
+ const SLUG_SAFE = /[^A-Za-z0-9._-]/g;
79
+ /**
80
+ * Filesystem-safe form of an arbitrary identifier: everything outside
81
+ * `[A-Za-z0-9._-]` becomes `-`. `.` and `..` survive the character allowlist
82
+ * but are path components that would escape `~/.dossier/sched/` (e.g.
83
+ * `--project ..` → `~/.dossier` itself, overwriting the global CLI config via
84
+ * saveConfig) — those collapse to `default`.
85
+ */
86
+ function sanitizeSlug(slug) {
87
+ const out = slug.replace(SLUG_SAFE, '-');
88
+ if (out === '.' || out === '..')
89
+ return 'default';
90
+ return out;
91
+ }
92
+ /**
93
+ * Resolve the project slug for `cwd` (default: process cwd). `gh` first, git
94
+ * basename as fallback; a final sanitized non-empty string otherwise.
95
+ */
96
+ function resolveProjectSlug(exec = exports.defaultExec, cwd) {
97
+ const ghOut = exec('gh', ['repo', 'view', '--json', 'owner,name'], cwd);
98
+ if (ghOut !== null) {
99
+ try {
100
+ const parsed = JSON.parse(ghOut);
101
+ const owner = parsed.owner?.login;
102
+ if (typeof owner === 'string' && typeof parsed.name === 'string') {
103
+ const slug = sanitizeSlug(`${owner}-${parsed.name}`);
104
+ if (slug.length > 0)
105
+ return slug;
106
+ }
107
+ }
108
+ catch {
109
+ // fall through to git
110
+ }
111
+ }
112
+ const root = exec('git', ['rev-parse', '--show-toplevel'], cwd);
113
+ if (root !== null && root.length > 0) {
114
+ const slug = sanitizeSlug(path.basename(root));
115
+ if (slug.length > 0)
116
+ return slug;
117
+ }
118
+ return 'default';
119
+ }
120
+ /** State directory for one project under the dossier home (testable via `home`). */
121
+ function schedStateDir(project, home = os.homedir()) {
122
+ return path.join(home, '.dossier', 'sched', sanitizeSlug(project));
123
+ }
124
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Readiness: which units can run right now, as a pure function of state
3
+ * (RFC-0001 §E.4: "scheduler gates on merge" — an issue with an unmerged
4
+ * dependency is never runnable, and a batch is never dispatched while a batch
5
+ * it depends on is unmerged).
6
+ */
7
+ import type { BatchEntry, IssueStatus, QueueEntry, SchedState } from './types';
8
+ /** A unit the scheduler can place on a slot: one full-mode issue, or one batch. */
9
+ export type RunnableUnit = {
10
+ kind: 'issue';
11
+ issue: number;
12
+ } | {
13
+ kind: 'batch';
14
+ batch: string;
15
+ };
16
+ /** Why a dependency edge currently blocks its holder. */
17
+ export interface DependencyBlocker {
18
+ /** The blocked issue. */
19
+ issue: number;
20
+ /** The unsatisfied dependency. */
21
+ dep: number;
22
+ reason: 'unmerged' | 'not-in-queue';
23
+ /** Current status of the dependency entry, when it exists. */
24
+ depStatus?: IssueStatus;
25
+ }
26
+ /** Issue statuses from which a full-cycle dispatch may start. */
27
+ export declare const DISPATCHABLE_ISSUE_STATUSES: ReadonlySet<IssueStatus>;
28
+ /**
29
+ * Unsatisfied dependency edges of a single entry. Deps satisfied by
30
+ * membership in the SAME batch are not blockers — intra-batch ordering is the
31
+ * batch's own concern once dispatched (RFC-0001 §E.4).
32
+ */
33
+ export declare function dependencyBlockers(state: SchedState, entry: QueueEntry): DependencyBlocker[];
34
+ /** Whether `batch` may dispatch: status `ready` and every cross-batch/cross-issue edge merged. */
35
+ export declare function batchBlockers(state: SchedState, batch: BatchEntry): DependencyBlocker[];
36
+ /**
37
+ * All runnable units in stable dispatch order: issues in queue order, then
38
+ * batches in creation order. A batch unit unlocks ALL its members' execution,
39
+ * so batches are listed after issues only for stability — the caller slices to
40
+ * free capacity either way.
41
+ */
42
+ export declare function runnableUnits(state: SchedState): RunnableUnit[];
43
+ //# sourceMappingURL=readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAG/E,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,GAAG,cAAc,CAAC;IACpC,8DAA8D;IAC9D,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED,iEAAiE;AACjE,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,WAAW,CAM/D,CAAC;AAMH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,iBAAiB,EAAE,CAe5F;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,iBAAiB,EAAE,CAuBvF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,EAAE,CAc/D"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * Readiness: which units can run right now, as a pure function of state
4
+ * (RFC-0001 §E.4: "scheduler gates on merge" — an issue with an unmerged
5
+ * dependency is never runnable, and a batch is never dispatched while a batch
6
+ * it depends on is unmerged).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DISPATCHABLE_ISSUE_STATUSES = void 0;
10
+ exports.dependencyBlockers = dependencyBlockers;
11
+ exports.batchBlockers = batchBlockers;
12
+ exports.runnableUnits = runnableUnits;
13
+ const state_1 = require("./state");
14
+ const types_1 = require("./types");
15
+ /** Issue statuses from which a full-cycle dispatch may start. */
16
+ exports.DISPATCHABLE_ISSUE_STATUSES = new Set([
17
+ // `queued` is dispatchable because the manifest already carries the mode —
18
+ // the classifier (#465) only refines it later.
19
+ 'queued',
20
+ 'classified',
21
+ 'requeued',
22
+ ]);
23
+ function batchOf(state, issue) {
24
+ return state.batches.find((b) => b.members.includes(issue));
25
+ }
26
+ /**
27
+ * Unsatisfied dependency edges of a single entry. Deps satisfied by
28
+ * membership in the SAME batch are not blockers — intra-batch ordering is the
29
+ * batch's own concern once dispatched (RFC-0001 §E.4).
30
+ */
31
+ function dependencyBlockers(state, entry) {
32
+ const blockers = [];
33
+ const ownBatch = entry.batch !== null ? (0, state_1.findBatch)(state, entry.batch) : undefined;
34
+ for (const dep of entry.deps) {
35
+ if (ownBatch?.members.includes(dep))
36
+ continue;
37
+ const depEntry = state.entries.find((e) => e.issue === dep);
38
+ if (!depEntry) {
39
+ blockers.push({ issue: entry.issue, dep, reason: 'not-in-queue' });
40
+ continue;
41
+ }
42
+ if (!types_1.SATISFIED_ISSUE_STATUSES.has(depEntry.status)) {
43
+ blockers.push({ issue: entry.issue, dep, reason: 'unmerged', depStatus: depEntry.status });
44
+ }
45
+ }
46
+ return blockers;
47
+ }
48
+ /** Whether `batch` may dispatch: status `ready` and every cross-batch/cross-issue edge merged. */
49
+ function batchBlockers(state, batch) {
50
+ const blockers = [];
51
+ for (const member of batch.members) {
52
+ const entry = state.entries.find((e) => e.issue === member);
53
+ if (!entry)
54
+ continue; // validateState guarantees membership consistency
55
+ for (const dep of entry.deps) {
56
+ if (batch.members.includes(dep))
57
+ continue; // intra-batch edge
58
+ const depBatch = batchOf(state, dep);
59
+ if (depBatch && depBatch.id !== batch.id) {
60
+ if (!types_1.MERGED_BATCH_STATUSES.has(depBatch.status)) {
61
+ blockers.push({ issue: member, dep, reason: 'unmerged' });
62
+ }
63
+ continue;
64
+ }
65
+ const depEntry = state.entries.find((e) => e.issue === dep);
66
+ if (!depEntry) {
67
+ blockers.push({ issue: member, dep, reason: 'not-in-queue' });
68
+ }
69
+ else if (!types_1.SATISFIED_ISSUE_STATUSES.has(depEntry.status)) {
70
+ blockers.push({ issue: member, dep, reason: 'unmerged', depStatus: depEntry.status });
71
+ }
72
+ }
73
+ }
74
+ return blockers;
75
+ }
76
+ /**
77
+ * All runnable units in stable dispatch order: issues in queue order, then
78
+ * batches in creation order. A batch unit unlocks ALL its members' execution,
79
+ * so batches are listed after issues only for stability — the caller slices to
80
+ * free capacity either way.
81
+ */
82
+ function runnableUnits(state) {
83
+ const units = [];
84
+ for (const entry of state.entries) {
85
+ if (entry.mode !== 'full')
86
+ continue;
87
+ if (!exports.DISPATCHABLE_ISSUE_STATUSES.has(entry.status))
88
+ continue;
89
+ if (dependencyBlockers(state, entry).length > 0)
90
+ continue;
91
+ units.push({ kind: 'issue', issue: entry.issue });
92
+ }
93
+ for (const batch of state.batches) {
94
+ if (batch.status !== 'ready')
95
+ continue;
96
+ if (batchBlockers(state, batch).length > 0)
97
+ continue;
98
+ units.push({ kind: 'batch', batch: batch.id });
99
+ }
100
+ return units;
101
+ }
102
+ //# sourceMappingURL=readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The deterministic scheduler core: which units are runnable, and how idle
3
+ * slots get filled (RFC-0001 §B — "refill is a state-machine transition, not
4
+ * a remembered instruction").
5
+ *
6
+ * Everything here is a pure function of (state, config). Dispatching —
7
+ * spawning agent processes, completion verification, stall timers — is #464;
8
+ * this module only decides assignments and performs the typed slot/issue
9
+ * transitions that make them durable. When `state.paused` is set, no
10
+ * assignments are made at all (`sched pause`).
11
+ */
12
+ import { batchBlockers, type DependencyBlocker, dependencyBlockers, type RunnableUnit, runnableUnits } from './readiness';
13
+ import type { SchedConfig, SchedState } from './types';
14
+ export { runnableUnits, dependencyBlockers, batchBlockers };
15
+ export type { DependencyBlocker, RunnableUnit };
16
+ /** One placement made by `computeAssignments`: slot ← unit. */
17
+ export type Assignment = {
18
+ slot: number;
19
+ kind: 'issue';
20
+ issue: number;
21
+ } | {
22
+ slot: number;
23
+ kind: 'batch';
24
+ batch: string;
25
+ };
26
+ /**
27
+ * Fill idle slots with runnable units, bounded by `config.max_slots` (AC5):
28
+ * `max_slots` caps LIVE slots (assigned | running | recovering), so free
29
+ * capacity = max_slots − live. Idle slots are reused first; new slots are
30
+ * materialized lazily (as `idle`, then transitioned `idle → assigned` — a
31
+ * typed edge, never a synthetic mid-state).
32
+ *
33
+ * `kinds` restricts which unit kinds may be assigned (default: both). The
34
+ * #464 engine dispatches `issue` units only — batch member sequencing is a
35
+ * follow-up — so it passes `['issue']` and a `ready` batch never occupies a
36
+ * slot it cannot run on yet.
37
+ */
38
+ export declare function computeAssignments(state: SchedState, config: SchedConfig, now?: Date, kinds?: readonly ('issue' | 'batch')[]): {
39
+ state: SchedState;
40
+ assignments: Assignment[];
41
+ };
42
+ /**
43
+ * `sched pause` / `sched resume`: toggle the paused flag. Pausing does not
44
+ * touch live units — it only stops new assignments.
45
+ */
46
+ export declare function setPaused(state: SchedState, paused: boolean): SchedState;
47
+ /**
48
+ * `sched abandon --issue N`: mark an entry failed via the universal failure
49
+ * edge, recording the reason, and release any slot holding it
50
+ * (running/exited/verifying → failed → idle). Terminal entries cannot be
51
+ * abandoned.
52
+ */
53
+ export declare function abandonIssue(state: SchedState, issue: number, reason?: string, now?: Date): {
54
+ state: SchedState;
55
+ releasedSlots: number[];
56
+ };
57
+ /**
58
+ * `sched abandon --batch B`: dissolve the batch and requeue every non-terminal
59
+ * member as full-cycle (RFC-0001 §D.2 dissolving → "members requeued"; F.8
60
+ * "nothing green is discarded" — members already shipped stay put).
61
+ */
62
+ export declare function abandonBatch(state: SchedState, batchId: string, reason?: string, now?: Date): {
63
+ state: SchedState;
64
+ requeued: number[];
65
+ };
66
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,aAAa,EACb,KAAK,iBAAiB,EACtB,kBAAkB,EAClB,KAAK,YAAY,EACjB,aAAa,EACd,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AASvD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAMnD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,EACnB,GAAG,GAAE,IAAiB,EACtB,KAAK,GAAE,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,EAAuB,GACzD;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CA6DlD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,UAAU,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,SAAc,EACpB,GAAG,GAAE,IAAiB,GACrB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAqBhD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,SAAoB,EAC1B,GAAG,GAAE,IAAiB,GACrB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C3C"}
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ /**
3
+ * The deterministic scheduler core: which units are runnable, and how idle
4
+ * slots get filled (RFC-0001 §B — "refill is a state-machine transition, not
5
+ * a remembered instruction").
6
+ *
7
+ * Everything here is a pure function of (state, config). Dispatching —
8
+ * spawning agent processes, completion verification, stall timers — is #464;
9
+ * this module only decides assignments and performs the typed slot/issue
10
+ * transitions that make them durable. When `state.paused` is set, no
11
+ * assignments are made at all (`sched pause`).
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.batchBlockers = exports.dependencyBlockers = exports.runnableUnits = void 0;
15
+ exports.computeAssignments = computeAssignments;
16
+ exports.setPaused = setPaused;
17
+ exports.abandonIssue = abandonIssue;
18
+ exports.abandonBatch = abandonBatch;
19
+ const readiness_1 = require("./readiness");
20
+ Object.defineProperty(exports, "batchBlockers", { enumerable: true, get: function () { return readiness_1.batchBlockers; } });
21
+ Object.defineProperty(exports, "dependencyBlockers", { enumerable: true, get: function () { return readiness_1.dependencyBlockers; } });
22
+ Object.defineProperty(exports, "runnableUnits", { enumerable: true, get: function () { return readiness_1.runnableUnits; } });
23
+ const state_1 = require("./state");
24
+ const types_1 = require("./types");
25
+ function unitId(unit) {
26
+ return unit.kind === 'issue' ? `issue:${unit.issue}` : `batch:${unit.batch}`;
27
+ }
28
+ /**
29
+ * Fill idle slots with runnable units, bounded by `config.max_slots` (AC5):
30
+ * `max_slots` caps LIVE slots (assigned | running | recovering), so free
31
+ * capacity = max_slots − live. Idle slots are reused first; new slots are
32
+ * materialized lazily (as `idle`, then transitioned `idle → assigned` — a
33
+ * typed edge, never a synthetic mid-state).
34
+ *
35
+ * `kinds` restricts which unit kinds may be assigned (default: both). The
36
+ * #464 engine dispatches `issue` units only — batch member sequencing is a
37
+ * follow-up — so it passes `['issue']` and a `ready` batch never occupies a
38
+ * slot it cannot run on yet.
39
+ */
40
+ function computeAssignments(state, config, now = new Date(), kinds = ['issue', 'batch']) {
41
+ if (state.paused) {
42
+ return { state, assignments: [] };
43
+ }
44
+ const live = state.slots.filter((s) => types_1.LIVE_SLOT_STATUSES.has(s.status)).length;
45
+ const freeCapacity = Math.max(0, config.max_slots - live);
46
+ if (freeCapacity === 0) {
47
+ return { state, assignments: [] };
48
+ }
49
+ const held = new Set(state.slots.map((s) => s.unit).filter((u) => u !== null));
50
+ const candidates = (0, readiness_1.runnableUnits)(state)
51
+ .filter((unit) => kinds.includes(unit.kind))
52
+ .filter((unit) => !held.has(unitId(unit)));
53
+ const taken = candidates.slice(0, freeCapacity);
54
+ if (taken.length === 0) {
55
+ return { state, assignments: [] };
56
+ }
57
+ let next = state;
58
+ const assignments = [];
59
+ for (const unit of taken) {
60
+ let idle = next.slots.find((s) => s.status === 'idle');
61
+ if (!idle) {
62
+ const slot = {
63
+ id: next.next_slot_id,
64
+ status: 'idle',
65
+ unit: null,
66
+ pid: null,
67
+ pid_start: null,
68
+ phase: null,
69
+ last_progress_at: null,
70
+ branch: null,
71
+ last_head: null,
72
+ recoveries: 0,
73
+ updated_at: now.toISOString(),
74
+ };
75
+ next = { ...next, slots: [...next.slots, slot], next_slot_id: next.next_slot_id + 1 };
76
+ idle = slot;
77
+ }
78
+ next = (0, state_1.transitionSlot)(next, idle.id, 'assigned', {
79
+ unit: unitId(unit),
80
+ pid: null,
81
+ phase: null,
82
+ last_progress_at: now.toISOString(),
83
+ }, now);
84
+ assignments.push(unit.kind === 'issue'
85
+ ? { slot: idle.id, kind: 'issue', issue: unit.issue }
86
+ : { slot: idle.id, kind: 'batch', batch: unit.batch });
87
+ }
88
+ return { state: next, assignments };
89
+ }
90
+ /**
91
+ * `sched pause` / `sched resume`: toggle the paused flag. Pausing does not
92
+ * touch live units — it only stops new assignments.
93
+ */
94
+ function setPaused(state, paused) {
95
+ return { ...state, paused };
96
+ }
97
+ /**
98
+ * `sched abandon --issue N`: mark an entry failed via the universal failure
99
+ * edge, recording the reason, and release any slot holding it
100
+ * (running/exited/verifying → failed → idle). Terminal entries cannot be
101
+ * abandoned.
102
+ */
103
+ function abandonIssue(state, issue, reason = 'abandoned', now = new Date()) {
104
+ const entry = state.entries.find((e) => e.issue === issue);
105
+ if (!entry) {
106
+ throw new types_1.SchedNotFoundError(`Queue entry not found: ${issue}`);
107
+ }
108
+ if (types_1.TERMINAL_ISSUE_STATUSES.has(entry.status)) {
109
+ throw new types_1.SchedNotFoundError(`Issue ${issue} is already ${entry.status} — nothing to abandon`);
110
+ }
111
+ let next = (0, state_1.transitionIssue)(state, issue, 'failed', { reason }, now);
112
+ const unit = `issue:${issue}`;
113
+ const released = [];
114
+ for (const slot of next.slots) {
115
+ if (slot.unit === unit && slot.status !== 'idle') {
116
+ if (slot.status !== 'failed' && slot.status !== 'complete') {
117
+ next = (0, state_1.transitionSlot)(next, slot.id, 'failed', {}, now);
118
+ }
119
+ next = (0, state_1.transitionSlot)(next, slot.id, 'idle', {}, now);
120
+ released.push(slot.id);
121
+ }
122
+ }
123
+ return { state: next, releasedSlots: released };
124
+ }
125
+ /**
126
+ * `sched abandon --batch B`: dissolve the batch and requeue every non-terminal
127
+ * member as full-cycle (RFC-0001 §D.2 dissolving → "members requeued"; F.8
128
+ * "nothing green is discarded" — members already shipped stay put).
129
+ */
130
+ function abandonBatch(state, batchId, reason = 'batch abandoned', now = new Date()) {
131
+ const batch = (0, state_1.findBatch)(state, batchId);
132
+ if (!batch) {
133
+ throw new types_1.SchedNotFoundError(`Batch not found: ${batchId}`);
134
+ }
135
+ if (types_1.TERMINAL_BATCH_STATUSES.has(batch.status)) {
136
+ throw new types_1.SchedNotFoundError(`Batch ${batchId} is already ${batch.status} — nothing to abandon`);
137
+ }
138
+ let next = (0, state_1.transitionBatch)(state, batchId, 'dissolving', {}, now);
139
+ next = (0, state_1.transitionBatch)(next, batchId, 'dissolved', {}, now);
140
+ const requeued = [];
141
+ for (const issue of batch.members) {
142
+ const entry = next.entries.find((e) => e.issue === issue);
143
+ if (!entry)
144
+ continue;
145
+ // Nothing green is discarded (F.8): terminal or already-shipped members
146
+ // keep their outcome; only active members requeue.
147
+ if (types_1.TERMINAL_ISSUE_STATUSES.has(entry.status) || types_1.SATISFIED_ISSUE_STATUSES.has(entry.status)) {
148
+ continue;
149
+ }
150
+ if (entry.status === 'queued' || entry.status === 'classified') {
151
+ // Never reached the batch rail — retag as full-cycle (metadata change,
152
+ // not a status transition) and it stays queued as-is.
153
+ next = {
154
+ ...next,
155
+ entries: next.entries.map((e) => e.issue === issue
156
+ ? { ...e, mode: 'full', batch: null, reason, updated_at: now.toISOString() }
157
+ : e),
158
+ };
159
+ requeued.push(issue);
160
+ continue;
161
+ }
162
+ if (entry.status === 'evicted' || entry.status === 'requeued') {
163
+ next = (0, state_1.transitionIssue)(next, issue, 'requeued', { mode: 'full', batch: null, reason }, now);
164
+ requeued.push(issue);
165
+ continue;
166
+ }
167
+ // Any other active state: force onto the failure rail (evicted → requeued{full}).
168
+ next = (0, state_1.transitionIssue)(next, issue, 'evicted', { reason }, now);
169
+ next = (0, state_1.transitionIssue)(next, issue, 'requeued', { mode: 'full', batch: null, reason }, now);
170
+ requeued.push(issue);
171
+ }
172
+ return { state: next, requeued };
173
+ }
174
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Pure state machines for the scheduler core — RFC-0001 §D as explicit typed
3
+ * transitions. Every function here is pure: it takes a state and returns a new
4
+ * state (immutable spreads, like `packages/worktree-pool/src/pool-state.ts`),
5
+ * and every edge NOT in the tables throws `IllegalTransitionError` (AC3).
6
+ *
7
+ * Failure edges: RFC-0001 §D.1 states "any → blocked(dep-failed) |
8
+ * decision-pending | failed(escalation-cap)" — encoded generically for every
9
+ * non-terminal issue status rather than repeated per row.
10
+ */
11
+ import { type BatchEntry, type BatchStatus, type IssueStatus, type QueueEntry, type SchedState, type SlotEntry, type SlotStatus } from './types';
12
+ /** An empty state carries no timestamps — nothing exists yet to stamp. */
13
+ export declare function createEmptyState(): SchedState;
14
+ /**
15
+ * Strict validation of persisted state (mirrors pool-state's `validateState`):
16
+ * wrong schema version or malformed shape throws instead of being coerced, so
17
+ * a corrupt file is a loud failure, never a silent queue reset.
18
+ *
19
+ * Legacy schema versions (1.0.0, pre-#464) are accepted and migrated: slot
20
+ * `branch`/`last_head` backfill to null and the state upgrades to the current
21
+ * schema on the next save.
22
+ */
23
+ export declare function validateState(data: unknown): SchedState;
24
+ export declare function transitionIssue(state: SchedState, issue: number, to: IssueStatus, patch?: Partial<QueueEntry>, now?: Date): SchedState;
25
+ export declare function transitionBatch(state: SchedState, batchId: string, to: BatchStatus, patch?: Partial<BatchEntry>, now?: Date): SchedState;
26
+ export declare function transitionSlot(state: SchedState, slotId: number, to: SlotStatus, patch?: Partial<SlotEntry>, now?: Date): SchedState;
27
+ export declare function findEntry(state: SchedState, issue: number): QueueEntry | undefined;
28
+ export declare function findBatch(state: SchedState, batchId: string): BatchEntry | undefined;
29
+ /**
30
+ * All transition tables, exposed as public API — future consumers (#464's
31
+ * dispatcher, status previews) render next-state choices from here instead of
32
+ * re-deriving the RFC tables.
33
+ */
34
+ export declare const TRANSITIONS: {
35
+ issue: (from: IssueStatus) => IssueStatus[];
36
+ batch: (from: BatchStatus) => BatchStatus[];
37
+ slot: (from: SlotStatus) => SlotStatus[];
38
+ };
39
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,WAAW,EAEhB,KAAK,UAAU,EAGf,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,EAEhB,MAAM,SAAS,CAAC;AAkGjB,0EAA0E;AAC1E,wBAAgB,gBAAgB,IAAI,UAAU,CAS7C;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CA6IvD;AA0BD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,OAAO,CAAC,UAAU,CAAM,EAC/B,GAAG,GAAE,IAAiB,GACrB,UAAU,CAeZ;AAID,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,WAAW,EACf,KAAK,GAAE,OAAO,CAAC,UAAU,CAAM,EAC/B,GAAG,GAAE,IAAiB,GACrB,UAAU,CAeZ;AAID,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,KAAK,GAAE,OAAO,CAAC,SAAS,CAAM,EAC9B,GAAG,GAAE,IAAiB,GACrB,UAAU,CAoBZ;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAElF;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAEpF;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW;kBACR,WAAW;kBACX,WAAW;iBACZ,UAAU;CACxB,CAAC"}