@ai-sdlc/orchestrator 0.9.0 → 0.10.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.
@@ -1,7 +1,25 @@
1
1
  /**
2
- * Feature-flag gate for RFC-0010 parallelism. While Phases 1-4 land, the worker-pool
3
- * conversion in execute.ts is gated behind AI_SDLC_PARALLELISM=experimental. Phase 5
4
- * (hardening) flips the default to on after a soak window.
2
+ * Feature-flag gate for RFC-0010 parallelism. Phases 1-4 shipped behind
3
+ * AI_SDLC_PARALLELISM=experimental; Phase 5 (hardening) added the runbook,
4
+ * chaos plan, and stuck-heartbeat detection. Per maintainer directive
5
+ * 2026-05-01 (AISDLC-116), the calendar-based 1-week soak gate from RFC-0010
6
+ * Phase 5 was dropped in favor of substantive readiness — pre-flight scan
7
+ * of orchestrator/_events.jsonl + recent commit history showed zero
8
+ * parallelism-related incidents — and the flag now defaults to 'on'.
9
+ *
10
+ * Backwards-compat envelope (preserved on purpose):
11
+ * - unset → 'on' (new default)
12
+ * - 'on' | 'true' | '1' → 'on'
13
+ * - 'experimental' → 'experimental' (still honored for
14
+ * callers pinning the
15
+ * pre-promotion mode)
16
+ * - 'off' | 'disabled' |
17
+ * 'false' | '0' → 'off' (explicit opt-out)
18
+ * - any other string → 'on' (fail-on rather than fail-off
19
+ * now that 'on' is the default;
20
+ * prevents typos like
21
+ * 'enable' from silently
22
+ * disabling parallelism)
5
23
  */
6
24
  export declare const FLAG_NAME = "AI_SDLC_PARALLELISM";
7
25
  export type ParallelismMode = 'off' | 'experimental' | 'on';
@@ -1,16 +1,37 @@
1
1
  /**
2
- * Feature-flag gate for RFC-0010 parallelism. While Phases 1-4 land, the worker-pool
3
- * conversion in execute.ts is gated behind AI_SDLC_PARALLELISM=experimental. Phase 5
4
- * (hardening) flips the default to on after a soak window.
2
+ * Feature-flag gate for RFC-0010 parallelism. Phases 1-4 shipped behind
3
+ * AI_SDLC_PARALLELISM=experimental; Phase 5 (hardening) added the runbook,
4
+ * chaos plan, and stuck-heartbeat detection. Per maintainer directive
5
+ * 2026-05-01 (AISDLC-116), the calendar-based 1-week soak gate from RFC-0010
6
+ * Phase 5 was dropped in favor of substantive readiness — pre-flight scan
7
+ * of orchestrator/_events.jsonl + recent commit history showed zero
8
+ * parallelism-related incidents — and the flag now defaults to 'on'.
9
+ *
10
+ * Backwards-compat envelope (preserved on purpose):
11
+ * - unset → 'on' (new default)
12
+ * - 'on' | 'true' | '1' → 'on'
13
+ * - 'experimental' → 'experimental' (still honored for
14
+ * callers pinning the
15
+ * pre-promotion mode)
16
+ * - 'off' | 'disabled' |
17
+ * 'false' | '0' → 'off' (explicit opt-out)
18
+ * - any other string → 'on' (fail-on rather than fail-off
19
+ * now that 'on' is the default;
20
+ * prevents typos like
21
+ * 'enable' from silently
22
+ * disabling parallelism)
5
23
  */
6
24
  export const FLAG_NAME = 'AI_SDLC_PARALLELISM';
7
25
  export function readParallelismMode(env = process.env) {
8
26
  const raw = env[FLAG_NAME]?.trim().toLowerCase();
27
+ if (raw === undefined || raw === '')
28
+ return 'on';
9
29
  if (raw === 'experimental')
10
30
  return 'experimental';
11
- if (raw === 'on' || raw === 'true' || raw === '1')
12
- return 'on';
13
- return 'off';
31
+ if (raw === 'off' || raw === 'disabled' || raw === 'false' || raw === '0')
32
+ return 'off';
33
+ // 'on' / 'true' / '1' / any other value → 'on' (default-on, fail-on)
34
+ return 'on';
14
35
  }
