peaks-loop 3.0.2 → 3.1.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 (37) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/src/cli/commands/core/session-command.js +6 -0
  5. package/dist/src/cli/commands/job-commands.d.ts +3 -0
  6. package/dist/src/cli/commands/job-commands.js +236 -0
  7. package/dist/src/cli/commands/session-auto-compact-hook-command.d.ts +37 -0
  8. package/dist/src/cli/commands/session-auto-compact-hook-command.js +127 -0
  9. package/dist/src/cli/program.js +6 -0
  10. package/dist/src/services/context/auto-compact-dispatcher.js +60 -10
  11. package/dist/src/services/context/main-session-monitor.d.ts +12 -0
  12. package/dist/src/services/context/main-session-monitor.js +12 -0
  13. package/dist/src/services/hooks/auto-compact-hook-install.d.ts +104 -0
  14. package/dist/src/services/hooks/auto-compact-hook-install.js +157 -0
  15. package/dist/src/services/ide/adapters/claude-code-adapter.js +21 -9
  16. package/dist/src/services/job/job-event-emitter.d.ts +30 -0
  17. package/dist/src/services/job/job-event-emitter.js +5 -0
  18. package/dist/src/services/job/job-orchestrator.d.ts +50 -0
  19. package/dist/src/services/job/job-orchestrator.js +104 -0
  20. package/dist/src/services/job/job-resource-snapshot.d.ts +7 -0
  21. package/dist/src/services/job/job-resource-snapshot.js +47 -0
  22. package/dist/src/services/job/job-rotation.d.ts +17 -0
  23. package/dist/src/services/job/job-rotation.js +30 -0
  24. package/dist/src/services/job/job-state-store.d.ts +24 -0
  25. package/dist/src/services/job/job-state-store.js +60 -0
  26. package/dist/src/services/job/job-types.d.ts +444 -0
  27. package/dist/src/services/job/job-types.js +87 -0
  28. package/dist/src/services/job/subagent-job-wrapper.d.ts +35 -0
  29. package/dist/src/services/job/subagent-job-wrapper.js +30 -0
  30. package/dist/src/shared/version.d.ts +1 -1
  31. package/dist/src/shared/version.js +1 -1
  32. package/package.json +1 -1
  33. package/skills/peaks-solo/SKILL.md +91 -77
  34. package/skills/peaks-solo/references/completion-handoff.md +9 -4
  35. package/skills/peaks-solo/references/job-loop.md +68 -0
  36. package/skills/peaks-solo/references/periodic-checkpoint.md +11 -1
  37. package/skills/peaks-solo/references/runbook.md +18 -2
