pipeline-worker 0.1.25 → 0.1.27

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 (68) hide show
  1. package/README.md +9 -2
  2. package/dist/agent/claude.js +32 -1
  3. package/dist/agent/claude.js.map +1 -1
  4. package/dist/agent/types.d.ts +18 -0
  5. package/dist/cli.js +30 -4
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config/loader.js +29 -0
  8. package/dist/config/loader.js.map +1 -1
  9. package/dist/forge/github.d.ts +8 -0
  10. package/dist/forge/github.js +40 -0
  11. package/dist/forge/github.js.map +1 -1
  12. package/dist/forge/gitlab.js +24 -0
  13. package/dist/forge/gitlab.js.map +1 -1
  14. package/dist/forge/shared.d.ts +22 -2
  15. package/dist/forge/shared.js +61 -13
  16. package/dist/forge/shared.js.map +1 -1
  17. package/dist/forge/types.d.ts +28 -1
  18. package/dist/git/commit.d.ts +9 -0
  19. package/dist/git/commit.js +11 -0
  20. package/dist/git/commit.js.map +1 -1
  21. package/dist/git/squash.d.ts +8 -0
  22. package/dist/git/squash.js +26 -0
  23. package/dist/git/squash.js.map +1 -0
  24. package/dist/state/runState.d.ts +10 -1
  25. package/dist/state/runState.js +29 -3
  26. package/dist/state/runState.js.map +1 -1
  27. package/dist/types.d.ts +23 -1
  28. package/dist/ui/format.d.ts +8 -0
  29. package/dist/ui/format.js +17 -0
  30. package/dist/ui/format.js.map +1 -0
  31. package/dist/ui/renderer.d.ts +41 -0
  32. package/dist/ui/renderer.js +93 -0
  33. package/dist/ui/renderer.js.map +1 -0
  34. package/dist/ui/runTree.d.ts +106 -0
  35. package/dist/ui/runTree.js +133 -0
  36. package/dist/ui/runTree.js.map +1 -0
  37. package/dist/ui/sessions.js +12 -4
  38. package/dist/ui/sessions.js.map +1 -1
  39. package/dist/ui/steps.d.ts +82 -35
  40. package/dist/ui/steps.js +158 -97
  41. package/dist/ui/steps.js.map +1 -1
  42. package/dist/ui/treeRenderer.d.ts +89 -0
  43. package/dist/ui/treeRenderer.js +271 -0
  44. package/dist/ui/treeRenderer.js.map +1 -0
  45. package/dist/ui/welcome.d.ts +1 -1
  46. package/dist/ui/welcome.js +23 -14
  47. package/dist/ui/welcome.js.map +1 -1
  48. package/dist/workflow/adoptBranch.js +18 -12
  49. package/dist/workflow/adoptBranch.js.map +1 -1
  50. package/dist/workflow/captureIntent.d.ts +7 -2
  51. package/dist/workflow/captureIntent.js +1 -25
  52. package/dist/workflow/captureIntent.js.map +1 -1
  53. package/dist/workflow/openMergeRequest.d.ts +3 -3
  54. package/dist/workflow/openMergeRequest.js +31 -6
  55. package/dist/workflow/openMergeRequest.js.map +1 -1
  56. package/dist/workflow/orchestrate.d.ts +1 -1
  57. package/dist/workflow/orchestrate.js +88 -40
  58. package/dist/workflow/orchestrate.js.map +1 -1
  59. package/dist/workflow/runPlan.d.ts +18 -0
  60. package/dist/workflow/runPlan.js +53 -0
  61. package/dist/workflow/runPlan.js.map +1 -0
  62. package/dist/workflow/syncTargetBranch.d.ts +37 -0
  63. package/dist/workflow/syncTargetBranch.js +83 -0
  64. package/dist/workflow/syncTargetBranch.js.map +1 -0
  65. package/dist/workflow/watchPipeline.d.ts +48 -5
  66. package/dist/workflow/watchPipeline.js +199 -74
  67. package/dist/workflow/watchPipeline.js.map +1 -1
  68. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  * implementation maps pull requests / workflow runs / jobs onto the same
4
4
  * shapes so the workflow and MCP server never branch on the forge.
5
5
  */
