@defrex/autobuild 0.8.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.
- package/README.md +1 -1
- package/SPEC.md +323 -26
- package/bin/ab-build-runner.ts +8 -2
- package/docs/architecture.md +166 -14
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +175 -28
- package/docs/hosted-dispatcher.md +69 -13
- package/docs/operator-api.md +2 -2
- package/docs/remote-store-protocol.md +204 -16
- package/docs/remote-ticket-protocol.md +1 -1
- package/docs/setup.md +12 -9
- package/docs/website.md +14 -0
- package/package.json +45 -23
- package/packages/core/src/cli/artifact.ts +130 -6
- package/packages/core/src/cli/binary.ts +9 -1
- package/packages/core/src/cli/build-control.ts +110 -13
- package/packages/core/src/cli/context.ts +46 -0
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +141 -153
- package/packages/core/src/cli/dashboard/poll.ts +76 -12
- package/packages/core/src/cli/dashboard/render.ts +34 -5
- package/packages/core/src/cli/dispatch-frontend.ts +54 -5
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +392 -75
- package/packages/core/src/cli/harvest.ts +122 -8
- package/packages/core/src/cli/help.ts +62 -9
- package/packages/core/src/cli/init-readiness-remote.ts +110 -0
- package/packages/core/src/cli/init-readiness-shared.ts +72 -0
- package/packages/core/src/cli/init-validation.ts +266 -222
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/main.ts +70 -11
- package/packages/core/src/cli/mcp.ts +23 -1
- package/packages/core/src/cli/plugin.ts +7 -4
- package/packages/core/src/cli/remote-poll.ts +277 -0
- package/packages/core/src/cli/repo-state.ts +22 -0
- package/packages/core/src/cli/repository-status.ts +39 -1
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +150 -28
- package/packages/core/src/cli/terminals.ts +2 -1
- package/packages/core/src/cli/ticket-assets.ts +247 -0
- package/packages/core/src/cli/ticket.ts +453 -8
- package/packages/core/src/cli/wait.ts +157 -41
- package/packages/core/src/cli/watch.ts +138 -168
- package/packages/core/src/config/live.ts +23 -0
- package/packages/core/src/config/pipeline-source.ts +388 -0
- package/packages/core/src/config/schema.ts +251 -19
- package/packages/core/src/events/catalog.ts +1 -0
- package/packages/core/src/events/globs.ts +47 -0
- package/packages/core/src/events/payloads.ts +52 -2
- package/packages/core/src/events/repository.ts +52 -1
- package/packages/core/src/events/sessions.ts +69 -10
- package/packages/core/src/events/workspace-remote.ts +12 -0
- package/packages/core/src/integration/harness.ts +115 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +157 -0
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/phases.ts +16 -2
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +254 -73
- package/packages/core/src/operator/annotations.ts +6 -0
- package/packages/core/src/operator/control.ts +19 -5
- package/packages/core/src/operator/index.ts +71 -6
- package/packages/core/src/operator/query.ts +253 -24
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/operator/registry.ts +55 -10
- package/packages/core/src/operator/requests.ts +9 -6
- package/packages/core/src/operator/sandbox-host.ts +140 -0
- package/packages/core/src/operator/sandbox.ts +247 -14
- package/packages/core/src/operator/ticket-source-backend.ts +47 -0
- package/packages/core/src/orchestrator/approvals.ts +115 -0
- package/packages/core/src/orchestrator/conversation.ts +167 -0
- package/packages/core/src/orchestrator/model.ts +86 -0
- package/packages/core/src/orchestrator/prompt.ts +61 -0
- package/packages/core/src/orchestrator/tools.ts +117 -0
- package/packages/core/src/orchestrator/turn-runner.ts +457 -0
- package/packages/core/src/plugin-sdk/index.ts +51 -1
- package/packages/core/src/plugins/load.ts +135 -7
- package/packages/core/src/plugins/manifest.ts +183 -10
- package/packages/core/src/plugins/registry.ts +73 -4
- package/packages/core/src/ports/forge/create.ts +1 -1
- package/packages/core/src/ports/forge/fake.ts +31 -0
- package/packages/core/src/ports/forge/github-transport.ts +13 -2
- package/packages/core/src/ports/forge/github.ts +13 -2
- package/packages/core/src/ports/forge/local-git.ts +31 -1
- package/packages/core/src/ports/runner/claude.ts +2 -2
- package/packages/core/src/ports/runner/codex.ts +6 -2
- package/packages/core/src/ports/runner/fake.ts +7 -1
- package/packages/core/src/ports/runner/pi.ts +8 -6
- package/packages/core/src/ports/runner/provider-error.ts +13 -3
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +49 -3
- package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
- package/packages/core/src/ports/workspace/create.ts +74 -39
- package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
- package/packages/core/src/ports/workspace/fake.ts +169 -4
- package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
- package/packages/core/src/ports/workspace/github-origin.ts +36 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
- package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
- package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
- package/packages/core/src/processes/build-child.ts +17 -2
- package/packages/core/src/processes/build-execution-state.ts +68 -15
- package/packages/core/src/processes/build-runner.ts +238 -93
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +501 -201
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-child.ts +5 -0
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/harvest-runner.ts +66 -19
- package/packages/core/src/processes/harvest.ts +196 -24
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +360 -0
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +36 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +41 -0
- package/packages/core/src/store/build-scope.ts +90 -0
- package/packages/core/src/store/contract.ts +1205 -0
- package/packages/core/src/store/digest.ts +102 -0
- package/packages/core/src/store/local/schema.ts +46 -2
- package/packages/core/src/store/local/store.ts +445 -4
- package/packages/core/src/store/memory.ts +246 -0
- package/packages/core/src/store/new-build.ts +17 -0
- package/packages/core/src/store/phase-session.ts +94 -0
- package/packages/core/src/store/remote/client.ts +310 -2
- package/packages/core/src/store/remote/index.ts +14 -2
- package/packages/core/src/store/remote/protocol.ts +115 -12
- package/packages/core/src/store/remote/version.ts +6 -2
- package/packages/core/src/store/repo-state-events.ts +205 -0
- package/packages/core/src/store/retention.ts +7 -0
- package/packages/core/src/store/session-handle.ts +94 -0
- package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/streams/session-writer.ts +42 -2
- package/packages/core/src/store/streams/wait.ts +4 -5
- package/packages/core/src/store/ticket-assets.ts +458 -0
- package/packages/core/src/store/types.ts +160 -1
- package/packages/core/src/testing/index.ts +59 -0
- package/skills/code-review/SKILL.md +5 -1
- package/skills/guide/SKILL.md +152 -40
- package/skills/guide/references/operator-api.md +2 -2
- package/skills/guide/references/plugin-authoring.md +17 -0
- package/skills/guide/references/remote-store-protocol.md +204 -16
- package/skills/guide/references/remote-ticket-protocol.md +1 -1
- package/skills/guide/references/setup.md +12 -9
- package/skills/implement/SKILL.md +3 -0
- package/skills/operate/SKILL.md +126 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/plan-review/SKILL.md +5 -1
- package/skills/spec/SKILL.md +46 -3
- package/skills/tickets/SKILL.md +57 -5
- package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
- package/packages/core/src/cli/store-opening.contract.ts +0 -289
- package/packages/core/src/cli/testkit.ts +0 -194
- package/packages/core/src/markdown.ts +0 -67
- package/packages/core/src/operator/client.ts +0 -311
- package/packages/core/src/operator/server.ts +0 -616
- package/packages/core/src/operator/sessions.ts +0 -223
- package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
- package/packages/core/src/ports/tickets/remote-server.ts +0 -185
- package/packages/core/src/store/remote/server.ts +0 -923
- package/packages/core/src/testing/store-failures.ts +0 -32
|
@@ -38,10 +38,22 @@
|
|
|
38
38
|
import type { AbEvent } from '../../events/catalog'
|
|
39
39
|
import type { RepositoryEvent } from '../../events/repository'
|
|
40
40
|
import type { Config } from '../../config/schema'
|
|
41
|
+
import type { PipelineSourceMeta } from '../../config/pipeline-source'
|
|
41
42
|
import type { BuildState, PhaseContext, PrLifecycle } from '../../kernel/reducer'
|
|
42
|
-
import {
|
|
43
|
-
|
|
43
|
+
import {
|
|
44
|
+
currentAutoMergeDeferralFrom,
|
|
45
|
+
currentDeferralObservationReducer,
|
|
46
|
+
type DeferralLedger,
|
|
47
|
+
} from '../../kernel/auto-merge'
|
|
48
|
+
import { decideNextFromFacts } from '../../kernel/engine'
|
|
44
49
|
import { verifyPhase } from '../../ontology'
|
|
50
|
+
import { indexLog, type LogIndex } from '../../kernel/log-index'
|
|
51
|
+
import {
|
|
52
|
+
dashboardFactsReducer,
|
|
53
|
+
producedAfter,
|
|
54
|
+
type DashboardFacts,
|
|
55
|
+
type PhaseTiming,
|
|
56
|
+
} from './facts'
|
|
45
57
|
import type { BuildRecord, StreamRecord } from '../../store/types'
|
|
46
58
|
import { reduceDispatchSettings } from '../../kernel/dispatch-settings'
|
|
47
59
|
import { projectSessions, type DashboardSession } from './detail'
|
|
@@ -104,6 +116,12 @@ export interface PipelineStep {
|
|
|
104
116
|
/** Absent ⇒ the step has never run in the current spec scope ⇒ no time is
|
|
105
117
|
* shown (AC 6). The renderer composes the elapsed segment from this. */
|
|
106
118
|
timing?: StepTiming
|
|
119
|
+
/** Display-only, like `qualifier`, and never consulted for routing: the
|
|
120
|
+
* parked merge step's no-consent explanation — why nothing will happen
|
|
121
|
+
* without operator action, and which control unblocks it. Attached when the
|
|
122
|
+
* build is current at merge with effective auto-merge off; the terminal
|
|
123
|
+
* row/detail and the web row/detail render the same words. */
|
|
124
|
+
reason?: string
|
|
107
125
|
}
|
|
108
126
|
|
|
109
127
|
export type DashboardSelection =
|
|
@@ -155,6 +173,11 @@ export interface DashboardBuild {
|
|
|
155
173
|
pr?: { url: string; state: PrLifecycle }
|
|
156
174
|
/** Chronological session history projected from the retained raw log. */
|
|
157
175
|
sessions?: DashboardSession[]
|
|
176
|
+
/** Which `autobuild.toml` the dispatcher pinned this build's pipeline from,
|
|
177
|
+
* and the effective-config revision it was deposited under (SPEC §16.1).
|
|
178
|
+
* Present once an effective-config artifact exists. */
|
|
179
|
+
pipelineSource?: PipelineSourceMeta
|
|
180
|
+
effectiveConfigRev?: number
|
|
158
181
|
}
|
|
159
182
|
|
|
160
183
|
export type DashboardView =
|
|
@@ -329,6 +352,18 @@ export function autoMergeDisplay(state: BuildState): AutoMergeDisplay {
|
|
|
329
352
|
return 'cancelling'
|
|
330
353
|
}
|
|
331
354
|
|
|
355
|
+
/** The shared no-consent wording for a build parked on an open PR. One string
|
|
356
|
+
* so the terminal row/detail and the web row/detail say the same words, and
|
|
357
|
+
* so it names the per-build control that unblocks the parked build. */
|
|
358
|
+
export function autoMergeConsentReason(slug: string): string {
|
|
359
|
+
return (
|
|
360
|
+
`no auto-merge consent has been requested — request it with m on the build row or ` +
|
|
361
|
+
'`ab auto-merge ' +
|
|
362
|
+
slug +
|
|
363
|
+
' on`'
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
332
367
|
/**
|
|
333
368
|
* One step, built through one helper so the precedence rule cannot be applied
|
|
334
369
|
* inconsistently: **`current > done > provisional > pending`**. In particular,
|
|
@@ -342,6 +377,7 @@ interface StepExtra {
|
|
|
342
377
|
qualifier?: 'failed' | 'skipped' | 'waiting'
|
|
343
378
|
count?: number
|
|
344
379
|
timing?: StepTiming
|
|
380
|
+
reason?: string
|
|
345
381
|
}
|
|
346
382
|
|
|
347
383
|
function step(label: string, done: boolean, current: boolean, extra: StepExtra = {}): PipelineStep {
|
|
@@ -357,102 +393,10 @@ function step(label: string, done: boolean, current: boolean, extra: StepExtra =
|
|
|
357
393
|
...(extra.qualifier !== undefined ? { qualifier: extra.qualifier } : {}),
|
|
358
394
|
...(extra.count !== undefined ? { count: extra.count } : {}),
|
|
359
395
|
...(extra.timing !== undefined ? { timing: extra.timing } : {}),
|
|
396
|
+
...(extra.reason !== undefined ? { reason: extra.reason } : {}),
|
|
360
397
|
}
|
|
361
398
|
}
|
|
362
399
|
|
|
363
|
-
/** One phase occurrence's wall-clock span, keyed by phase. `startSeq` is the
|
|
364
|
-
* seq of the `*.started` event, so callers can scope by the same seq
|
|
365
|
-
* boundaries (`restartSince`/`cycleSince`) the step STATES already use. */
|
|
366
|
-
interface PhaseInterval {
|
|
367
|
-
start: number
|
|
368
|
-
end: number
|
|
369
|
-
startSeq: number
|
|
370
|
-
}
|
|
371
|
-
interface PhaseTiming {
|
|
372
|
-
closed: PhaseInterval[]
|
|
373
|
-
open?: { start: number; startSeq: number }
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Walk the raw event log into per-phase `[start, end]` intervals in epoch-ms
|
|
378
|
-
* (`Date.parse(ev.ts)`), the durations the reducer collapses away. Each
|
|
379
|
-
* `*.started` opens an interval for its phase key and its terminal event
|
|
380
|
-
* (`.completed`/`.verdict`) closes it. A second `*.started` while one is still
|
|
381
|
-
* open REPLACES the open start — a §15.6-C cross-sandbox re-run starts the
|
|
382
|
-
* phase afresh, so the crashed attempt contributes nothing. Finalize post-steps
|
|
383
|
-
* (`finalize.step-completed`) have no `.started` and so no interval.
|
|
384
|
-
*/
|
|
385
|
-
function phaseIntervals(events: AbEvent[]): Map<string, PhaseTiming> {
|
|
386
|
-
const timings = new Map<string, PhaseTiming>()
|
|
387
|
-
const get = (key: string): PhaseTiming => {
|
|
388
|
-
let t = timings.get(key)
|
|
389
|
-
if (t === undefined) {
|
|
390
|
-
t = { closed: [] }
|
|
391
|
-
timings.set(key, t)
|
|
392
|
-
}
|
|
393
|
-
return t
|
|
394
|
-
}
|
|
395
|
-
for (const ev of events) {
|
|
396
|
-
const ms = Date.parse(ev.ts)
|
|
397
|
-
const open = (key: string): void => {
|
|
398
|
-
get(key).open = { start: ms, startSeq: ev.seq }
|
|
399
|
-
}
|
|
400
|
-
const close = (key: string): void => {
|
|
401
|
-
const t = get(key)
|
|
402
|
-
if (t.open === undefined) return
|
|
403
|
-
t.closed.push({ start: t.open.start, end: ms, startSeq: t.open.startSeq })
|
|
404
|
-
t.open = undefined
|
|
405
|
-
}
|
|
406
|
-
switch (ev.type) {
|
|
407
|
-
case 'plan.started':
|
|
408
|
-
open('plan')
|
|
409
|
-
break
|
|
410
|
-
case 'plan.completed':
|
|
411
|
-
close('plan')
|
|
412
|
-
break
|
|
413
|
-
case 'plan-review.started':
|
|
414
|
-
open('plan-review')
|
|
415
|
-
break
|
|
416
|
-
case 'plan-review.verdict':
|
|
417
|
-
close('plan-review')
|
|
418
|
-
break
|
|
419
|
-
case 'implement.started':
|
|
420
|
-
open('implement')
|
|
421
|
-
break
|
|
422
|
-
case 'implement.completed':
|
|
423
|
-
close('implement')
|
|
424
|
-
break
|
|
425
|
-
case 'code-review.started':
|
|
426
|
-
open('code-review')
|
|
427
|
-
break
|
|
428
|
-
case 'code-review.verdict':
|
|
429
|
-
close('code-review')
|
|
430
|
-
break
|
|
431
|
-
case 'verify.started':
|
|
432
|
-
open(verifyPhase(ev.payload.step))
|
|
433
|
-
break
|
|
434
|
-
case 'verify.completed':
|
|
435
|
-
close(verifyPhase(ev.payload.step))
|
|
436
|
-
break
|
|
437
|
-
case 'finalize.started':
|
|
438
|
-
open('finalize')
|
|
439
|
-
break
|
|
440
|
-
case 'finalize.completed':
|
|
441
|
-
close('finalize')
|
|
442
|
-
break
|
|
443
|
-
case 'reconcile.started':
|
|
444
|
-
open('reconcile')
|
|
445
|
-
break
|
|
446
|
-
case 'reconcile.completed':
|
|
447
|
-
close('reconcile')
|
|
448
|
-
break
|
|
449
|
-
default:
|
|
450
|
-
break
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return timings
|
|
454
|
-
}
|
|
455
|
-
|
|
456
400
|
/**
|
|
457
401
|
* Sum the in-scope occurrences of one phase into a `StepTiming`, or `undefined`
|
|
458
402
|
* when the phase never ran in scope (the step shows no time, AC 6). Scope is
|
|
@@ -463,12 +407,12 @@ function phaseIntervals(events: AbEvent[]): Map<string, PhaseTiming> {
|
|
|
463
407
|
* not advance (AC 10).
|
|
464
408
|
*/
|
|
465
409
|
function timingFor(
|
|
466
|
-
intervals:
|
|
410
|
+
intervals: Record<string, PhaseTiming>,
|
|
467
411
|
key: string,
|
|
468
412
|
sinceSeq: number,
|
|
469
413
|
frozenNow: number | undefined,
|
|
470
414
|
): StepTiming | undefined {
|
|
471
|
-
const t = intervals
|
|
415
|
+
const t = intervals[key]
|
|
472
416
|
if (t === undefined) return undefined
|
|
473
417
|
let accumulatedMs = 0
|
|
474
418
|
let inScope = false
|
|
@@ -490,6 +434,14 @@ function timingFor(
|
|
|
490
434
|
|
|
491
435
|
/**
|
|
492
436
|
* `record` + `state` → one dashboard row, or `null` only for terminal builds.
|
|
437
|
+
*
|
|
438
|
+
* `config` is the dispatcher's live snapshot; `pinnedConfig` (SPEC §16.1) is
|
|
439
|
+
* the build's own pipeline — its deposited effective-config artifact's
|
|
440
|
+
* build-owned sections composed over `config`'s deployment-owned ones. A
|
|
441
|
+
* build's verify/finalize universe and next-action are decided from the
|
|
442
|
+
* pinned pipeline: the live snapshot describes the base branch, which a
|
|
443
|
+
* pinned build may never execute (the AUT-366 class). Absent artifact ⇒ the
|
|
444
|
+
* live config, the pre-pin behavior.
|
|
493
445
|
*/
|
|
494
446
|
export function projectBuild(
|
|
495
447
|
record: BuildRecord,
|
|
@@ -497,7 +449,42 @@ export function projectBuild(
|
|
|
497
449
|
config: Config,
|
|
498
450
|
events: AbEvent[],
|
|
499
451
|
streams?: readonly StreamRecord[],
|
|
452
|
+
pinnedConfig?: Config,
|
|
453
|
+
): DashboardBuild | null {
|
|
454
|
+
return projectBuildFromFacts(
|
|
455
|
+
record,
|
|
456
|
+
state,
|
|
457
|
+
{
|
|
458
|
+
dashboard: dashboardFactsReducer.reduce(events),
|
|
459
|
+
log: indexLog(events),
|
|
460
|
+
deferral: currentDeferralObservationReducer.reduce(events),
|
|
461
|
+
},
|
|
462
|
+
config,
|
|
463
|
+
streams,
|
|
464
|
+
pinnedConfig,
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** The reduced facts a row renders from, beside the build state: what
|
|
469
|
+
* `projectBuild` derives from the event array, advanced incrementally. */
|
|
470
|
+
export interface DashboardRowFacts {
|
|
471
|
+
dashboard: DashboardFacts
|
|
472
|
+
log: LogIndex
|
|
473
|
+
deferral: DeferralLedger
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** `projectBuild` over already-reduced facts — the one implementation of the
|
|
477
|
+
* row projection, so a snapshot-backed row and a replayed row cannot differ. */
|
|
478
|
+
export function projectBuildFromFacts(
|
|
479
|
+
record: BuildRecord,
|
|
480
|
+
state: BuildState,
|
|
481
|
+
facts: DashboardRowFacts,
|
|
482
|
+
config: Config,
|
|
483
|
+
streams?: readonly StreamRecord[],
|
|
484
|
+
pinnedConfig?: Config,
|
|
500
485
|
): DashboardBuild | null {
|
|
486
|
+
const pipeline = pinnedConfig ?? config
|
|
487
|
+
const dashboard = facts.dashboard
|
|
501
488
|
const status = effectiveStatus(state)
|
|
502
489
|
if (!isVisible(status)) return null
|
|
503
490
|
const abortProgress =
|
|
@@ -510,35 +497,29 @@ export function projectBuild(
|
|
|
510
497
|
: undefined
|
|
511
498
|
|
|
512
499
|
if (status === 'queued') {
|
|
513
|
-
const created =
|
|
514
|
-
const workspace =
|
|
515
|
-
const released =
|
|
500
|
+
const created = dashboard.created
|
|
501
|
+
const workspace = dashboard.workspaceProvisioned
|
|
502
|
+
const released = dashboard.workspaceReleased
|
|
516
503
|
const openWorkspace =
|
|
517
|
-
workspace !== undefined && (released === undefined || workspace
|
|
518
|
-
const spec =
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const commentPosted = events.findLast((event) => event.type === 'dispatch.comment-posted')
|
|
522
|
-
const latestFailure = events.findLast((event) => event.type === 'dispatch.failed')
|
|
504
|
+
workspace !== undefined && (released === undefined || workspace > released)
|
|
505
|
+
const spec = dashboard.specLanded
|
|
506
|
+
const commentPosted = dashboard.commentPosted
|
|
507
|
+
const latestFailure = dashboard.latestFailure
|
|
523
508
|
const failureSuperseded =
|
|
524
509
|
latestFailure !== undefined &&
|
|
525
|
-
((latestFailure.
|
|
526
|
-
|
|
527
|
-
created.seq > latestFailure.seq) ||
|
|
528
|
-
(latestFailure.payload.stage === 'workspace' &&
|
|
510
|
+
((latestFailure.stage === 'create' && created !== undefined && created > latestFailure.seq) ||
|
|
511
|
+
(latestFailure.stage === 'workspace' &&
|
|
529
512
|
workspace !== undefined &&
|
|
530
|
-
workspace
|
|
531
|
-
(latestFailure.
|
|
532
|
-
|
|
533
|
-
spec.seq > latestFailure.seq) ||
|
|
534
|
-
(latestFailure.payload.stage === 'comment' &&
|
|
513
|
+
workspace > latestFailure.seq) ||
|
|
514
|
+
(latestFailure.stage === 'spec' && spec !== undefined && spec > latestFailure.seq) ||
|
|
515
|
+
(latestFailure.stage === 'comment' &&
|
|
535
516
|
commentPosted !== undefined &&
|
|
536
|
-
commentPosted
|
|
517
|
+
commentPosted > latestFailure.seq))
|
|
537
518
|
const dispatch =
|
|
538
519
|
state.discardRequest !== undefined
|
|
539
520
|
? 'discard requested; cleanup and Ready handback pending'
|
|
540
521
|
: latestFailure !== undefined && !failureSuperseded
|
|
541
|
-
? `dispatch ${latestFailure.
|
|
522
|
+
? `dispatch ${latestFailure.stage} failed (attempt ${latestFailure.attempt}): ${latestFailure.error}`
|
|
542
523
|
: created === undefined
|
|
543
524
|
? 'dispatch initialization pending'
|
|
544
525
|
: !openWorkspace
|
|
@@ -555,7 +536,7 @@ export function projectBuild(
|
|
|
555
536
|
dispatch,
|
|
556
537
|
blockers: [],
|
|
557
538
|
autoMerge: autoMergeDisplay(state),
|
|
558
|
-
sessions: projectSessions(
|
|
539
|
+
sessions: projectSessions(dashboard.sessionEvents, streams),
|
|
559
540
|
}
|
|
560
541
|
}
|
|
561
542
|
|
|
@@ -576,7 +557,7 @@ export function projectBuild(
|
|
|
576
557
|
...(state.pr !== undefined && state.prState !== undefined
|
|
577
558
|
? { pr: { url: state.pr.url, state: state.prState } }
|
|
578
559
|
: {}),
|
|
579
|
-
sessions: projectSessions(
|
|
560
|
+
sessions: projectSessions(dashboard.sessionEvents, streams),
|
|
580
561
|
}
|
|
581
562
|
}
|
|
582
563
|
|
|
@@ -640,11 +621,8 @@ export function projectBuild(
|
|
|
640
621
|
// blocked build freezes at the latest durable event. Timers therefore do not
|
|
641
622
|
// advance behind either gate (AC 10). Each step scopes by the SAME seq boundary its state uses, so
|
|
642
623
|
// durations and states restart in lockstep (AC 12).
|
|
643
|
-
const intervals =
|
|
644
|
-
const pausedAt =
|
|
645
|
-
state.status === 'paused'
|
|
646
|
-
? events.findLast((event) => event.type === 'build.paused')?.ts
|
|
647
|
-
: undefined
|
|
624
|
+
const intervals = dashboard.intervals
|
|
625
|
+
const pausedAt = state.status === 'paused' ? dashboard.pausedAt : undefined
|
|
648
626
|
const frozenNow =
|
|
649
627
|
state.status === 'running'
|
|
650
628
|
? undefined
|
|
@@ -663,7 +641,7 @@ export function projectBuild(
|
|
|
663
641
|
const verifySkipped = (s: string): boolean =>
|
|
664
642
|
cycle.some((r) => r.step === s && r.outcome === 'skipped')
|
|
665
643
|
const verifySatisfied = (s: string): boolean => verifyPassed(s) || verifySkipped(s)
|
|
666
|
-
const verifyDrained = !cycleFailed &&
|
|
644
|
+
const verifyDrained = !cycleFailed && pipeline.verify.steps.every(verifySatisfied)
|
|
667
645
|
|
|
668
646
|
// A loop is settled only if its standing approval survived the last restart
|
|
669
647
|
// AND no later producer round reopened it. `approved` alone is a full-log
|
|
@@ -685,35 +663,31 @@ export function projectBuild(
|
|
|
685
663
|
// boundary. Starts and timing intervals are deliberately insufficient: a
|
|
686
664
|
// crashed phase has run, but has produced no output. Matching the round also
|
|
687
665
|
// drops a review verdict as soon as the next producer round starts.
|
|
688
|
-
const planProduced =
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
ev.payload.round === state.plan.round,
|
|
666
|
+
const planProduced = producedAfter(
|
|
667
|
+
dashboard.produced.planCompleted,
|
|
668
|
+
state.plan.round,
|
|
669
|
+
restartSince,
|
|
693
670
|
)
|
|
694
|
-
const planReviewProduced =
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
ev.payload.round === state.plan.round,
|
|
671
|
+
const planReviewProduced = producedAfter(
|
|
672
|
+
dashboard.produced.planReviewVerdict,
|
|
673
|
+
state.plan.round,
|
|
674
|
+
restartSince,
|
|
699
675
|
)
|
|
700
|
-
const implementProduced =
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
ev.payload.round === state.implement.round,
|
|
676
|
+
const implementProduced = producedAfter(
|
|
677
|
+
dashboard.produced.implementCompleted,
|
|
678
|
+
state.implement.round,
|
|
679
|
+
restartSince,
|
|
705
680
|
)
|
|
706
|
-
const codeReviewProduced =
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
ev.payload.round === state.implement.round,
|
|
681
|
+
const codeReviewProduced = producedAfter(
|
|
682
|
+
dashboard.produced.codeReviewVerdict,
|
|
683
|
+
state.implement.round,
|
|
684
|
+
restartSince,
|
|
711
685
|
)
|
|
712
686
|
|
|
713
687
|
// Finalize ran for the CURRENT spec — NOT `prState !== undefined`, which a
|
|
714
688
|
// spec restart never resets while the engine re-runs finalize from scratch.
|
|
715
689
|
const finalizeDone = state.finalizeCompletedSeq > restartSince
|
|
716
|
-
const postStepsDrained =
|
|
690
|
+
const postStepsDrained = pipeline.finalize.steps.every((s) =>
|
|
717
691
|
finalizeSteps.some((f) => f.step === s),
|
|
718
692
|
)
|
|
719
693
|
|
|
@@ -746,7 +720,7 @@ export function projectBuild(
|
|
|
746
720
|
}),
|
|
747
721
|
]
|
|
748
722
|
|
|
749
|
-
for (const s of
|
|
723
|
+
for (const s of pipeline.verify.steps) {
|
|
750
724
|
const phase = verifyPhase(s)
|
|
751
725
|
const current = at(phase)
|
|
752
726
|
const stepResults = cycle.filter((r) => r.step === s)
|
|
@@ -787,7 +761,7 @@ export function projectBuild(
|
|
|
787
761
|
timing: timingFor(intervals, 'finalize', restartSince, frozenNow),
|
|
788
762
|
}),
|
|
789
763
|
)
|
|
790
|
-
for (const s of
|
|
764
|
+
for (const s of pipeline.finalize.steps) {
|
|
791
765
|
// Post-steps have no `.started` event, so they carry no timing.
|
|
792
766
|
const done = finalizeSteps.find((f) => f.step === s)
|
|
793
767
|
steps.push(
|
|
@@ -839,6 +813,17 @@ export function projectBuild(
|
|
|
839
813
|
? { accumulatedMs: Math.max(0, frozenNow - lastMs) }
|
|
840
814
|
: { accumulatedMs: 0, runningSince: lastMs }
|
|
841
815
|
steps.push(step('merge', false, mergeCurrent, { qualifier: 'waiting', timing: mergeTiming }))
|
|
816
|
+
const autoMerge = autoMergeDisplay(state)
|
|
817
|
+
// The parked no-consent reason: a merge-current build whose effective
|
|
818
|
+
// auto-merge state is off will never merge without operator action, and the
|
|
819
|
+
// step must say so (and name the control) wherever the pipeline renders.
|
|
820
|
+
// Display-only; the deferral `mergeWaitReason` stays in `blockers`.
|
|
821
|
+
if (mergeCurrent && autoMerge === 'off') {
|
|
822
|
+
steps[steps.length - 1] = {
|
|
823
|
+
...steps[steps.length - 1]!,
|
|
824
|
+
reason: autoMergeConsentReason(record.slug),
|
|
825
|
+
}
|
|
826
|
+
}
|
|
842
827
|
|
|
843
828
|
const setupError =
|
|
844
829
|
state.infrastructureFailure !== undefined
|
|
@@ -855,10 +840,10 @@ export function projectBuild(
|
|
|
855
840
|
: `exit status ${state.setupFailure.exitStatus}`
|
|
856
841
|
}): ${state.setupFailure.output || '(no output)'}`
|
|
857
842
|
|
|
858
|
-
const decision =
|
|
843
|
+
const decision = decideNextFromFacts(state, facts.log, pipeline)
|
|
859
844
|
const mergeWaitReason =
|
|
860
845
|
decision.kind === 'wait' && decision.reason === 'awaiting-pr'
|
|
861
|
-
?
|
|
846
|
+
? currentAutoMergeDeferralFrom(facts.deferral, state)
|
|
862
847
|
: undefined
|
|
863
848
|
|
|
864
849
|
return {
|
|
@@ -875,11 +860,11 @@ export function projectBuild(
|
|
|
875
860
|
...(state.reviewRoundCeilings.plan !== undefined || state.reviewRoundCeilings.code !== undefined
|
|
876
861
|
? { reviewRoundCeilings: { ...state.reviewRoundCeilings } }
|
|
877
862
|
: {}),
|
|
878
|
-
autoMerge
|
|
863
|
+
autoMerge,
|
|
879
864
|
...(state.pr !== undefined && state.prState !== undefined
|
|
880
865
|
? { pr: { url: state.pr.url, state: state.prState } }
|
|
881
866
|
: {}),
|
|
882
|
-
sessions: projectSessions(
|
|
867
|
+
sessions: projectSessions(dashboard.sessionEvents, streams),
|
|
883
868
|
}
|
|
884
869
|
}
|
|
885
870
|
|
|
@@ -1084,7 +1069,10 @@ interface RepositoryHarvestProjection {
|
|
|
1084
1069
|
harvest?: DashboardHarvest
|
|
1085
1070
|
}
|
|
1086
1071
|
|
|
1087
|
-
|
|
1072
|
+
/** The repository-journal half of a legacy dashboard frame (AUT-545): the
|
|
1073
|
+
* harvest projection and the pause flag exactly as `buildDashboardFromProjected`
|
|
1074
|
+
* derives them, exposed for tests that pin the bounded read's equivalence. */
|
|
1075
|
+
export function projectRepositoryHarvest(events: RepositoryEvent[]): RepositoryHarvestProjection {
|
|
1088
1076
|
const state = reduceHarvest(events)
|
|
1089
1077
|
const harvest = projectHarvestRun(events, state)
|
|
1090
1078
|
return {
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import type { Config } from '../../config/schema'
|
|
2
|
+
import { composeBuildConfig } from '../../config/live'
|
|
3
|
+
import type { PipelineSourceMeta } from '../../config/pipeline-source'
|
|
2
4
|
import type { AbEvent } from '../../events/catalog'
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
+
import { buildReducer, type BuildAcc, type BuildState } from '../../kernel/reducer'
|
|
6
|
+
import {
|
|
7
|
+
BUILD_EFFECTIVE_CONFIG_ARTIFACT,
|
|
8
|
+
parseBuildConfigMetadata,
|
|
9
|
+
parseEffectiveBuildConfig,
|
|
10
|
+
} from '../../processes/build-execution-state'
|
|
11
|
+
import type { Artifact, BuildRecord, StreamRecord, StreamScope } from '../../store/types'
|
|
5
12
|
import { projectBuild, type DashboardBuild } from './model'
|
|
6
13
|
|
|
7
14
|
/** The read-only BuildStore surface needed to construct dashboard build rows.
|
|
8
15
|
* `listStreams` is optional: when present, build-scope records enrich each
|
|
9
|
-
* row's session history with authoritative stream ids and statuses (SPEC §9).
|
|
16
|
+
* row's session history with authoritative stream ids and statuses (SPEC §9).
|
|
17
|
+
* `getArtifact` is optional: when present, the effective-config artifact's
|
|
18
|
+
* metadata decorates each row with its pinned pipeline source and revision
|
|
19
|
+
* (SPEC §16.1). */
|
|
10
20
|
export interface DashboardBuildReader {
|
|
11
21
|
listBuilds(): Promise<BuildRecord[]>
|
|
12
22
|
getEvents(slug: string, sinceSeq?: number): Promise<AbEvent[]>
|
|
13
23
|
listStreams?(scope: StreamScope): Promise<StreamRecord[]>
|
|
24
|
+
getArtifact?(slug: string, kind: string): Promise<Artifact | null>
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
export interface DashboardPollSnapshot {
|
|
@@ -27,6 +38,9 @@ export interface DashboardPollSnapshot {
|
|
|
27
38
|
interface LiveEntry {
|
|
28
39
|
kind: 'live'
|
|
29
40
|
events: AbEvent[]
|
|
41
|
+
/** The reducer accumulator `state` was derived from; the next delta is
|
|
42
|
+
* folded into a copy of it instead of re-reducing the whole log. */
|
|
43
|
+
acc: BuildAcc
|
|
30
44
|
state: BuildState
|
|
31
45
|
build: DashboardBuild | null
|
|
32
46
|
}
|
|
@@ -109,6 +123,60 @@ export class DashboardBuildPollCache {
|
|
|
109
123
|
return snapshot.revision === this.committedRevision
|
|
110
124
|
}
|
|
111
125
|
|
|
126
|
+
/** Effective-config artifact facts for one build (SPEC §16.1), when the
|
|
127
|
+
* reader exposes the artifact primitive: the metadata projection plus the
|
|
128
|
+
* artifact's parsed content. A malformed artifact still yields its metadata
|
|
129
|
+
* (config absent); all failures are display-only. */
|
|
130
|
+
private async readEffectiveConfig(
|
|
131
|
+
slug: string,
|
|
132
|
+
): Promise<
|
|
133
|
+
{ revision?: number; pipelineSource?: PipelineSourceMeta; config?: Config } | undefined
|
|
134
|
+
> {
|
|
135
|
+
const getArtifact = this.reader.getArtifact
|
|
136
|
+
if (getArtifact === undefined) return undefined
|
|
137
|
+
try {
|
|
138
|
+
const artifact = await getArtifact.call(this.reader, slug, BUILD_EFFECTIVE_CONFIG_ARTIFACT)
|
|
139
|
+
if (artifact === null) return undefined
|
|
140
|
+
let config: Config | undefined
|
|
141
|
+
try {
|
|
142
|
+
config = parseEffectiveBuildConfig(artifact)
|
|
143
|
+
} catch {
|
|
144
|
+
config = undefined
|
|
145
|
+
}
|
|
146
|
+
return { ...parseBuildConfigMetadata(artifact), config }
|
|
147
|
+
} catch {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Project one build row against its PINNED pipeline (SPEC §16.1) and
|
|
153
|
+
* attach the effective-config metadata. The row's verify/finalize steps and
|
|
154
|
+
* next-action must describe the pipeline the build actually runs — the
|
|
155
|
+
* artifact's build-owned sections — never the dispatcher's live base-branch
|
|
156
|
+
* snapshot, which a pinned build may never execute. Deployment-owned
|
|
157
|
+
* sections (roles/policy) still come from the live snapshot, so a reload
|
|
158
|
+
* that reaches the build is reflected. A build with no artifact yet (queued
|
|
159
|
+
* or pre-pin) projects from the live config, the pre-pin behavior. */
|
|
160
|
+
private async projectRow(
|
|
161
|
+
record: BuildRecord,
|
|
162
|
+
state: BuildState,
|
|
163
|
+
events: AbEvent[],
|
|
164
|
+
config: Config,
|
|
165
|
+
): Promise<DashboardBuild | null> {
|
|
166
|
+
const [streams, artifact] = await Promise.all([
|
|
167
|
+
this.readStreams(record.slug),
|
|
168
|
+
this.readEffectiveConfig(record.slug),
|
|
169
|
+
])
|
|
170
|
+
const pinnedConfig =
|
|
171
|
+
artifact?.config !== undefined ? composeBuildConfig(artifact.config, config) : undefined
|
|
172
|
+
const build = projectBuild(record, state, config, events, streams, pinnedConfig)
|
|
173
|
+
if (build !== null && artifact !== undefined) {
|
|
174
|
+
if (artifact.revision !== undefined) build.effectiveConfigRev = artifact.revision
|
|
175
|
+
if (artifact.pipelineSource !== undefined) build.pipelineSource = artifact.pipelineSource
|
|
176
|
+
}
|
|
177
|
+
return build
|
|
178
|
+
}
|
|
179
|
+
|
|
112
180
|
refresh(
|
|
113
181
|
config: Config = this.config,
|
|
114
182
|
configRevision = this.configRevision,
|
|
@@ -146,13 +214,7 @@ export class DashboardBuildPollCache {
|
|
|
146
214
|
configChanged
|
|
147
215
|
? {
|
|
148
216
|
...current,
|
|
149
|
-
build:
|
|
150
|
-
record,
|
|
151
|
-
current.state,
|
|
152
|
-
config,
|
|
153
|
-
current.events,
|
|
154
|
-
await this.readStreams(record.slug),
|
|
155
|
-
),
|
|
217
|
+
build: await this.projectRow(record, current.state, current.events, config),
|
|
156
218
|
}
|
|
157
219
|
: current,
|
|
158
220
|
)
|
|
@@ -160,7 +222,8 @@ export class DashboardBuildPollCache {
|
|
|
160
222
|
}
|
|
161
223
|
|
|
162
224
|
const events = current === undefined ? delta : [...current.events, ...delta]
|
|
163
|
-
const
|
|
225
|
+
const acc = buildReducer.advance(current?.acc ?? buildReducer.initial(), delta)
|
|
226
|
+
const state = buildReducer.finish(acc)
|
|
164
227
|
if (isTerminal(state)) {
|
|
165
228
|
next.set(record.slug, { kind: 'terminal' })
|
|
166
229
|
continue
|
|
@@ -168,8 +231,9 @@ export class DashboardBuildPollCache {
|
|
|
168
231
|
next.set(record.slug, {
|
|
169
232
|
kind: 'live',
|
|
170
233
|
events,
|
|
234
|
+
acc,
|
|
171
235
|
state,
|
|
172
|
-
build:
|
|
236
|
+
build: await this.projectRow(record, state, events, config),
|
|
173
237
|
})
|
|
174
238
|
}
|
|
175
239
|
|
|
@@ -250,7 +250,8 @@ function renderStep(step: PipelineStep, color: boolean, now: number): string {
|
|
|
250
250
|
parts.push(`${formatDuration(elapsedMs)}${count}`)
|
|
251
251
|
}
|
|
252
252
|
const note = parts.length > 0 ? `(${parts.join(', ')})` : ''
|
|
253
|
-
const
|
|
253
|
+
const reason = step.reason !== undefined ? ` — ${step.reason}` : ''
|
|
254
|
+
const text = `${GLYPH[step.state]} ${step.label}${note}${reason}`
|
|
254
255
|
const painted = paint(text, STEP_COLOR[step.state], color)
|
|
255
256
|
return step.state === 'current' ? paint(painted, 'bold', color) : painted
|
|
256
257
|
}
|
|
@@ -307,6 +308,7 @@ function renderBuild(
|
|
|
307
308
|
widths: Widths,
|
|
308
309
|
selected: boolean,
|
|
309
310
|
selecting: boolean,
|
|
311
|
+
defaultAutoMerge: boolean,
|
|
310
312
|
): string[] {
|
|
311
313
|
const { color, width, now } = opts
|
|
312
314
|
|
|
@@ -329,10 +331,22 @@ function renderBuild(
|
|
|
329
331
|
// right-justifies the status word so it is never truncated and always ends at
|
|
330
332
|
// the frame's right edge (AC 3, AC 5).
|
|
331
333
|
const rightTokens: string[] = []
|
|
332
|
-
|
|
334
|
+
// The state is ALWAYS drawn — including `off` — so consent is never conveyed
|
|
335
|
+
// by the absence of a token alone. `off` turns red while the repository
|
|
336
|
+
// default reads ON: the default-on/build-off mismatch an operator must see
|
|
337
|
+
// without opening the detail view.
|
|
338
|
+
{
|
|
333
339
|
const autoColor: ColorName =
|
|
334
|
-
build.autoMerge === 'enabled'
|
|
335
|
-
|
|
340
|
+
build.autoMerge === 'enabled'
|
|
341
|
+
? 'green'
|
|
342
|
+
: build.autoMerge === 'requested'
|
|
343
|
+
? 'cyan'
|
|
344
|
+
: build.autoMerge === 'cancelling'
|
|
345
|
+
? 'yellow'
|
|
346
|
+
: defaultAutoMerge
|
|
347
|
+
? 'red'
|
|
348
|
+
: 'dim'
|
|
349
|
+
rightTokens.push(paint(`auto merge ${build.autoMerge}`, autoColor, color))
|
|
336
350
|
}
|
|
337
351
|
if (build.pr !== undefined) rightTokens.push(link(build.pr.url, `PR ${build.pr.state}`, color))
|
|
338
352
|
// A repository hold changes no build lifecycle state. Keep QUEUED literal
|
|
@@ -578,7 +592,8 @@ function detailStep(step: PipelineStep, opts: RenderOpts): string {
|
|
|
578
592
|
notes.push(formatDuration(elapsed))
|
|
579
593
|
}
|
|
580
594
|
const note = notes.length > 0 ? ` (${notes.join(', ')})` : ''
|
|
581
|
-
|
|
595
|
+
const reason = step.reason !== undefined ? ` — ${step.reason}` : ''
|
|
596
|
+
return paint(`${GLYPH[step.state]} ${step.label}${note}${reason}`, STEP_COLOR[step.state], color)
|
|
582
597
|
}
|
|
583
598
|
|
|
584
599
|
function sessionLines(session: DashboardSession, selected: boolean, opts: RenderOpts): string[] {
|
|
@@ -639,6 +654,19 @@ function detailBody(
|
|
|
639
654
|
if (ceiling !== undefined) body.push(` ${ceiling}`)
|
|
640
655
|
if (build.pr !== undefined)
|
|
641
656
|
body.push(` PR ${build.pr.state} ${link(build.pr.url, build.pr.url, color)}`)
|
|
657
|
+
if (build.pipelineSource !== undefined || build.effectiveConfigRev !== undefined) {
|
|
658
|
+
const commit =
|
|
659
|
+
build.pipelineSource?.commit !== undefined
|
|
660
|
+
? build.pipelineSource.commit.slice(0, 7)
|
|
661
|
+
: 'unknown'
|
|
662
|
+
const source =
|
|
663
|
+
build.pipelineSource !== undefined
|
|
664
|
+
? `autobuild.toml@${commit} (${build.pipelineSource.ref})`
|
|
665
|
+
: 'autobuild.toml@unknown (pre-pin)'
|
|
666
|
+
const rev =
|
|
667
|
+
build.effectiveConfigRev !== undefined ? ` config rev ${build.effectiveConfigRev}` : ''
|
|
668
|
+
body.push(` pipeline ${source}${rev}`)
|
|
669
|
+
}
|
|
642
670
|
if (build.abortProgress !== undefined) {
|
|
643
671
|
body.push('', paint('Abort progress', 'bold', color))
|
|
644
672
|
body.push(...wrappedText(build.abortProgress, width, ' '))
|
|
@@ -1449,6 +1477,7 @@ function renderDashboardContent(model: DashboardModel, opts: RenderOpts): string
|
|
|
1449
1477
|
widths,
|
|
1450
1478
|
sameSelection(selection, model.selection),
|
|
1451
1479
|
selecting,
|
|
1480
|
+
model.defaultAutoMerge,
|
|
1452
1481
|
),
|
|
1453
1482
|
},
|
|
1454
1483
|
]
|