@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.
- package/dist/dispatch.d.ts +103 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +262 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/engine.d.ts +71 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +590 -0
- package/dist/engine.js.map +1 -0
- package/dist/enqueue.d.ts +38 -0
- package/dist/enqueue.d.ts.map +1 -0
- package/dist/enqueue.js +220 -0
- package/dist/enqueue.js.map +1 -0
- package/dist/groundtruth.d.ts +74 -0
- package/dist/groundtruth.d.ts.map +1 -0
- package/dist/groundtruth.js +114 -0
- package/dist/groundtruth.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +32 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +113 -0
- package/dist/journal.js.map +1 -0
- package/dist/persist.d.ts +54 -0
- package/dist/persist.d.ts.map +1 -0
- package/dist/persist.js +334 -0
- package/dist/persist.js.map +1 -0
- package/dist/project.d.ts +41 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +124 -0
- package/dist/project.js.map +1 -0
- package/dist/readiness.d.ts +43 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +102 -0
- package/dist/readiness.js.map +1 -0
- package/dist/scheduler.d.ts +66 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +174 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/state.d.ts +39 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +360 -0
- package/dist/state.js.map +1 -0
- package/dist/status.d.ts +32 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +76 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +87 -0
- package/dist/types.js.map +1 -0
- package/package.json +2 -2
package/dist/project.js
ADDED
|
@@ -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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"}
|
package/dist/state.d.ts
ADDED
|
@@ -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"}
|