@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,113 @@
1
+ "use strict";
2
+ /**
3
+ * Append-only event journal (#464, AC6 — "all events journaled"; RFC-0001 §D.4
4
+ * "Audit"). Every engine decision lands here as one JSONL line in
5
+ * `<sched-dir>/events.jsonl`. The journal must never crash a tick: write
6
+ * failures are swallowed after a one-line stderr warning — state.json remains
7
+ * the operational truth, the journal is the operator's flight recorder.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.Journal = exports.JOURNAL_FILE = void 0;
44
+ exports.readJsonl = readJsonl;
45
+ exports.issueOfUnit = issueOfUnit;
46
+ exports.unitEvent = unitEvent;
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ /** The journal file name — the single source (persist.ts's journalPath uses it). */
50
+ exports.JOURNAL_FILE = 'events.jsonl';
51
+ class Journal {
52
+ filePath;
53
+ constructor(dir) {
54
+ this.filePath = path.join(dir, exports.JOURNAL_FILE);
55
+ }
56
+ /** Append one event, stamping `ts` from the caller's clock. Never throws. */
57
+ append(event, now = new Date()) {
58
+ const line = { ts: now.toISOString(), ...event };
59
+ try {
60
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 });
61
+ fs.appendFileSync(this.filePath, `${JSON.stringify(line)}\n`, {
62
+ encoding: 'utf8',
63
+ mode: 0o600,
64
+ });
65
+ }
66
+ catch (err) {
67
+ process.stderr.write(`⚠ sched: could not append journal event ${event.event}: ${err.message}\n`);
68
+ }
69
+ }
70
+ /** Read every event, oldest first; malformed lines are skipped. */
71
+ read() {
72
+ return readJsonl(this.filePath);
73
+ }
74
+ }
75
+ exports.Journal = Journal;
76
+ /**
77
+ * Read a JSONL file oldest-first, skipping malformed lines; `[]` when the
78
+ * file is absent or unreadable. Shared by the journal and the CLI's run log
79
+ * so the read-loop exists once.
80
+ */
81
+ function readJsonl(file) {
82
+ try {
83
+ if (!fs.existsSync(file))
84
+ return [];
85
+ const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
86
+ const out = [];
87
+ for (const line of lines) {
88
+ try {
89
+ out.push(JSON.parse(line));
90
+ }
91
+ catch {
92
+ // Skip malformed lines — append-only best-effort files.
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ }
101
+ /** `issue:464` → 464; null for batch or malformed unit ids. */
102
+ function issueOfUnit(unit) {
103
+ if (unit === null || !unit.startsWith('issue:'))
104
+ return null;
105
+ const n = Number.parseInt(unit.slice('issue:'.length), 10);
106
+ return Number.isInteger(n) && n > 0 ? n : null;
107
+ }
108
+ /** Convenience: build the journaled event for a unit without repeating ids. */
109
+ function unitEvent(event, unit, extra = {}) {
110
+ const issue = issueOfUnit(unit);
111
+ return { event, unit, ...(issue !== null ? { issue } : {}), ...extra };
112
+ }
113
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Crash-safe persistence for the scheduler core.
3
+ *
4
+ * `state.json` is written atomically: contents land in `state.json.tmp` in the
5
+ * SAME directory (rename is atomic only within a filesystem), get fsynced, and
6
+ * are renamed over `state.json`. A process killed between writes therefore
7
+ * leaves either the previous complete state or the new one — never a partial
8
+ * file (AC2). A stray `.tmp` is ignored by `load()` and overwritten by the
9
+ * next write.
10
+ *
11
+ * Cross-process mutations are serialized by a directory mutex (`.sched-lock`),
12
+ * the same acquire/release protocol as `packages/worktree-pool/src/pool-actions.ts`
13
+ * (acquireLock/releaseLock there) — the two copies must be kept in lockstep.
14
+ */
15
+ import { type SchedConfig, type SchedState } from './types';
16
+ /** Thrown when the cross-process lock cannot be acquired in time. */
17
+ export declare class LockTimeoutError extends Error {
18
+ readonly lockPath: string;
19
+ constructor(lockPath: string, heldByPid: number | null);
20
+ }
21
+ export declare class CorruptStateError extends Error {
22
+ readonly filePath: string;
23
+ constructor(filePath: string, cause: unknown);
24
+ }
25
+ /** Write `contents` to `filePath` atomically (tmp + fsync + rename, same directory). */
26
+ export declare function writeAtomic(filePath: string, contents: string): void;
27
+ /**
28
+ * Filesystem-backed store for one project's scheduler state
29
+ * (`~/.dossier/sched/<project>/`). The constructor only records the directory;
30
+ * all I/O happens per call so a long-lived instance always sees fresh disk
31
+ * state under the lock.
32
+ */
33
+ export declare class SchedStore {
34
+ readonly dir: string;
35
+ constructor(dir: string);
36
+ get statePath(): string;
37
+ get configPath(): string;
38
+ get journalPath(): string;
39
+ /** Directory for per-unit agent output logs (`runs/<unit>.log`). */
40
+ get runsDir(): string;
41
+ load(): SchedState;
42
+ save(state: SchedState): void;
43
+ /**
44
+ * Load → mutate → save under the cross-process lock. The mutator must be
45
+ * pure (state in, state out) — same contract as pool's `withLock`.
46
+ */
47
+ withLock<T>(fn: (state: SchedState) => {
48
+ state: SchedState;
49
+ result: T;
50
+ }): T;
51
+ loadConfig(): SchedConfig;
52
+ saveConfig(config: SchedConfig): void;
53
+ }
54
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,EAQL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;AASjB,qEAAqE;AACrE,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CASvD;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAS7C;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAwBpE;AA8DD;;;;;GAKG;AACH,qBAAa,UAAU;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;IAIvB,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,oEAAoE;IACpE,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,UAAU;IAyBlB,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAI7B;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,CAAC,CAAA;KAAE,GAAG,CAAC;IAY3E,UAAU,IAAI,WAAW;IA+CzB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;CActC"}
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ /**
3
+ * Crash-safe persistence for the scheduler core.
4
+ *
5
+ * `state.json` is written atomically: contents land in `state.json.tmp` in the
6
+ * SAME directory (rename is atomic only within a filesystem), get fsynced, and
7
+ * are renamed over `state.json`. A process killed between writes therefore
8
+ * leaves either the previous complete state or the new one — never a partial
9
+ * file (AC2). A stray `.tmp` is ignored by `load()` and overwritten by the
10
+ * next write.
11
+ *
12
+ * Cross-process mutations are serialized by a directory mutex (`.sched-lock`),
13
+ * the same acquire/release protocol as `packages/worktree-pool/src/pool-actions.ts`
14
+ * (acquireLock/releaseLock there) — the two copies must be kept in lockstep.
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.SchedStore = exports.CorruptStateError = exports.LockTimeoutError = void 0;
51
+ exports.writeAtomic = writeAtomic;
52
+ const fs = __importStar(require("node:fs"));
53
+ const path = __importStar(require("node:path"));
54
+ const journal_1 = require("./journal");
55
+ const state_1 = require("./state");
56
+ const types_1 = require("./types");
57
+ const LOCK_TIMEOUT_MS = 10_000;
58
+ const LOCK_RETRY_MS = 200;
59
+ const STATE_FILE = 'state.json';
60
+ const CONFIG_FILE = 'config.json';
61
+ const LOCK_DIR = '.sched-lock';
62
+ /** Thrown when the cross-process lock cannot be acquired in time. */
63
+ class LockTimeoutError extends Error {
64
+ lockPath;
65
+ constructor(lockPath, heldByPid) {
66
+ const holder = heldByPid !== null ? `; held by pid ${heldByPid}` : ' (no pid file found)';
67
+ super(`Timed out waiting for scheduler lock (${LOCK_TIMEOUT_MS}ms)${holder}. ` +
68
+ `If that process is not running, remove ${lockPath}`);
69
+ this.name = 'LockTimeoutError';
70
+ this.lockPath = lockPath;
71
+ }
72
+ }
73
+ exports.LockTimeoutError = LockTimeoutError;
74
+ class CorruptStateError extends Error {
75
+ filePath;
76
+ constructor(filePath, cause) {
77
+ const reason = cause instanceof Error ? cause.message : String(cause);
78
+ super(`Scheduler state file is corrupt: ${filePath} (${reason}). ` +
79
+ 'Rename or remove it to reset the queue — GitHub remains the system of record.');
80
+ this.name = 'CorruptStateError';
81
+ this.filePath = filePath;
82
+ }
83
+ }
84
+ exports.CorruptStateError = CorruptStateError;
85
+ /** Write `contents` to `filePath` atomically (tmp + fsync + rename, same directory). */
86
+ function writeAtomic(filePath, contents) {
87
+ const dir = path.dirname(filePath);
88
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
89
+ const tmp = `${filePath}.tmp`;
90
+ const fd = fs.openSync(tmp, 'w', 0o600);
91
+ try {
92
+ fs.writeFileSync(fd, contents, 'utf8');
93
+ fs.fsyncSync(fd);
94
+ }
95
+ finally {
96
+ fs.closeSync(fd);
97
+ }
98
+ fs.renameSync(tmp, filePath);
99
+ // Best-effort directory fsync so the rename itself is durable. Not available
100
+ // on every platform/fs — a failure here never blocks the write.
101
+ try {
102
+ const dfd = fs.openSync(dir, 'r');
103
+ try {
104
+ fs.fsyncSync(dfd);
105
+ }
106
+ finally {
107
+ fs.closeSync(dfd);
108
+ }
109
+ }
110
+ catch {
111
+ // ignore
112
+ }
113
+ }
114
+ // --- Lock (directory mutex, worktree-pool protocol) ---
115
+ function acquireLock(dir) {
116
+ const lockPath = path.join(dir, LOCK_DIR);
117
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
118
+ const start = Date.now();
119
+ let heldByPid = null;
120
+ while (true) {
121
+ try {
122
+ fs.mkdirSync(lockPath);
123
+ fs.writeFileSync(path.join(lockPath, 'pid'), String(process.pid), { mode: 0o600 });
124
+ return;
125
+ }
126
+ catch {
127
+ heldByPid = null;
128
+ try {
129
+ const pidFile = path.join(lockPath, 'pid');
130
+ if (fs.existsSync(pidFile)) {
131
+ const lockPid = Number.parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
132
+ heldByPid = Number.isNaN(lockPid) ? null : lockPid;
133
+ try {
134
+ process.kill(lockPid, 0);
135
+ }
136
+ catch {
137
+ // Recorded holder is dead. Take the stale lock over by RENAMING it
138
+ // first: with two contenders both seeing the dead pid, only one
139
+ // rename succeeds — the loser's rmSync cannot delete the winner's
140
+ // freshly-acquired lock (the plain rmSync-race the pool protocol
141
+ // inherits from its original mkdir-mutex).
142
+ const stale = `${lockPath}.stale-${process.pid}-${Date.now()}`;
143
+ try {
144
+ fs.renameSync(lockPath, stale);
145
+ }
146
+ catch {
147
+ // another contender already took it over — fall through to retry
148
+ }
149
+ try {
150
+ fs.rmSync(stale, { recursive: true, force: true });
151
+ }
152
+ catch {
153
+ // best effort cleanup
154
+ }
155
+ continue;
156
+ }
157
+ }
158
+ }
159
+ catch {
160
+ // retry
161
+ }
162
+ if (Date.now() - start > LOCK_TIMEOUT_MS) {
163
+ throw new LockTimeoutError(lockPath, heldByPid);
164
+ }
165
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MS);
166
+ }
167
+ }
168
+ }
169
+ function releaseLock(dir) {
170
+ fs.rmSync(path.join(dir, LOCK_DIR), { recursive: true, force: true });
171
+ }
172
+ // --- Store ---
173
+ /**
174
+ * Filesystem-backed store for one project's scheduler state
175
+ * (`~/.dossier/sched/<project>/`). The constructor only records the directory;
176
+ * all I/O happens per call so a long-lived instance always sees fresh disk
177
+ * state under the lock.
178
+ */
179
+ class SchedStore {
180
+ dir;
181
+ constructor(dir) {
182
+ this.dir = dir;
183
+ }
184
+ get statePath() {
185
+ return path.join(this.dir, STATE_FILE);
186
+ }
187
+ get configPath() {
188
+ return path.join(this.dir, CONFIG_FILE);
189
+ }
190
+ get journalPath() {
191
+ return path.join(this.dir, journal_1.JOURNAL_FILE);
192
+ }
193
+ /** Directory for per-unit agent output logs (`runs/<unit>.log`). */
194
+ get runsDir() {
195
+ return path.join(this.dir, 'runs');
196
+ }
197
+ load() {
198
+ const statePath = this.statePath;
199
+ if (!fs.existsSync(statePath)) {
200
+ return (0, state_1.createEmptyState)();
201
+ }
202
+ let raw;
203
+ try {
204
+ raw = fs.readFileSync(statePath, 'utf-8');
205
+ }
206
+ catch (err) {
207
+ // An I/O failure (permissions, disk, read-only mount) is NOT corruption —
208
+ // the destructive "rename to reset" advice must not attach to it.
209
+ throw new CorruptStateError(statePath, new Error(`could not READ the file (${err.message}) — this is an I/O failure, not corruption; renaming will not fix it`));
210
+ }
211
+ try {
212
+ return (0, state_1.validateState)(JSON.parse(raw));
213
+ }
214
+ catch (err) {
215
+ throw new CorruptStateError(statePath, err);
216
+ }
217
+ }
218
+ save(state) {
219
+ writeAtomic(this.statePath, `${JSON.stringify(state, null, 2)}\n`);
220
+ }
221
+ /**
222
+ * Load → mutate → save under the cross-process lock. The mutator must be
223
+ * pure (state in, state out) — same contract as pool's `withLock`.
224
+ */
225
+ withLock(fn) {
226
+ acquireLock(this.dir);
227
+ try {
228
+ const current = this.load();
229
+ const { state, result } = fn(current);
230
+ this.save(state);
231
+ return result;
232
+ }
233
+ finally {
234
+ releaseLock(this.dir);
235
+ }
236
+ }
237
+ loadConfig() {
238
+ if (!fs.existsSync(this.configPath)) {
239
+ return { max_slots: types_1.DEFAULT_MAX_SLOTS };
240
+ }
241
+ try {
242
+ const parsed = JSON.parse(fs.readFileSync(this.configPath, 'utf-8'));
243
+ const version = String(parsed.schema_version);
244
+ if (version !== types_1.CONFIG_SCHEMA_VERSION && !types_1.LEGACY_CONFIG_SCHEMA_VERSIONS.includes(version)) {
245
+ throw new Error(`unsupported schema version ${version}`);
246
+ }
247
+ if (!Number.isInteger(parsed.max_slots) ||
248
+ parsed.max_slots < types_1.MIN_MAX_SLOTS ||
249
+ parsed.max_slots > types_1.MAX_MAX_SLOTS) {
250
+ throw new Error(`max_slots must be an integer between ${types_1.MIN_MAX_SLOTS} and ${types_1.MAX_MAX_SLOTS}`);
251
+ }
252
+ const config = { max_slots: parsed.max_slots };
253
+ if (parsed.stall_timeout_ms !== undefined) {
254
+ if (!Number.isInteger(parsed.stall_timeout_ms) || parsed.stall_timeout_ms <= 0) {
255
+ throw new Error('stall_timeout_ms must be a positive integer (milliseconds)');
256
+ }
257
+ config.stall_timeout_ms = parsed.stall_timeout_ms;
258
+ }
259
+ if (parsed.reconcile_interval_ms !== undefined) {
260
+ if (!Number.isInteger(parsed.reconcile_interval_ms) || parsed.reconcile_interval_ms <= 0) {
261
+ throw new Error('reconcile_interval_ms must be a positive integer (milliseconds)');
262
+ }
263
+ config.reconcile_interval_ms = parsed.reconcile_interval_ms;
264
+ }
265
+ if (parsed.dispatch !== undefined) {
266
+ config.dispatch = validateDispatchConfig(parsed.dispatch);
267
+ }
268
+ return config;
269
+ }
270
+ catch (err) {
271
+ // Deliberate degrade-to-default (unlike state.json, config is re-derivable
272
+ // operator intent and hard-failing every command on a typo would brick
273
+ // even `sched status`) — but never silently.
274
+ console.error(`⚠ Scheduler config ${this.configPath} is unreadable (${err.message}) — using default max_slots=${types_1.DEFAULT_MAX_SLOTS}`);
275
+ return { max_slots: types_1.DEFAULT_MAX_SLOTS };
276
+ }
277
+ }
278
+ saveConfig(config) {
279
+ const file = {
280
+ schema_version: types_1.CONFIG_SCHEMA_VERSION,
281
+ max_slots: config.max_slots,
282
+ ...(config.stall_timeout_ms !== undefined
283
+ ? { stall_timeout_ms: config.stall_timeout_ms }
284
+ : {}),
285
+ ...(config.reconcile_interval_ms !== undefined
286
+ ? { reconcile_interval_ms: config.reconcile_interval_ms }
287
+ : {}),
288
+ ...(config.dispatch !== undefined ? { dispatch: config.dispatch } : {}),
289
+ };
290
+ writeAtomic(this.configPath, `${JSON.stringify(file, null, 2)}\n`);
291
+ }
292
+ }
293
+ exports.SchedStore = SchedStore;
294
+ const MODEL_TIERS = ['mechanical', 'mid', 'strong'];
295
+ /** Strict validation of the optional `dispatch` section (#464). */
296
+ function validateDispatchConfig(raw) {
297
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
298
+ throw new Error('dispatch must be an object');
299
+ }
300
+ const dispatch = raw;
301
+ if (dispatch.command !== undefined) {
302
+ if (!Array.isArray(dispatch.command) ||
303
+ dispatch.command.length === 0 ||
304
+ dispatch.command.some((c) => typeof c !== 'string' || c.length === 0)) {
305
+ throw new Error('dispatch.command must be a non-empty array of non-empty strings');
306
+ }
307
+ }
308
+ if (dispatch.prompt !== undefined && typeof dispatch.prompt !== 'string') {
309
+ throw new Error('dispatch.prompt must be a string');
310
+ }
311
+ if (dispatch.tier_models !== undefined) {
312
+ if (dispatch.tier_models === null || typeof dispatch.tier_models !== 'object') {
313
+ throw new Error('dispatch.tier_models must be an object');
314
+ }
315
+ for (const [tier, model] of Object.entries(dispatch.tier_models)) {
316
+ if (!MODEL_TIERS.includes(tier)) {
317
+ throw new Error(`dispatch.tier_models: unknown tier '${tier}'`);
318
+ }
319
+ if (typeof model !== 'string' || model.length === 0) {
320
+ throw new Error(`dispatch.tier_models.${tier} must be a non-empty string`);
321
+ }
322
+ }
323
+ }
324
+ const out = {};
325
+ if (dispatch.command !== undefined)
326
+ out.command = dispatch.command;
327
+ if (dispatch.prompt !== undefined)
328
+ out.prompt = dispatch.prompt;
329
+ if (dispatch.tier_models !== undefined) {
330
+ out.tier_models = dispatch.tier_models;
331
+ }
332
+ return out;
333
+ }
334
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Project identity for the scheduler state directory
3
+ * (`~/.dossier/sched/<project>/`).
4
+ *
5
+ * `<project>` follows fleet-cycle's convention exactly: the repo slug
6
+ * `<owner>-<repo>` from `gh repo view --json owner,name`; when that fails (no
7
+ * remote, no `gh`), the basename of `git rev-parse --show-toplevel`.
8
+ *
9
+ * The exec function is injectable so tests (and any non-CLI consumer) never
10
+ * spawn processes. Since #464 these are no longer the package's only
11
+ * subprocesses (dispatch.ts spawns the configured agent; groundtruth.ts execs
12
+ * runstate/gh/git), but all process I/O in the package is injectable, and
13
+ * none of it is an LLM call the scheduler makes itself (AC7).
14
+ */
15
+ /**
16
+ * Run a command and return trimmed stdout, or null when it fails (non-zero
17
+ * exit, missing binary). Never throws — callers degrade to their fallback.
18
+ */
19
+ export type ExecFn = (file: string, args: string[], cwd?: string) => string | null;
20
+ /** Build an `ExecFn` via `execFileSync` (never throws), optionally with a hard timeout and a failure observer. */
21
+ export declare function createExecFn(timeoutMs?: number, opts?: {
22
+ onError?: (file: string, args: string[], err: Error) => void;
23
+ }): ExecFn;
24
+ /** Default exec: no timeout. */
25
+ export declare const defaultExec: ExecFn;
26
+ /**
27
+ * Filesystem-safe form of an arbitrary identifier: everything outside
28
+ * `[A-Za-z0-9._-]` becomes `-`. `.` and `..` survive the character allowlist
29
+ * but are path components that would escape `~/.dossier/sched/` (e.g.
30
+ * `--project ..` → `~/.dossier` itself, overwriting the global CLI config via
31
+ * saveConfig) — those collapse to `default`.
32
+ */
33
+ export declare function sanitizeSlug(slug: string): string;
34
+ /**
35
+ * Resolve the project slug for `cwd` (default: process cwd). `gh` first, git
36
+ * basename as fallback; a final sanitized non-empty string otherwise.
37
+ */
38
+ export declare function resolveProjectSlug(exec?: ExecFn, cwd?: string): string;
39
+ /** State directory for one project under the dossier home (testable via `home`). */
40
+ export declare function schedStateDir(project: string, home?: string): string;
41
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEnF,kHAAkH;AAClH,wBAAgB,YAAY,CAC1B,SAAS,CAAC,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAAE,GACtE,MAAM,CAgBR;AAED,gCAAgC;AAChC,eAAO,MAAM,WAAW,EAAE,MAAuB,CAAC;AAKlD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAoB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBnF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAAqB,GAAG,MAAM,CAElF"}