kantban-cli 0.1.6 → 0.1.8

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 (118) hide show
  1. package/dist/client.d.ts +3 -0
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +54 -0
  4. package/dist/client.js.map +1 -1
  5. package/dist/commands/cron.js +2 -2
  6. package/dist/commands/cron.js.map +1 -1
  7. package/dist/commands/pipeline-init.d.ts +2 -0
  8. package/dist/commands/pipeline-init.d.ts.map +1 -0
  9. package/dist/commands/pipeline-init.js +100 -0
  10. package/dist/commands/pipeline-init.js.map +1 -0
  11. package/dist/commands/pipeline.d.ts.map +1 -1
  12. package/dist/commands/pipeline.js +637 -44
  13. package/dist/commands/pipeline.js.map +1 -1
  14. package/dist/lib/advisor.d.ts +108 -0
  15. package/dist/lib/advisor.d.ts.map +1 -0
  16. package/dist/lib/advisor.js +139 -0
  17. package/dist/lib/advisor.js.map +1 -0
  18. package/dist/lib/checkpoint.d.ts +15 -0
  19. package/dist/lib/checkpoint.d.ts.map +1 -0
  20. package/dist/lib/checkpoint.js +49 -0
  21. package/dist/lib/checkpoint.js.map +1 -0
  22. package/dist/lib/constraint-evaluator.d.ts +40 -0
  23. package/dist/lib/constraint-evaluator.d.ts.map +1 -0
  24. package/dist/lib/constraint-evaluator.js +189 -0
  25. package/dist/lib/constraint-evaluator.js.map +1 -0
  26. package/dist/lib/cost-tracker.d.ts +46 -0
  27. package/dist/lib/cost-tracker.d.ts.map +1 -0
  28. package/dist/lib/cost-tracker.js +120 -0
  29. package/dist/lib/cost-tracker.js.map +1 -0
  30. package/dist/lib/evaluator.d.ts +17 -0
  31. package/dist/lib/evaluator.d.ts.map +1 -0
  32. package/dist/lib/evaluator.js +71 -0
  33. package/dist/lib/evaluator.js.map +1 -0
  34. package/dist/lib/event-emitter.d.ts +28 -0
  35. package/dist/lib/event-emitter.d.ts.map +1 -0
  36. package/dist/lib/event-emitter.js +100 -0
  37. package/dist/lib/event-emitter.js.map +1 -0
  38. package/dist/lib/gate-config.d.ts +7 -0
  39. package/dist/lib/gate-config.d.ts.map +1 -0
  40. package/dist/lib/gate-config.js +68 -0
  41. package/dist/lib/gate-config.js.map +1 -0
  42. package/dist/lib/gate-proxy-server.d.ts +16 -0
  43. package/dist/lib/gate-proxy-server.d.ts.map +1 -0
  44. package/dist/lib/gate-proxy-server.js +385 -0
  45. package/dist/lib/gate-proxy-server.js.map +1 -0
  46. package/dist/lib/gate-proxy.d.ts +46 -0
  47. package/dist/lib/gate-proxy.d.ts.map +1 -0
  48. package/dist/lib/gate-proxy.js +104 -0
  49. package/dist/lib/gate-proxy.js.map +1 -0
  50. package/dist/lib/gate-runner.d.ts +13 -0
  51. package/dist/lib/gate-runner.d.ts.map +1 -0
  52. package/dist/lib/gate-runner.js +104 -0
  53. package/dist/lib/gate-runner.js.map +1 -0
  54. package/dist/lib/gate-snapshot.d.ts +12 -0
  55. package/dist/lib/gate-snapshot.d.ts.map +1 -0
  56. package/dist/lib/gate-snapshot.js +49 -0
  57. package/dist/lib/gate-snapshot.js.map +1 -0
  58. package/dist/lib/light-call.d.ts +37 -0
  59. package/dist/lib/light-call.d.ts.map +1 -0
  60. package/dist/lib/light-call.js +62 -0
  61. package/dist/lib/light-call.js.map +1 -0
  62. package/dist/lib/logger.d.ts +2 -0
  63. package/dist/lib/logger.d.ts.map +1 -1
  64. package/dist/lib/logger.js +55 -9
  65. package/dist/lib/logger.js.map +1 -1
  66. package/dist/lib/mcp-config.d.ts +15 -0
  67. package/dist/lib/mcp-config.d.ts.map +1 -1
  68. package/dist/lib/mcp-config.js +70 -6
  69. package/dist/lib/mcp-config.js.map +1 -1
  70. package/dist/lib/orchestrator.d.ts +220 -6
  71. package/dist/lib/orchestrator.d.ts.map +1 -1
  72. package/dist/lib/orchestrator.js +1265 -58
  73. package/dist/lib/orchestrator.js.map +1 -1
  74. package/dist/lib/parse-utils.d.ts +6 -0
  75. package/dist/lib/parse-utils.d.ts.map +1 -0
  76. package/dist/lib/parse-utils.js +64 -0
  77. package/dist/lib/parse-utils.js.map +1 -0
  78. package/dist/lib/prompt-composer.d.ts +30 -1
  79. package/dist/lib/prompt-composer.d.ts.map +1 -1
  80. package/dist/lib/prompt-composer.js +162 -27
  81. package/dist/lib/prompt-composer.js.map +1 -1
  82. package/dist/lib/ralph-loop.d.ts +78 -4
  83. package/dist/lib/ralph-loop.d.ts.map +1 -1
  84. package/dist/lib/ralph-loop.js +249 -40
  85. package/dist/lib/ralph-loop.js.map +1 -1
  86. package/dist/lib/reaper.d.ts +14 -0
  87. package/dist/lib/reaper.d.ts.map +1 -0
  88. package/dist/lib/reaper.js +114 -0
  89. package/dist/lib/reaper.js.map +1 -0
  90. package/dist/lib/replanner.d.ts +49 -0
  91. package/dist/lib/replanner.d.ts.map +1 -0
  92. package/dist/lib/replanner.js +61 -0
  93. package/dist/lib/replanner.js.map +1 -0
  94. package/dist/lib/run-memory.d.ts +37 -0
  95. package/dist/lib/run-memory.d.ts.map +1 -0
  96. package/dist/lib/run-memory.js +115 -0
  97. package/dist/lib/run-memory.js.map +1 -0
  98. package/dist/lib/stream-parser.d.ts +20 -0
  99. package/dist/lib/stream-parser.d.ts.map +1 -0
  100. package/dist/lib/stream-parser.js +65 -0
  101. package/dist/lib/stream-parser.js.map +1 -0
  102. package/dist/lib/stuck-detector.d.ts +47 -0
  103. package/dist/lib/stuck-detector.d.ts.map +1 -0
  104. package/dist/lib/stuck-detector.js +105 -0
  105. package/dist/lib/stuck-detector.js.map +1 -0
  106. package/dist/lib/tool-profiles.d.ts +19 -0
  107. package/dist/lib/tool-profiles.d.ts.map +1 -0
  108. package/dist/lib/tool-profiles.js +22 -0
  109. package/dist/lib/tool-profiles.js.map +1 -0
  110. package/dist/lib/worktree.d.ts +12 -0
  111. package/dist/lib/worktree.d.ts.map +1 -0
  112. package/dist/lib/worktree.js +29 -0
  113. package/dist/lib/worktree.js.map +1 -0
  114. package/dist/lib/ws-client.d.ts +1 -1
  115. package/dist/lib/ws-client.d.ts.map +1 -1
  116. package/dist/lib/ws-client.js +5 -2
  117. package/dist/lib/ws-client.js.map +1 -1
  118. package/package.json +3 -1
