@gajae-code/coding-agent 0.12.4 → 0.12.6

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 (139) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +4 -1
  3. package/dist/types/async/job-manager.d.ts +9 -1
  4. package/dist/types/eval/backend.d.ts +2 -2
  5. package/dist/types/exec/bash-executor.d.ts +10 -8
  6. package/dist/types/extensibility/extensions/runner.d.ts +9 -7
  7. package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
  8. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  9. package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
  10. package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
  11. package/dist/types/gjc-runtime/team-store.d.ts +0 -4
  12. package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
  13. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  14. package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
  15. package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
  16. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
  17. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
  19. package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
  20. package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
  21. package/dist/types/modes/components/plugin-settings.d.ts +2 -0
  22. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  23. package/dist/types/sdk/broker/broker.d.ts +18 -0
  24. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
  25. package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
  26. package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -2
  27. package/dist/types/sdk/bus/index.d.ts +4 -0
  28. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -3
  29. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
  30. package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
  31. package/dist/types/sdk/host/host.d.ts +23 -2
  32. package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
  33. package/dist/types/session/agent-session.d.ts +5 -4
  34. package/dist/types/session/artifacts.d.ts +8 -5
  35. package/dist/types/session/attempt-record-store.d.ts +19 -0
  36. package/dist/types/session/client-bridge.d.ts +14 -0
  37. package/dist/types/session/internal/managed-session-storage.d.ts +7 -23
  38. package/dist/types/session/messages.d.ts +4 -1
  39. package/dist/types/session/session-manager.d.ts +7 -5
  40. package/dist/types/session/session-storage.d.ts +8 -0
  41. package/dist/types/session/streaming-output.d.ts +32 -0
  42. package/dist/types/task/executor.d.ts +1 -1
  43. package/dist/types/task/index.d.ts +2 -1
  44. package/dist/types/task/receipt.d.ts +3 -0
  45. package/dist/types/task/types.d.ts +194 -0
  46. package/dist/types/task/worktree.d.ts +13 -6
  47. package/dist/types/tools/bash.d.ts +1 -1
  48. package/dist/types/tools/fetch.d.ts +3 -0
  49. package/dist/types/tools/output-meta.d.ts +37 -5
  50. package/dist/types/tools/subagent.d.ts +2 -0
  51. package/dist/types/tools/tool-result.d.ts +1 -0
  52. package/dist/types/utils/artifact-id.d.ts +2 -0
  53. package/dist/types/utils/git.d.ts +1 -0
  54. package/package.json +8 -8
  55. package/scripts/capture-sticky-viewport-showcase.ts +131 -5
  56. package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
  57. package/scripts/verify-sticky-viewport-showcase.ts +620 -93
  58. package/src/async/job-manager.ts +13 -5
  59. package/src/config/model-profiles.ts +8 -0
  60. package/src/config/settings-schema.ts +17 -3
  61. package/src/cursor.ts +20 -13
  62. package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
  63. package/src/eval/backend.ts +2 -2
  64. package/src/exec/bash-executor.ts +425 -151
  65. package/src/extensibility/extensions/runner.ts +80 -6
  66. package/src/extensibility/extensions/wrapper.ts +22 -15
  67. package/src/extensibility/runtime-skill-discovery.ts +4 -6
  68. package/src/gjc-runtime/launch-tmux.ts +309 -293
  69. package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
  70. package/src/gjc-runtime/psmux-detect.ts +52 -6
  71. package/src/gjc-runtime/team-launch.ts +50 -2
  72. package/src/gjc-runtime/team-runtime.ts +716 -251
  73. package/src/gjc-runtime/team-store.ts +25 -7
  74. package/src/gjc-runtime/team-workers.ts +161 -1
  75. package/src/gjc-runtime/tmux-common.ts +16 -4
  76. package/src/gjc-runtime/tmux-gc.ts +37 -5
  77. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
  78. package/src/gjc-runtime/tmux-provider-context.ts +451 -0
  79. package/src/gjc-runtime/tmux-sessions.ts +409 -156
  80. package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
  81. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
  82. package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
  83. package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
  84. package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
  85. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  86. package/src/internal-urls/agent-protocol.ts +98 -38
  87. package/src/internal-urls/artifact-protocol.ts +28 -17
  88. package/src/internal-urls/docs-index.generated.ts +6 -6
  89. package/src/internal-urls/local-protocol.ts +39 -18
  90. package/src/internal-urls/registry-helpers.ts +62 -0
  91. package/src/modes/acp/acp-agent.ts +51 -13
  92. package/src/modes/acp/acp-client-bridge.ts +154 -11
  93. package/src/modes/components/gjc-bundle-settings.ts +2 -0
  94. package/src/modes/components/plugin-settings.ts +5 -0
  95. package/src/modes/components/settings-selector.ts +4 -0
  96. package/src/modes/controllers/selector-controller.ts +1 -0
  97. package/src/prompts/tools/bash.md +2 -2
  98. package/src/prompts/tools/monitor.md +4 -4
  99. package/src/sdk/acp/adapter.ts +200 -9
  100. package/src/sdk/broker/broker.ts +93 -10
  101. package/src/sdk/broker/lifecycle-ledger.ts +227 -11
  102. package/src/sdk/broker/lifecycle.ts +692 -98
  103. package/src/sdk/bus/chat-daemon-control.ts +33 -5
  104. package/src/sdk/bus/index.ts +347 -14
  105. package/src/sdk/bus/telegram-daemon-contract.ts +7 -3
  106. package/src/sdk/bus/telegram-daemon.ts +283 -44
  107. package/src/sdk/bus/topic-registry.ts +133 -10
  108. package/src/sdk/host/host.ts +187 -9
  109. package/src/sdk/host/reverse-leases.ts +249 -42
  110. package/src/sdk/session.ts +8 -6
  111. package/src/session/agent-session.ts +930 -354
  112. package/src/session/artifacts.ts +211 -74
  113. package/src/session/attempt-record-store.ts +106 -0
  114. package/src/session/client-bridge.ts +15 -0
  115. package/src/session/internal/managed-session-scope.ts +332 -198
  116. package/src/session/internal/managed-session-storage.ts +162 -450
  117. package/src/session/messages.ts +32 -7
  118. package/src/session/session-manager.ts +31 -28
  119. package/src/session/session-storage.ts +190 -53
  120. package/src/session/streaming-output.ts +198 -40
  121. package/src/task/executor.ts +5 -2
  122. package/src/task/index.ts +277 -64
  123. package/src/task/provider-retry-status.ts +4 -1
  124. package/src/task/receipt.ts +26 -2
  125. package/src/task/render.ts +42 -1
  126. package/src/task/types.ts +52 -0
  127. package/src/task/worktree.ts +122 -43
  128. package/src/tools/bash.ts +818 -239
  129. package/src/tools/eval.ts +99 -4
  130. package/src/tools/fetch.ts +77 -15
  131. package/src/tools/find.ts +7 -3
  132. package/src/tools/monitor.ts +7 -1
  133. package/src/tools/output-meta.ts +706 -116
  134. package/src/tools/search.ts +9 -4
  135. package/src/tools/subagent-render.ts +5 -2
  136. package/src/tools/subagent.ts +8 -0
  137. package/src/tools/tool-result.ts +1 -0
  138. package/src/utils/artifact-id.ts +6 -0
  139. package/src/utils/git.ts +2 -0