@@ -0,0 +1,60 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { JobStateSchema } from './job-types.js';
4
+ export class JobStateStore {
5
+ rootDir;
6
+ constructor(rootDir) {
7
+ this.rootDir = rootDir;
8
+ }
9
+ jobDir(jobId) {
10
+ return join(this.rootDir, jobId);
11
+ }
12
+ init(input) {
13
+ const state = JobStateSchema.parse({
14
+ jobId: input.jobId,
15
+ sessionId: input.sessionId,
16
+ startedAt: new Date().toISOString(),
17
+ lastCheckpointAt: new Date().toISOString(),
18
+ parallelismHint: input.parallelismHint,
19
+ exitPolicy: input.exitPolicy,
20
+ mainLoopStrategy: input.mainLoopStrategy,
21
+ rotateEvery: input.rotateEvery,
22
+ mainSessionCycle: 0,
23
+ slices: input.sliceList.map((label, i) => ({
24
+ sliceId: `slice-${String(i + 1).padStart(3, '0')}`,
25
+ label,
26
+ status: 'pending',
27
+ repairCycles: 0,
28
+ })),
29
+ });
30
+ this.save(state);
31
+ return state;
32
+ }
33
+ load(jobId) {
34
+ const p = join(this.jobDir(jobId), 'state.json');
35
+ if (!existsSync(p))
36
+ throw new Error(`JobStateStore.load: no state for ${jobId} at ${p}`);
37
+ return JobStateSchema.parse(JSON.parse(readFileSync(p, 'utf8')));
38
+ }
39
+ save(state) {
40
+ // Re-parse to fail loudly on schema regression (M2 unit-test expects this).
41
+ const valid = JobStateSchema.parse(state);
42
+ const dir = this.jobDir(valid.jobId);
43
+ mkdirSync(dir, { recursive: true });
44
+ const path = join(dir, 'state.json');
45
+ writeFileSync(path, JSON.stringify(valid, null, 2) + '\n', 'utf8');
46
+ }
47
+ tryAcquireLock(jobId) {
48
+ const lockPath = join(this.jobDir(jobId), 'state.lock');
49
+ mkdirSync(dirname(lockPath), { recursive: true });
50
+ if (existsSync(lockPath))
51
+ throw new Error(`JobStateStore: state already locked at ${lockPath}`);
52
+ writeFileSync(lockPath, `${process.pid}@${new Date().toISOString()}\n`, 'utf8');
53
+ return { jobId, path: lockPath };
54
+ }
55
+ releaseLock(lock) {
56
+ if (existsSync(lock.path)) {
57
+ unlinkSync(lock.path);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,444 @@
1
+ import { z } from 'zod';
2
+ export declare const SliceStateSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodObject<{
3
+ sliceId: z.ZodString;
4
+ label: z.ZodString;
5
+ status: z.ZodEnum<["pending", "in-progress", "done", "failed", "blocked", "skipped"]>;
6
+ commitSha: z.ZodOptional<z.ZodString>;
7
+ finishedAt: z.ZodOptional<z.ZodString>;
8
+ failureReason: z.ZodOptional<z.ZodString>;
9
+ repairCycles: z.ZodDefault<z.ZodNumber>;
10
+ blockedReason: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
13
+ label: string;
14
+ sliceId: string;
15
+ repairCycles: number;
16
+ commitSha?: string | undefined;
17
+ finishedAt?: string | undefined;
18
+ failureReason?: string | undefined;
19
+ blockedReason?: string | undefined;
20
+ }, {
21
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
22
+ label: string;
23
+ sliceId: string;
24
+ commitSha?: string | undefined;
25
+ finishedAt?: string | undefined;
26
+ failureReason?: string | undefined;
27
+ repairCycles?: number | undefined;
28
+ blockedReason?: string | undefined;
29
+ }>, {
30
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
31
+ label: string;
32
+ sliceId: string;
33
+ repairCycles: number;
34
+ commitSha?: string | undefined;
35
+ finishedAt?: string | undefined;
36
+ failureReason?: string | undefined;
37
+ blockedReason?: string | undefined;
38
+ }, {
39
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
40
+ label: string;
41
+ sliceId: string;
42
+ commitSha?: string | undefined;
43
+ finishedAt?: string | undefined;
44
+ failureReason?: string | undefined;
45
+ repairCycles?: number | undefined;
46
+ blockedReason?: string | undefined;
47
+ }>, {
48
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
49
+ label: string;
50
+ sliceId: string;
51
+ repairCycles: number;
52
+ commitSha?: string | undefined;
53
+ finishedAt?: string | undefined;
54
+ failureReason?: string | undefined;
55
+ blockedReason?: string | undefined;
56
+ }, {
57
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
58
+ label: string;
59
+ sliceId: string;
60
+ commitSha?: string | undefined;
61
+ finishedAt?: string | undefined;
62
+ failureReason?: string | undefined;
63
+ repairCycles?: number | undefined;
64
+ blockedReason?: string | undefined;
65
+ }>, {
66
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
67
+ label: string;
68
+ sliceId: string;
69
+ repairCycles: number;
70
+ commitSha?: string | undefined;
71
+ finishedAt?: string | undefined;
72
+ failureReason?: string | undefined;
73
+ blockedReason?: string | undefined;
74
+ }, {
75
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
76
+ label: string;
77
+ sliceId: string;
78
+ commitSha?: string | undefined;
79
+ finishedAt?: string | undefined;
80
+ failureReason?: string | undefined;
81
+ repairCycles?: number | undefined;
82
+ blockedReason?: string | undefined;
83
+ }>;
84
+ export declare const JobStateSchema: z.ZodObject<{
85
+ jobId: z.ZodString;
86
+ sessionId: z.ZodString;
87
+ startedAt: z.ZodString;
88
+ lastCheckpointAt: z.ZodString;
89
+ parallelismHint: z.ZodDefault<z.ZodEnum<["serial", "llm-decides"]>>;
90
+ exitPolicy: z.ZodDefault<z.ZodEnum<["strict", "best-effort"]>>;
91
+ mainLoopStrategy: z.ZodDefault<z.ZodEnum<["single", "rotating"]>>;
92
+ rotateEvery: z.ZodDefault<z.ZodNumber>;
93
+ mainSessionCycle: z.ZodDefault<z.ZodNumber>;
94
+ mainLoopOverride: z.ZodOptional<z.ZodObject<{
95
+ from: z.ZodLiteral<"rotating">;
96
+ to: z.ZodLiteral<"single">;
97
+ reason: z.ZodString;
98
+ at: z.ZodString;
99
+ }, "strip", z.ZodTypeAny, {
100
+ at: string;
101
+ reason: string;
102
+ from: "rotating";
103
+ to: "single";
104
+ }, {
105
+ at: string;
106
+ reason: string;
107
+ from: "rotating";
108
+ to: "single";
109
+ }>>;
110
+ slices: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodObject<{
111
+ sliceId: z.ZodString;
112
+ label: z.ZodString;
113
+ status: z.ZodEnum<["pending", "in-progress", "done", "failed", "blocked", "skipped"]>;
114
+ commitSha: z.ZodOptional<z.ZodString>;
115
+ finishedAt: z.ZodOptional<z.ZodString>;
116
+ failureReason: z.ZodOptional<z.ZodString>;
117
+ repairCycles: z.ZodDefault<z.ZodNumber>;
118
+ blockedReason: z.ZodOptional<z.ZodString>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
121
+ label: string;
122
+ sliceId: string;
123
+ repairCycles: number;
124
+ commitSha?: string | undefined;
125
+ finishedAt?: string | undefined;
126
+ failureReason?: string | undefined;
127
+ blockedReason?: string | undefined;
128
+ }, {
129
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
130
+ label: string;
131
+ sliceId: string;
132
+ commitSha?: string | undefined;
133
+ finishedAt?: string | undefined;
134
+ failureReason?: string | undefined;
135
+ repairCycles?: number | undefined;
136
+ blockedReason?: string | undefined;
137
+ }>, {
138
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
139
+ label: string;
140
+ sliceId: string;
141
+ repairCycles: number;
142
+ commitSha?: string | undefined;
143
+ finishedAt?: string | undefined;
144
+ failureReason?: string | undefined;
145
+ blockedReason?: string | undefined;
146
+ }, {
147
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
148
+ label: string;
149
+ sliceId: string;
150
+ commitSha?: string | undefined;
151
+ finishedAt?: string | undefined;
152
+ failureReason?: string | undefined;
153
+ repairCycles?: number | undefined;
154
+ blockedReason?: string | undefined;
155
+ }>, {
156
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
157
+ label: string;
158
+ sliceId: string;
159
+ repairCycles: number;
160
+ commitSha?: string | undefined;
161
+ finishedAt?: string | undefined;
162
+ failureReason?: string | undefined;
163
+ blockedReason?: string | undefined;
164
+ }, {
165
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
166
+ label: string;
167
+ sliceId: string;
168
+ commitSha?: string | undefined;
169
+ finishedAt?: string | undefined;
170
+ failureReason?: string | undefined;
171
+ repairCycles?: number | undefined;
172
+ blockedReason?: string | undefined;
173
+ }>, {
174
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
175
+ label: string;
176
+ sliceId: string;
177
+ repairCycles: number;
178
+ commitSha?: string | undefined;
179
+ finishedAt?: string | undefined;
180
+ failureReason?: string | undefined;
181
+ blockedReason?: string | undefined;
182
+ }, {
183
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
184
+ label: string;
185
+ sliceId: string;
186
+ commitSha?: string | undefined;
187
+ finishedAt?: string | undefined;
188
+ failureReason?: string | undefined;
189
+ repairCycles?: number | undefined;
190
+ blockedReason?: string | undefined;
191
+ }>, "many">;
192
+ }, "strip", z.ZodTypeAny, {
193
+ sessionId: string;
194
+ startedAt: string;
195
+ slices: {
196
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
197
+ label: string;
198
+ sliceId: string;
199
+ repairCycles: number;
200
+ commitSha?: string | undefined;
201
+ finishedAt?: string | undefined;
202
+ failureReason?: string | undefined;
203
+ blockedReason?: string | undefined;
204
+ }[];
205
+ jobId: string;
206
+ lastCheckpointAt: string;
207
+ parallelismHint: "serial" | "llm-decides";
208
+ exitPolicy: "strict" | "best-effort";
209
+ mainLoopStrategy: "single" | "rotating";
210
+ rotateEvery: number;
211
+ mainSessionCycle: number;
212
+ mainLoopOverride?: {
213
+ at: string;
214
+ reason: string;
215
+ from: "rotating";
216
+ to: "single";
217
+ } | undefined;
218
+ }, {
219
+ sessionId: string;
220
+ startedAt: string;
221
+ slices: {
222
+ status: "pending" | "skipped" | "done" | "failed" | "blocked" | "in-progress";
223
+ label: string;
224
+ sliceId: string;
225
+ commitSha?: string | undefined;
226
+ finishedAt?: string | undefined;
227
+ failureReason?: string | undefined;
228
+ repairCycles?: number | undefined;
229
+ blockedReason?: string | undefined;
230
+ }[];
231
+ jobId: string;
232
+ lastCheckpointAt: string;
233
+ parallelismHint?: "serial" | "llm-decides" | undefined;
234
+ exitPolicy?: "strict" | "best-effort" | undefined;
235
+ mainLoopStrategy?: "single" | "rotating" | undefined;
236
+ rotateEvery?: number | undefined;
237
+ mainSessionCycle?: number | undefined;
238
+ mainLoopOverride?: {
239
+ at: string;
240
+ reason: string;
241
+ from: "rotating";
242
+ to: "single";
243
+ } | undefined;
244
+ }>;
245
+ export declare const ResourceSnapshotSchema: z.ZodObject<{
246
+ capturedAt: z.ZodString;
247
+ cpuPercent: z.ZodNumber;
248
+ memMb: z.ZodNumber;
249
+ diskMb: z.ZodNumber;
250
+ contextRatio: z.ZodNumber;
251
+ }, "strip", z.ZodTypeAny, {
252
+ capturedAt: string;
253
+ cpuPercent: number;
254
+ memMb: number;
255
+ diskMb: number;
256
+ contextRatio: number;
257
+ }, {
258
+ capturedAt: string;
259
+ cpuPercent: number;
260
+ memMb: number;
261
+ diskMb: number;
262
+ contextRatio: number;
263
+ }>;
264
+ export declare const JobStatusSummarySchema: z.ZodObject<{
265
+ total: z.ZodNumber;
266
+ done: z.ZodNumber;
267
+ failed: z.ZodNumber;
268
+ blocked: z.ZodNumber;
269
+ skipped: z.ZodNumber;
270
+ currentSlice: z.ZodOptional<z.ZodString>;
271
+ lastCheckpoint: z.ZodString;
272
+ mainLoopStrategy: z.ZodEnum<["single", "rotating"]>;
273
+ mainSessionCycle: z.ZodNumber;
274
+ etaSec: z.ZodOptional<z.ZodNumber>;
275
+ resourcesNow: z.ZodOptional<z.ZodObject<{
276
+ capturedAt: z.ZodString;
277
+ cpuPercent: z.ZodNumber;
278
+ memMb: z.ZodNumber;
279
+ diskMb: z.ZodNumber;
280
+ contextRatio: z.ZodNumber;
281
+ }, "strip", z.ZodTypeAny, {
282
+ capturedAt: string;
283
+ cpuPercent: number;
284
+ memMb: number;
285
+ diskMb: number;
286
+ contextRatio: number;
287
+ }, {
288
+ capturedAt: string;
289
+ cpuPercent: number;
290
+ memMb: number;
291
+ diskMb: number;
292
+ contextRatio: number;
293
+ }>>;
294
+ }, "strip", z.ZodTypeAny, {
295
+ skipped: number;
296
+ done: number;
297
+ failed: number;
298
+ blocked: number;
299
+ mainLoopStrategy: "single" | "rotating";
300
+ mainSessionCycle: number;
301
+ total: number;
302
+ lastCheckpoint: string;
303
+ currentSlice?: string | undefined;
304
+ etaSec?: number | undefined;
305
+ resourcesNow?: {
306
+ capturedAt: string;
307
+ cpuPercent: number;
308
+ memMb: number;
309
+ diskMb: number;
310
+ contextRatio: number;
311
+ } | undefined;
312
+ }, {
313
+ skipped: number;
314
+ done: number;
315
+ failed: number;
316
+ blocked: number;
317
+ mainLoopStrategy: "single" | "rotating";
318
+ mainSessionCycle: number;
319
+ total: number;
320
+ lastCheckpoint: string;
321
+ currentSlice?: string | undefined;
322
+ etaSec?: number | undefined;
323
+ resourcesNow?: {
324
+ capturedAt: string;
325
+ cpuPercent: number;
326
+ memMb: number;
327
+ diskMb: number;
328
+ contextRatio: number;
329
+ } | undefined;
330
+ }>;
331
+ export type SliceState = z.infer<typeof SliceStateSchema>;
332
+ export type JobState = z.infer<typeof JobStateSchema>;
333
+ export type ResourceSnapshot = z.infer<typeof ResourceSnapshotSchema>;
334
+ export type JobStatusSummary = z.infer<typeof JobStatusSummarySchema>;
335
+ export declare const JobInitInputSchema: z.ZodObject<{
336
+ jobId: z.ZodString;
337
+ sessionId: z.ZodString;
338
+ sliceList: z.ZodArray<z.ZodString, "many">;
339
+ parallelismHint: z.ZodDefault<z.ZodEnum<["serial", "llm-decides"]>>;
340
+ exitPolicy: z.ZodDefault<z.ZodEnum<["strict", "best-effort"]>>;
341
+ mainLoopStrategy: z.ZodDefault<z.ZodEnum<["single", "rotating"]>>;
342
+ rotateEvery: z.ZodDefault<z.ZodNumber>;
343
+ project: z.ZodString;
344
+ json: z.ZodDefault<z.ZodBoolean>;
345
+ }, "strip", z.ZodTypeAny, {
346
+ project: string;
347
+ sessionId: string;
348
+ json: boolean;
349
+ jobId: string;
350
+ parallelismHint: "serial" | "llm-decides";
351
+ exitPolicy: "strict" | "best-effort";
352
+ mainLoopStrategy: "single" | "rotating";
353
+ rotateEvery: number;
354
+ sliceList: string[];
355
+ }, {
356
+ project: string;
357
+ sessionId: string;
358
+ jobId: string;
359
+ sliceList: string[];
360
+ json?: boolean | undefined;
361
+ parallelismHint?: "serial" | "llm-decides" | undefined;
362
+ exitPolicy?: "strict" | "best-effort" | undefined;
363
+ mainLoopStrategy?: "single" | "rotating" | undefined;
364
+ rotateEvery?: number | undefined;
365
+ }>;
366
+ export type JobInitInput = z.infer<typeof JobInitInputSchema>;
367
+ export declare const JobCheckpointInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
368
+ jobId: z.ZodString;
369
+ sliceId: z.ZodString;
370
+ state: z.ZodEnum<["done", "failed", "skipped"]>;
371
+ commitSha: z.ZodOptional<z.ZodString>;
372
+ reason: z.ZodOptional<z.ZodString>;
373
+ project: z.ZodString;
374
+ json: z.ZodDefault<z.ZodBoolean>;
375
+ }, "strip", z.ZodTypeAny, {
376
+ project: string;
377
+ json: boolean;
378
+ state: "skipped" | "done" | "failed";
379
+ sliceId: string;
380
+ jobId: string;
381
+ reason?: string | undefined;
382
+ commitSha?: string | undefined;
383
+ }, {
384
+ project: string;
385
+ state: "skipped" | "done" | "failed";
386
+ sliceId: string;
387
+ jobId: string;
388
+ json?: boolean | undefined;
389
+ reason?: string | undefined;
390
+ commitSha?: string | undefined;
391
+ }>, {
392
+ project: string;
393
+ json: boolean;
394
+ state: "skipped" | "done" | "failed";
395
+ sliceId: string;
396
+ jobId: string;
397
+ reason?: string | undefined;
398
+ commitSha?: string | undefined;
399
+ }, {
400
+ project: string;
401
+ state: "skipped" | "done" | "failed";
402
+ sliceId: string;
403
+ jobId: string;
404
+ json?: boolean | undefined;
405
+ reason?: string | undefined;
406
+ commitSha?: string | undefined;
407
+ }>, {
408
+ project: string;
409
+ json: boolean;
410
+ state: "skipped" | "done" | "failed";
411
+ sliceId: string;
412
+ jobId: string;
413
+ reason?: string | undefined;
414
+ commitSha?: string | undefined;
415
+ }, {
416
+ project: string;
417
+ state: "skipped" | "done" | "failed";
418
+ sliceId: string;
419
+ jobId: string;
420
+ json?: boolean | undefined;
421
+ reason?: string | undefined;
422
+ commitSha?: string | undefined;
423
+ }>;
424
+ export type JobCheckpointInput = z.infer<typeof JobCheckpointInputSchema>;
425
+ export declare const JobBlockInputSchema: z.ZodObject<{
426
+ jobId: z.ZodString;
427
+ sliceId: z.ZodString;
428
+ reason: z.ZodString;
429
+ project: z.ZodString;
430
+ json: z.ZodDefault<z.ZodBoolean>;
431
+ }, "strip", z.ZodTypeAny, {
432
+ project: string;
433
+ json: boolean;
434
+ reason: string;
435
+ sliceId: string;
436
+ jobId: string;
437
+ }, {
438
+ project: string;
439
+ reason: string;
440
+ sliceId: string;
441
+ jobId: string;
442
+ json?: boolean | undefined;
443
+ }>;
444
+ export type JobBlockInput = z.infer<typeof JobBlockInputSchema>;
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod';
2
+ // ── Spec §4.2, verbatim from spec ────────────────────────────────────────
3
+ export const SliceStateSchema = z
4
+ .object({
5
+ sliceId: z.string(),
6
+ label: z.string(),
7
+ status: z.enum(['pending', 'in-progress', 'done', 'failed', 'blocked', 'skipped']),
8
+ commitSha: z.string().optional(), // required when status=done
9
+ finishedAt: z.string().datetime().optional(),
10
+ failureReason: z.string().optional(), // required when status=failed
11
+ repairCycles: z.number().int().nonnegative().default(0),
12
+ blockedReason: z.string().optional(), // required when status=blocked
13
+ })
14
+ .refine((v) => v.status !== 'done' || !!(v.commitSha && v.commitSha.length >= 7), { message: 'commitSha required (≥7 hex) when status=done', path: ['commitSha'] })
15
+ .refine((v) => v.status !== 'failed' || !!(v.failureReason && v.failureReason.length >= 1), { message: 'failureReason required when status=failed', path: ['failureReason'] })
16
+ .refine((v) => v.status !== 'blocked' || !!(v.blockedReason && v.blockedReason.length >= 1), { message: 'blockedReason required when status=blocked', path: ['blockedReason'] });
17
+ export const JobStateSchema = z.object({
18
+ jobId: z.string(),
19
+ sessionId: z.string(),
20
+ startedAt: z.string().datetime(),
21
+ lastCheckpointAt: z.string().datetime(),
22
+ parallelismHint: z.enum(['serial', 'llm-decides']).default('llm-decides'),
23
+ exitPolicy: z.enum(['strict', 'best-effort']).default('strict'),
24
+ mainLoopStrategy: z.enum(['single', 'rotating']).default('rotating'),
25
+ rotateEvery: z.number().int().positive().default(3),
26
+ mainSessionCycle: z.number().int().nonnegative().default(0),
27
+ mainLoopOverride: z
28
+ .object({
29
+ from: z.literal('rotating'),
30
+ to: z.literal('single'),
31
+ reason: z.string().min(10),
32
+ at: z.string().datetime(),
33
+ })
34
+ .optional(),
35
+ slices: z.array(SliceStateSchema),
36
+ });
37
+ export const ResourceSnapshotSchema = z.object({
38
+ capturedAt: z.string().datetime(),
39
+ cpuPercent: z.number().min(0).max(100),
40
+ memMb: z.number().nonnegative(),
41
+ diskMb: z.number().nonnegative(),
42
+ contextRatio: z.number().min(0).max(1),
43
+ });
44
+ export const JobStatusSummarySchema = z.object({
45
+ total: z.number().int(),
46
+ done: z.number().int(),
47
+ failed: z.number().int(),
48
+ blocked: z.number().int(),
49
+ skipped: z.number().int(),
50
+ currentSlice: z.string().optional(),
51
+ lastCheckpoint: z.string().datetime(),
52
+ mainLoopStrategy: z.enum(['single', 'rotating']),
53
+ mainSessionCycle: z.number().int(),
54
+ etaSec: z.number().int().optional(),
55
+ resourcesNow: ResourceSnapshotSchema.optional(),
56
+ });
57
+ // ── Spec §4.1 CLI input schemas (used by M3) ─────────────────────────────
58
+ export const JobInitInputSchema = z.object({
59
+ jobId: z.string().min(1),
60
+ sessionId: z.string().min(1),
61
+ sliceList: z.array(z.string().min(1)).min(1),
62
+ parallelismHint: z.enum(['serial', 'llm-decides']).default('llm-decides'),
63
+ exitPolicy: z.enum(['strict', 'best-effort']).default('strict'),
64
+ mainLoopStrategy: z.enum(['single', 'rotating']).default('rotating'),
65
+ rotateEvery: z.number().int().positive().default(3),
66
+ project: z.string(),
67
+ json: z.boolean().default(true),
68
+ });
69
+ export const JobCheckpointInputSchema = z
70
+ .object({
71
+ jobId: z.string(),
72
+ sliceId: z.string(),
73
+ state: z.enum(['done', 'failed', 'skipped']),
74
+ commitSha: z.string().optional(),
75
+ reason: z.string().optional(),
76
+ project: z.string(),
77
+ json: z.boolean().default(true),
78
+ })
79
+ .refine((v) => v.state !== 'done' || (v.commitSha && v.commitSha.length >= 7), { message: 'commitSha required (≥7 hex) when state=done', path: ['commitSha'] })
80
+ .refine((v) => v.state !== 'failed' && v.state !== 'skipped' ? true : !!(v.reason && v.reason.length >= 3), { message: 'reason required (≥3 chars) when state=failed|skipped', path: ['reason'] });
81
+ export const JobBlockInputSchema = z.object({
82
+ jobId: z.string(),
83
+ sliceId: z.string(),
84
+ reason: z.string().min(3),
85
+ project: z.string(),
86
+ json: z.boolean().default(true),
87
+ });
@@ -0,0 +1,35 @@
1
+ import { JobStateStore } from './job-state-store.js';
2
+ export interface WrapInput {
3
+ jobId: string;
4
+ role: 'prd' | 'rd' | 'qa' | 'ui' | 'txt' | 'sc' | 'agent';
5
+ prompt: string;
6
+ budgetMb?: number;
7
+ }
8
+ export interface DispatchResult {
9
+ batchId: string;
10
+ }
11
+ export interface DispatchOpts {
12
+ budgetMb: number;
13
+ jobScope: true;
14
+ batchId: string;
15
+ }
16
+ export type DispatchFn = (role: string, prompt: string, opts: DispatchOpts) => Promise<DispatchResult>;
17
+ export interface WrapOutput {
18
+ batchId: string;
19
+ requiresCleanup: true;
20
+ }
21
+ export declare class SubAgentJobWrapper {
22
+ private readonly store;
23
+ private readonly dispatch;
24
+ private readonly dispatchedBatches;
25
+ constructor(store: JobStateStore, dispatch: DispatchFn);
26
+ wrap(input: WrapInput): Promise<WrapOutput>;
27
+ canCheckpointDone(jobId: string, batchId: string): boolean;
28
+ cleanup(input: {
29
+ jobId: string;
30
+ batchId: string;
31
+ force: boolean;
32
+ }): Promise<{
33
+ cleaned: boolean;
34
+ }>;
35
+ }
@@ -0,0 +1,30 @@
1
+ export class SubAgentJobWrapper {
2
+ store;
3
+ dispatch;
4
+ dispatchedBatches = new Map(); // jobId -> set of batchIds pending cleanup
5
+ constructor(store, dispatch) {
6
+ this.store = store;
7
+ this.dispatch = dispatch;
8
+ }
9
+ async wrap(input) {
10
+ const budgetMb = input.budgetMb ?? 512;
11
+ const candidateBatchId = `batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
12
+ const result = await this.dispatch(input.role, input.prompt, { budgetMb, jobScope: true, batchId: candidateBatchId });
13
+ const batchId = result.batchId;
14
+ if (!this.dispatchedBatches.has(input.jobId))
15
+ this.dispatchedBatches.set(input.jobId, new Set());
16
+ this.dispatchedBatches.get(input.jobId).add(batchId);
17
+ return { batchId, requiresCleanup: true };
18
+ }
19
+ canCheckpointDone(jobId, batchId) {
20
+ const set = this.dispatchedBatches.get(jobId);
21
+ return !set?.has(batchId);
22
+ }
23
+ async cleanup(input) {
24
+ const set = this.dispatchedBatches.get(input.jobId);
25
+ if (!set?.has(input.batchId))
26
+ return { cleaned: true };
27
+ set.delete(input.batchId);
28
+ return { cleaned: true };
29
+ }
30
+ }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "3.0.2";
1
+ export declare const CLI_VERSION = "3.1.0";
@@ -1 +1 @@
1
- export const CLI_VERSION = "3.0.2";
1
+ export const CLI_VERSION = "3.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",