@defrex/autobuild 0.8.0 → 0.9.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.
Files changed (147) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +313 -26
  3. package/bin/ab-build-runner.ts +8 -2
  4. package/docs/architecture.md +57 -14
  5. package/docs/assets/headline-wide.png +0 -0
  6. package/docs/configuration.md +151 -25
  7. package/docs/hosted-dispatcher.md +69 -13
  8. package/docs/operator-api.md +2 -2
  9. package/docs/remote-store-protocol.md +169 -16
  10. package/docs/remote-ticket-protocol.md +1 -1
  11. package/docs/setup.md +12 -9
  12. package/docs/website.md +10 -0
  13. package/package.json +43 -23
  14. package/packages/core/src/cli/artifact.ts +130 -6
  15. package/packages/core/src/cli/binary.ts +9 -1
  16. package/packages/core/src/cli/build-control.ts +110 -13
  17. package/packages/core/src/cli/context.ts +46 -0
  18. package/packages/core/src/cli/dashboard/model.ts +57 -7
  19. package/packages/core/src/cli/dashboard/poll.ts +69 -10
  20. package/packages/core/src/cli/dashboard/render.ts +34 -5
  21. package/packages/core/src/cli/dispatch-frontend.ts +54 -5
  22. package/packages/core/src/cli/dispatch.ts +320 -46
  23. package/packages/core/src/cli/harvest.ts +122 -8
  24. package/packages/core/src/cli/help.ts +47 -8
  25. package/packages/core/src/cli/init-readiness-remote.ts +110 -0
  26. package/packages/core/src/cli/init-readiness-shared.ts +72 -0
  27. package/packages/core/src/cli/init-validation.ts +248 -222
  28. package/packages/core/src/cli/main.ts +70 -11
  29. package/packages/core/src/cli/mcp.ts +23 -1
  30. package/packages/core/src/cli/plugin.ts +7 -4
  31. package/packages/core/src/cli/remote-poll.ts +277 -0
  32. package/packages/core/src/cli/repo-state.ts +22 -0
  33. package/packages/core/src/cli/repository-status.ts +39 -1
  34. package/packages/core/src/cli/status.ts +149 -27
  35. package/packages/core/src/cli/terminals.ts +2 -1
  36. package/packages/core/src/cli/ticket-assets.ts +247 -0
  37. package/packages/core/src/cli/ticket.ts +307 -7
  38. package/packages/core/src/cli/wait.ts +147 -39
  39. package/packages/core/src/cli/watch.ts +128 -166
  40. package/packages/core/src/config/live.ts +22 -0
  41. package/packages/core/src/config/pipeline-source.ts +376 -0
  42. package/packages/core/src/config/schema.ts +230 -19
  43. package/packages/core/src/events/catalog.ts +1 -0
  44. package/packages/core/src/events/globs.ts +47 -0
  45. package/packages/core/src/events/payloads.ts +52 -2
  46. package/packages/core/src/events/repository.ts +52 -1
  47. package/packages/core/src/events/sessions.ts +69 -10
  48. package/packages/core/src/events/workspace-remote.ts +12 -0
  49. package/packages/core/src/integration/harness.ts +2 -1
  50. package/packages/core/src/kernel/auto-merge-default.ts +123 -0
  51. package/packages/core/src/kernel/phases.ts +16 -2
  52. package/packages/core/src/kernel/reducer.ts +40 -1
  53. package/packages/core/src/operator/annotations.ts +6 -0
  54. package/packages/core/src/operator/control.ts +19 -5
  55. package/packages/core/src/operator/index.ts +71 -6
  56. package/packages/core/src/operator/query.ts +208 -19
  57. package/packages/core/src/operator/registry.ts +55 -10
  58. package/packages/core/src/operator/requests.ts +9 -6
  59. package/packages/core/src/operator/sandbox-host.ts +140 -0
  60. package/packages/core/src/operator/sandbox.ts +247 -14
  61. package/packages/core/src/operator/ticket-source-backend.ts +47 -0
  62. package/packages/core/src/orchestrator/approvals.ts +115 -0
  63. package/packages/core/src/orchestrator/conversation.ts +167 -0
  64. package/packages/core/src/orchestrator/model.ts +86 -0
  65. package/packages/core/src/orchestrator/prompt.ts +61 -0
  66. package/packages/core/src/orchestrator/tools.ts +117 -0
  67. package/packages/core/src/orchestrator/turn-runner.ts +457 -0
  68. package/packages/core/src/plugin-sdk/index.ts +51 -1
  69. package/packages/core/src/plugins/load.ts +135 -7
  70. package/packages/core/src/plugins/manifest.ts +183 -10
  71. package/packages/core/src/plugins/registry.ts +73 -4
  72. package/packages/core/src/ports/forge/create.ts +1 -1
  73. package/packages/core/src/ports/forge/fake.ts +31 -0
  74. package/packages/core/src/ports/forge/github-transport.ts +13 -2
  75. package/packages/core/src/ports/forge/github.ts +13 -2
  76. package/packages/core/src/ports/forge/local-git.ts +31 -1
  77. package/packages/core/src/ports/runner/claude.ts +2 -2
  78. package/packages/core/src/ports/runner/codex.ts +6 -2
  79. package/packages/core/src/ports/runner/fake.ts +7 -1
  80. package/packages/core/src/ports/runner/pi.ts +8 -6
  81. package/packages/core/src/ports/runner/provider-error.ts +13 -3
  82. package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
  83. package/packages/core/src/ports/types.ts +17 -2
  84. package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
  85. package/packages/core/src/ports/workspace/create.ts +74 -39
  86. package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
  87. package/packages/core/src/ports/workspace/fake.ts +169 -4
  88. package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
  89. package/packages/core/src/ports/workspace/github-origin.ts +36 -0
  90. package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
  91. package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
  92. package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
  93. package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
  94. package/packages/core/src/processes/build-child.ts +17 -2
  95. package/packages/core/src/processes/build-execution-state.ts +37 -0
  96. package/packages/core/src/processes/build-runner.ts +54 -3
  97. package/packages/core/src/processes/dispatcher.ts +247 -41
  98. package/packages/core/src/processes/harvest-child.ts +5 -0
  99. package/packages/core/src/processes/harvest-runner.ts +66 -19
  100. package/packages/core/src/processes/harvest.ts +196 -24
  101. package/packages/core/src/processes/orchestrator-tick.ts +319 -0
  102. package/packages/core/src/processes/sandbox-state.ts +10 -0
  103. package/packages/core/src/store/adapter.ts +32 -0
  104. package/packages/core/src/store/build-scope.ts +64 -0
  105. package/packages/core/src/store/contract.ts +868 -0
  106. package/packages/core/src/store/digest.ts +49 -0
  107. package/packages/core/src/store/local/schema.ts +29 -2
  108. package/packages/core/src/store/local/store.ts +338 -4
  109. package/packages/core/src/store/memory.ts +179 -0
  110. package/packages/core/src/store/new-build.ts +17 -0
  111. package/packages/core/src/store/phase-session.ts +71 -0
  112. package/packages/core/src/store/remote/client.ts +260 -1
  113. package/packages/core/src/store/remote/index.ts +12 -2
  114. package/packages/core/src/store/remote/protocol.ts +100 -12
  115. package/packages/core/src/store/remote/version.ts +6 -2
  116. package/packages/core/src/store/repo-state-events.ts +140 -0
  117. package/packages/core/src/store/retention.ts +7 -0
  118. package/packages/core/src/store/session-handle.ts +64 -0
  119. package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +28 -9
  120. package/packages/core/src/store/streams/session-writer.ts +42 -2
  121. package/packages/core/src/store/streams/wait.ts +4 -5
  122. package/packages/core/src/store/ticket-assets.ts +425 -0
  123. package/packages/core/src/store/types.ts +122 -1
  124. package/packages/core/src/testing/index.ts +59 -0
  125. package/skills/code-review/SKILL.md +5 -1
  126. package/skills/guide/SKILL.md +126 -33
  127. package/skills/guide/references/operator-api.md +2 -2
  128. package/skills/guide/references/plugin-authoring.md +17 -0
  129. package/skills/guide/references/remote-store-protocol.md +169 -16
  130. package/skills/guide/references/remote-ticket-protocol.md +1 -1
  131. package/skills/guide/references/setup.md +12 -9
  132. package/skills/implement/SKILL.md +3 -0
  133. package/skills/operate/SKILL.md +126 -0
  134. package/skills/plan/SKILL.md +4 -0
  135. package/skills/plan-review/SKILL.md +5 -1
  136. package/skills/spec/SKILL.md +46 -3
  137. package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
  138. package/packages/core/src/cli/store-opening.contract.ts +0 -289
  139. package/packages/core/src/cli/testkit.ts +0 -194
  140. package/packages/core/src/markdown.ts +0 -67
  141. package/packages/core/src/operator/client.ts +0 -311
  142. package/packages/core/src/operator/server.ts +0 -616
  143. package/packages/core/src/operator/sessions.ts +0 -223
  144. package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
  145. package/packages/core/src/ports/tickets/remote-server.ts +0 -185
  146. package/packages/core/src/store/remote/server.ts +0 -923
  147. package/packages/core/src/testing/store-failures.ts +0 -32