@@ -0,0 +1,114 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { writeFileSync, unlinkSync, readFileSync } from 'node:fs';
3
+ export const REAPER_POLL_MS = 3000;
4
+ export function buildReaperScript(config) {
5
+ return `
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const ORCHESTRATOR_PID = ${config.orchestratorPid};
10
+ const MANIFEST_PATH = ${JSON.stringify(config.manifestPath)};
11
+ const PID_FILE_PATH = ${JSON.stringify(config.pidFilePath)};
12
+ const REAPER_PID_PATH = ${JSON.stringify(config.reaperPidPath)};
13
+ const MCP_CONFIG_PATH = ${JSON.stringify(config.mcpConfigPath)};
14
+ const PIPELINE_DIR = ${JSON.stringify(config.pipelineDir)};
15
+ const POLL_MS = ${REAPER_POLL_MS};
16
+
17
+ function isAlive(pid) {
18
+ try { process.kill(pid, 0); return true; } catch { return false; }
19
+ }
20
+
21
+ function readManifest() {
22
+ try {
23
+ return fs.readFileSync(MANIFEST_PATH, 'utf-8')
24
+ .split('\\n')
25
+ .map(l => parseInt(l.trim(), 10))
26
+ .filter(p => !isNaN(p) && p > 0);
27
+ } catch { return []; }
28
+ }
29
+
30
+ function killPid(pid, signal) {
31
+ try { process.kill(pid, signal); } catch { /* already dead */ }
32
+ }
33
+
34
+ function cleanup() {
35
+ const pids = readManifest();
36
+ for (const pid of pids) {
37
+ killPid(pid, 'SIGTERM');
38
+ try { process.kill(-pid, 'SIGTERM'); } catch { /* ignore */ }
39
+ }
40
+
41
+ setTimeout(() => {
42
+ for (const pid of pids) {
43
+ if (isAlive(pid)) {
44
+ killPid(pid, 'SIGKILL');
45
+ try { process.kill(-pid, 'SIGKILL'); } catch { /* ignore */ }
46
+ }
47
+ }
48
+
49
+ try { fs.unlinkSync(MANIFEST_PATH); } catch {}
50
+ try { fs.unlinkSync(PID_FILE_PATH); } catch {}
51
+ try { fs.unlinkSync(REAPER_PID_PATH); } catch {}
52
+ try { fs.unlinkSync(MCP_CONFIG_PATH); } catch {}
53
+
54
+ try {
55
+ const files = fs.readdirSync(PIPELINE_DIR);
56
+ for (const f of files) {
57
+ if (f.startsWith('mcp-config-') && f.endsWith('.json')) {
58
+ try { fs.unlinkSync(path.join(PIPELINE_DIR, f)); } catch {}
59
+ }
60
+ }
61
+ } catch {}
62
+
63
+ process.exit(0);
64
+ }, 5000);
65
+ }
66
+
67
+ function pidFileExists() {
68
+ try { fs.accessSync(PID_FILE_PATH); return true; } catch { return false; }
69
+ }
70
+
71
+ const timer = setInterval(() => {
72
+ if (!pidFileExists()) {
73
+ clearInterval(timer);
74
+ process.exit(0);
75
+ return;
76
+ }
77
+
78
+ if (!isAlive(ORCHESTRATOR_PID)) {
79
+ clearInterval(timer);
80
+ cleanup();
81
+ }
82
+ }, POLL_MS);
83
+ `;
84
+ }
85
+ export function spawnReaper(config) {
86
+ const script = buildReaperScript(config);
87
+ const child = spawn(process.execPath, ['-e', script], {
88
+ detached: true,
89
+ stdio: 'ignore',
90
+ });
91
+ child.unref();
92
+ if (child.pid) {
93
+ writeFileSync(config.reaperPidPath, String(child.pid), { mode: 0o600 });
94
+ }
95
+ return child;
96
+ }
97
+ export function killReaper(reaperPidPath) {
98
+ try {
99
+ const pid = parseInt(readFileSync(reaperPidPath, 'utf-8').trim(), 10);
100
+ if (pid && !isNaN(pid)) {
101
+ process.kill(pid, 'SIGTERM');
102
+ }
103
+ }
104
+ catch {
105
+ /* reaper already dead or PID file gone */
106
+ }
107
+ try {
108
+ unlinkSync(reaperPidPath);
109
+ }
110
+ catch {
111
+ /* already removed */
112
+ }
113
+ }
114
+ //# sourceMappingURL=reaper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reaper.js","sourceRoot":"","sources":["../../src/lib/reaper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGlE,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAWnC,MAAM,UAAU,iBAAiB,CAAC,MAAoB;IACpD,OAAO;;;;2BAIkB,MAAM,CAAC,eAAe;wBACzB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC;wBACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC;0BAChC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC;0BACpC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC;uBACvC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC;kBACvC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoE/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAoB;IAC9C,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAEzC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;QACpD,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;IAEH,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;QACd,aAAa,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,aAAqB;IAC9C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAClB,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAC3C,EAAE,CACH,CAAC;QACF,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,aAAa,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ export interface ReplannerTriggers {
3
+ escalation_count: number;
4
+ cost_threshold_pct: number;
5
+ repeated_gate_failure_count: number;
6
+ duration_threshold_minutes: number;
7
+ }
8
+ export interface PipelineState {
9
+ escalatedTickets: number;
10
+ totalTokensIn: number;
11
+ maxInputTokens: number;
12
+ repeatedGateFailures: Record<string, number>;
13
+ durationMinutes: number;
14
+ }
15
+ export declare function shouldFireReplanner(triggers: ReplannerTriggers, state: PipelineState): boolean;
16
+ declare const ReplannerResponseSchema: z.ZodObject<{
17
+ action: z.ZodEnum<["CONTINUE", "PAUSE_PIPELINE", "ARCHIVE_TICKETS", "CREATE_SIGNAL", "ADJUST_BUDGET", "ESCALATE_ALL"]>;
18
+ reason: z.ZodString;
19
+ ticket_ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
20
+ signal_content: z.ZodOptional<z.ZodString>;
21
+ new_max_input_tokens: z.ZodOptional<z.ZodNumber>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ action: "CONTINUE" | "PAUSE_PIPELINE" | "ARCHIVE_TICKETS" | "CREATE_SIGNAL" | "ADJUST_BUDGET" | "ESCALATE_ALL";
24
+ reason: string;
25
+ ticket_ids?: string[] | undefined;
26
+ signal_content?: string | undefined;
27
+ new_max_input_tokens?: number | undefined;
28
+ }, {
29
+ action: "CONTINUE" | "PAUSE_PIPELINE" | "ARCHIVE_TICKETS" | "CREATE_SIGNAL" | "ADJUST_BUDGET" | "ESCALATE_ALL";
30
+ reason: string;
31
+ ticket_ids?: string[] | undefined;
32
+ signal_content?: string | undefined;
33
+ new_max_input_tokens?: number | undefined;
34
+ }>;
35
+ export type ReplannerResponse = z.infer<typeof ReplannerResponseSchema>;
36
+ export declare function composeReplannerPrompt(state: PipelineState & {
37
+ ticketSummaries: Array<{
38
+ id: string;
39
+ title: string;
40
+ column: string;
41
+ status: string;
42
+ iterations: number;
43
+ gatePassRate: number;
44
+ }>;
45
+ triggerReason: string;
46
+ }): string;
47
+ export declare function parseReplannerResponse(raw: string): ReplannerResponse;
48
+ export {};
49
+ //# sourceMappingURL=replanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replanner.d.ts","sourceRoot":"","sources":["../../src/lib/replanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2BAA2B,EAAE,MAAM,CAAC;IACpC,0BAA0B,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAW9F;AAMD,QAAA,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;EAM3B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG;IAC5D,eAAe,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChI,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CA0BT;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAOrE"}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { parseJsonFromLlmOutput } from './parse-utils.js';
3
+ export function shouldFireReplanner(triggers, state) {
4
+ if (state.escalatedTickets >= triggers.escalation_count)
5
+ return true;
6
+ if (state.maxInputTokens > 0) {
7
+ const pct = (state.totalTokensIn / state.maxInputTokens) * 100;
8
+ if (pct >= triggers.cost_threshold_pct)
9
+ return true;
10
+ }
11
+ for (const count of Object.values(state.repeatedGateFailures)) {
12
+ if (count >= triggers.repeated_gate_failure_count)
13
+ return true;
14
+ }
15
+ if (state.durationMinutes >= triggers.duration_threshold_minutes)
16
+ return true;
17
+ return false;
18
+ }
19
+ const ReplannerActionSchema = z.enum([
20
+ 'CONTINUE', 'PAUSE_PIPELINE', 'ARCHIVE_TICKETS', 'CREATE_SIGNAL', 'ADJUST_BUDGET', 'ESCALATE_ALL',
21
+ ]);
22
+ const ReplannerResponseSchema = z.object({
23
+ action: ReplannerActionSchema,
24
+ reason: z.string(),
25
+ ticket_ids: z.array(z.string()).optional(),
26
+ signal_content: z.string().optional(),
27
+ new_max_input_tokens: z.number().optional(),
28
+ });
29
+ export function composeReplannerPrompt(state) {
30
+ const parts = [];
31
+ parts.push('You are a pipeline replanner. A trigger threshold has been crossed.');
32
+ parts.push(`\nTrigger: ${state.triggerReason}`);
33
+ parts.push(`\nPipeline State:`);
34
+ parts.push(`- Escalated tickets: ${state.escalatedTickets}`);
35
+ parts.push(`- Token usage: ${state.totalTokensIn} / ${state.maxInputTokens}`);
36
+ parts.push(`- Duration: ${state.durationMinutes} minutes`);
37
+ if (Object.keys(state.repeatedGateFailures).length > 0) {
38
+ parts.push(`\nRepeated gate failures:`);
39
+ for (const [gate, count] of Object.entries(state.repeatedGateFailures)) {
40
+ parts.push(`- ${gate}: ${count} tickets failing`);
41
+ }
42
+ }
43
+ parts.push(`\nTicket summaries:`);
44
+ for (const t of state.ticketSummaries) {
45
+ parts.push(`- ${t.title} [${t.column}] status=${t.status} iterations=${t.iterations} gate_pass=${t.gatePassRate}%`);
46
+ }
47
+ parts.push(`\nActions available: CONTINUE, PAUSE_PIPELINE, ARCHIVE_TICKETS, CREATE_SIGNAL, ADJUST_BUDGET, ESCALATE_ALL`);
48
+ parts.push(`\nBe conservative. Primary value is knowing when to stop.`);
49
+ parts.push(`\nRespond with a JSON object: { "action": "...", "reason": "..." }`);
50
+ return parts.join('\n');
51
+ }
52
+ export function parseReplannerResponse(raw) {
53
+ try {
54
+ const parsed = parseJsonFromLlmOutput(raw);
55
+ return ReplannerResponseSchema.parse(parsed);
56
+ }
57
+ catch {
58
+ return { action: 'PAUSE_PIPELINE', reason: 'Failed to parse replanner response — defaulting to pause for safety' };
59
+ }
60
+ }
61
+ //# sourceMappingURL=replanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replanner.js","sourceRoot":"","sources":["../../src/lib/replanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ export interface RunMemoryDeps {
2
+ createDocument: (content: string, title: string) => Promise<string>;
3
+ getDocument: (documentId: string) => Promise<string>;
4
+ updateDocument: (documentId: string, content: string) => Promise<void>;
5
+ }
6
+ export declare class RunMemory {
7
+ private boardName;
8
+ private deps;
9
+ private _documentId;
10
+ private _writeQueue;
11
+ constructor(_boardId: string, boardName: string, deps: RunMemoryDeps);
12
+ get documentId(): string | null;
13
+ /**
14
+ * Create the run memory document. Optional seedContent prepopulates the
15
+ * Codebase Conventions section.
16
+ */
17
+ initialize(seedContent?: string): Promise<void>;
18
+ /**
19
+ * Get current document content. Returns '' if not initialized or on error.
20
+ */
21
+ getContent(): Promise<string>;
22
+ /**
23
+ * Check if document exceeds line threshold (default 500).
24
+ */
25
+ needsCompaction(threshold?: number): Promise<boolean>;
26
+ /**
27
+ * Append content under a specific section heading.
28
+ * Agents call this after successful iterations to share discoveries.
29
+ */
30
+ append(section: string, content: string): Promise<void>;
31
+ /**
32
+ * Replace document content with a compacted version.
33
+ * Called by the orchestrator after a Haiku summarization call.
34
+ */
35
+ compact(compactedContent: string): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=run-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-memory.d.ts","sourceRoot":"","sources":["../../src/lib/run-memory.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAID,qBAAa,SAAS;IACpB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,WAAW,CAAoC;gBAE3C,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa;IAKpE,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED;;;OAGG;IACG,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAYnC;;OAEG;IACG,eAAe,CAAC,SAAS,GAAE,MAAqC,GAAG,OAAO,CAAC,OAAO,CAAC;IAOzF;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B7D;;;OAGG;IACG,OAAO,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAcvD"}
@@ -0,0 +1,115 @@
1
+ const DEFAULT_COMPACTION_THRESHOLD = 500;
2
+ export class RunMemory {
3
+ boardName;
4
+ deps;
5
+ _documentId = null;
6
+ _writeQueue = Promise.resolve();
7
+ constructor(_boardId, boardName, deps) {
8
+ this.boardName = boardName;
9
+ this.deps = deps;
10
+ }
11
+ get documentId() {
12
+ return this._documentId;
13
+ }
14
+ /**
15
+ * Create the run memory document. Optional seedContent prepopulates the
16
+ * Codebase Conventions section.
17
+ */
18
+ async initialize(seedContent) {
19
+ if (this._documentId !== null)
20
+ return; // Already initialized
21
+ const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
22
+ const title = `Pipeline Run Memory — ${this.boardName} — ${date}`;
23
+ const content = buildTemplate(seedContent);
24
+ this._documentId = await this.deps.createDocument(content, title);
25
+ }
26
+ /**
27
+ * Get current document content. Returns '' if not initialized or on error.
28
+ */
29
+ async getContent() {
30
+ if (this._documentId === null) {
31
+ return '';
32
+ }
33
+ try {
34
+ return await this.deps.getDocument(this._documentId);
35
+ }
36
+ catch (err) {
37
+ console.warn(`[run-memory] getContent failed: ${err instanceof Error ? err.message : String(err)}`);
38
+ return '';
39
+ }
40
+ }
41
+ /**
42
+ * Check if document exceeds line threshold (default 500).
43
+ */
44
+ async needsCompaction(threshold = DEFAULT_COMPACTION_THRESHOLD) {
45
+ const content = await this.getContent();
46
+ if (!content)
47
+ return false;
48
+ const lineCount = content.split('\n').length;
49
+ return lineCount > threshold;
50
+ }
51
+ /**
52
+ * Append content under a specific section heading.
53
+ * Agents call this after successful iterations to share discoveries.
54
+ */
55
+ async append(section, content) {
56
+ if (this._documentId === null)
57
+ return;
58
+ this._writeQueue = this._writeQueue.then(async () => {
59
+ try {
60
+ const current = await this.deps.getDocument(this._documentId);
61
+ const sectionHeader = `## ${section}`;
62
+ const idx = current.indexOf(sectionHeader);
63
+ if (idx === -1) {
64
+ await this.deps.updateDocument(this._documentId, current + `\n\n${sectionHeader}\n\n${content}`);
65
+ }
66
+ else {
67
+ const afterHeader = idx + sectionHeader.length;
68
+ const nextSection = current.indexOf('\n## ', afterHeader);
69
+ const insertAt = nextSection === -1 ? current.length : nextSection;
70
+ const updated = current.slice(0, insertAt) + `\n${content}\n` + current.slice(insertAt);
71
+ await this.deps.updateDocument(this._documentId, updated);
72
+ }
73
+ }
74
+ catch (err) {
75
+ // Fire-and-forget — run memory writes must never block the loop
76
+ console.warn(`[run-memory] append failed (section="${section}"): ${err instanceof Error ? err.message : String(err)}`);
77
+ }
78
+ }).catch(() => {
79
+ // Ensure queue is never in rejected state — individual failures are logged inside
80
+ });
81
+ await this._writeQueue;
82
+ }
83
+ /**
84
+ * Replace document content with a compacted version.
85
+ * Called by the orchestrator after a Haiku summarization call.
86
+ */
87
+ async compact(compactedContent) {
88
+ if (this._documentId === null)
89
+ return;
90
+ this._writeQueue = this._writeQueue.then(async () => {
91
+ try {
92
+ await this.deps.updateDocument(this._documentId, compactedContent);
93
+ }
94
+ catch (err) {
95
+ // Fire-and-forget
96
+ console.warn(`[run-memory] compact failed: ${err instanceof Error ? err.message : String(err)}`);
97
+ }
98
+ }).catch(() => {
99
+ // Ensure queue is never in rejected state — individual failures are logged inside
100
+ });
101
+ await this._writeQueue;
102
+ }
103
+ }
104
+ function buildTemplate(seedContent) {
105
+ const conventionsSection = seedContent
106
+ ? `## Codebase Conventions\n\n${seedContent}`
107
+ : `## Codebase Conventions`;
108
+ return [
109
+ conventionsSection,
110
+ `## Discovered Interfaces`,
111
+ `## Failure Patterns`,
112
+ `## QA Rejection History`,
113
+ ].join('\n\n');
114
+ }
115
+ //# sourceMappingURL=run-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-memory.js","sourceRoot":"","sources":["../../src/lib/run-memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface StreamJsonEvent {
3
+ type: string;
4
+ subtype?: string;
5
+ [key: string]: unknown;
6
+ }
7
+ /**
8
+ * Parses newline-delimited JSON from a readable stream (Claude --output-format stream-json).
9
+ * Emits 'event' for each parsed line, 'error' for parse failures.
10
+ */
11
+ export declare class StreamJsonParser extends EventEmitter {
12
+ private buffer;
13
+ private toolCallCount;
14
+ feed(chunk: string): void;
15
+ /** Flush any remaining buffer content (call after stream ends). */
16
+ flush(): void;
17
+ getToolCallCount(): number;
18
+ reset(): void;
19
+ }
20
+ //# sourceMappingURL=stream-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-parser.d.ts","sourceRoot":"","sources":["../../src/lib/stream-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,aAAa,CAAK;IAE1B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAyBzB,mEAAmE;IACnE,KAAK,IAAI,IAAI;IAmBb,gBAAgB,IAAI,MAAM;IAI1B,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,65 @@
1
+ import { EventEmitter } from 'node:events';
2
+ /**
3
+ * Parses newline-delimited JSON from a readable stream (Claude --output-format stream-json).
4
+ * Emits 'event' for each parsed line, 'error' for parse failures.
5
+ */
6
+ export class StreamJsonParser extends EventEmitter {
7
+ buffer = '';
8
+ toolCallCount = 0;
9
+ feed(chunk) {
10
+ this.buffer += chunk;
11
+ const lines = this.buffer.split('\n');
12
+ // Keep the last incomplete line in the buffer
13
+ this.buffer = lines.pop() ?? '';
14
+ for (const line of lines) {
15
+ const trimmed = line.trim();
16
+ if (!trimmed)
17
+ continue;
18
+ try {
19
+ const event = JSON.parse(trimmed);
20
+ // Count tool_use blocks within assistant events only — the assistant event is
21
+ // the authoritative source. Standalone top-level tool_use events are duplicates.
22
+ if (event.type === 'assistant' && Array.isArray(event.content)) {
23
+ for (const block of event.content) {
24
+ if (block.type === 'tool_use')
25
+ this.toolCallCount++;
26
+ }
27
+ }
28
+ this.emit('event', event);
29
+ }
30
+ catch {
31
+ this.emit('error', new Error(`Failed to parse stream-json line: ${trimmed.slice(0, 100)}`));
32
+ }
33
+ }
34
+ }
35
+ /** Flush any remaining buffer content (call after stream ends). */
36
+ flush() {
37
+ const trimmed = this.buffer.trim();
38
+ this.buffer = '';
39
+ if (!trimmed)
40
+ return;
41
+ try {
42
+ const event = JSON.parse(trimmed);
43
+ // Count tool_use blocks within assistant events only — the assistant event is
44
+ // the authoritative source. Standalone top-level tool_use events are duplicates.
45
+ if (event.type === 'assistant' && Array.isArray(event.content)) {
46
+ for (const block of event.content) {
47
+ if (block.type === 'tool_use')
48
+ this.toolCallCount++;
49
+ }
50
+ }
51
+ this.emit('event', event);
52
+ }
53
+ catch {
54
+ // Ignore incomplete final line
55
+ }
56
+ }
57
+ getToolCallCount() {
58
+ return this.toolCallCount;
59
+ }
60
+ reset() {
61
+ this.buffer = '';
62
+ this.toolCallCount = 0;
63
+ }
64
+ }
65
+ //# sourceMappingURL=stream-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-parser.js","sourceRoot":"","sources":["../../src/lib/stream-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ3C;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IACxC,MAAM,GAAG,EAAE,CAAC;IACZ,aAAa,GAAG,CAAC,CAAC;IAE1B,IAAI,CAAC,KAAa;QAChB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACtC,8CAA8C;QAC9C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAEhC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;gBACrD,8EAA8E;gBAC9E,iFAAiF;gBACjF,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC/D,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAmC,EAAE,CAAC;wBAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;4BAAE,IAAI,CAAC,aAAa,EAAE,CAAC;oBACtD,CAAC;gBACH,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,qCAAqC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9F,CAAC;QACH,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,KAAK;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;YACrD,8EAA8E;YAC9E,iFAAiF;YACjF,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/D,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAmC,EAAE,CAAC;oBAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;wBAAE,IAAI,CAAC,aAAa,EAAE,CAAC;gBACtD,CAAC;YACH,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;IACH,CAAC;IAED,gBAAgB;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import type { GateSnapshot } from '@kantban/types';
3
+ export interface StuckPattern {
4
+ status: 'progressing' | 'spinning' | 'regressing' | 'blocked';
5
+ evidence: string;
6
+ confidence: number;
7
+ }
8
+ export interface StuckDetectionConfig {
9
+ enabled: boolean;
10
+ firstCheck?: number;
11
+ interval?: number;
12
+ }
13
+ export declare function shouldCheckStuckDetection(config: StuckDetectionConfig, iteration: number): boolean;
14
+ export interface StuckDetectionInput {
15
+ ticketNumber: number;
16
+ ticketTitle: string;
17
+ columnName: string;
18
+ iteration: number;
19
+ maxIterations: number;
20
+ recentComments: Array<{
21
+ author: string;
22
+ body: string;
23
+ }>;
24
+ }
25
+ export declare function composeStuckDetectionPrompt(input: StuckDetectionInput): string;
26
+ declare const StuckDetectionResponseSchema: z.ZodObject<{
27
+ status: z.ZodEnum<["progressing", "spinning", "blocked"]>;
28
+ confidence: z.ZodNumber;
29
+ evidence: z.ZodString;
30
+ }, "strip", z.ZodTypeAny, {
31
+ status: "progressing" | "spinning" | "blocked";
32
+ confidence: number;
33
+ evidence: string;
34
+ }, {
35
+ status: "progressing" | "spinning" | "blocked";
36
+ confidence: number;
37
+ evidence: string;
38
+ }>;
39
+ export type StuckDetectionResult = z.infer<typeof StuckDetectionResponseSchema>;
40
+ export declare function parseStuckDetectionResponse(raw: string): StuckDetectionResult;
41
+ /**
42
+ * Deterministic trajectory classification from gate snapshot history.
43
+ * Replaces the Haiku-based comment classifier.
44
+ */
45
+ export declare function classifyTrajectory(snapshots: GateSnapshot[]): StuckPattern;
46
+ export {};
47
+ //# sourceMappingURL=stuck-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stuck-detector.d.ts","sourceRoot":"","sources":["../../src/lib/stuck-detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,gBAAgB,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAQlG;AAGD,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAGD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAsB9E;AAED,QAAA,MAAM,4BAA4B;;;;;;;;;;;;EAIhC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAGhF,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CAsB7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CA4C1E"}
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import { parseJsonFromLlmOutput } from './parse-utils.js';
3
+ export function shouldCheckStuckDetection(config, iteration) {
4
+ if (!config.enabled)
5
+ return false;
6
+ const firstCheck = config.firstCheck ?? 3;
7
+ const interval = config.interval ?? 2;
8
+ if (interval <= 0)
9
+ return iteration === firstCheck;
10
+ if (iteration < firstCheck)
11
+ return false;
12
+ if (iteration === firstCheck)
13
+ return true;
14
+ return (iteration - firstCheck) % interval === 0;
15
+ }
16
+ // Legacy function — kept for pipeline.ts compatibility until orchestrator layer is updated
17
+ export function composeStuckDetectionPrompt(input) {
18
+ const commentLines = input.recentComments.length > 0
19
+ ? input.recentComments.map((c) => ` [${c.author}] ${c.body.slice(0, 200)}`).join('\n')
20
+ : ' (no comments)';
21
+ return `You are a pipeline trajectory classifier. Analyze recent agent activity and classify the trajectory.
22
+
23
+ Ticket: #${String(input.ticketNumber)} "${input.ticketTitle}"
24
+ Column: ${input.columnName}
25
+ Iteration: ${String(input.iteration)} of ${String(input.maxIterations)}
26
+
27
+ Recent iteration comments:
28
+ ${commentLines}
29
+
30
+ Classify as one of:
31
+ - "progressing" — each iteration makes distinct forward progress (new code, new tests, new fields set)
32
+ - "spinning" — agent is active but repeating similar actions without advancing (same error, same approach retried, output duplicated)
33
+ - "blocked" — agent cannot make progress due to external dependency, missing information, or fundamental task issue
34
+
35
+ Respond with ONLY a JSON object:
36
+ {"status": "progressing|spinning|blocked", "confidence": 0.0-1.0, "evidence": "one sentence"}`;
37
+ }
38
+ const StuckDetectionResponseSchema = z.object({
39
+ status: z.enum(['progressing', 'spinning', 'blocked']),
40
+ confidence: z.number().min(0).max(1),
41
+ evidence: z.string(),
42
+ });
43
+ // Legacy function — kept for pipeline.ts compatibility until orchestrator layer is updated
44
+ export function parseStuckDetectionResponse(raw) {
45
+ let parsed;
46
+ try {
47
+ parsed = parseJsonFromLlmOutput(raw);
48
+ }
49
+ catch (err) {
50
+ throw new Error(`StuckDetectionResponse: ${err instanceof Error ? err.message : String(err)}`);
51
+ }
52
+ // Clamp confidence before validation
53
+ if (parsed && typeof parsed === 'object' && 'confidence' in parsed) {
54
+ const p = parsed;
55
+ if (typeof p.confidence === 'number') {
56
+ p.confidence = Math.min(1, Math.max(0, p.confidence));
57
+ }
58
+ }
59
+ const result = StuckDetectionResponseSchema.safeParse(parsed);
60
+ if (!result.success) {
61
+ throw new Error(`StuckDetectionResponse: validation failed — ${result.error.message}`);
62
+ }
63
+ return result.data;
64
+ }
65
+ /**
66
+ * Deterministic trajectory classification from gate snapshot history.
67
+ * Replaces the Haiku-based comment classifier.
68
+ */
69
+ export function classifyTrajectory(snapshots) {
70
+ if (snapshots.length === 0) {
71
+ return { status: 'progressing', evidence: 'no data', confidence: 0.5 };
72
+ }
73
+ const recent = snapshots.slice(-3);
74
+ const deltas = recent.map((s) => s.delta_from_previous);
75
+ // Filter out first_check — it's not a real signal
76
+ const meaningful = deltas.filter((d) => d !== 'first_check');
77
+ if (meaningful.length === 0) {
78
+ return { status: 'progressing', evidence: 'only initial checks', confidence: 0.5 };
79
+ }
80
+ // All same = spinning
81
+ if (meaningful.every((d) => d === 'same')) {
82
+ return { status: 'spinning', evidence: 'identical gate results', confidence: 1.0 };
83
+ }
84
+ // Monotonic regression
85
+ if (meaningful.every((d) => d === 'regressed' || d === 'same')) {
86
+ return { status: 'regressing', evidence: 'gate results degrading', confidence: 1.0 };
87
+ }
88
+ // Recent improvement takes precedence over oscillation
89
+ if (meaningful.some((d) => d === 'improved')) {
90
+ const lastMeaningful = meaningful[meaningful.length - 1];
91
+ if (lastMeaningful === 'improved') {
92
+ return { status: 'progressing', evidence: 'gate results improving', confidence: 1.0 };
93
+ }
94
+ }
95
+ // Oscillation: improved AND regressed, but last delta is NOT improved
96
+ if (meaningful.some((d) => d === 'improved') && meaningful.some((d) => d === 'regressed')) {
97
+ return { status: 'spinning', evidence: 'oscillating gate results', confidence: 1.0 };
98
+ }
99
+ // Stale improvement — has improved sometime but no recent progress
100
+ if (meaningful.some((d) => d === 'improved')) {
101
+ return { status: 'spinning', evidence: 'stale improvement — no recent progress', confidence: 0.8 };
102
+ }
103
+ return { status: 'spinning', evidence: 'no improvement detected', confidence: 1.0 };
104
+ }
105
+ //# sourceMappingURL=stuck-detector.js.map