15
36
  export function isParallelismEnabled(env) {
16
37
  return readParallelismMode(env) !== 'off';
@@ -14,6 +14,17 @@ export declare class PortAllocationError extends Error {
14
14
  readonly attemptedPorts: number[];
15
15
  constructor(message: string, worktreePath: string, attemptedPorts: number[]);
16
16
  }
17
+ /**
18
+ * Derive a deterministic port for `worktreePath` by hashing its absolute form with MD5
19
+ * and mapping the first two bytes into [basePort + PORT_RANGE_OFFSET_MIN,
20
+ * basePort + PORT_RANGE_OFFSET_MAX].
21
+ *
22
+ * **Collision risk**: the output space is only 900 ports wide. When called with multiple
23
+ * independent (e.g. mkdtemp-generated) paths, two or more paths may map to the same port.
24
+ * For n=3 paths the birthday probability is ~0.33%. Integration tests that need to assert
25
+ * port distinctness MUST use pre-chosen, manually-verified paths rather than random
26
+ * temporaries — see the module-level JSDoc for the recommended assertion pattern.
27
+ */
17
28
  export declare function deterministicPort(worktreePath: string, basePort?: number): number;
18
29
  export declare function allocatePort(worktreePath: string, options?: AllocatePortOptions): Promise<number>;
19
30
  export declare function allocateContiguousPorts(worktreePath: string, options: AllocateContiguousOptions): Promise<number[]>;
@@ -1,3 +1,22 @@
1
+ /**
2
+ * Deterministic port allocator for git worktrees.
3
+ *
4
+ * `deterministicPort` maps an absolute worktree path to a port number in a fixed
5
+ * 900-port range (DEFAULT_BASE_PORT + PORT_RANGE_OFFSET_MIN … PORT_RANGE_OFFSET_MAX)
6
+ * by hashing the path with MD5 and taking the first two bytes modulo the range span.
7
+ *
8
+ * **Hash-collision caveat**: the 900-port range is finite. When multiple worktree paths
9
+ * are fed through `deterministicPort` in a single test, birthday-paradox collisions are
10
+ * possible. For n=3 paths the probability is roughly (n*(n-1)/2) / span ≈ 0.33%. Over
11
+ * thousands of CI runs this causes spurious "distinct ports" assertion failures.
12
+ *
13
+ * **Integration-test guidance**: do NOT assert `new Set(ports).size === n` unless the
14
+ * test controls the exact path strings and has pre-verified they hash to distinct values.
15
+ * Instead assert:
16
+ * - each port is in [DEFAULT_BASE_PORT + PORT_RANGE_OFFSET_MIN, DEFAULT_BASE_PORT + PORT_RANGE_OFFSET_MAX]
17
+ * - calling allocatePort twice for the same path returns the same value (idempotency)
18
+ * - ports[i] === deterministicPort(h.path) (the core deterministic-port property)
19
+ */
1
20
  import { createHash } from 'node:crypto';
2
21
  import { resolve } from 'node:path';
3
22
  import { createServer } from 'node:net';
@@ -16,6 +35,17 @@ export class PortAllocationError extends Error {
16
35
  this.name = 'PortAllocationError';
17
36
  }
18
37
  }
38
+ /**
39
+ * Derive a deterministic port for `worktreePath` by hashing its absolute form with MD5
40
+ * and mapping the first two bytes into [basePort + PORT_RANGE_OFFSET_MIN,
41
+ * basePort + PORT_RANGE_OFFSET_MAX].
42
+ *
43
+ * **Collision risk**: the output space is only 900 ports wide. When called with multiple
44
+ * independent (e.g. mkdtemp-generated) paths, two or more paths may map to the same port.
45
+ * For n=3 paths the birthday probability is ~0.33%. Integration tests that need to assert
46
+ * port distinctness MUST use pre-chosen, manually-verified paths rather than random
47
+ * temporaries — see the module-level JSDoc for the recommended assertion pattern.
48
+ */
19
49
  export function deterministicPort(worktreePath, basePort = DEFAULT_BASE_PORT) {
20
50
  const absolute = resolve(worktreePath);
21
51
  const digest = createHash('md5').update(absolute).digest();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdlc/orchestrator",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "AI-SDLC Orchestrator — long-running runtime that drives issues through the complete SDLC with AI agents",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -42,10 +42,11 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
+ "@inquirer/prompts": "^7.0.0",
45
46
  "better-sqlite3": "^11.0.0",
46
47
  "commander": "^12.0.0",
47
48
  "yaml": "^2.7.0",
48
- "@ai-sdlc/reference": "0.9.0"
49
+ "@ai-sdlc/reference": "0.10.0"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@types/better-sqlite3": "^7.6.0",