@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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free repository-origin helpers.
|
|
3
|
+
*
|
|
4
|
+
* Repository identity in the BuildStore, dispatcher, `ab builds`,
|
|
5
|
+
* `ab repository status`, and the web app is the repository's normalized
|
|
6
|
+
* origin URL, not a checkout path — a path-based identity makes two hosts
|
|
7
|
+
* disagree about which repository they operate. These helpers live in the
|
|
8
|
+
* kernel layer so ports code (the GitHub REST forge) and the hosted store
|
|
9
|
+
* service can share them without importing the CLI layer.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Location-independent form of a git remote URL, for comparing a recorded
|
|
14
|
+
* repository origin with the origin of the current checkout. Trims; maps
|
|
15
|
+
* scp-like `git@host:path` (and bare `host:path`) remotes and explicit
|
|
16
|
+
* `ssh://`/`git://` URLs to their `https://` spelling; drops credentials;
|
|
17
|
+
* lowercases the host; strips a trailing `.git` and trailing slashes.
|
|
18
|
+
* Anything unparseable — notably a local-path remote — is returned trimmed
|
|
19
|
+
* as-is, so such remotes only ever compare equal to themselves.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeGitRemoteUrl(raw: string): string {
|
|
22
|
+
const trimmed = raw.trim()
|
|
23
|
+
// scp-like syntax: `[user@]host:relative/path`. A colon followed by an
|
|
24
|
+
// absolute path (or a Windows drive letter) is not scp-like — leave it for
|
|
25
|
+
// URL parsing, which fails and falls through to the trimmed passthrough.
|
|
26
|
+
const scp =
|
|
27
|
+
trimmed.includes('://') || /^[A-Za-z]:/.test(trimmed)
|
|
28
|
+
? null
|
|
29
|
+
: /^(?:[^@/]+@)?([^/:]+):([^/].*)$/.exec(trimmed)
|
|
30
|
+
const candidate = scp !== null ? `https://${scp[1]}/${scp[2]}` : trimmed
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(candidate)
|
|
33
|
+
// Only network URLs carry a host; a Windows drive or relative path parses
|
|
34
|
+
// as a scheme-only URL and passes through untouched.
|
|
35
|
+
if (url.hostname === '') return trimmed
|
|
36
|
+
const path = url.pathname.replace(/\.git\/?$/i, '').replace(/\/+$/, '')
|
|
37
|
+
// ssh and git URLs name the same repository as their https spelling, so
|
|
38
|
+
// an ssh-origin host checkout and the sandbox guest's pinned https origin
|
|
39
|
+
// must normalize to one form (the scp-like branch already does).
|
|
40
|
+
const protocol =
|
|
41
|
+
url.protocol === 'ssh:' || url.protocol === 'git:' || url.protocol === 'git+ssh:'
|
|
42
|
+
? 'https:'
|
|
43
|
+
: url.protocol
|
|
44
|
+
return `${protocol}//${url.host.toLowerCase()}${path}`
|
|
45
|
+
} catch {
|
|
46
|
+
return trimmed
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether `ref` is a valid exact Git branch name, by git-check-ref-format(1)'s
|
|
52
|
+
* rules: no leading `-`, no `..`, none of `~^:?*[\` anywhere, no ASCII
|
|
53
|
+
* control characters or space, no `@{`, not bare `@`, no trailing `.lock`
|
|
54
|
+
* or `.`, and no empty slash-delimited components. The REST forge uses this
|
|
55
|
+
* to reject a branch name before interpolating it into a ref endpoint,
|
|
56
|
+
* replacing the host `git check-ref-format` subprocess.
|
|
57
|
+
*/
|
|
58
|
+
export function isValidGitBranchName(branch: string): boolean {
|
|
59
|
+
if (branch === '' || branch.startsWith('-')) return false
|
|
60
|
+
if (branch.endsWith('.lock') || branch.endsWith('.')) return false
|
|
61
|
+
if (branch.includes('..') || branch.includes('@{') || branch === '@') return false
|
|
62
|
+
for (const char of branch) {
|
|
63
|
+
const code = char.codePointAt(0)!
|
|
64
|
+
if (code < 0x20 || code === 0x7f) return false
|
|
65
|
+
if ('~^:?*[\\ '.includes(char)) return false
|
|
66
|
+
}
|
|
67
|
+
// No empty slash-delimited component (`a//b`, leading/trailing slash).
|
|
68
|
+
return branch.split('/').every((component) => component !== '')
|
|
69
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The phase table (SPEC §4): a phase's name derives its skill, its events,
|
|
3
|
+
* and its artifact kind — mechanically. This table also encodes each phase's
|
|
4
|
+
* terminal discipline (D5), required deposits, context inputs (§8.3), and
|
|
5
|
+
* plumbing (D7), so the CLI, the context builder, and the engine all validate
|
|
6
|
+
* against the same data.
|
|
7
|
+
*/
|
|
8
|
+
import { isVerifyPhase, type CorePhase, type Phase } from '../ontology'
|
|
9
|
+
import type { EventType } from '../events/payloads'
|
|
10
|
+
import { installedSkillName } from '../skills'
|
|
11
|
+
|
|
12
|
+
export type PhaseKind = 'producer' | 'review' | 'agent-verify'
|
|
13
|
+
export type TerminalCommand = 'done' | 'verdict' | 'escalate'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Kernel-side plumbing triggered by the phase's terminal (D7, §8.6):
|
|
17
|
+
* agents only ever commit locally; push/PR happen at the boundary.
|
|
18
|
+
*/
|
|
19
|
+
export type PhasePlumbing = 'push-branch' | 'open-pr' | 'push-merge-commit'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* What `ab context` materializes for the phase (§8.3). Scoping is
|
|
23
|
+
* deliberate: what a phase *can't* see is part of its design.
|
|
24
|
+
*/
|
|
25
|
+
export interface ContextInputs {
|
|
26
|
+
ticket?: boolean
|
|
27
|
+
spec?: boolean
|
|
28
|
+
plan?: 'latest' | 'approved'
|
|
29
|
+
/**
|
|
30
|
+
* `current`: this round's feedback only (producers).
|
|
31
|
+
* `all-rounds`: every prior round's findings, for `persists` marking
|
|
32
|
+
* (reviewers — §15.4).
|
|
33
|
+
*/
|
|
34
|
+
findings?: 'current' | 'all-rounds'
|
|
35
|
+
/** Feedback cited by this exact phase occurrence's started event. Agent
|
|
36
|
+
* verify uses this for answered guidance without widening findings input. */
|
|
37
|
+
currentFeedback?: boolean
|
|
38
|
+
/** Producer's own prior-round artifacts (plan revs / implement notes). */
|
|
39
|
+
priorOwnArtifacts?: boolean
|
|
40
|
+
commitRange?: boolean
|
|
41
|
+
implementNotes?: boolean
|
|
42
|
+
verifyReports?: boolean
|
|
43
|
+
/** Reconcile: the fresh `{baseSha}` from this attempt's `reconcile.started` (§15.7). */
|
|
44
|
+
conflict?: boolean
|
|
45
|
+
prTemplate?: boolean
|
|
46
|
+
/** Agent-verify: the step's config from autobuild.toml (§16.1). */
|
|
47
|
+
stepConfig?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* Answered guidance escalations addressed to THIS phase (§15.6-B, §11).
|
|
50
|
+
* Finalize and reconcile have no producer round for the engine to feed the
|
|
51
|
+
* answer into, so `ab context` is their delivery channel — the engine's
|
|
52
|
+
* loop routing (loopOfPhase → 'other') relies on this input existing.
|
|
53
|
+
*/
|
|
54
|
+
answeredGuidance?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PhaseSpec {
|
|
58
|
+
name: CorePhase | 'verify'
|
|
59
|
+
kind: PhaseKind
|
|
60
|
+
/** Installed, namespaced skill name. For verify steps the actual skill
|
|
61
|
+
* comes from step config (§16.1). */
|
|
62
|
+
skill: string
|
|
63
|
+
startedEvent: EventType
|
|
64
|
+
terminal: 'done' | 'verdict'
|
|
65
|
+
terminalEvent: EventType
|
|
66
|
+
/**
|
|
67
|
+
* Artifact kind the terminal requires deposited (D5). For agent-verify
|
|
68
|
+
* steps the report kind is per-step (`verify-report:<step>`) and required
|
|
69
|
+
* only on `fail` — enforced by the CLI, not listed here.
|
|
70
|
+
*/
|
|
71
|
+
requiredArtifact?: string
|
|
72
|
+
/** Verdict vocabulary the CLI enforces (§8.2). */
|
|
73
|
+
verdictVocabulary?: readonly string[]
|
|
74
|
+
/** `implement` only: no `done` on a dirty worktree (D5). */
|
|
75
|
+
requiresCleanWorktree?: boolean
|
|
76
|
+
plumbing?: PhasePlumbing
|
|
77
|
+
inputs: ContextInputs
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const PHASE_SPECS: Record<CorePhase | 'verify', PhaseSpec> = {
|
|
81
|
+
plan: {
|
|
82
|
+
name: 'plan',
|
|
83
|
+
kind: 'producer',
|
|
84
|
+
skill: installedSkillName('plan'),
|
|
85
|
+
startedEvent: 'plan.started',
|
|
86
|
+
terminal: 'done',
|
|
87
|
+
terminalEvent: 'plan.completed',
|
|
88
|
+
requiredArtifact: 'plan',
|
|
89
|
+
inputs: {
|
|
90
|
+
ticket: true,
|
|
91
|
+
spec: true,
|
|
92
|
+
priorOwnArtifacts: true,
|
|
93
|
+
findings: 'current',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
'plan-review': {
|
|
97
|
+
name: 'plan-review',
|
|
98
|
+
kind: 'review',
|
|
99
|
+
skill: installedSkillName('plan-review'),
|
|
100
|
+
startedEvent: 'plan-review.started',
|
|
101
|
+
terminal: 'verdict',
|
|
102
|
+
terminalEvent: 'plan-review.verdict',
|
|
103
|
+
requiredArtifact: 'plan-review',
|
|
104
|
+
verdictVocabulary: ['approve', 'revise', 'escalate'],
|
|
105
|
+
inputs: { spec: true, plan: 'latest', findings: 'all-rounds' },
|
|
106
|
+
},
|
|
107
|
+
implement: {
|
|
108
|
+
name: 'implement',
|
|
109
|
+
kind: 'producer',
|
|
110
|
+
skill: installedSkillName('implement'),
|
|
111
|
+
startedEvent: 'implement.started',
|
|
112
|
+
terminal: 'done',
|
|
113
|
+
terminalEvent: 'implement.completed',
|
|
114
|
+
requiredArtifact: 'implement-notes',
|
|
115
|
+
requiresCleanWorktree: true,
|
|
116
|
+
plumbing: 'push-branch',
|
|
117
|
+
inputs: {
|
|
118
|
+
spec: true,
|
|
119
|
+
plan: 'approved',
|
|
120
|
+
findings: 'current',
|
|
121
|
+
verifyReports: true,
|
|
122
|
+
priorOwnArtifacts: true,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
'code-review': {
|
|
126
|
+
name: 'code-review',
|
|
127
|
+
kind: 'review',
|
|
128
|
+
skill: installedSkillName('code-review'),
|
|
129
|
+
startedEvent: 'code-review.started',
|
|
130
|
+
terminal: 'verdict',
|
|
131
|
+
terminalEvent: 'code-review.verdict',
|
|
132
|
+
requiredArtifact: 'code-review',
|
|
133
|
+
verdictVocabulary: ['approve', 'revise', 'escalate'],
|
|
134
|
+
inputs: {
|
|
135
|
+
spec: true,
|
|
136
|
+
plan: 'approved',
|
|
137
|
+
commitRange: true,
|
|
138
|
+
findings: 'all-rounds',
|
|
139
|
+
implementNotes: true,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
verify: {
|
|
143
|
+
name: 'verify',
|
|
144
|
+
kind: 'agent-verify',
|
|
145
|
+
skill: installedSkillName('verify'),
|
|
146
|
+
startedEvent: 'verify.started',
|
|
147
|
+
terminal: 'verdict',
|
|
148
|
+
terminalEvent: 'verify.completed',
|
|
149
|
+
verdictVocabulary: ['pass', 'fail', 'skip'],
|
|
150
|
+
inputs: { spec: true, stepConfig: true, commitRange: true, currentFeedback: true },
|
|
151
|
+
},
|
|
152
|
+
finalize: {
|
|
153
|
+
name: 'finalize',
|
|
154
|
+
kind: 'producer',
|
|
155
|
+
skill: installedSkillName('finalize'),
|
|
156
|
+
startedEvent: 'finalize.started',
|
|
157
|
+
terminal: 'done',
|
|
158
|
+
terminalEvent: 'finalize.completed',
|
|
159
|
+
requiredArtifact: 'pr-description',
|
|
160
|
+
plumbing: 'open-pr',
|
|
161
|
+
inputs: {
|
|
162
|
+
spec: true,
|
|
163
|
+
plan: 'approved',
|
|
164
|
+
verifyReports: true,
|
|
165
|
+
prTemplate: true,
|
|
166
|
+
answeredGuidance: true,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
reconcile: {
|
|
170
|
+
name: 'reconcile',
|
|
171
|
+
kind: 'producer',
|
|
172
|
+
skill: installedSkillName('reconcile'),
|
|
173
|
+
startedEvent: 'reconcile.started',
|
|
174
|
+
terminal: 'done',
|
|
175
|
+
terminalEvent: 'reconcile.completed',
|
|
176
|
+
requiredArtifact: 'reconcile-notes',
|
|
177
|
+
plumbing: 'push-merge-commit',
|
|
178
|
+
inputs: {
|
|
179
|
+
spec: true,
|
|
180
|
+
plan: 'approved',
|
|
181
|
+
implementNotes: true,
|
|
182
|
+
conflict: true,
|
|
183
|
+
answeredGuidance: true,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function phaseSpecFor(phase: Phase): PhaseSpec {
|
|
189
|
+
return isVerifyPhase(phase) ? PHASE_SPECS.verify : PHASE_SPECS[phase]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Every phase ends with exactly one terminal command (D5); `escalate` is
|
|
193
|
+
* always available in addition to the phase's own terminal. */
|
|
194
|
+
export function allowedTerminals(phase: Phase): TerminalCommand[] {
|
|
195
|
+
return [phaseSpecFor(phase).terminal, 'escalate']
|
|
196
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { parse as parseToml } from 'smol-toml'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { Config } from '../config/schema'
|
|
4
|
+
|
|
5
|
+
const planMetadataSchema = z.strictObject({
|
|
6
|
+
verifySteps: z.array(
|
|
7
|
+
z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1, 'verify step names must be nonempty')
|
|
10
|
+
.refine((step) => step.trim() === step, 'verify step names must not be blank or padded'),
|
|
11
|
+
),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Read an opening TOML `+++` front-matter block. A document without an
|
|
16
|
+
* opening fence has no metadata; once an opening fence is attempted, malformed
|
|
17
|
+
* fences are errors rather than silently becoming Markdown body text.
|
|
18
|
+
*/
|
|
19
|
+
function parsePlanMetadata(plan: string): z.infer<typeof planMetadataSchema> | undefined {
|
|
20
|
+
if (!plan.startsWith('+++')) return undefined
|
|
21
|
+
|
|
22
|
+
const firstBreak = plan.indexOf('\n')
|
|
23
|
+
const opening = (firstBreak === -1 ? plan : plan.slice(0, firstBreak)).replace(/\r$/, '')
|
|
24
|
+
if (opening !== '+++') {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'plan front matter has a malformed opening fence — expected a line containing only "+++"',
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
if (firstBreak === -1) {
|
|
30
|
+
throw new Error('plan front matter is missing its closing "+++" fence')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const metadataStart = firstBreak + 1
|
|
34
|
+
let cursor = metadataStart
|
|
35
|
+
let metadataEnd: number | undefined
|
|
36
|
+
while (cursor <= plan.length) {
|
|
37
|
+
const nextBreak = plan.indexOf('\n', cursor)
|
|
38
|
+
const lineEnd = nextBreak === -1 ? plan.length : nextBreak
|
|
39
|
+
const line = plan.slice(cursor, lineEnd).replace(/\r$/, '')
|
|
40
|
+
if (line === '+++') {
|
|
41
|
+
metadataEnd = cursor
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
if (nextBreak === -1) break
|
|
45
|
+
cursor = nextBreak + 1
|
|
46
|
+
}
|
|
47
|
+
if (metadataEnd === undefined) {
|
|
48
|
+
throw new Error('plan front matter is missing its closing "+++" fence')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let parsed: unknown
|
|
52
|
+
try {
|
|
53
|
+
parsed = parseToml(plan.slice(metadataStart, metadataEnd))
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
56
|
+
throw new Error(`plan front matter is not valid TOML: ${message}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = planMetadataSchema.safeParse(parsed)
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
const issues = result.error.issues
|
|
62
|
+
.map((issue) => ` ${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
63
|
+
.join('\n')
|
|
64
|
+
throw new Error(`plan front matter is invalid:\n${issues}`)
|
|
65
|
+
}
|
|
66
|
+
return result.data
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a plan's requested verification set against repository config.
|
|
71
|
+
*
|
|
72
|
+
* Missing front matter preserves the historical default (all configured
|
|
73
|
+
* steps). An explicit list is a set, while returned order always comes from
|
|
74
|
+
* `[verify].steps`. `always = true` is the mandatory/non-deselectable marker.
|
|
75
|
+
*/
|
|
76
|
+
export function resolvePlanVerifySteps(plan: string, config: Config): string[] {
|
|
77
|
+
const metadata = parsePlanMetadata(plan)
|
|
78
|
+
if (metadata === undefined) return [...config.verify.steps]
|
|
79
|
+
|
|
80
|
+
const selected = new Set<string>()
|
|
81
|
+
for (const step of metadata.verifySteps) {
|
|
82
|
+
if (selected.has(step)) {
|
|
83
|
+
throw new Error(`plan verifySteps contains duplicate step ${JSON.stringify(step)}`)
|
|
84
|
+
}
|
|
85
|
+
selected.add(step)
|
|
86
|
+
|
|
87
|
+
if (!config.verify.steps.includes(step) || !Object.hasOwn(config.verify.stepConfigs, step)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`plan verifySteps names unknown step ${JSON.stringify(step)} — ` +
|
|
90
|
+
`no [verify.${step}] table is configured`,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const step of config.verify.steps) {
|
|
96
|
+
if (config.verify.stepConfigs[step]?.always === true && !selected.has(step)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`plan verifySteps omits mandatory step ${JSON.stringify(step)} — ` +
|
|
99
|
+
`[verify.${step}].always = true steps cannot be deselected`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return config.verify.steps.filter((step) => selected.has(step))
|
|
105
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { AbEvent } from '../events/catalog'
|
|
2
|
+
import type { PrImageHostTarget } from '../ontology'
|
|
3
|
+
|
|
4
|
+
export type PrAttachmentDesignationEvent = Extract<AbEvent, { type: 'pr-attachment.designated' }>
|
|
5
|
+
export type PrAttachmentHostedEvent = Extract<AbEvent, { type: 'pr-attachment.hosted' }>
|
|
6
|
+
|
|
7
|
+
/** The host consent frozen into the build at claim time. */
|
|
8
|
+
export function frozenPrImageHost(events: readonly AbEvent[]): PrImageHostTarget | undefined {
|
|
9
|
+
let created: Extract<AbEvent, { type: 'build.created' }> | undefined
|
|
10
|
+
for (const event of events) {
|
|
11
|
+
if (event.type === 'build.created' && (created === undefined || event.seq < created.seq)) {
|
|
12
|
+
created = event
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return created?.payload.pr?.imageHost
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Current PR attachments are explicit designations after the latest spec
|
|
20
|
+
* restart, with the newest exact revision replacing earlier designations of
|
|
21
|
+
* the same artifact kind. Distinct kinds remain distinct attachments.
|
|
22
|
+
*/
|
|
23
|
+
export function currentPrAttachments(events: readonly AbEvent[]): PrAttachmentDesignationEvent[] {
|
|
24
|
+
let restartSeq = 0
|
|
25
|
+
for (const event of events) {
|
|
26
|
+
if (event.type === 'spec.revised') restartSeq = Math.max(restartSeq, event.seq)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const byKind = new Map<string, PrAttachmentDesignationEvent>()
|
|
30
|
+
for (const event of events) {
|
|
31
|
+
if (event.type !== 'pr-attachment.designated' || event.seq <= restartSeq) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
const previous = byKind.get(event.payload.artifact.kind)
|
|
35
|
+
if (previous === undefined || event.seq > previous.seq) {
|
|
36
|
+
byKind.set(event.payload.artifact.kind, event)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return [...byKind.values()].sort((left, right) => left.seq - right.seq)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Correlate one durable hosted copy to each current designation. Unknown,
|
|
44
|
+
* backwards, and stale correlations are ignored rather than throwing so the
|
|
45
|
+
* selector remains total over any structurally valid event ordering.
|
|
46
|
+
*/
|
|
47
|
+
export function hostedPrAttachments(
|
|
48
|
+
events: readonly AbEvent[],
|
|
49
|
+
designations = currentPrAttachments(events),
|
|
50
|
+
): Map<number, PrAttachmentHostedEvent> {
|
|
51
|
+
const designationBySeq = new Map(designations.map((event) => [event.seq, event]))
|
|
52
|
+
const hosted = new Map<number, PrAttachmentHostedEvent>()
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
if (event.type !== 'pr-attachment.hosted') continue
|
|
55
|
+
const designation = designationBySeq.get(event.payload.designationSeq)
|
|
56
|
+
if (designation === undefined || event.seq <= designation.seq) continue
|
|
57
|
+
const previous = hosted.get(designation.seq)
|
|
58
|
+
if (previous === undefined || event.seq > previous.seq) {
|
|
59
|
+
hosted.set(designation.seq, event)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return hosted
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Every durable hosted copy that has not yet received a later reclaim fact. */
|
|
66
|
+
export function pendingPrAttachmentReclaims(events: readonly AbEvent[]): PrAttachmentHostedEvent[] {
|
|
67
|
+
const hosted = events
|
|
68
|
+
.filter((event): event is PrAttachmentHostedEvent => event.type === 'pr-attachment.hosted')
|
|
69
|
+
.sort((left, right) => left.seq - right.seq)
|
|
70
|
+
const hostedSeqs = new Set(hosted.map((event) => event.seq))
|
|
71
|
+
const reclaimed = new Set<number>()
|
|
72
|
+
for (const event of events) {
|
|
73
|
+
if (
|
|
74
|
+
event.type === 'pr-attachment.reclaimed' &&
|
|
75
|
+
hostedSeqs.has(event.payload.hostedSeq) &&
|
|
76
|
+
event.seq > event.payload.hostedSeq
|
|
77
|
+
) {
|
|
78
|
+
reclaimed.add(event.payload.hostedSeq)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return hosted.filter((event) => !reclaimed.has(event.seq))
|
|
82
|
+
}
|