6
- import type { MergeRequest, Pipeline, PipelineJob } from '../types.js';
6
+ import type { MergeMethod, MergeRequest, Pipeline, PipelineJob } from '../types.js';
7
7
  export interface CreateMrArgs {
8
8
  sourceBranch: string;
9
9
  targetBranch: string;
@@ -33,4 +33,31 @@ export interface ForgeClient {
33
33
  * resolution on every push, before the forge has even checked.
34
34
  */
35
35
  hasMergeConflicts(mrIid: number): Promise<boolean>;
36
+ /**
37
+ * Asks the forge to merge this MR/PR automatically once CI (and any
38
+ * required approvals) allow it — GitHub's `enablePullRequestAutoMerge`
39
+ * GraphQL mutation, GitLab's `merge_when_pipeline_succeeds`. Throws on
40
+ * rejection (e.g. the forge's auto-merge feature isn't enabled for this
41
+ * repo, or approvals are still pending); the caller treats this as
42
+ * best-effort and must not let a rejection fail the run.
43
+ */
44
+ enableAutoMerge(mrIid: number, mergeMethod: MergeMethod): Promise<void>;
45
+ /**
46
+ * True once the forge reports the MR/PR as actually merged — GitHub's
47
+ * `merged` flag on the pull request, GitLab's `state: "merged"`. Used
48
+ * after enableAutoMerge to detect that the auto-merge really landed, so
49
+ * the local target branch can be fast-forwarded to include it (see
50
+ * workflow/syncTargetBranch.ts). A still-open or closed-unmerged MR/PR
51
+ * reads as false.
52
+ */
53
+ isMrMerged(mrIid: number): Promise<boolean>;
54
+ /**
55
+ * The project's custom CI/CD configuration file path, if one is set
56
+ * (GitLab's "CI/CD configuration file" project setting) — used by
57
+ * watchPipeline.ts's hasCiConfig to recognize CI as configured even when
58
+ * it isn't at the conventional `.gitlab-ci.yml` path. GitHub has no
59
+ * equivalent concept (workflows are always under `.github/workflows`) and
60
+ * always resolves undefined, with no network call.
61
+ */
62
+ getCiConfigPath(): Promise<string | undefined>;
36
63
  }
@@ -2,6 +2,15 @@
2
2
  export declare function stageAll(worktreePath: string): Promise<void>;
3
3
  export declare function commit(worktreePath: string, message: string): Promise<void>;
4
4
  export declare function push(worktreePath: string, remote: string, branch: string): Promise<void>;
5
+ /**
6
+ * Force-pushes with `--force-with-lease`, never plain `--force`: the lease
7
+ * refuses the push if `remote`'s branch has moved since we last observed it
8
+ * (a stray commit from elsewhere, a human pushing a fixup), failing loudly
9
+ * instead of silently discarding that work. Used only by the opt-in
10
+ * squash-on-merge step (git/squash.ts), the one operation in this tool that
11
+ * rewrites already-pushed history.
12
+ */
13
+ export declare function forcePushWithLease(worktreePath: string, remote: string, branch: string): Promise<void>;
5
14
  /** True when the worktree has staged, unstaged, or untracked changes. */
6
15
  export declare function hasChanges(worktreePath: string): Promise<boolean>;
7
16
  /** Files git still reports as unmerged — conflict markers from a `git apply --3way` or `git merge` not yet resolved and staged. */
@@ -13,6 +13,17 @@ export async function commit(worktreePath, message) {
13
13
  export async function push(worktreePath, remote, branch) {
14
14
  await execFileAsync('git', ['push', '--set-upstream', remote, branch], { cwd: worktreePath });
15
15
  }
16
+ /**
17
+ * Force-pushes with `--force-with-lease`, never plain `--force`: the lease
18
+ * refuses the push if `remote`'s branch has moved since we last observed it
19
+ * (a stray commit from elsewhere, a human pushing a fixup), failing loudly
20
+ * instead of silently discarding that work. Used only by the opt-in
21
+ * squash-on-merge step (git/squash.ts), the one operation in this tool that
22
+ * rewrites already-pushed history.
23
+ */
24
+ export async function forcePushWithLease(worktreePath, remote, branch) {
25
+ await execFileAsync('git', ['push', '--force-with-lease', remote, branch], { cwd: worktreePath });
26
+ }
16
27
  /** True when the worktree has staged, unstaged, or untracked changes. */
17
28
  export async function hasChanges(worktreePath) {
18
29
  const { stdout } = await execFileAsync('git', ['status', '--porcelain'], { cwd: worktreePath });
@@ -1 +1 @@
1
- {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../src/git/commit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commit.js","sourceRoot":"","sources":["../../src/git/commit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Collapses every commit on `worktreePath`'s current branch since it
3
+ * diverged from `origin/targetBranch` into a single commit titled
4
+ * `commitMessage` — the mechanics behind the opt-in `squashOnMerge` config
5
+ * flag (see orchestrate.ts's finalizeRun). Preserves tree content exactly;
6
+ * `git reset --soft` only rewrites history, never the working tree.
7
+ */
8
+ export declare function squashCommitsSinceMergeBase(worktreePath: string, targetBranch: string, commitMessage: string): Promise<void>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Collapses every commit on `worktreePath`'s current branch since it
3
+ * diverged from `origin/targetBranch` into a single commit titled
4
+ * `commitMessage` — the mechanics behind the opt-in `squashOnMerge` config
5
+ * flag (see orchestrate.ts's finalizeRun). Preserves tree content exactly;
6
+ * `git reset --soft` only rewrites history, never the working tree.
7
+ */
8
+ import { execFile } from 'node:child_process';
9
+ import { promisify } from 'node:util';
10
+ import { mergeBase, commit } from './commit.js';
11
+ const execFileAsync = promisify(execFile);
12
+ async function currentHeadSha(worktreePath) {
13
+ const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: worktreePath });
14
+ return stdout.trim();
15
+ }
16
+ export async function squashCommitsSinceMergeBase(worktreePath, targetBranch, commitMessage) {
17
+ await execFileAsync('git', ['fetch', 'origin', targetBranch], { cwd: worktreePath });
18
+ const base = await mergeBase(worktreePath, `origin/${targetBranch}`);
19
+ const head = await currentHeadSha(worktreePath);
20
+ if (head === base) {
21
+ throw new Error(`nothing to squash — HEAD is already at the merge-base with origin/${targetBranch}`);
22
+ }
23
+ await execFileAsync('git', ['reset', '--soft', base], { cwd: worktreePath });
24
+ await commit(worktreePath, commitMessage);
25
+ }
26
+ //# sourceMappingURL=squash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"squash.js","sourceRoot":"","sources":["../../src/git/squash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,KAAK,UAAU,cAAc,CAAC,YAAoB;IAChD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5F,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,YAAoB,EAAE,YAAoB,EAAE,aAAqB;IACjH,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IACrF,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,UAAU,YAAY,EAAE,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,qEAAqE,YAAY,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;IAC7E,MAAM,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;AAC5C,CAAC"}
@@ -5,6 +5,7 @@
5
5
  * state (the forge client's findExistingMr is the real idempotency guard).
6
6
  */
7
7
  import type { RunState } from '../types.js';
8
+ import type { AgentUsage } from '../agent/types.js';
8
9
  export declare function saveRunState(repoRoot: string, state: RunState): void;
9
10
  export declare function loadRunState(repoRoot: string, branch: string): RunState | undefined;
10
11
  /**
@@ -13,7 +14,15 @@ export declare function loadRunState(repoRoot: string, branch: string): RunState
13
14
  * `state` in place, matching how saveRunState is already called throughout
14
15
  * orchestrate.ts/watchPipeline.ts.
15
16
  */
16
- export declare function recordEvent(repoRoot: string, state: RunState, message: string, level?: 'info' | 'error'): void;
17
+ export declare function recordEvent(repoRoot: string, state: RunState, message: string, level?: 'info' | 'error', tokens?: number): void;
18
+ /**
19
+ * Narrates one agent turn into the run's history, carrying its token spend
20
+ * when the adapter reported any (see AgentUsage). A no-op when there is no
21
+ * usage to record: an entry saying "spent unknown tokens" would add noise to
22
+ * `sessions` without informing anyone — the turn itself is already narrated
23
+ * by its surrounding step events.
24
+ */
25
+ export declare function recordAgentTokens(repoRoot: string, state: RunState, purpose: string, usage: AgentUsage | undefined): void;
17
26
  export interface RunSession {
18
27
  branch: string;
19
28
  state: RunState;
@@ -35,12 +35,20 @@ export function saveRunState(repoRoot, state) {
35
35
  console.error(`Warning: failed to persist run state for branch ${state.branch}: ${message}`);
36
36
  }
37
37
  }
38
+ function migrateRunState(raw) {
39
+ if (raw.ciFixAttempt === undefined || raw.conflictAttempt === undefined) {
40
+ const legacy = raw.attempt ?? 0;
41
+ raw.ciFixAttempt ??= legacy;
42
+ raw.conflictAttempt ??= legacy;
43
+ }
44
+ return raw;
45
+ }
38
46
  export function loadRunState(repoRoot, branch) {
39
47
  const path = statePath(repoRoot, branch);
40
48
  if (!existsSync(path))
41
49
  return undefined;
42
50
  try {
43
- return JSON.parse(readFileSync(path, 'utf-8'));
51
+ return migrateRunState(JSON.parse(readFileSync(path, 'utf-8')));
44
52
  }
45
53
  catch (error) {
46
54
  const message = error instanceof Error ? error.message : String(error);
@@ -54,14 +62,32 @@ export function loadRunState(repoRoot, branch) {
54
62
  * `state` in place, matching how saveRunState is already called throughout
55
63
  * orchestrate.ts/watchPipeline.ts.
56
64
  */
57
- export function recordEvent(repoRoot, state, message, level = 'info') {
65
+ export function recordEvent(repoRoot, state, message, level = 'info', tokens) {
58
66
  const now = new Date().toISOString();
59
67
  state.startedAt ??= now;
60
68
  state.updatedAt = now;
61
- const history = [...(state.history ?? []), { at: now, phase: state.phase, level, message }];
69
+ const entry = { at: now, phase: state.phase, level, message };
70
+ if (tokens !== undefined) {
71
+ entry.tokens = tokens;
72
+ state.totalTokens = (state.totalTokens ?? 0) + tokens;
73
+ }
74
+ const history = [...(state.history ?? []), entry];
62
75
  state.history = history.length > MAX_HISTORY_ENTRIES ? history.slice(history.length - MAX_HISTORY_ENTRIES) : history;
63
76
  saveRunState(repoRoot, state);
64
77
  }
78
+ /**
79
+ * Narrates one agent turn into the run's history, carrying its token spend
80
+ * when the adapter reported any (see AgentUsage). A no-op when there is no
81
+ * usage to record: an entry saying "spent unknown tokens" would add noise to
82
+ * `sessions` without informing anyone — the turn itself is already narrated
83
+ * by its surrounding step events.
84
+ */
85
+ export function recordAgentTokens(repoRoot, state, purpose, usage) {
86
+ const tokens = usage?.totalTokens;
87
+ if (tokens === undefined)
88
+ return;
89
+ recordEvent(repoRoot, state, `Agent turn (${purpose})`, 'info', tokens);
90
+ }
65
91
  /**
66
92
  * Lists every persisted run in this repo (one per state file), most
67
93
  * recently updated first. A corrupt/partial state file is skipped rather
@@ -1 +1 @@
1
- {"version":3,"file":"runState.js","sourceRoot":"","sources":["../../src/state/runState.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runState.js","sourceRoot":"","sources":["../../src/state/runState.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** Single source of truth for pipeline-worker's cross-module data shapes. */
2
2
  export type AgentName = 'claude' | 'copilot' | 'pi';
3
3
  export type ForgeName = 'gitlab' | 'github';
4
+ export type MergeMethod = 'merge' | 'squash' | 'rebase';
4
5
  export interface PipelineWorkerConfig {
5
6
  agent: AgentName;
6
7
  forge: ForgeName;
@@ -31,6 +32,20 @@ export interface PipelineWorkerConfig {
31
32
  runLintAndTest: boolean;
32
33
  /** Once checks pass, add a bullet for this change under CHANGELOG.md's [Unreleased] section (creating the file if absent) and include it in the commit. Off by default: not every consuming repo keeps a changelog. */
33
34
  updateChangelog: boolean;
35
+ /** Once the MR/PR opens, ask the forge to merge it automatically as soon as CI (and any required approvals) allow — best-effort, never fails the run if the forge rejects it. Off by default: merging is a policy decision, not something to turn on silently. */
36
+ autoMergeOnGreen: boolean;
37
+ /** Merge strategy passed to enableAutoMerge. Defaults to 'squash'. GitLab has no per-request rebase option — 'rebase' there silently falls back to the project's own default merge method. */
38
+ mergeMethod: MergeMethod;
39
+ /**
40
+ * Once CI is green, collapse every commit this run made on the branch
41
+ * since it diverged from targetBranch into one (titled from the captured
42
+ * intent) and force-push — keeps history clean regardless of the target
43
+ * repo's merge-strategy setting. Off by default: unlike every other write
44
+ * this tool makes, this rewrites already-pushed history (force-push), a
45
+ * materially different risk. Only reliable with autoMergeOnGreen off — the
46
+ * forge may already have merged (and deleted) the branch before this runs.
47
+ */
48
+ squashOnMerge: boolean;
34
49
  }
35
50
  export type RunPhase = 'diff' | 'intent' | 'checks' | 'mr' | 'watch' | 'done' | 'escalated';
36
51
  /** One entry in RunState.history — a timestamped narration of what happened during the run, for `pipeline-worker sessions`. */
@@ -39,6 +54,8 @@ export interface RunHistoryEntry {
39
54
  phase: RunPhase;
40
55
  level: 'info' | 'error';
41
56
  message: string;
57
+ /** Agent tokens spent by the turn this entry narrates. Absent when the adapter reports no usage (pi/copilot) or the entry isn't an agent turn. */
58
+ tokens?: number;
42
59
  }
43
60
  export interface RunState {
44
61
  branch: string;
@@ -46,8 +63,13 @@ export interface RunState {
46
63
  worktreePath: string;
47
64
  mrIid?: number;
48
65
  pipelineId?: number;
49
- attempt: number;
66
+ /** Automated-fix attempts spent on a real CI failure (watchPipeline.ts's runCiFixAttempt); bounded by config.maxFixAttempts independently of conflictAttempt. */
67
+ ciFixAttempt: number;
68
+ /** Automated-fix attempts spent resolving merge conflicts with the target branch (watchPipeline.ts's tryResolveConflicts); bounded by config.maxFixAttempts independently of ciFixAttempt, so a long-lived PR needing several trivial rebases can't exhaust the budget meant for real bug-fixing. */
69
+ conflictAttempt: number;
50
70
  phase: RunPhase;
71
+ /** Running sum of every history entry's `tokens` — the run's total agent spend, as far as the adapter reports it. Absent on state files written before this field existed and on runs whose adapter reports no usage. */
72
+ totalTokens?: number;
51
73
  /** ISO 8601 timestamp of when this run was first created. Absent on state files written before this field existed. */
52
74
  startedAt?: string;
53
75
  /** ISO 8601 timestamp of the most recent state write. Absent on state files written before this field existed. */
@@ -0,0 +1,8 @@
1
+ /** Shared human-readable formatting helpers for token/duration figures. */
2
+ /**
3
+ * Renders a token count the way the run header and session views show it:
4
+ * `949 tok`, `1.9k tok`, `41.2k tok`, `1.2M tok`. One decimal place above
5
+ * 1000, with a trailing `.0` trimmed so round figures read as `2k tok`, not
6
+ * `2.0k tok`.
7
+ */
8
+ export declare function formatTokens(tokens: number): string;
@@ -0,0 +1,17 @@
1
+ /** Shared human-readable formatting helpers for token/duration figures. */
2
+ /**
3
+ * Renders a token count the way the run header and session views show it:
4
+ * `949 tok`, `1.9k tok`, `41.2k tok`, `1.2M tok`. One decimal place above
5
+ * 1000, with a trailing `.0` trimmed so round figures read as `2k tok`, not
6
+ * `2.0k tok`.
7
+ */
8
+ export function formatTokens(tokens) {
9
+ if (!Number.isFinite(tokens) || tokens < 0)
10
+ return '? tok';
11
+ if (tokens < 1000)
12
+ return `${Math.round(tokens)} tok`;
13
+ const scaled = tokens < 1_000_000 ? { value: tokens / 1000, suffix: 'k' } : { value: tokens / 1_000_000, suffix: 'M' };
14
+ const text = scaled.value.toFixed(1).replace(/\.0$/, '');
15
+ return `${text}${scaled.suffix} tok`;
16
+ }
17
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/ui/format.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3D,IAAI,MAAM,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IACvH,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,MAAM,CAAC;AACvC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Rendering strategies for the run tree (ui/runTree.ts). Two implementations:
3
+ *
4
+ * - LineRenderer (here): append-only scrolled lines — used when stdout is not
5
+ * a TTY (CI logs, piped output) or when PIPELINE_WORKER_PLAIN_OUTPUT=true.
6
+ * Zero cursor movement; colors are applied via node:util's styleText, which
7
+ * already disables itself on non-color streams.
8
+ * - TreeRenderer (ui/treeRenderer.ts): the live TTY dashboard with a pinned
9
+ * bottom region.
10
+ *
11
+ * Renderers receive fine-grained TreeEvents from the facade (ui/steps.ts) and
12
+ * may ignore the granularity (TreeRenderer just repaints). log() is the one
13
+ * channel for freeform text — notes, agent output, warnings — so a renderer
14
+ * that owns the screen can route it above its pinned region.
15
+ */
16
+ import type { RunStatus, RunTree, StepNode, TreeEvent } from './runTree.js';
17
+ export interface Renderer {
18
+ onEvent(event: TreeEvent, tree: RunTree): void;
19
+ /** Freeform text outside the tree: notes, agent output, warnings. */
20
+ log(text: string): void;
21
+ /** Final paint for the run's terminal status; must leave the terminal usable. */
22
+ stop(status: RunStatus, detail: string | undefined, tree: RunTree): void;
23
+ }
24
+ export declare function formatElapsed(ms: number): string;
25
+ /** The parenthesized tail of a finished step: '(3.2s · 1.9k tok)', or '' when neither figure is known. */
26
+ export declare function formatStepFigures(node: StepNode): string;
27
+ /** 'attempt N/M' when both sides are known, else ''. */
28
+ export declare function formatAttempt(node: StepNode): string;
29
+ /**
30
+ * Append-only narration, one block per step: a bold start line with the
31
+ * detail beneath it, phase changes as indented arrows, and a colored
32
+ * ✓/✗/– settle line with duration and token figures. Mirrors the shape of
33
+ * the old numbered-stage output so non-TTY logs stay diffable and greppable.
34
+ */
35
+ export declare class LineRenderer implements Renderer {
36
+ private readonly out;
37
+ constructor(out?: (line: string) => void);
38
+ onEvent(event: TreeEvent): void;
39
+ log(text: string): void;
40
+ stop(status: RunStatus, detail: string | undefined, tree: RunTree): void;
41
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Rendering strategies for the run tree (ui/runTree.ts). Two implementations:
3
+ *
4
+ * - LineRenderer (here): append-only scrolled lines — used when stdout is not
5
+ * a TTY (CI logs, piped output) or when PIPELINE_WORKER_PLAIN_OUTPUT=true.
6
+ * Zero cursor movement; colors are applied via node:util's styleText, which
7
+ * already disables itself on non-color streams.
8
+ * - TreeRenderer (ui/treeRenderer.ts): the live TTY dashboard with a pinned
9
+ * bottom region.
10
+ *
11
+ * Renderers receive fine-grained TreeEvents from the facade (ui/steps.ts) and
12
+ * may ignore the granularity (TreeRenderer just repaints). log() is the one
13
+ * channel for freeform text — notes, agent output, warnings — so a renderer
14
+ * that owns the screen can route it above its pinned region.
15
+ */
16
+ import { styleText } from 'node:util';
17
+ import { formatTokens } from './format.js';
18
+ export function formatElapsed(ms) {
19
+ return `${(ms / 1000).toFixed(1)}s`;
20
+ }
21
+ /** The parenthesized tail of a finished step: '(3.2s · 1.9k tok)', or '' when neither figure is known. */
22
+ export function formatStepFigures(node) {
23
+ const parts = [];
24
+ if (node.durationMs !== undefined)
25
+ parts.push(formatElapsed(node.durationMs));
26
+ if (node.tokens !== undefined)
27
+ parts.push(formatTokens(node.tokens));
28
+ return parts.length > 0 ? ` (${parts.join(' · ')})` : '';
29
+ }
30
+ /** 'attempt N/M' when both sides are known, else ''. */
31
+ export function formatAttempt(node) {
32
+ return node.attempt !== undefined && node.maxAttempts !== undefined ? `attempt ${node.attempt}/${node.maxAttempts}` : '';
33
+ }
34
+ const FINISH_GLYPH = { done: '✓', failed: '✗', skipped: '–' };
35
+ const FINISH_COLOR = { done: 'green', failed: 'red', skipped: 'dim' };
36
+ const FINAL_LINE = {
37
+ done: '🎉 Done',
38
+ failed: '✗ Run failed',
39
+ escalated: '🚨 Stopped for human review',
40
+ interrupted: '⏹ Interrupted',
41
+ };
42
+ /**
43
+ * Append-only narration, one block per step: a bold start line with the
44
+ * detail beneath it, phase changes as indented arrows, and a colored
45
+ * ✓/✗/– settle line with duration and token figures. Mirrors the shape of
46
+ * the old numbered-stage output so non-TTY logs stay diffable and greppable.
47
+ */
48
+ export class LineRenderer {
49
+ out;
50
+ // The default defers the console.log lookup to call time (not construction),
51
+ // so a test — or the TreeRenderer's console interception — that swaps
52
+ // console.log after this renderer exists still receives the output.
53
+ constructor(out = (line) => console.log(line)) {
54
+ this.out = out;
55
+ }
56
+ onEvent(event) {
57
+ if (event.kind === 'start') {
58
+ const attempt = formatAttempt(event.node);
59
+ this.out('');
60
+ this.out(styleText(['bold', 'cyan'], `▶ ${event.node.label}${attempt ? ` — ${attempt}` : ''}`));
61
+ if (event.node.detail)
62
+ this.out(styleText('dim', ` ${event.node.detail}`));
63
+ return;
64
+ }
65
+ if (event.kind === 'update' && event.node.status === 'running' && event.node.detail) {
66
+ this.out(styleText('dim', ` → ${event.node.detail}`));
67
+ return;
68
+ }
69
+ if (event.kind === 'finish') {
70
+ const status = event.node.status;
71
+ const glyph = FINISH_GLYPH[status] ?? '?';
72
+ const skipTail = status === 'skipped' ? ' (skipped)' : '';
73
+ const detail = event.node.detail ? ` — ${event.node.detail}` : '';
74
+ this.out(styleText(FINISH_COLOR[status] ?? 'dim', `${glyph} ${event.node.label}${skipTail}${detail}${formatStepFigures(event.node)}`));
75
+ }
76
+ // 'add', 'tokens', and 'header' need no line of their own: tokens show on
77
+ // the finish line, and header changes only matter to the live dashboard.
78
+ }
79
+ log(text) {
80
+ this.out(text);
81
+ }
82
+ stop(status, detail, tree) {
83
+ if (status === 'running')
84
+ return;
85
+ const total = tree.totalTokens();
86
+ const tokensPart = total > 0 ? ` (${formatTokens(total)})` : '';
87
+ this.out('');
88
+ this.out(styleText('bold', `${FINAL_LINE[status]}${tokensPart}`));
89
+ if (detail)
90
+ this.out(styleText('dim', ` ${detail}`));
91
+ }
92
+ }
93
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/ui/renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The run's step tree: a pure in-memory data model of every workflow step,
3
+ * its status, timing, attempt counters, and best-effort token spend. This is
4
+ * what the renderers (ui/renderer.ts, ui/treeRenderer.ts) draw from — it
5
+ * contains no ANSI, no console access, and never throws: an unknown step id
6
+ * is auto-added as a top-level node rather than crashing the workflow (the
7
+ * UI must never kill a run — see CLAUDE.md's never-throw contracts).
8
+ *
9
+ * Step identity is a string id ('capture', 'ci-watch', 'ci-watch/fix-2').
10
+ * Ids replaced the old hard-coded stage numbers ([7/14]) because the watch
11
+ * loop grows children dynamically — fix attempts, rebases — and stable ids
12
+ * don't need renumbering when a step is inserted.
13
+ */
14
+ export type StepStatus = 'pending' | 'running' | 'done' | 'failed' | 'skipped';
15
+ /** The whole run's terminal status, shown in the header line. */
16
+ export type RunStatus = 'running' | 'done' | 'failed' | 'escalated' | 'interrupted';
17
+ export interface StepNode {
18
+ /** Unique within the tree; children conventionally use 'parent/child' ids. */
19
+ id: string;
20
+ /** Short name in the left column: 'capture', 'ci-watch', 'fix 2'. */
21
+ label: string;
22
+ /** Right-hand description; mutable while the step runs (phase updates). */
23
+ detail: string;
24
+ status: StepStatus;
25
+ /** Epoch ms when the step entered 'running'. */
26
+ startedAt?: number;
27
+ /** Set when the step finishes ('done'/'failed'). */
28
+ durationMs?: number;
29
+ /** Best-effort agent tokens attributed to this step; absent means unknown, never zero. */
30
+ tokens?: number;
31
+ /** Rendered as 'attempt N/M' when both are present. */
32
+ attempt?: number;
33
+ maxAttempts?: number;
34
+ children: StepNode[];
35
+ }
36
+ /** What a skeleton declares per step up front — everything else starts pending/empty. */
37
+ export interface StepSeed {
38
+ id: string;
39
+ label: string;
40
+ detail: string;
41
+ }
42
+ export interface RunHeader {
43
+ /** The run's name: initially generic, updated once intent names the branch. */
44
+ title: string;
45
+ /** Short worktree identifier (last hex chars of the temp-branch uuid). */
46
+ worktreeShortId?: string;
47
+ status: RunStatus;
48
+ }
49
+ export type TreeEvent = {
50
+ kind: 'add';
51
+ node: StepNode;
52
+ } | {
53
+ kind: 'start';
54
+ node: StepNode;
55
+ } | {
56
+ kind: 'finish';
57
+ node: StepNode;
58
+ } | {
59
+ kind: 'update';
60
+ node: StepNode;
61
+ } | {
62
+ kind: 'tokens';
63
+ node: StepNode;
64
+ } | {
65
+ kind: 'header';
66
+ };
67
+ /** One renderable row of the flattened tree: the node, its depth, and whether it is the last sibling at each ancestor level (for branch glyphs). */
68
+ export interface TreeRow {
69
+ node: StepNode;
70
+ depth: number;
71
+ /** isLast[d] — whether the chain through this row is the final sibling at depth d. Drives '├─' vs '└─' and '│' vs ' ' continuation. */
72
+ isLast: boolean[];
73
+ }
74
+ export declare class RunTree {
75
+ private readonly onChange;
76
+ readonly roots: StepNode[];
77
+ readonly header: RunHeader;
78
+ private readonly index;
79
+ /** Tokens spent before this tree existed (a resumed run's persisted total) — folded into totalTokens(). */
80
+ private seededTokens;
81
+ constructor(skeleton: StepSeed[], header: {
82
+ title: string;
83
+ worktreeShortId?: string;
84
+ }, onChange: (event: TreeEvent) => void);
85
+ private insert;
86
+ get(id: string): StepNode | undefined;
87
+ /**
88
+ * The never-throw guarantee: any operation on an id nobody declared falls
89
+ * back to materializing that id as a fresh top-level node, so the workflow
90
+ * keeps narrating instead of dying on a UI bookkeeping mismatch.
91
+ */
92
+ private getOrAdd;
93
+ /** Adds a child under parentId (or top-level when parentId is undefined). Adding an id that already exists is a no-op returning the existing node. */
94
+ add(parentId: string | undefined, seed: StepSeed, extras?: Partial<StepNode>): StepNode;
95
+ start(id: string, patch?: Partial<Pick<StepNode, 'detail' | 'attempt' | 'maxAttempts'>>): StepNode;
96
+ finish(id: string, status: 'done' | 'failed' | 'skipped', patch?: Partial<Pick<StepNode, 'detail'>>): StepNode;
97
+ update(id: string, patch: Partial<Pick<StepNode, 'detail' | 'attempt' | 'maxAttempts'>>): StepNode;
98
+ addTokens(id: string, tokens: number): void;
99
+ /** Registers tokens spent before this tree existed (a resumed run's persisted total). */
100
+ seedTokens(tokens: number): void;
101
+ setHeader(patch: Partial<RunHeader>): void;
102
+ /** The run's total spend: every node's tokens plus any seeded pre-resume total. */
103
+ totalTokens(): number;
104
+ /** Depth-first rows for rendering, with last-sibling flags per ancestor level for branch glyphs. */
105
+ flatten(): TreeRow[];
106
+ }