instar 0.28.95 → 0.28.97

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 (51) hide show
  1. package/dashboard/index.html +249 -2
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +90 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/MachineHeartbeat.d.ts +94 -0
  6. package/dist/core/MachineHeartbeat.d.ts.map +1 -0
  7. package/dist/core/MachineHeartbeat.js +155 -0
  8. package/dist/core/MachineHeartbeat.js.map +1 -0
  9. package/dist/core/ProjectAutoAdvancePoller.d.ts +107 -0
  10. package/dist/core/ProjectAutoAdvancePoller.d.ts.map +1 -0
  11. package/dist/core/ProjectAutoAdvancePoller.js +176 -0
  12. package/dist/core/ProjectAutoAdvancePoller.js.map +1 -0
  13. package/dist/core/ProjectRoundCompleteMessage.d.ts +158 -0
  14. package/dist/core/ProjectRoundCompleteMessage.d.ts.map +1 -0
  15. package/dist/core/ProjectRoundCompleteMessage.js +253 -0
  16. package/dist/core/ProjectRoundCompleteMessage.js.map +1 -0
  17. package/dist/core/ProjectRoundExecution.d.ts +111 -0
  18. package/dist/core/ProjectRoundExecution.d.ts.map +1 -0
  19. package/dist/core/ProjectRoundExecution.js +359 -0
  20. package/dist/core/ProjectRoundExecution.js.map +1 -0
  21. package/dist/core/ProjectRoundLock.d.ts +67 -0
  22. package/dist/core/ProjectRoundLock.d.ts.map +1 -0
  23. package/dist/core/ProjectRoundLock.js +161 -0
  24. package/dist/core/ProjectRoundLock.js.map +1 -0
  25. package/dist/core/ProjectRoundRunner.d.ts +146 -0
  26. package/dist/core/ProjectRoundRunner.d.ts.map +1 -0
  27. package/dist/core/ProjectRoundRunner.js +371 -0
  28. package/dist/core/ProjectRoundRunner.js.map +1 -0
  29. package/dist/core/ProjectRoundWorktrees.d.ts +57 -0
  30. package/dist/core/ProjectRoundWorktrees.d.ts.map +1 -0
  31. package/dist/core/ProjectRoundWorktrees.js +111 -0
  32. package/dist/core/ProjectRoundWorktrees.js.map +1 -0
  33. package/dist/server/AgentServer.d.ts +12 -0
  34. package/dist/server/AgentServer.d.ts.map +1 -1
  35. package/dist/server/AgentServer.js +3 -0
  36. package/dist/server/AgentServer.js.map +1 -1
  37. package/dist/server/routes.d.ts +19 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +782 -7
  40. package/dist/server/routes.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +48 -48
  43. package/upgrades/0.28.96.md +347 -0
  44. package/upgrades/0.28.97.md +146 -0
  45. package/upgrades/side-effects/project-scope-phase1-7-skill-surface.md +175 -0
  46. package/upgrades/side-effects/project-scope-phase1b-connect-the-dots.md +184 -0
  47. package/upgrades/side-effects/project-scope-phase1b-pr3.md +228 -0
  48. package/upgrades/side-effects/project-scope-phase1b-pr4.md +198 -0
  49. package/upgrades/side-effects/project-scope-phase1b-pr5.md +150 -0
  50. package/upgrades/side-effects/project-scope-phase1b-pr6.md +136 -0
  51. package/upgrades/side-effects/project-scope-phase1b-pr7.md +175 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ProjectRoundCompleteMessage — tone-gated round-complete delivery
