@defrex/autobuild 0.8.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/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { parseConfig } from './load'
|
|
2
|
+
import type { Config, TicketsConfig } from './schema'
|
|
3
|
+
import { createRuntimeResolver, type RuntimeResolver } from '../ports/runner/routing'
|
|
4
|
+
import type { RuntimeRegistry } from '../ports/runner/runtime'
|
|
5
|
+
|
|
6
|
+
/** Exact repository artifact kind used for accepted dispatcher config revisions. */
|
|
7
|
+
export const DISPATCHER_CONFIG_ARTIFACT = 'dispatcher-config'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Explicit hot/restart classification for every strict root field. Open maps
|
|
11
|
+
* are classified at their owning table: their entries all share that table's
|
|
12
|
+
* behavior.
|
|
13
|
+
*/
|
|
14
|
+
export const CONFIG_RELOAD_CLASSIFICATION = {
|
|
15
|
+
baseBranch: 'hot',
|
|
16
|
+
capacity: 'hot',
|
|
17
|
+
forge: 'restart',
|
|
18
|
+
plugins: 'restart',
|
|
19
|
+
pr: 'hot',
|
|
20
|
+
workspace: {
|
|
21
|
+
provider: 'restart',
|
|
22
|
+
config: 'restart',
|
|
23
|
+
},
|
|
24
|
+
commands: 'hot',
|
|
25
|
+
verify: 'hot',
|
|
26
|
+
finalize: 'hot',
|
|
27
|
+
roles: 'hot',
|
|
28
|
+
policy: 'hot',
|
|
29
|
+
orchestrator: 'restart',
|
|
30
|
+
tickets: {
|
|
31
|
+
source: 'restart',
|
|
32
|
+
readyLabels: 'hot',
|
|
33
|
+
readyState: 'hot',
|
|
34
|
+
teamKey: 'restart',
|
|
35
|
+
claimedState: 'restart',
|
|
36
|
+
createState: 'restart',
|
|
37
|
+
triageState: 'hot',
|
|
38
|
+
proposalState: 'hot',
|
|
39
|
+
dir: 'restart',
|
|
40
|
+
},
|
|
41
|
+
} as const satisfies {
|
|
42
|
+
[K in keyof Config]: K extends 'workspace'
|
|
43
|
+
? { [P in keyof Config['workspace']]: 'hot' | 'restart' }
|
|
44
|
+
: K extends 'tickets'
|
|
45
|
+
? { [P in keyof TicketsConfig]: 'hot' | 'restart' }
|
|
46
|
+
: 'hot' | 'restart'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const RESTART_REQUIRED_CONFIG_PATHS = [
|
|
50
|
+
'forge',
|
|
51
|
+
'plugins',
|
|
52
|
+
'workspace.provider',
|
|
53
|
+
'workspace.config',
|
|
54
|
+
'orchestrator',
|
|
55
|
+
'tickets.source',
|
|
56
|
+
'tickets.teamKey',
|
|
57
|
+
'tickets.claimedState',
|
|
58
|
+
'tickets.createState',
|
|
59
|
+
'tickets.dir',
|
|
60
|
+
] as const
|
|
61
|
+
export type RestartRequiredConfigPath = (typeof RESTART_REQUIRED_CONFIG_PATHS)[number]
|
|
62
|
+
|
|
63
|
+
function same(left: unknown, right: unknown): boolean {
|
|
64
|
+
return JSON.stringify(left) === JSON.stringify(right)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Paths whose candidate values cannot replace startup-constructed adapters. */
|
|
68
|
+
export function restartRequiredChanges(
|
|
69
|
+
startup: Config,
|
|
70
|
+
candidate: Config,
|
|
71
|
+
): RestartRequiredConfigPath[] {
|
|
72
|
+
const changed: RestartRequiredConfigPath[] = []
|
|
73
|
+
const compare = (
|
|
74
|
+
path: (typeof RESTART_REQUIRED_CONFIG_PATHS)[number],
|
|
75
|
+
a: unknown,
|
|
76
|
+
b: unknown,
|
|
77
|
+
) => {
|
|
78
|
+
if (!same(a, b)) changed.push(path)
|
|
79
|
+
}
|
|
80
|
+
compare('forge', startup.forge, candidate.forge)
|
|
81
|
+
compare('plugins', startup.plugins, candidate.plugins)
|
|
82
|
+
compare('workspace.provider', startup.workspace.provider, candidate.workspace.provider)
|
|
83
|
+
compare('workspace.config', startup.workspace.config, candidate.workspace.config)
|
|
84
|
+
compare('tickets.source', startup.tickets.source, candidate.tickets.source)
|
|
85
|
+
compare('tickets.teamKey', startup.tickets.teamKey, candidate.tickets.teamKey)
|
|
86
|
+
compare('tickets.claimedState', startup.tickets.claimedState, candidate.tickets.claimedState)
|
|
87
|
+
compare('tickets.createState', startup.tickets.createState, candidate.tickets.createState)
|
|
88
|
+
compare('tickets.dir', startup.tickets.dir, candidate.tickets.dir)
|
|
89
|
+
compare('orchestrator', startup.orchestrator, candidate.orchestrator)
|
|
90
|
+
return changed
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Overlay all hot fields while retaining every startup-built adapter field. */
|
|
94
|
+
export function composeReloadedConfig(startup: Config, candidate: Config): Config {
|
|
95
|
+
return {
|
|
96
|
+
...candidate,
|
|
97
|
+
forge: startup.forge,
|
|
98
|
+
plugins: startup.plugins,
|
|
99
|
+
workspace: startup.workspace,
|
|
100
|
+
orchestrator: startup.orchestrator,
|
|
101
|
+
tickets: {
|
|
102
|
+
...candidate.tickets,
|
|
103
|
+
source: startup.tickets.source,
|
|
104
|
+
teamKey: startup.tickets.teamKey,
|
|
105
|
+
claimedState: startup.tickets.claimedState,
|
|
106
|
+
createState: startup.tickets.createState,
|
|
107
|
+
dir: startup.tickets.dir,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ConfigSnapshot {
|
|
113
|
+
readonly config: Config
|
|
114
|
+
readonly resolver: RuntimeResolver
|
|
115
|
+
/** Process-local revision. Zero is the startup file. */
|
|
116
|
+
readonly revision: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type ConfigReloadOutcome =
|
|
120
|
+
| { kind: 'unchanged' }
|
|
121
|
+
| { kind: 'rejected'; error: string; notify: boolean }
|
|
122
|
+
| { kind: 'publication-failed'; error: string }
|
|
123
|
+
| {
|
|
124
|
+
kind: 'adopted'
|
|
125
|
+
snapshot: ConfigSnapshot
|
|
126
|
+
restartRequired: RestartRequiredConfigPath[]
|
|
127
|
+
effectiveChanged: boolean
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type ConfigReloadPublisher = (input: {
|
|
131
|
+
content: string
|
|
132
|
+
/** Fully composed snapshot that will become current after publication. */
|
|
133
|
+
effectiveConfig: Config
|
|
134
|
+
restartRequired: readonly RestartRequiredConfigPath[]
|
|
135
|
+
effectiveChanged: boolean
|
|
136
|
+
}) => Promise<void>
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Process-local owner of the dispatcher's effective config. A candidate is
|
|
140
|
+
* published only after parsing, eager route validation, and durable journal
|
|
141
|
+
* publication all succeed. Callers capture `current()` once per boundary.
|
|
142
|
+
*/
|
|
143
|
+
export class LiveConfig {
|
|
144
|
+
private snapshot: ConfigSnapshot
|
|
145
|
+
private acceptedContent: string
|
|
146
|
+
private rejectedContent: string | undefined
|
|
147
|
+
|
|
148
|
+
constructor(
|
|
149
|
+
private readonly source: string,
|
|
150
|
+
private readonly startup: Config,
|
|
151
|
+
startupContent: string,
|
|
152
|
+
private readonly runtimes: RuntimeRegistry,
|
|
153
|
+
private readonly publish: ConfigReloadPublisher,
|
|
154
|
+
) {
|
|
155
|
+
this.acceptedContent = startupContent
|
|
156
|
+
this.snapshot = {
|
|
157
|
+
config: startup,
|
|
158
|
+
resolver: createRuntimeResolver(runtimes, startup.roles, startup.policy.sessionBudgetSeconds),
|
|
159
|
+
revision: 0,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
current(): ConfigSnapshot {
|
|
164
|
+
return this.snapshot
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async refreshFromDisk(): Promise<ConfigReloadOutcome> {
|
|
168
|
+
return this.refreshFrom(() => Bun.file(this.source).text())
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Reload from any content source — the startup file (refreshFromDisk) or,
|
|
172
|
+
* in checkout-less origin mode, the base branch's autobuild.toml fetched
|
|
173
|
+
* through the forge. Read failures take the same rejected-read path: the
|
|
174
|
+
* last valid snapshot remains active and the error is reported once per
|
|
175
|
+
* distinct failure. */
|
|
176
|
+
async refreshFrom(read: () => Promise<string>): Promise<ConfigReloadOutcome> {
|
|
177
|
+
let content: string
|
|
178
|
+
try {
|
|
179
|
+
content = await read()
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const code = (error as NodeJS.ErrnoException).code
|
|
182
|
+
const detail =
|
|
183
|
+
code === 'ENOENT'
|
|
184
|
+
? 'is missing during live reload'
|
|
185
|
+
: `could not be read during live reload: ${
|
|
186
|
+
error instanceof Error ? error.message : String(error)
|
|
187
|
+
}`
|
|
188
|
+
const message =
|
|
189
|
+
`${this.source} ${detail}; the last valid configuration snapshot remains active — ` +
|
|
190
|
+
'restore a valid autobuild.toml to resume live reload'
|
|
191
|
+
const rejectedRead = `\0${message}`
|
|
192
|
+
const notify = this.rejectedContent !== rejectedRead
|
|
193
|
+
this.rejectedContent = rejectedRead
|
|
194
|
+
return { kind: 'rejected', error: message, notify }
|
|
195
|
+
}
|
|
196
|
+
return this.refresh(content)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async refresh(content: string): Promise<ConfigReloadOutcome> {
|
|
200
|
+
if (content === this.acceptedContent) {
|
|
201
|
+
this.rejectedContent = undefined
|
|
202
|
+
return { kind: 'unchanged' }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let candidate: Config
|
|
206
|
+
try {
|
|
207
|
+
candidate = parseConfig(content, this.source)
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
210
|
+
const notify = this.rejectedContent !== content
|
|
211
|
+
this.rejectedContent = content
|
|
212
|
+
return { kind: 'rejected', error: message, notify }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const config = composeReloadedConfig(this.startup, candidate)
|
|
216
|
+
let resolver: RuntimeResolver
|
|
217
|
+
try {
|
|
218
|
+
resolver = createRuntimeResolver(
|
|
219
|
+
this.runtimes,
|
|
220
|
+
config.roles,
|
|
221
|
+
config.policy.sessionBudgetSeconds,
|
|
222
|
+
)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
225
|
+
const notify = this.rejectedContent !== content
|
|
226
|
+
this.rejectedContent = content
|
|
227
|
+
return { kind: 'rejected', error: message, notify }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const restartRequired = restartRequiredChanges(this.startup, candidate)
|
|
231
|
+
const effectiveChanged = !same(this.snapshot.config, config)
|
|
232
|
+
try {
|
|
233
|
+
await this.publish({ content, effectiveConfig: config, restartRequired, effectiveChanged })
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
kind: 'publication-failed',
|
|
237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.acceptedContent = content
|
|
242
|
+
this.rejectedContent = undefined
|
|
243
|
+
this.snapshot = {
|
|
244
|
+
config,
|
|
245
|
+
resolver,
|
|
246
|
+
revision: this.snapshot.revision + 1,
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
kind: 'adopted',
|
|
250
|
+
snapshot: this.snapshot,
|
|
251
|
+
restartRequired,
|
|
252
|
+
effectiveChanged,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autobuild.toml loading (SPEC §16.1, D9).
|
|
3
|
+
*
|
|
4
|
+
* Branch-owned worktree config and supplied immutable text are parsed as
|
|
5
|
+
* one-shot inputs: `parseConfig(text)` is the primary API, while
|
|
6
|
+
* `loadConfig(path)` is a disk convenience for scoped CLI and local tooling.
|
|
7
|
+
* The running dispatcher has a separate live-reload context: `LiveConfig`
|
|
8
|
+
* reads the main checkout, retains its last valid snapshot across rejected
|
|
9
|
+
* candidates, and lets each action capture the current snapshot at its boundary.
|
|
10
|
+
*/
|
|
11
|
+
import { parse as parseToml, TomlError } from 'smol-toml'
|
|
12
|
+
import type { z } from 'zod'
|
|
13
|
+
import { expandIssues } from '../zod-issues'
|
|
14
|
+
import { configSchema, TOP_LEVEL_SCALARS, TOP_LEVEL_TABLES, type Config } from './schema'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Config failures are feedback to whoever edits the file: the message carries
|
|
18
|
+
* the source name, the path of every problem, and what would be accepted.
|
|
19
|
+
*/
|
|
20
|
+
export class ConfigError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
message: string,
|
|
23
|
+
readonly issues?: unknown,
|
|
24
|
+
) {
|
|
25
|
+
super(message)
|
|
26
|
+
this.name = 'ConfigError'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatPath(path: ReadonlyArray<PropertyKey>): string {
|
|
31
|
+
if (path.length === 0) return '(top level)'
|
|
32
|
+
return path
|
|
33
|
+
.map((segment, index) =>
|
|
34
|
+
typeof segment === 'number'
|
|
35
|
+
? `[${segment}]`
|
|
36
|
+
: index === 0
|
|
37
|
+
? String(segment)
|
|
38
|
+
: `.${String(segment)}`,
|
|
39
|
+
)
|
|
40
|
+
.join('')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function describeIssue(issue: z.core.$ZodIssue): string {
|
|
44
|
+
let message = issue.message
|
|
45
|
+
if (issue.code === 'unrecognized_keys' && issue.path.length === 0) {
|
|
46
|
+
if (issue.keys.includes('agent')) {
|
|
47
|
+
message +=
|
|
48
|
+
' — [agent] was removed; move those fields to [roles.default] ' +
|
|
49
|
+
'(the default entry in [roles])'
|
|
50
|
+
}
|
|
51
|
+
if (issue.keys.includes('server')) {
|
|
52
|
+
message +=
|
|
53
|
+
' — [server] was removed; arrange any application lifecycle inside the verify command or skill'
|
|
54
|
+
}
|
|
55
|
+
message +=
|
|
56
|
+
` — known top-level keys: ${TOP_LEVEL_SCALARS.join(', ')}; ` +
|
|
57
|
+
`known tables: ${TOP_LEVEL_TABLES.join(', ')}`
|
|
58
|
+
}
|
|
59
|
+
const removedNeedsServer =
|
|
60
|
+
(issue.code === 'unrecognized_keys' && issue.keys.includes('needsServer')) ||
|
|
61
|
+
message.includes('"needsServer"')
|
|
62
|
+
if (removedNeedsServer) {
|
|
63
|
+
message +=
|
|
64
|
+
" — needsServer was removed; arrange any application lifecycle inside this step's command or skill"
|
|
65
|
+
}
|
|
66
|
+
const path =
|
|
67
|
+
removedNeedsServer && issue.path.at(-1) !== 'needsServer'
|
|
68
|
+
? [...issue.path, 'needsServer']
|
|
69
|
+
: issue.path
|
|
70
|
+
return ` ${formatPath(path)}: ${message}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseConfig(tomlText: string, source = 'autobuild.toml'): Config {
|
|
74
|
+
let table: unknown
|
|
75
|
+
try {
|
|
76
|
+
table = parseToml(tomlText)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof TomlError) {
|
|
79
|
+
throw new ConfigError(`${source}: TOML syntax error: ${error.message}`)
|
|
80
|
+
}
|
|
81
|
+
throw error
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = configSchema.safeParse(table)
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
// Expanded for the operator, raw for the payload: the expanded copies carry
|
|
87
|
+
// synthesized `option N of M:` messages, while `ConfigError.issues` should
|
|
88
|
+
// stay what validation actually reported.
|
|
89
|
+
const details = expandIssues(result.error.issues).map(describeIssue).join('\n')
|
|
90
|
+
throw new ConfigError(`${source}: invalid config\n${details}`, result.error.issues)
|
|
91
|
+
}
|
|
92
|
+
return result.data
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function loadConfig(path: string): Promise<Config> {
|
|
96
|
+
const text = await Bun.file(path).text()
|
|
97
|
+
return parseConfig(text, path)
|
|
98
|
+
}
|