@@ -1,5 +1,7 @@
1
1
  import type { Config } from '../config/schema'
2
2
  import { configSchema } from '../config/schema'
3
+ import { BUILD_OWNED_CONFIG_PATHS } from '../config/live'
4
+ import { isPipelineSourceRef, type PipelineSourceMeta } from '../config/pipeline-source'
3
5
  import type { AbEvent } from '../events/catalog'
4
6
  import type { Artifact, ArtifactInput } from '../store/types'
5
7
 
@@ -46,6 +48,41 @@ export function parseEffectiveBuildConfig(artifact: Artifact): Config {
46
48
  return result.data
47
49
  }
48
50
 
51
+ /** Read the artifact metadata's revision + pipeline-source projection. Old
52
+ * deposits (and externally written ones) carry no `pipelineSource`; status and
53
+ * the dashboard render that as "unknown (pre-pin)" instead of guessing. */
54
+ export function parseBuildConfigMetadata(artifact: Artifact): {
55
+ revision?: number
56
+ pipelineSource?: PipelineSourceMeta
57
+ } {
58
+ const metadata = artifact.meta.metadata
59
+ const revision = metadata.revision
60
+ const raw = metadata.pipelineSource
61
+ let pipelineSource: PipelineSourceMeta | undefined
62
+ if (typeof raw === 'object' && raw !== null) {
63
+ const value = raw as Record<string, unknown>
64
+ if (isPipelineSourceRef(value.ref)) {
65
+ pipelineSource = {
66
+ ref: value.ref,
67
+ ...(typeof value.commit === 'string' ? { commit: value.commit } : {}),
68
+ }
69
+ }
70
+ }
71
+ return {
72
+ ...(typeof revision === 'number' ? { revision } : {}),
73
+ ...(pipelineSource !== undefined ? { pipelineSource } : {}),
74
+ }
75
+ }
76
+
77
+ /** True when two configs' build-owned sections differ — the guest's defensive
78
+ * guard against a deposit that rewrites the pinned pipeline without a recorded
79
+ * pipeline-source commit advance. */
80
+ export function buildOwnedSectionsDiffer(a: Config, b: Config): boolean {
81
+ return BUILD_OWNED_CONFIG_PATHS.some(
82
+ (path) => JSON.stringify(a[path]) !== JSON.stringify(b[path]),
83
+ )
84
+ }
85
+
49
86
  /** Latest workspace location not followed by release. Historical events use
50
87
  * the provider ref as the path compatibility fallback. */