3
+ * template + retry + idempotency for project rounds.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.8.
6
+ *
7
+ * Two responsibilities:
8
+ * 1. `formatRoundCompleteMessage(input)` — pure template function
9
+ * that validates required-field PRESENCE (never non-emptiness)
10
+ * and returns a `{message, idempotencyKey}` pair. Pre-flight
11
+ * halts have a documented empty-default for `whatLanded` so the
12
+ * gate never silently rejects a legitimate halt event.
13
+ * 2. `RoundCompleteDeliveryHelper` — bookkeeping over the
14
+ * idempotency key set with a `sendOnce` helper that delegates
15
+ * actual transport to a caller-provided sender, retries on
16
+ * transient failures with exponential backoff, and triggers the
17
+ * caller's fallback (`onPermanentFail`) when all retries fail.
18
+ *
19
+ * What this file is NOT responsible for:
20
+ * - Choosing WHEN to send a round-complete message. The autonomous
21
+ * run loop (next PR) decides that.
22
+ * - The actual Telegram transport. The caller passes a `send`
23
+ * function — keeping the template + retry isolated from the
24
+ * `TelegramAdapter` so tests don't need to spin up the adapter.
25
+ * - Going through the `MessagingToneGate`. The gate operates on
26
+ * final outbound text; the consumer is the one that calls
27
+ * `gate.evaluate(message)` before passing the message to a
28
+ * transport. We expose the formatted text so the consumer can
29
+ * route it through the gate.
30
+ */
31
+ import { SafeFsExecutor } from './SafeFsExecutor.js';
32
+ /** Event kind the message describes. */
33
+ export type RoundCompleteEventKind = 'round-complete' | 'round-partially-complete' | 'round-halted' | 'round-failed';
34
+ /** Inputs to the template. Required-fields are enforced by the validator below. */
35
+ export interface RoundCompleteMessageInput {
36
+ projectId: string;
37
+ projectTitle: string;
38
+ roundIndex: number;
39
+ /** Bumped on every project record write — part of the idempotency key. */
40
+ projectVersion: number;
41
+ eventKind: RoundCompleteEventKind;
42
+ /**
43
+ * Bullet list of merged itemIds with titles. May default to
44
+ * "No items shipped this round — halted at <step>" when the event
45
+ * is a pre-flight halt; presence is enforced, non-emptiness is not.
46
+ */
47
+ whatLanded: string;
48
+ /** Required for halt events (round-halted / round-failed). */
49
+ whatHalted?: string;
50
+ /**
51
+ * Verified citations (text excerpts the dashboard renders), or PR
52
+ * mergeCommit.oid values for shipped items. Empty array is allowed.
53
+ */
54
+ evidenceCited: string[];
55
+ /**
56
+ * Required for halt + failed events. For clean `round-complete`,
57
+ * the default `"(none)"` is accepted by the template.
58
+ */
59
+ rootCauseHypothesis: string;
60
+ /**
61
+ * Required. Tells the user how to act:
62
+ * `"Reply 'pause <project-id>' within 24 hours to hold"` is the
63
+ * canonical phrasing per spec.
64
+ */
65
+ concreteNextStep: string;
66
+ /** Optional dashboard deep link. */
67
+ overrideLink?: string;
68
+ /** Required. Canonical phrasing for the user's hold path. */
69
+ brakeHandlePhrase: string;
70
+ }
71
+ /** Stable key keyed off `(projectId, roundIndex, eventKind, projectVersion)`. */
72
+ export declare function idempotencyKeyFor(input: Pick<RoundCompleteMessageInput, 'projectId' | 'roundIndex' | 'eventKind' | 'projectVersion'>): string;
73
+ export type FormatResult = {
74
+ ok: true;
75
+ message: string;
76
+ idempotencyKey: string;
77
+ } | {
78
+ ok: false;
79
+ missingFields: string[];
80
+ };
81
+ /**
82
+ * Build the user-facing message. Returns `{ok:false, missingFields}`
83
+ * when a required field is undefined / null. Returns `{ok:true, message,
84
+ * idempotencyKey}` otherwise. Empty strings are accepted by design — the
85
+ * template enforces *presence*, not *non-emptiness*. The empty-default
86
+ * for `whatLanded` on pre-flight halts is the responsibility of the
87
+ * caller (they should supply the documented default before calling
88
+ * this function — the validator just records that the field is present).
89
+ */
90
+ export declare function formatRoundCompleteMessage(input: RoundCompleteMessageInput): FormatResult;
91
+ export type SendResult = {
92
+ ok: true;
93
+ } | {
94
+ ok: false;
95
+ transient: boolean;
96
+ error: Error;
97
+ };
98
+ export interface SendAttempt {
99
+ attempt: number;
100
+ ok: boolean;
101
+ error?: Error;
102
+ transient?: boolean;
103
+ }
104
+ export interface DeliveryReport {
105
+ sent: boolean;
106
+ attempts: SendAttempt[];
107
+ /** Set when send succeeded — null on failure. */
108
+ idempotencyKey: string | null;
109
+ /** Set when ALL attempts failed AND the caller's fallback ran. */
110
+ fallbackTriggered: boolean;
111
+ }
112
+ export interface RoundCompleteDeliveryHelperConfig {
113
+ /** Absolute path to the agent's `.instar/` directory (for the dedup file). */
114
+ stateDir: string;
115
+ /** Max attempts before the fallback fires. Default 3. */
116
+ maxAttempts?: number;
117
+ /** Backoff base in ms. Default 1000 (1s, 2s, 4s). */
118
+ backoffBaseMs?: number;
119
+ /** Override for tests. */
120
+ setTimeoutFn?: (cb: () => void, ms: number) => void;
121
+ }
122
+ declare const DEDUP_FILE = "round-complete-sent.json";
123
+ export declare class RoundCompleteDeliveryHelper {
124
+ private stateDir;
125
+ private maxAttempts;
126
+ private backoffBaseMs;
127
+ private setTimeoutFn;
128
+ private sentKeys;
129
+ private loaded;
130
+ constructor(config: RoundCompleteDeliveryHelperConfig);
131
+ /**
132
+ * Send a round-complete message, with retry + idempotency.
133
+ *
134
+ * On the first call for an idempotency key:
135
+ * - Calls `send(input, message)` up to `maxAttempts` times.
136
+ * - Between attempts, waits `backoffBaseMs * 2^(attempt-1)` ms.
137
+ * - Records the key after the first successful send.
138
+ *
139
+ * On subsequent calls with the same key:
140
+ * - Returns `{sent: false, alreadySent: true}` without calling `send`.
141
+ *
142
+ * On permanent failure (all attempts return non-transient or transient
143
+ * exhausted), invokes `onPermanentFail(input, message, attempts)`.
144
+ */
145
+ sendOnce(input: RoundCompleteMessageInput, send: (input: RoundCompleteMessageInput, message: string) => Promise<SendResult>, onPermanentFail?: (input: RoundCompleteMessageInput, message: string, attempts: SendAttempt[]) => Promise<void>): Promise<DeliveryReport & {
146
+ alreadySent?: boolean;
147
+ }>;
148
+ /** Test-only: query whether a key was recorded. */
149
+ hasSent(key: string): boolean;
150
+ private loadIfNeeded;
151
+ private persist;
152
+ private dedupPath;
153
+ }
154
+ /** Re-export so tests can clean up the dedup file directly. */
155
+ export { DEDUP_FILE };
156
+ /** Re-export SafeFsExecutor namespace for callers that need to clean up safely. */
157
+ export { SafeFsExecutor };
158
+ //# sourceMappingURL=ProjectRoundCompleteMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectRoundCompleteMessage.d.ts","sourceRoot":"","sources":["../../src/core/ProjectRoundCompleteMessage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * ProjectRoundCompleteMessage — tone-gated round-complete delivery
3
+ * template + retry + idempotency for project rounds.
4
+ *
5
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.8.
6
+ *
7
+ * Two responsibilities:
8
+ * 1. `formatRoundCompleteMessage(input)` — pure template function
9
+ * that validates required-field PRESENCE (never non-emptiness)
10
+ * and returns a `{message, idempotencyKey}` pair. Pre-flight
11
+ * halts have a documented empty-default for `whatLanded` so the
12
+ * gate never silently rejects a legitimate halt event.
13
+ * 2. `RoundCompleteDeliveryHelper` — bookkeeping over the
14
+ * idempotency key set with a `sendOnce` helper that delegates
15
+ * actual transport to a caller-provided sender, retries on
16
+ * transient failures with exponential backoff, and triggers the
17
+ * caller's fallback (`onPermanentFail`) when all retries fail.
18
+ *
19
+ * What this file is NOT responsible for:
20
+ * - Choosing WHEN to send a round-complete message. The autonomous
21
+ * run loop (next PR) decides that.
22
+ * - The actual Telegram transport. The caller passes a `send`
23
+ * function — keeping the template + retry isolated from the
24
+ * `TelegramAdapter` so tests don't need to spin up the adapter.
25
+ * - Going through the `MessagingToneGate`. The gate operates on
26
+ * final outbound text; the consumer is the one that calls
27
+ * `gate.evaluate(message)` before passing the message to a
28
+ * transport. We expose the formatted text so the consumer can
29
+ * route it through the gate.
30
+ */
31
+ import fs from 'node:fs';
32
+ import path from 'node:path';
33
+ import { SafeFsExecutor } from './SafeFsExecutor.js';
34
+ /** Stable key keyed off `(projectId, roundIndex, eventKind, projectVersion)`. */
35
+ export function idempotencyKeyFor(input) {
36
+ return `${input.projectId}::${input.roundIndex}::${input.eventKind}::v${input.projectVersion}`;
37
+ }
38
+ /**
39
+ * Build the user-facing message. Returns `{ok:false, missingFields}`
40
+ * when a required field is undefined / null. Returns `{ok:true, message,
41
+ * idempotencyKey}` otherwise. Empty strings are accepted by design — the
42
+ * template enforces *presence*, not *non-emptiness*. The empty-default
43
+ * for `whatLanded` on pre-flight halts is the responsibility of the
44
+ * caller (they should supply the documented default before calling
45
+ * this function — the validator just records that the field is present).
46
+ */
47
+ export function formatRoundCompleteMessage(input) {
48
+ const missing = [];
49
+ if (typeof input.projectId !== 'string')
50
+ missing.push('projectId');
51
+ if (typeof input.projectTitle !== 'string')
52
+ missing.push('projectTitle');
53
+ if (!Number.isInteger(input.roundIndex))
54
+ missing.push('roundIndex');
55
+ if (!Number.isInteger(input.projectVersion))
56
+ missing.push('projectVersion');
57
+ if (typeof input.eventKind !== 'string')
58
+ missing.push('eventKind');
59
+ if (typeof input.whatLanded !== 'string')
60
+ missing.push('whatLanded');
61
+ if (typeof input.rootCauseHypothesis !== 'string')
62
+ missing.push('rootCauseHypothesis');
63
+ if (typeof input.concreteNextStep !== 'string')
64
+ missing.push('concreteNextStep');
65
+ if (typeof input.brakeHandlePhrase !== 'string')
66
+ missing.push('brakeHandlePhrase');
67
+ if (!Array.isArray(input.evidenceCited))
68
+ missing.push('evidenceCited');
69
+ // halt-flavor events also require whatHalted to be present (string).
70
+ const haltLike = input.eventKind === 'round-halted' || input.eventKind === 'round-failed';
71
+ if (haltLike && typeof input.whatHalted !== 'string')
72
+ missing.push('whatHalted');
73
+ if (missing.length > 0) {
74
+ return { ok: false, missingFields: missing };
75
+ }
76
+ const lines = [];
77
+ const headlineByKind = {
78
+ 'round-complete': 'Round complete',
79
+ 'round-partially-complete': 'Round partially complete',
80
+ 'round-halted': 'Round halted',
81
+ 'round-failed': 'Round failed',
82
+ };
83
+ lines.push(`${headlineByKind[input.eventKind]}: ${input.projectTitle} (round ${input.roundIndex}).`);
84
+ lines.push('');
85
+ lines.push('What landed:');
86
+ lines.push(input.whatLanded);
87
+ if (haltLike && input.whatHalted) {
88
+ lines.push('');
89
+ lines.push('What halted:');
90
+ lines.push(input.whatHalted);
91
+ }
92
+ if (input.evidenceCited.length > 0) {
93
+ lines.push('');
94
+ lines.push('Evidence cited:');
95
+ for (const e of input.evidenceCited.slice(0, 10))
96
+ lines.push(`• ${e}`);
97
+ }
98
+ if (input.rootCauseHypothesis && input.rootCauseHypothesis !== '(none)') {
99
+ lines.push('');
100
+ lines.push(`Root cause hypothesis: ${input.rootCauseHypothesis}`);
101
+ }
102
+ lines.push('');
103
+ lines.push(`Next step: ${input.concreteNextStep}`);
104
+ lines.push(`To hold: ${input.brakeHandlePhrase}`);
105
+ if (input.overrideLink) {
106
+ lines.push(`Dashboard: ${input.overrideLink}`);
107
+ }
108
+ return {
109
+ ok: true,
110
+ message: lines.join('\n'),
111
+ idempotencyKey: idempotencyKeyFor(input),
112
+ };
113
+ }
114
+ const DEDUP_FILE = 'round-complete-sent.json';
115
+ /** Bounded set so dedupe state doesn't grow forever. */
116
+ const DEDUP_RING_SIZE = 1000;
117
+ export class RoundCompleteDeliveryHelper {
118
+ stateDir;
119
+ maxAttempts;
120
+ backoffBaseMs;
121
+ setTimeoutFn;
122
+ sentKeys = new Set();
123
+ loaded = false;
124
+ constructor(config) {
125
+ this.stateDir = config.stateDir;
126
+ this.maxAttempts = config.maxAttempts ?? 3;
127
+ this.backoffBaseMs = config.backoffBaseMs ?? 1000;
128
+ this.setTimeoutFn = config.setTimeoutFn ?? ((cb, ms) => setTimeout(cb, ms).unref());
129
+ }
130
+ /**
131
+ * Send a round-complete message, with retry + idempotency.
132
+ *
133
+ * On the first call for an idempotency key:
134
+ * - Calls `send(input, message)` up to `maxAttempts` times.
135
+ * - Between attempts, waits `backoffBaseMs * 2^(attempt-1)` ms.
136
+ * - Records the key after the first successful send.
137
+ *
138
+ * On subsequent calls with the same key:
139
+ * - Returns `{sent: false, alreadySent: true}` without calling `send`.
140
+ *
141
+ * On permanent failure (all attempts return non-transient or transient
142
+ * exhausted), invokes `onPermanentFail(input, message, attempts)`.
143
+ */
144
+ async sendOnce(input, send, onPermanentFail) {
145
+ this.loadIfNeeded();
146
+ const formatted = formatRoundCompleteMessage(input);
147
+ if (!formatted.ok) {
148
+ return { sent: false, attempts: [], idempotencyKey: null, fallbackTriggered: false };
149
+ }
150
+ if (this.sentKeys.has(formatted.idempotencyKey)) {
151
+ return {
152
+ sent: true,
153
+ attempts: [{ attempt: 0, ok: true }],
154
+ idempotencyKey: formatted.idempotencyKey,
155
+ fallbackTriggered: false,
156
+ alreadySent: true,
157
+ };
158
+ }
159
+ const attempts = [];
160
+ for (let i = 1; i <= this.maxAttempts; i++) {
161
+ let result;
162
+ try {
163
+ result = await send(input, formatted.message);
164
+ }
165
+ catch (err) {
166
+ result = { ok: false, transient: true, error: err instanceof Error ? err : new Error(String(err)) };
167
+ }
168
+ if (result.ok) {
169
+ attempts.push({ attempt: i, ok: true });
170
+ this.sentKeys.add(formatted.idempotencyKey);
171
+ this.persist();
172
+ return {
173
+ sent: true,
174
+ attempts,
175
+ idempotencyKey: formatted.idempotencyKey,
176
+ fallbackTriggered: false,
177
+ };
178
+ }
179
+ attempts.push({ attempt: i, ok: false, error: result.error, transient: result.transient });
180
+ // Non-transient → bail immediately.
181
+ if (!result.transient)
182
+ break;
183
+ // Transient → backoff before next attempt, unless this was the last.
184
+ if (i < this.maxAttempts) {
185
+ const delay = this.backoffBaseMs * Math.pow(2, i - 1);
186
+ await new Promise((resolve) => this.setTimeoutFn(resolve, delay));
187
+ }
188
+ }
189
+ if (onPermanentFail) {
190
+ try {
191
+ await onPermanentFail(input, formatted.message, attempts);
192
+ }
193
+ catch {
194
+ // Fallback's own failure is logged at the call site; we don't
195
+ // double-fail the delivery report.
196
+ }
197
+ }
198
+ return {
199
+ sent: false,
200
+ attempts,
201
+ idempotencyKey: null,
202
+ fallbackTriggered: !!onPermanentFail,
203
+ };
204
+ }
205
+ /** Test-only: query whether a key was recorded. */
206
+ hasSent(key) {
207
+ this.loadIfNeeded();
208
+ return this.sentKeys.has(key);
209
+ }
210
+ // ── Internals ─────────────────────────────────────────────────────
211
+ loadIfNeeded() {
212
+ if (this.loaded)
213
+ return;
214
+ this.loaded = true;
215
+ try {
216
+ const raw = fs.readFileSync(this.dedupPath(), 'utf-8');
217
+ const obj = JSON.parse(raw);
218
+ if (obj && Array.isArray(obj.sent)) {
219
+ for (const k of obj.sent)
220
+ if (typeof k === 'string')
221
+ this.sentKeys.add(k);
222
+ }
223
+ }
224
+ catch {
225
+ // Missing or malformed dedup file — start fresh.
226
+ }
227
+ }
228
+ persist() {
229
+ try {
230
+ const dir = path.join(this.stateDir, 'local');
231
+ if (!fs.existsSync(dir))
232
+ fs.mkdirSync(dir, { recursive: true });
233
+ // Cap the ring so the file doesn't grow forever.
234
+ const list = Array.from(this.sentKeys).slice(-DEDUP_RING_SIZE);
235
+ this.sentKeys = new Set(list);
236
+ const tmp = this.dedupPath() + '.tmp';
237
+ fs.writeFileSync(tmp, JSON.stringify({ sent: list }, null, 2), { mode: 0o600 });
238
+ fs.renameSync(tmp, this.dedupPath());
239
+ }
240
+ catch {
241
+ // Best-effort persistence; in-memory set still works for the lifetime
242
+ // of the process.
243
+ }
244
+ }
245
+ dedupPath() {
246
+ return path.join(this.stateDir, 'local', DEDUP_FILE);
247
+ }
248
+ }
249
+ /** Re-export so tests can clean up the dedup file directly. */
250
+ export { DEDUP_FILE };
251
+ /** Re-export SafeFsExecutor namespace for callers that need to clean up safely. */
252
+ export { SafeFsExecutor };
253
+ //# sourceMappingURL=ProjectRoundCompleteMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectRoundCompleteMessage.js","sourceRoot":"","sources":["../../src/core/ProjectRoundCompleteMessage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ProjectRoundExecution — the autonomous run loop for a project round.
3
+ *
4
+ * Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Run loop", steps 1-11).
5
+ *
6
+ * What this module is responsible for:
7
+ * 1. Acquiring the round-runner lock (delegated to ProjectRoundLock).
8
+ * 2. Spawning the autonomous child process in a detached process
9
+ * group, so SIGTERM/SIGKILL of the group never reaps the runner.
10
+ * 3. Polling the project record every `pollIntervalMs` to detect
11
+ * mid-round mutations to the round's itemIds (e.g., a user
12
+ * manually skips an item) — when detected, SIGTERM the group
13
+ * (5s grace, then SIGKILL) and relaunch with a recomputed stop
14
+ * condition.
15
+ * 4. On the autonomous child's NATURAL exit, verify per-item
16
+ * artifacts and set round.status = complete | partially-complete.
17
+ * 5. Cleanup: `git worktree prune` for the round namespace.
18
+ * 6. Release the lock.
19
+ *
20
+ * What this module is NOT responsible for:
21
+ * - The preflight gate. That's `ProjectRoundRunner.preflight`. The
22
+ * caller (the auto-advance poller, the /project run-round skill,
23
+ * a future HTTP endpoint) is expected to preflight before calling
24
+ * `runRound(input)`.
25
+ * - Drift checking. Same — preflight handles it.
26
+ * - Choosing the autonomous command. The `spawnCommand` is injected,
27
+ * defaulting to `claude` (which invokes the local /autonomous skill).
28
+ * Tests pass a custom command so they don't depend on the skill.
29
+ *
30
+ * Process group safety:
31
+ * The child is spawned with `detached: true` so it gets its own
32
+ * process group. `kill(-pgid, signal)` (Node passes negative PIDs
33
+ * for group signals) targets only the child's group — never reaps
34
+ * the runner.
35
+ */
36
+ import type { InitiativeTracker } from './InitiativeTracker.js';
37
+ /** Per-spec defaults. Tests dial both down to keep the suite fast. */
38
+ export declare const DEFAULT_POLL_INTERVAL_MS = 60000;
39
+ export declare const DEFAULT_SIGTERM_GRACE_MS = 5000;
40
+ /** Resume cap from spec § Phase 1.5 step 11. */
41
+ export declare const DEFAULT_MAX_RESUME_ATTEMPTS = 3;
42
+ export interface RunRoundInput {
43
+ tracker: InitiativeTracker;
44
+ projectId: string;
45
+ roundIndex: number;
46
+ /**
47
+ * Absolute target repo path; required (the spec's `targetRepoPath`).
48
+ * The caller must have verified it points at a git repo (preflight does this).
49
+ */
50
+ targetRepoPath: string;
51
+ /**
52
+ * `process.cwd()` for the spawned autonomous child. Defaults to the
53
+ * first item's worktree path. The autonomous child opens additional
54
+ * worktrees as it works through items.
55
+ */
56
+ initialWorkdir?: string;
57
+ /**
58
+ * Command + args to spawn. Default is the production invocation:
59
+ * `claude --skill autonomous` (resolved via PATH). Tests pass a
60
+ * harmless command (`bash -c "exit 0"`) so the test doesn't actually
61
+ * invoke claude.
62
+ *
63
+ * Stop condition + project/round identifiers are passed via env:
64
+ * INSTAR_PROJECT_ID, INSTAR_ROUND_INDEX, INSTAR_STOP_CONDITION
65
+ * INSTAR_ROUND_ITEM_IDS (JSON-encoded array)
66
+ */
67
+ spawnCommand?: string;
68
+ spawnArgs?: string[];
69
+ /** Poll cadence; defaults to 60s. */
70
+ pollIntervalMs?: number;
71
+ /** SIGTERM→SIGKILL grace; defaults to 5s. */
72
+ sigtermGraceMs?: number;
73
+ /** Max resume attempts on transient failures; defaults to 3. */
74
+ maxResumeAttempts?: number;
75
+ /**
76
+ * Test hook: shell out to verify per-item artifacts. Defaults to a
77
+ * real `gh pr view` shell-out via SafeGitExecutor. Tests inject a
78
+ * stub. The function returns the set of itemIds whose artifact
79
+ * is verified merged-on-main-with-CI-green.
80
+ */
81
+ verifyMergedItems?: (childIds: string[]) => Promise<Set<string>>;
82
+ }
83
+ export type RoundOutcome = 'complete' | 'partially-complete' | 'failed' | 'halted';
84
+ export interface RunRoundResult {
85
+ outcome: RoundOutcome;
86
+ /** Verified-merged itemIds. */
87
+ mergedItemIds: string[];
88
+ /** itemIds that did NOT verify (only populated for partially-complete / halted). */
89
+ unmergedItemIds: string[];
90
+ /** Number of times the runner relaunched the child due to dynamic-stop changes. */
91
+ relaunchCount: number;
92
+ /** Number of times the round resumed after a transient child failure. */
93
+ resumeAttempts: number;
94
+ /** Human-readable reason — useful for halted / failed outcomes. */
95
+ reason?: string;
96
+ }
97
+ /**
98
+ * Lock that the runner needs but is too high-level to require here as
99
+ * a positional parameter. Wires through the standard
100
+ * `.instar/local/round-runner.lock` location.
101
+ */
102
+ export interface RunRoundDeps {
103
+ stateDir: string;
104
+ }
105
+ /**
106
+ * Run one round to completion. Caller is expected to preflight first.
107
+ */
108
+ export declare function runRound(input: RunRoundInput, deps: RunRoundDeps): Promise<RunRoundResult>;
109
+ /** Helper exported so callers can wire a SafeGit-backed verifier. */
110
+ export declare function verifyMergedItemsViaGit(targetRepoPath: string, childIds: string[], tracker: InitiativeTracker): Promise<Set<string>>;
111
+ //# sourceMappingURL=ProjectRoundExecution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectRoundExecution.d.ts","sourceRoot":"","sources":["../../src/core/ProjectRoundExecution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAc,iBAAiB,EAAe,MAAM,wBAAwB,CAAC;AAKzF,sEAAsE;AACtE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAC9C,gDAAgD;AAChD,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAClE;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oFAAoF;IACpF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CA2JhG;AAqMD,qEAAqE;AACrE,wBAAsB,uBAAuB,CAC3C,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAgBtB"}