@@ -4,10 +4,11 @@
4
4
  * Tools populate details.meta using the fluent OutputMetaBuilder.
5
5
  * The tool wrapper automatically formats and appends notices at message boundary.
6
6
  */
7
- import type { AgentTool } from "@gajae-code/agent-core";
7
+ import type { AgentTool, AgentToolContext, AgentToolResult } from "@gajae-code/agent-core";
8
8
  import { type Settings } from "../config/settings";
9
9
  import type { Theme } from "../modes/theme/theme";
10
10
  import { type OutputSummary, type ReadWindow, type TruncationResult } from "../session/streaming-output";
11
+ export { isValidArtifactId } from "../utils/artifact-id";
11
12
  /**
12
13
  * Truncation metadata for the output notice.
13
14
  */
@@ -48,8 +49,16 @@ export interface TruncationMeta {
48
49
  elidedLines?: number;
49
50
  /** Artifact ID when output was persisted; completeness is tracked separately below. */
50
51
  artifactId?: string;
52
+ /** Artifact existence was proven by a successful save or publisher receipt. */
53
+ artifactVerified?: boolean;
51
54
  /** Bytes omitted from an artifact after its hard storage cap was reached. */
52
55
  artifactTruncatedBytes?: number;
56
+ /** Bytes dropped before Bash received the native output stream. */
57
+ sourceTruncatedBytes?: number;
58
+ /** Exact source capture completeness could not be proven. */
59
+ sourceCaptureIncomplete?: boolean;
60
+ /** Bytes omitted from the visible output by per-line column capping. */
61
+ columnDroppedBytes?: number;
53
62
  /** Bounded diagnostic when artifact writer creation, write, or finalization failed. */
54
63
  artifactFailureDiagnostic?: string;
55
64
  /** Next offset for pagination (head truncation only) */
@@ -103,12 +112,17 @@ export interface OutputMeta {
103
112
  source?: SourceMeta;
104
113
  diagnostics?: DiagnosticMeta;
105
114
  limits?: LimitsMeta;
115
+ /** Diagnostics were omitted to preserve the configured inline byte cap. */
116
+ diagnosticsOmitted?: boolean;
106
117
  }
107
118
  export interface TruncationOptions {
108
119
  direction: "head" | "tail" | "middle";
109
120
  startLine?: number;
110
121
  totalFileLines?: number;
111
122
  artifactId?: string;
123
+ artifactVerified?: boolean;
124
+ artifactFailureDiagnostic?: string;
125
+ sourceCaptureIncomplete?: boolean;
112
126
  maxBytes?: number;
113
127
  noticeOwner?: "body";
114
128
  }
@@ -124,6 +138,8 @@ export interface TruncationTextOptions {
124
138
  totalBytes?: number;
125
139
  maxBytes?: number;
126
140
  artifactId?: string;
141
+ artifactVerified?: boolean;
142
+ sourceCaptureIncomplete?: boolean;
127
143
  noticeOwner?: "body";
128
144
  }
129
145
  /**
@@ -145,6 +161,7 @@ export declare class OutputMetaBuilder {
145
161
  /** Add metadata from the actual head/tail windows retained by middle truncation. */
146
162
  truncationWindows(windows: ReadWindow, options?: {
147
163
  artifactId?: string;
164
+ artifactVerified?: boolean;
148
165
  noticeOwner?: "body";
149
166
  maxBytes?: number;
150
167
  rangeBase?: "file" | "window";
@@ -182,10 +199,24 @@ export declare class OutputMetaBuilder {
182
199
  /** Create a new OutputMetaBuilder. */
183
200
  export declare function outputMeta(): OutputMetaBuilder;
184
201
  export declare function formatFullOutputReference(artifactId: string): string;
185
- /**
186
- * Format an artifact reference without claiming completeness when storage was hard-capped.
187
- */
188
- export declare function formatArtifactReference(artifactId: string, artifactTruncatedBytes?: number): string;
202
+ export interface ArtifactFailurePreview {
203
+ text: string;
204
+ omittedBytes: number;
205
+ }
206
+ /** Build a bounded, UTF-8-safe preview when artifact storage cannot retain the full payload. */
207
+ export declare function createArtifactFailurePreview(fullText: string, maxInlineBytes: number | undefined, diagnostic: string): ArtifactFailurePreview;
208
+ /** Format an artifact reference without claiming completeness when capture or storage omitted bytes. */
209
+ export declare function formatArtifactReference(artifactId: string, artifactTruncatedBytes?: number, sourceTruncatedBytes?: number, sourceCaptureIncomplete?: boolean): string;
210
+ export interface ArtifactEvidence {
211
+ artifactId?: string;
212
+ artifactVerified?: boolean;
213
+ artifactTruncatedBytes?: number;
214
+ sourceTruncatedBytes?: number;
215
+ sourceCaptureIncomplete?: boolean;
216
+ artifactFailureDiagnostic?: string;
217
+ columnDroppedBytes?: number;
218
+ }
219
+ export declare function formatArtifactEvidenceNotice(evidence: ArtifactEvidence): string | undefined;
189
220
  export declare function formatTruncationMetaNotice(truncation: TruncationMeta): string;
190
221
  /**
191
222
  * Format styled artifact reference with warning color and brackets.
@@ -238,6 +269,7 @@ export declare function resolveBashOutputSinkHeadBytes(s: Settings): number;
238
269
  * line-buffer post-processing, so one setting controls both surfaces.
239
270
  */
240
271
  export declare function resolveOutputMaxColumns(s: Settings | undefined): number;
272
+ export declare function finalizeToolResultForDelivery(result: AgentToolResult, toolName: string, context: AgentToolContext | undefined): Promise<AgentToolResult>;
241
273
  /**
242
274
  * Wrap a tool to:
243
275
  * 1. Automatically append output notices based on details.meta
@@ -57,6 +57,8 @@ export interface SubagentSnapshot {
57
57
  requestedModel?: string;
58
58
  /** True when the requested model lacked credentials and fell back to the parent model. */
59
59
  modelFellBack?: boolean;
60
+ /** True when the effective subagent provider is in fast mode. */
61
+ fastMode?: boolean;
60
62
  }
61
63
  export type SubagentAwaitOutcome = "completed" | "timed_out" | "interrupted";
62
64
  export interface SubagentToolDetails {
@@ -14,6 +14,7 @@ export declare class ToolResultBuilder<TDetails extends DetailsWithMeta> {
14
14
  truncation(result: TruncationResult, options: TruncationOptions): this;
15
15
  truncationWindows(windows: ReadWindow, options?: {
16
16
  artifactId?: string;
17
+ artifactVerified?: boolean;
17
18
  noticeOwner?: "body";
18
19
  maxBytes?: number;
19
20
  rangeBase?: "file" | "window";
@@ -0,0 +1,2 @@
1
+ /** Artifact protocol IDs are decimal numbers allocated by the session artifact store. */
2
+ export declare function isValidArtifactId(value: unknown): value is string;
@@ -72,6 +72,7 @@ export interface PushOptions {
72
72
  export interface PatchOptions {
73
73
  readonly cached?: boolean;
74
74
  readonly check?: boolean;
75
+ readonly reverse?: boolean;
75
76
  readonly env?: Record<string, string | undefined>;
76
77
  readonly signal?: AbortSignal;
77
78
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.12.4",
4
+ "version": "0.12.6",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -53,13 +53,13 @@
53
53
  "@agentclientprotocol/sdk": "1.3.0",
54
54
  "@babel/parser": "^7.29.3",
55
55
  "@mozilla/readability": "^0.6.0",
56
- "@gajae-code/stats": "0.12.4",
57
- "@gajae-code/agent-core": "0.12.4",
58
- "@gajae-code/ai": "0.12.4",
59
- "@gajae-code/bridge-client": "0.12.4",
60
- "@gajae-code/natives": "0.12.4",
61
- "@gajae-code/tui": "0.12.4",
62
- "@gajae-code/utils": "0.12.4",
56
+ "@gajae-code/stats": "0.12.6",
57
+ "@gajae-code/agent-core": "0.12.6",
58
+ "@gajae-code/ai": "0.12.6",
59
+ "@gajae-code/bridge-client": "0.12.6",
60
+ "@gajae-code/natives": "0.12.6",
61
+ "@gajae-code/tui": "0.12.6",
62
+ "@gajae-code/utils": "0.12.6",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+
3
4
  import {
4
5
  renderStickyViewportShowcase,
5
6
  STICKY_VIEWPORT_SHOWCASE_ENTRIES,
@@ -7,6 +8,10 @@ import {
7
8
  type StickyViewportShowcaseEntry,
8
9
  } from "../test/fixtures/tui/sticky-viewport-showcase";
9
10
 
11
+ export const REPOSITORY_ROOT = path.resolve(import.meta.dir, "../../..");
12
+ export const resolveRepositoryPath = (repositoryRelativePath: string): string =>
13
+ path.join(REPOSITORY_ROOT, repositoryRelativePath);
14
+
10
15
  const COMMAND =
11
16
  "bun packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts --out .gjc/qa/sticky-viewport-<run>";
12
17
  const REVISION = "sticky-viewport-showcase-v2";
@@ -168,17 +173,126 @@ export function ansiToHtml(value: string): string {
168
173
  return `<!doctype html><html lang="en"><meta charset="utf-8"><title>Sticky viewport showcase</title><style>body{margin:0;background:#110b0b;color:#ffe7dc}pre{white-space:pre;font-family:ui-monospace,monospace}@keyframes blink{50%{visibility:hidden}}</style><pre>${body}</pre></html>\n`;
169
174
  }
170
175
  const json = (value: unknown) => `${JSON.stringify(value, null, 2)}\n`;
171
- const hash = (value: string) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
176
+ const hash = (value: string | Uint8Array) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
177
+ const PROVENANCE_SOURCES = [
178
+ "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts",
179
+ "packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts",
180
+ "packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts",
181
+ "packages/coding-agent/src/modes/interactive-mode.ts",
182
+ "packages/coding-agent/src/modes/components/irc-sidebar.ts",
183
+ "packages/tui/src/tui.ts",
184
+ ] as const;
185
+ // Working-tree scope for `git_diff_binary_sha256`, persisted alongside the digest
186
+ // as `git_diff_scope` so a reviewer reads the covered surface off the bundle
187
+ // instead of inferring it.
188
+ //
189
+ // This digest used to hash `git diff --binary HEAD --` over the ENTIRE worktree.
190
+ // The verifier recomputes it live at verify time, so that coupled bundle validity
191
+ // to every tracked file in the repo: an unrelated edit anywhere — a doc typo,
192
+ // another package's test — retroactively made every already-captured bundle
193
+ // "stale". Those are false positives, and they are nondeterministic, because any
194
+ // write landing in the capture→verify window flips the digest mid-run and masks
195
+ // whichever guard was actually under test.
196
+ //
197
+ // The scope below is the transitive render-dependency closure of the fixture:
198
+ // every workspace package the capture reaches (coding-agent → agent, ai,
199
+ // bridge-client, natives, stats, tui, utils), the fixture plus the virtual
200
+ // terminal it paints into, both showcase scripts, and the lockfile pinning the
201
+ // installed dependency versions. Uncommitted edits inside this closure still
202
+ // invalidate a bundle — that is the property the staleness guard exists to
203
+ // enforce. Edits outside it no longer can, because they cannot change the paint.
204
+ export const PROVENANCE_DIFF_SCOPE = [
205
+ "Cargo.lock",
206
+ "Cargo.toml",
207
+ "bun.lock",
208
+ "crates",
209
+
210
+ "packages/agent/src",
211
+ "packages/ai/src",
212
+ "packages/bridge-client/src",
213
+ "packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts",
214
+ "packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts",
215
+ "packages/coding-agent/src",
216
+ "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts",
217
+ "packages/natives/native",
218
+ "packages/stats/src",
219
+ "packages/tui/src",
220
+ "packages/tui/test/virtual-terminal.ts",
221
+ "packages/utils/src",
222
+ ] as const;
223
+ async function git(args: string[]): Promise<Uint8Array> {
224
+ const result = Bun.spawn(["git", ...args], { cwd: REPOSITORY_ROOT, stdout: "pipe", stderr: "pipe" });
225
+ if ((await result.exited) !== 0)
226
+ throw new Error(`git ${args.join(" ")} failed: ${await new Response(result.stderr).text()}`);
227
+ return new Uint8Array(await new Response(result.stdout).arrayBuffer());
228
+ }
229
+
230
+ // Digest of a path's COMMITTED blob at a given commit, read straight out of the
231
+ // object database. This is the only provenance input a bundle author cannot
232
+ // restamp: `captureProvenance()` hashes the worktree, so mutating an oracle file
233
+ // and re-running it yields a self-consistent stamp. The committed blob is fixed
234
+ // by the commit id, so changing it requires a new commit -- which changes
235
+ // `git_head` and is therefore visible to the reviewer.
236
+ /** sha256 of every distinct blob this path has ever had in any ref-reachable commit. */
237
+ export async function gitObjectType(commitish: string): Promise<string | null> {
238
+ try {
239
+ const out = new TextDecoder().decode(await git(["cat-file", "-t", commitish])).trim();
240
+ return out || null;
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+ export async function committedBlobSha256(commit: string, filePath: string): Promise<string | null> {
246
+ const result = Bun.spawn(["git", "cat-file", "blob", `${commit}:${filePath}`], {
247
+ cwd: REPOSITORY_ROOT,
248
+ stdout: "pipe",
249
+ stderr: "pipe",
250
+ });
251
+ const bytes = new Uint8Array(await new Response(result.stdout).arrayBuffer());
252
+ if ((await result.exited) !== 0) return null;
253
+ return hash(bytes);
254
+ }
255
+ export type CaptureProvenance = {
256
+ git_head: string;
257
+ oracle_commit: string;
258
+ git_diff_scope: readonly string[];
259
+ git_diff_binary_sha256: string;
260
+ source_sha256: Record<string, string>;
261
+ };
262
+
263
+ // Single source of truth: the verifier imports this so capture and verify can
264
+ // never drift into computing the field two different ways.
265
+ export async function captureProvenance(): Promise<CaptureProvenance> {
266
+ const gitHead = new TextDecoder().decode(await git(["rev-parse", "HEAD"])).trim();
267
+ const sourceSha256 = Object.fromEntries(
268
+ await Promise.all(
269
+ PROVENANCE_SOURCES.map(async source => [
270
+ source,
271
+ hash(new Uint8Array(await Bun.file(resolveRepositoryPath(source)).arrayBuffer())),
272
+ ]),
273
+ ),
274
+ );
275
+ return {
276
+ git_head: gitHead,
277
+ oracle_commit: process.env.GJC_STICKY_VIEWPORT_ORACLE_COMMIT?.trim() ?? gitHead,
278
+ git_diff_scope: PROVENANCE_DIFF_SCOPE,
279
+ git_diff_binary_sha256: hash(await git(["diff", "--binary", "HEAD", "--", ...PROVENANCE_DIFF_SCOPE])),
280
+ source_sha256: sourceSha256,
281
+ };
282
+ }
172
283
  function out(args: string[]): string {
173
284
  if (args.length !== 2 || args[0] !== "--out" || !args[1]) throw new Error(`Usage: ${COMMAND}`);
174
285
  return args[1];
175
286
  }
176
- async function capture(entry: StickyViewportShowcaseEntry, root: string) {
287
+ async function capture(entry: StickyViewportShowcaseEntry, root: string, sourceProvenance: CaptureProvenance) {
177
288
  const rendered = await renderStickyViewportShowcase(entry);
178
289
  if (!rendered.state.composer_visible)
179
290
  throw new Error(`${entry.key}: focused composer was not visible in production frame`);
180
- if (entry.stateId === "manual-new-output" && rendered.state.notice !== true)
181
- throw new Error(`${entry.key}: manual output notice precondition failed`);
291
+ if (
292
+ (entry.stateId === "manual-new-output" && rendered.state.notice !== true) ||
293
+ (entry.stateId !== "manual-new-output" && rendered.state.notice !== false)
294
+ )
295
+ throw new Error(`${entry.key}: renderer-owned output notice precondition failed`);
182
296
  if (
183
297
  JSON.stringify(rendered.cjkPhraseBoundaries) !==
184
298
  JSON.stringify(entry.stateId === "narrow-cjk" ? CJK_PHRASE_BOUNDARIES : [])
@@ -210,6 +324,7 @@ async function capture(entry: StickyViewportShowcaseEntry, root: string) {
210
324
  fixed_clock: true,
211
325
  author_identity: "capture-sticky-viewport-showcase",
212
326
  executor_identity: "capture-sticky-viewport-showcase",
327
+ ...sourceProvenance,
213
328
  },
214
329
  cjk_phrase_boundaries: rendered.cjkPhraseBoundaries,
215
330
  });
@@ -241,8 +356,9 @@ async function capture(entry: StickyViewportShowcaseEntry, root: string) {
241
356
  async function main() {
242
357
  const root = path.resolve(out(process.argv.slice(2)));
243
358
  await fs.mkdir(root, { recursive: true });
359
+ const sourceProvenance = await captureProvenance();
244
360
  const entries = [];
245
- for (const entry of STICKY_VIEWPORT_SHOWCASE_ENTRIES) entries.push(await capture(entry, root));
361
+ for (const entry of STICKY_VIEWPORT_SHOWCASE_ENTRIES) entries.push(await capture(entry, root, sourceProvenance));
246
362
  const manifest = json({
247
363
  schema_version: 2,
248
364
  fixture_revision: REVISION,
@@ -258,6 +374,7 @@ async function main() {
258
374
  fixed_clock: true,
259
375
  author_identity: "capture-sticky-viewport-showcase",
260
376
  executor_identity: "capture-sticky-viewport-showcase",
377
+ ...sourceProvenance,
261
378
  },
262
379
  review_input_file: "review-input.json",
263
380
  entries,
@@ -281,6 +398,15 @@ async function main() {
281
398
  acceptance_version: ACCEPTANCE_VERSION,
282
399
  design_version: DESIGN_VERSION,
283
400
  host_matrix: HOST_MATRIX,
401
+ provenance: {
402
+ capture_mode: "production-tui-virtual-terminal",
403
+ live_pty: false,
404
+ network: false,
405
+ fixed_clock: true,
406
+ author_identity: "capture-sticky-viewport-showcase",
407
+ executor_identity: "capture-sticky-viewport-showcase",
408
+ ...sourceProvenance,
409
+ },
284
410
  narrow_cjk: {
285
411
  entry_key: "narrow-cjk/48x10/unicode-color",
286
412
  phrase_boundaries: ["의미 있는 문장 경계", "意味のある文の境界", "保留语义短语边界"],