51
88
  export function selectOpenWorkspace(events: readonly AbEvent[]): {
@@ -48,6 +48,7 @@ import type { Config } from '../config/schema'
48
48
  import type { AbEvent, EventWrite } from '../events/catalog'
49
49
  import type { EventPayload } from '../events/payloads'
50
50
  import { KERNEL, agentActor, type Actor } from '../events/envelope'
51
+ import { isRemoteWorkspace } from '../events/workspace-remote'
51
52
  import type { IdSource } from '../ids'
52
53
  import { decideNext, type Decision } from '../kernel/engine'
53
54
  import { PHASE_SPECS } from '../kernel/phases'
@@ -240,6 +241,12 @@ interface SessionSpec {
240
241
  /** Longest tail of check output preserved in a verify report (§8.2). */
241
242
  const REPORT_TAIL_CHARS = 10_000
242
243
 
244
+ /** Per-section cap for the finalize preflight's dirty-worktree diff capture:
245
+ * each diff view (working tree, staged) is truncated at this many characters
246
+ * so a large dirty diff cannot bloat the failure record unboundedly — worst
247
+ * case ≈ porcelain + 2 × (cap + header). */
248
+ const FINALIZE_DIFF_CAPTURE_CAP = 20_000
249
+
243
250
  /**
244
251
  * One session bracket's live stream (SPEC §9): the sink the build-runner
245
252
  * opened through the resolved target's streaming capability, the store id it
@@ -260,6 +267,14 @@ function errorMessage(error: unknown): string {
260
267
  return error instanceof Error ? error.message : String(error)
261
268
  }
262
269
 
270
+ /** Bound one captured diff section: unchanged within the cap, otherwise the
271
+ * first FINALIZE_DIFF_CAPTURE_CAP characters plus an explicit truncation
272
+ * marker, so truncation is visible in the record itself. */
273
+ function capDiffOutput(output: string): string {
274
+ if (output.length <= FINALIZE_DIFF_CAPTURE_CAP) return output
275
+ return `${output.slice(0, FINALIZE_DIFF_CAPTURE_CAP)}\n... (truncated at ${FINALIZE_DIFF_CAPTURE_CAP} characters)`
276
+ }
277
+
263
278
  function sessionBudgetError(seconds: number): string {
264
279
  return `phase session budget expired after ${seconds} seconds`
265
280
  }
@@ -1137,8 +1152,7 @@ export class BuildRunner {
1137
1152
  )
1138
1153
  if (scratchPaths.length > 0) throw phaseScratchRejection(scratchPaths)
1139
1154
  const remote = events.some(
1140
- (event) =>
1141
- event.type === 'workspace.provisioned' && event.payload.provider === 'vercel-sandbox',
1155
+ (event) => event.type === 'workspace.provisioned' && isRemoteWorkspace(event.payload),
1142
1156
  )
1143
1157
  if (remote) {
1144
1158
  await this.deps.store.append(this.deps.slug, {
@@ -1490,13 +1504,50 @@ export class BuildRunner {
1490
1504
  )
1491
1505
  }
1492
1506
  if (result.stdout.length > 0) {
1507
+ // The failure record must characterize the dirtiness on its own: the
1508
+ // build workspace is disposable, and porcelain lists paths and status
1509
+ // codes only — a mode-only flip (AUT-439) is indistinguishable from a
1510
+ // content modification once the workspace is gone. Append both diff
1511
+ // views so the record shows what actually changed.
1493
1512
  throw new Error(
1494
1513
  `workspace must be clean ${when}; commit intended files and remove ` +
1495
- `temporary files before finishing:\n${result.stdout.trimEnd()}`,
1514
+ `temporary files before finishing:\n${result.stdout.trimEnd()}\n\n` +
1515
+ (await this.collectWorktreeDiffSections()),
1496
1516
  )
1497
1517
  }
1498
1518
  }
1499
1519
 
1520
+ /** Labeled working-tree and staged diff sections for a dirty finalize
1521
+ * preflight record. Runs only on the dirty branch (a clean worktree or a
1522
+ * failed status command never executes a diff), caps each section at
1523
+ * FINALIZE_DIFF_CAPTURE_CAP characters, and is best-effort: a failing `git
1524
+ * diff` command annotates its own section instead of masking the
1525
+ * dirty-worktree failure it is annotating. */
1526
+ private async collectWorktreeDiffSections(): Promise<string> {
1527
+ const views = [
1528
+ { args: ['git', 'diff'], header: '--- git diff (working tree) ---' },
1529
+ { args: ['git', 'diff', '--cached'], header: '--- git diff --cached (staged) ---' },
1530
+ ] as const
1531
+ const sections: string[] = []
1532
+ for (const { args, header } of views) {
1533
+ const command = args.join(' ')
1534
+ let body: string
1535
+ try {
1536
+ const result = await this.deps.exec([...args], { cwd: this.deps.workspacePath })
1537
+ if (result.exitCode !== 0) {
1538
+ const detail = result.stderr.trim() || result.stdout.trim() || '(no output)'
1539
+ body = `(${command} failed: exit ${result.exitCode}: ${capDiffOutput(detail)})`
1540
+ } else {
1541
+ body = capDiffOutput(result.stdout) || '(no output)'
1542
+ }
1543
+ } catch (error) {
1544
+ body = `(${command} failed: ${capDiffOutput(errorMessage(error))})`
1545
+ }
1546
+ sections.push(`${header}\n${body}`)
1547
+ }
1548
+ return sections.join('\n\n')
1549
+ }
1550
+
1500
1551
  private async resolveFinalizeHead(): Promise<string> {
1501
1552
  const args = ['git', 'rev-parse', 'HEAD']
1502
1553
  const result = await this.deps.exec(args, { cwd: this.deps.workspacePath })