@gajae-code/coding-agent 0.11.10 → 0.12.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/CHANGELOG.md +86 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +86 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +18 -2
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
- package/dist/types/sdk/host/control/operations.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +51 -19
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +59 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +49 -2
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +91 -7
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +24 -0
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +84 -3
- package/src/config/settings.ts +254 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -921
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +979 -71
- package/src/gjc-runtime/state-runtime.ts +50 -12
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +10 -11
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +597 -67
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +76 -5
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +62 -6
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +127 -29
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/lifecycle.ts +107 -7
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +503 -60
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
- package/src/sdk/bus/telegram-daemon.ts +531 -53
- package/src/sdk/host/control/dispatch.ts +7 -1
- package/src/sdk/host/control/operations.ts +2 -1
- package/src/sdk/host/host.ts +25 -3
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +76 -0
- package/src/sdk/protocol/operation-registry.ts +8 -2
- package/src/sdk/session.ts +25 -2
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +720 -190
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +40 -10
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +1372 -42
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +2 -0
- package/src/task/types.ts +22 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ansiToHtml, xterm256Color } from "./capture-sticky-viewport-showcase";
|
|
4
|
+
|
|
5
|
+
const KEYS = [
|
|
6
|
+
"live-overflow/80x24/unicode-color",
|
|
7
|
+
"live-overflow/120x36/unicode-color",
|
|
8
|
+
"manual-history/80x24/unicode-color",
|
|
9
|
+
"manual-history/120x36/unicode-color",
|
|
10
|
+
"manual-new-output/80x24/unicode-color",
|
|
11
|
+
"manual-new-output/120x36/unicode-color",
|
|
12
|
+
"multiline-editor-hooks-pet/80x24/unicode-color",
|
|
13
|
+
"multiline-editor-hooks-pet/120x36/unicode-color",
|
|
14
|
+
"capacity-many/80x24/unicode-color",
|
|
15
|
+
"capacity-many/120x36/unicode-color",
|
|
16
|
+
"capacity-one/80x24/unicode-color",
|
|
17
|
+
"capacity-one/120x36/unicode-color",
|
|
18
|
+
"capacity-zero/80x24/unicode-color",
|
|
19
|
+
"capacity-zero/120x36/unicode-color",
|
|
20
|
+
"selection-boundary/80x24/unicode-color",
|
|
21
|
+
"selection-boundary/120x36/unicode-color",
|
|
22
|
+
"manual-new-output/80x24/ascii-no-color",
|
|
23
|
+
"capacity-zero/48x10/ascii-no-color",
|
|
24
|
+
"multiline-editor-hooks-pet/48x10/unicode-color",
|
|
25
|
+
"narrow-cjk/48x10/unicode-color",
|
|
26
|
+
] as const;
|
|
27
|
+
const PAYLOADS = ["terminal.txt", "terminal-ansi.txt", "terminal.html", "metadata.json"] as const;
|
|
28
|
+
const COMMAND =
|
|
29
|
+
"bun packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts --out .gjc/qa/sticky-viewport-<run>";
|
|
30
|
+
const TIMESTAMP = "1970-01-01T00:00:00.000Z";
|
|
31
|
+
const FIXTURE = "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts";
|
|
32
|
+
const DEFAULT_FOREGROUND = "#ffe7dc";
|
|
33
|
+
const DEFAULT_BACKGROUND = "#110b0b";
|
|
34
|
+
const CJK = ["의미 있는 문장 경계", "意味のある文の境界", "保留语义短语边界"] as const;
|
|
35
|
+
const FONT_RENDERING_ASSUMPTIONS =
|
|
36
|
+
"Embedded red-claw theme at deterministic truecolor; HTML uses a monospace terminal fallback stack.";
|
|
37
|
+
const WRAPPING_TRUNCATION_POLICY =
|
|
38
|
+
"ANSI-aware terminal-cell wrapping preserves semantic CJK phrase boundaries; constrained height drops the notice, decorative pet, then low-priority hooks without truncating pinned status or the focused composer.";
|
|
39
|
+
const ACCEPTANCE_VERSION = "sticky-viewport-stage-03";
|
|
40
|
+
const DESIGN_VERSION = "modes-design-sticky-viewport-v3";
|
|
41
|
+
const HOST_MATRIX = { capture_host: "VirtualTerminal", live_pty: false, network: false } as const;
|
|
42
|
+
const ARTIFACT_CHECKS = {
|
|
43
|
+
terminal_txt: true,
|
|
44
|
+
terminal_ansi_txt: true,
|
|
45
|
+
terminal_html: true,
|
|
46
|
+
metadata_json: true,
|
|
47
|
+
} as const;
|
|
48
|
+
const INDEPENDENT_REVIEW_KEYS = [
|
|
49
|
+
"schema_version",
|
|
50
|
+
"manifest_sha256",
|
|
51
|
+
"reviewer_identity",
|
|
52
|
+
"reviewer_role",
|
|
53
|
+
"fixture_revision",
|
|
54
|
+
"expected_entry_count",
|
|
55
|
+
"observed_entry_count",
|
|
56
|
+
"final",
|
|
57
|
+
"checked_keys",
|
|
58
|
+
"defects",
|
|
59
|
+
"artifact_decision",
|
|
60
|
+
"cjk_semantic_line_breaks",
|
|
61
|
+
"host_matrix",
|
|
62
|
+
"per_key_results",
|
|
63
|
+
] as const;
|
|
64
|
+
const INDEPENDENT_REVIEW_RESULT_KEYS = ["key", "result", "notes", "artifact_checks"] as const;
|
|
65
|
+
const INDEPENDENT_REVIEW_DEFECT_KEYS = ["description", "accepted"] as const;
|
|
66
|
+
const ARTIFACT_CHECK_KEYS = ["terminal_txt", "terminal_ansi_txt", "terminal_html", "metadata_json"] as const;
|
|
67
|
+
type Style = {
|
|
68
|
+
foreground: string;
|
|
69
|
+
background: string;
|
|
70
|
+
bold: boolean;
|
|
71
|
+
dim: boolean;
|
|
72
|
+
italic: boolean;
|
|
73
|
+
underline: boolean;
|
|
74
|
+
blink: boolean;
|
|
75
|
+
inverse: boolean;
|
|
76
|
+
invisible: boolean;
|
|
77
|
+
strikethrough: boolean;
|
|
78
|
+
overline: boolean;
|
|
79
|
+
};
|
|
80
|
+
type Run = { text: string; style: Style };
|
|
81
|
+
const hash = (value: string) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
82
|
+
const fail = (message: string): never => {
|
|
83
|
+
throw new Error(`Sticky viewport evidence invalid: ${message}`);
|
|
84
|
+
};
|
|
85
|
+
const exactKeys = (value: Record<string, unknown>, expected: readonly string[], label: string) => {
|
|
86
|
+
const actual = Object.keys(value).sort();
|
|
87
|
+
const sortedExpected = [...expected].sort();
|
|
88
|
+
if (JSON.stringify(actual) !== JSON.stringify(sortedExpected)) fail(`${label} keys must be exact`);
|
|
89
|
+
};
|
|
90
|
+
const object = (value: unknown, label: string): Record<string, unknown> => {
|
|
91
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label} must be an object`);
|
|
92
|
+
return value as Record<string, unknown>;
|
|
93
|
+
};
|
|
94
|
+
const array = (value: unknown, label: string): unknown[] =>
|
|
95
|
+
Array.isArray(value) ? value : fail(`${label} must be an array`);
|
|
96
|
+
const strings = (value: unknown, expected: readonly string[], label: string) => {
|
|
97
|
+
if (
|
|
98
|
+
!Array.isArray(value) ||
|
|
99
|
+
value.length !== expected.length ||
|
|
100
|
+
value.some((item, index) => item !== expected[index])
|
|
101
|
+
)
|
|
102
|
+
fail(`${label} differs from immutable matrix`);
|
|
103
|
+
};
|
|
104
|
+
async function readJson(file: string, label: string): Promise<Record<string, unknown>> {
|
|
105
|
+
try {
|
|
106
|
+
return object(JSON.parse(await fs.readFile(file, "utf8")), label);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return fail(`${label} is unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function allFiles(root: string): Promise<string[]> {
|
|
112
|
+
const result: string[] = [];
|
|
113
|
+
const walk = async (directory: string): Promise<void> => {
|
|
114
|
+
for (const item of await fs.readdir(directory, { withFileTypes: true })) {
|
|
115
|
+
const target = path.join(directory, item.name);
|
|
116
|
+
if (item.isDirectory()) await walk(target);
|
|
117
|
+
else if (item.isFile()) result.push(path.relative(root, target).split(path.sep).join("/"));
|
|
118
|
+
else fail(`unsupported filesystem entry ${target}`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
await walk(root);
|
|
122
|
+
return result.sort();
|
|
123
|
+
}
|
|
124
|
+
const baseStyle = (): Style => ({
|
|
125
|
+
foreground: DEFAULT_FOREGROUND,
|
|
126
|
+
background: DEFAULT_BACKGROUND,
|
|
127
|
+
bold: false,
|
|
128
|
+
dim: false,
|
|
129
|
+
italic: false,
|
|
130
|
+
underline: false,
|
|
131
|
+
blink: false,
|
|
132
|
+
inverse: false,
|
|
133
|
+
invisible: false,
|
|
134
|
+
strikethrough: false,
|
|
135
|
+
overline: false,
|
|
136
|
+
});
|
|
137
|
+
const color = (code: number): string | undefined => {
|
|
138
|
+
const colors: Record<number, string> = {
|
|
139
|
+
30: "#000000",
|
|
140
|
+
31: "#cc0000",
|
|
141
|
+
32: "#4e9a06",
|
|
142
|
+
33: "#c4a000",
|
|
143
|
+
34: "#3465a4",
|
|
144
|
+
35: "#75507b",
|
|
145
|
+
36: "#06989a",
|
|
146
|
+
37: "#d3d7cf",
|
|
147
|
+
90: "#555753",
|
|
148
|
+
91: "#ef2929",
|
|
149
|
+
92: "#8ae234",
|
|
150
|
+
93: "#fce94f",
|
|
151
|
+
94: "#729fcf",
|
|
152
|
+
95: "#ad7fa8",
|
|
153
|
+
96: "#34e2e2",
|
|
154
|
+
97: "#eeeeec",
|
|
155
|
+
};
|
|
156
|
+
return colors[code];
|
|
157
|
+
};
|
|
158
|
+
const pushRun = (runs: Run[], text: string, style: Style) => {
|
|
159
|
+
if (!text) return;
|
|
160
|
+
const effective = style.inverse
|
|
161
|
+
? {
|
|
162
|
+
...style,
|
|
163
|
+
foreground: style.background,
|
|
164
|
+
background: style.foreground,
|
|
165
|
+
inverse: false,
|
|
166
|
+
}
|
|
167
|
+
: { ...style };
|
|
168
|
+
runs.push({ text, style: effective });
|
|
169
|
+
};
|
|
170
|
+
function ansiRuns(ansi: string): Run[] {
|
|
171
|
+
const runs: Run[] = [];
|
|
172
|
+
let style = baseStyle(),
|
|
173
|
+
offset = 0;
|
|
174
|
+
for (const match of ansi.matchAll(/\x1b\[([0-9;]*)m/g)) {
|
|
175
|
+
pushRun(runs, ansi.slice(offset, match.index), style);
|
|
176
|
+
offset = (match.index ?? 0) + match[0].length;
|
|
177
|
+
const codes = (match[1] || "0").split(";").map(Number);
|
|
178
|
+
for (let index = 0; index < codes.length; index += 1) {
|
|
179
|
+
const code = codes[index]!;
|
|
180
|
+
if (code === 0) style = baseStyle();
|
|
181
|
+
else if (code === 1) style.bold = true;
|
|
182
|
+
else if (code === 2) style.dim = true;
|
|
183
|
+
else if (code === 3) style.italic = true;
|
|
184
|
+
else if (code === 4) style.underline = true;
|
|
185
|
+
else if (code === 5) style.blink = true;
|
|
186
|
+
else if (code === 7) style.inverse = true;
|
|
187
|
+
else if (code === 8) style.invisible = true;
|
|
188
|
+
else if (code === 9) style.strikethrough = true;
|
|
189
|
+
else if (code === 22) {
|
|
190
|
+
style.bold = false;
|
|
191
|
+
style.dim = false;
|
|
192
|
+
} else if (code === 23) style.italic = false;
|
|
193
|
+
else if (code === 24) style.underline = false;
|
|
194
|
+
else if (code === 25) style.blink = false;
|
|
195
|
+
else if (code === 27) style.inverse = false;
|
|
196
|
+
else if (code === 28) style.invisible = false;
|
|
197
|
+
else if (code === 29) style.strikethrough = false;
|
|
198
|
+
else if (code === 53) style.overline = true;
|
|
199
|
+
else if (code === 55) style.overline = false;
|
|
200
|
+
else if (code === 39) style.foreground = DEFAULT_FOREGROUND;
|
|
201
|
+
else if (code === 49) style.background = DEFAULT_BACKGROUND;
|
|
202
|
+
else if (color(code)) style.foreground = color(code)!;
|
|
203
|
+
else if (code >= 40 && code <= 47) style.background = color(code - 10)!;
|
|
204
|
+
else if (code >= 100 && code <= 107) style.background = color(code - 10)!;
|
|
205
|
+
else if ((code === 38 || code === 48) && codes[index + 1] === 5 && Number.isInteger(codes[index + 2])) {
|
|
206
|
+
const value = xterm256Color(codes[index + 2]!);
|
|
207
|
+
if (code === 38) style.foreground = value;
|
|
208
|
+
else style.background = value;
|
|
209
|
+
index += 2;
|
|
210
|
+
} else if (
|
|
211
|
+
(code === 38 || code === 48) &&
|
|
212
|
+
codes[index + 1] === 2 &&
|
|
213
|
+
[codes[index + 2], codes[index + 3], codes[index + 4]].every(Number.isInteger)
|
|
214
|
+
) {
|
|
215
|
+
const value = `rgb(${codes[index + 2]},${codes[index + 3]},${codes[index + 4]})`;
|
|
216
|
+
if (code === 38) style.foreground = value;
|
|
217
|
+
else style.background = value;
|
|
218
|
+
index += 4;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
pushRun(runs, ansi.slice(offset), style);
|
|
223
|
+
return runs;
|
|
224
|
+
}
|
|
225
|
+
const decode = (value: string) =>
|
|
226
|
+
value
|
|
227
|
+
.replace(/"/g, '"')
|
|
228
|
+
.replace(/&/g, "&")
|
|
229
|
+
.replace(/</g, "<")
|
|
230
|
+
.replace(/>/g, ">");
|
|
231
|
+
function htmlRuns(html: string): Run[] {
|
|
232
|
+
const preMatch = html.match(/<pre>([\s\S]*)<\/pre>/);
|
|
233
|
+
if (preMatch?.[1] === undefined) return fail("HTML pre missing");
|
|
234
|
+
const pre = preMatch[1];
|
|
235
|
+
const runs: Run[] = [];
|
|
236
|
+
let style = baseStyle();
|
|
237
|
+
let offset = 0;
|
|
238
|
+
for (const match of pre.matchAll(/<span style="([^"]*)">|<\/span>/g)) {
|
|
239
|
+
pushRun(runs, decode(pre.slice(offset, match.index)), style);
|
|
240
|
+
offset = (match.index ?? 0) + match[0].length;
|
|
241
|
+
if (match[0] === "</span>") {
|
|
242
|
+
style = baseStyle();
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const attributes = new Map<string, string>(
|
|
246
|
+
match[1]!
|
|
247
|
+
.split(";")
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.map(part => {
|
|
250
|
+
const separator = part.indexOf(":");
|
|
251
|
+
if (separator < 0) return fail("HTML style declaration malformed");
|
|
252
|
+
return [part.slice(0, separator), part.slice(separator + 1)] as const;
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
if (attributes.has("filter")) fail("HTML inverse must use effective colors, not CSS filter");
|
|
256
|
+
const decorations = new Set((attributes.get("text-decoration") ?? "").split(" "));
|
|
257
|
+
style = {
|
|
258
|
+
...baseStyle(),
|
|
259
|
+
foreground: attributes.get("color") ?? DEFAULT_FOREGROUND,
|
|
260
|
+
background: attributes.get("background-color") ?? DEFAULT_BACKGROUND,
|
|
261
|
+
bold: attributes.get("font-weight") === "700",
|
|
262
|
+
dim: attributes.get("opacity") === ".65",
|
|
263
|
+
italic: attributes.get("font-style") === "italic",
|
|
264
|
+
underline: decorations.has("underline"),
|
|
265
|
+
blink: attributes.get("animation") === "blink 1s step-end infinite",
|
|
266
|
+
invisible: attributes.get("visibility") === "hidden",
|
|
267
|
+
strikethrough: decorations.has("line-through"),
|
|
268
|
+
overline: decorations.has("overline"),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
pushRun(runs, decode(pre.slice(offset)), style);
|
|
272
|
+
return runs;
|
|
273
|
+
}
|
|
274
|
+
const normalized = (runs: Run[]) => {
|
|
275
|
+
const merged: Run[] = [];
|
|
276
|
+
for (const run of runs) {
|
|
277
|
+
const prior = merged.at(-1);
|
|
278
|
+
if (prior && JSON.stringify(prior.style) === JSON.stringify(run.style)) prior.text += run.text;
|
|
279
|
+
else merged.push({ ...run, style: { ...run.style } });
|
|
280
|
+
}
|
|
281
|
+
return merged;
|
|
282
|
+
};
|
|
283
|
+
const equalRuns = (left: Run[], right: Run[]) => JSON.stringify(normalized(left)) === JSON.stringify(normalized(right));
|
|
284
|
+
const transcriptCapacity = (text: string) => {
|
|
285
|
+
const row = text.split("\n").findIndex(line => line.includes("status:"));
|
|
286
|
+
return row < 0 ? fail("frame omits pinned status row") : row;
|
|
287
|
+
};
|
|
288
|
+
const expectedState = (state: string) => ({
|
|
289
|
+
manual: state !== "live-overflow",
|
|
290
|
+
notice: state === "manual-new-output",
|
|
291
|
+
status: state === "live-overflow" ? "status: live follow" : "status: manual history · composer pinned",
|
|
292
|
+
multiline: state === "multiline-editor-hooks-pet",
|
|
293
|
+
});
|
|
294
|
+
function validateOracle(
|
|
295
|
+
key: string,
|
|
296
|
+
text: string,
|
|
297
|
+
metadata: Record<string, unknown>,
|
|
298
|
+
stateEvidence: Record<string, unknown>,
|
|
299
|
+
) {
|
|
300
|
+
const state = key.split("/")[0]!;
|
|
301
|
+
const expected = expectedState(state);
|
|
302
|
+
const lines = text.split("\n");
|
|
303
|
+
if (stateEvidence.manual !== expected.manual || stateEvidence.notice !== expected.notice)
|
|
304
|
+
fail(`immutable state oracle mismatch for ${key}`);
|
|
305
|
+
if (lines.filter(line => line.includes(expected.status)).length !== 1)
|
|
306
|
+
fail(`exact status oracle mismatch for ${key}`);
|
|
307
|
+
const markers = [
|
|
308
|
+
expected.status,
|
|
309
|
+
...(expected.multiline
|
|
310
|
+
? ["hook: ready", "completed: visual proof", "pet: ◕‿◕", "> ", "first composer line", "second composer line"]
|
|
311
|
+
: ["> "]),
|
|
312
|
+
];
|
|
313
|
+
let position = -1;
|
|
314
|
+
for (const marker of markers) {
|
|
315
|
+
const next = text.indexOf(marker, position + 1);
|
|
316
|
+
if (next < 0) fail(`ordered suffix oracle missing ${marker} for ${key}`);
|
|
317
|
+
position = next;
|
|
318
|
+
}
|
|
319
|
+
if (text.split("New output — type to follow").length - 1 !== (expected.notice ? 1 : 0))
|
|
320
|
+
fail(`notice cardinality oracle mismatch for ${key}`);
|
|
321
|
+
if (
|
|
322
|
+
expected.multiline !==
|
|
323
|
+
(text.includes("hook: ready") &&
|
|
324
|
+
text.includes("pet: ◕‿◕") &&
|
|
325
|
+
text.includes("first composer line") &&
|
|
326
|
+
text.includes("second composer line"))
|
|
327
|
+
)
|
|
328
|
+
fail(`multiline editor/hooks/pet oracle mismatch for ${key}`);
|
|
329
|
+
if (metadata.output_revision !== (expected.notice ? "1" : "0")) fail(`manual notice invariant mismatch for ${key}`);
|
|
330
|
+
if (state === "selection-boundary") {
|
|
331
|
+
const copied = stateEvidence.selection_copied_text;
|
|
332
|
+
if (
|
|
333
|
+
stateEvidence.selection_scope !== "transcript" ||
|
|
334
|
+
typeof copied !== "string" ||
|
|
335
|
+
!copied.trim() ||
|
|
336
|
+
copied.includes("status:") ||
|
|
337
|
+
copied.includes("> ") ||
|
|
338
|
+
!lines.some(line => line.includes(copied.trim()))
|
|
339
|
+
)
|
|
340
|
+
fail(`selection oracle mismatch for ${key}`);
|
|
341
|
+
} else if (stateEvidence.selection_scope !== "none" || stateEvidence.selection_copied_text !== "")
|
|
342
|
+
fail(`selection oracle mismatch for ${key}`);
|
|
343
|
+
const historicalRows = stateEvidence.manual_historical_rows;
|
|
344
|
+
const preOutputCapacity = stateEvidence.manual_pre_output_capacity;
|
|
345
|
+
if (expected.manual) {
|
|
346
|
+
if (
|
|
347
|
+
!Number.isInteger(preOutputCapacity) ||
|
|
348
|
+
(preOutputCapacity as number) < 0 ||
|
|
349
|
+
!Array.isArray(historicalRows) ||
|
|
350
|
+
historicalRows.length !== ((preOutputCapacity as number) > 0 ? 1 : 0) ||
|
|
351
|
+
historicalRows.some(row => typeof row !== "string" || !row.trim() || !lines.includes(row))
|
|
352
|
+
)
|
|
353
|
+
fail(`manual historical transcript evidence mismatch for ${key}`);
|
|
354
|
+
} else if (preOutputCapacity !== 0 || !Array.isArray(historicalRows) || historicalRows.length !== 0)
|
|
355
|
+
fail(`manual historical transcript evidence mismatch for ${key}`);
|
|
356
|
+
}
|
|
357
|
+
export async function verifyStickyViewportShowcase(rootInput: string, requireIndependentReview = false): Promise<void> {
|
|
358
|
+
const root = path.resolve(rootInput);
|
|
359
|
+
const manifestText = await fs.readFile(path.join(root, "manifest.json"), "utf8");
|
|
360
|
+
const manifest = await readJson(path.join(root, "manifest.json"), "manifest");
|
|
361
|
+
if (
|
|
362
|
+
manifest.schema_version !== 2 ||
|
|
363
|
+
manifest.fixture_revision !== "sticky-viewport-showcase-v2" ||
|
|
364
|
+
manifest.expected_entry_count !== 20 ||
|
|
365
|
+
manifest.entry_count !== 20 ||
|
|
366
|
+
manifest.command !== COMMAND ||
|
|
367
|
+
manifest.capture_timestamp !== TIMESTAMP ||
|
|
368
|
+
manifest.review_input_file !== "review-input.json"
|
|
369
|
+
)
|
|
370
|
+
fail("manifest schema or provenance literals mismatch");
|
|
371
|
+
strings(manifest.ordered_keys, KEYS, "manifest ordered_keys");
|
|
372
|
+
const provenance = object(manifest.provenance, "manifest provenance");
|
|
373
|
+
if (
|
|
374
|
+
provenance.capture_mode !== "production-tui-virtual-terminal" ||
|
|
375
|
+
provenance.live_pty !== false ||
|
|
376
|
+
provenance.network !== false ||
|
|
377
|
+
provenance.fixed_clock !== true ||
|
|
378
|
+
typeof provenance.author_identity !== "string" ||
|
|
379
|
+
!provenance.author_identity.trim() ||
|
|
380
|
+
typeof provenance.executor_identity !== "string" ||
|
|
381
|
+
!provenance.executor_identity.trim()
|
|
382
|
+
)
|
|
383
|
+
fail("manifest provenance mismatch");
|
|
384
|
+
const entries = array(manifest.entries, "manifest entries");
|
|
385
|
+
if (entries.length !== KEYS.length) fail("manifest entries must contain exactly 20 entries");
|
|
386
|
+
for (let index = 0; index < KEYS.length; index += 1) {
|
|
387
|
+
const key = KEYS[index]!;
|
|
388
|
+
const entry = object(entries[index], `entry ${index}`);
|
|
389
|
+
const [state, id, mode] = key.split("/");
|
|
390
|
+
const [columns, rows] = id!.split("x").map(Number);
|
|
391
|
+
if (entry.key !== key || entry.state_id !== state || entry.render_mode !== mode)
|
|
392
|
+
fail(`entry ${key} variant mismatch`);
|
|
393
|
+
const viewport = object(entry.viewport, `entry ${key} viewport`);
|
|
394
|
+
if (viewport.id !== id || viewport.columns !== columns || viewport.rows !== rows)
|
|
395
|
+
fail(`entry ${key} viewport mismatch`);
|
|
396
|
+
const listed = array(entry.files, `entry ${key} files`);
|
|
397
|
+
if (listed.length !== PAYLOADS.length) fail(`entry ${key} file list is not exact`);
|
|
398
|
+
strings(
|
|
399
|
+
listed.map(value => object(value, `entry ${key} file`).path),
|
|
400
|
+
PAYLOADS.map(name => `${key}/${name}`),
|
|
401
|
+
`entry ${key} payload paths`,
|
|
402
|
+
);
|
|
403
|
+
for (const value of listed) {
|
|
404
|
+
const file = object(value, `entry ${key} file`);
|
|
405
|
+
if (typeof file.path !== "string" || typeof file.sha256 !== "string" || !Number.isInteger(file.byte_length))
|
|
406
|
+
fail(`entry ${key} malformed file manifest`);
|
|
407
|
+
const filePath = file.path as string;
|
|
408
|
+
const content = await fs.readFile(path.join(root, filePath), "utf8");
|
|
409
|
+
if (hash(content) !== file.sha256 || Buffer.byteLength(content) !== file.byte_length)
|
|
410
|
+
fail(`entry ${key} hash or byte length mismatch`);
|
|
411
|
+
}
|
|
412
|
+
const text = await fs.readFile(path.join(root, key, "terminal.txt"), "utf8");
|
|
413
|
+
const ansi = await fs.readFile(path.join(root, key, "terminal-ansi.txt"), "utf8");
|
|
414
|
+
const html = await fs.readFile(path.join(root, key, "terminal.html"), "utf8");
|
|
415
|
+
if (Bun.stripANSI(ansi) !== text) fail(`entry ${key} text/ANSI semantic evidence mismatch`);
|
|
416
|
+
if (html !== ansiToHtml(ansi)) fail(`entry ${key} HTML artifact is not canonical ANSI conversion`);
|
|
417
|
+
const ansiStyleRuns = ansiRuns(ansi);
|
|
418
|
+
const htmlStyleRuns = htmlRuns(html);
|
|
419
|
+
if (
|
|
420
|
+
ansiStyleRuns.map(run => run.text).join("") !== text ||
|
|
421
|
+
htmlStyleRuns.map(run => run.text).join("") !== text ||
|
|
422
|
+
!equalRuns(ansiStyleRuns, htmlStyleRuns)
|
|
423
|
+
)
|
|
424
|
+
fail(`entry ${key} ANSI/HTML style-run mismatch`);
|
|
425
|
+
if (state === "multiline-editor-hooks-pet" && (!ansi.includes("\x1b[9m") || !html.includes("line-through")))
|
|
426
|
+
fail(`entry ${key} strikethrough evidence missing`);
|
|
427
|
+
if (state === "selection-boundary" && !ansi.includes("\x1b[7m"))
|
|
428
|
+
fail(`entry ${key} inverse selection evidence missing`);
|
|
429
|
+
if (state === "narrow-cjk" && CJK.some(boundary => !text.includes(boundary)))
|
|
430
|
+
fail("narrow CJK visible terminal evidence missing");
|
|
431
|
+
if (
|
|
432
|
+
text
|
|
433
|
+
.split("\n")
|
|
434
|
+
.slice(0, -1)
|
|
435
|
+
.some(row => Bun.stringWidth(row) !== columns) ||
|
|
436
|
+
text.split("\n").length - 1 !== rows
|
|
437
|
+
)
|
|
438
|
+
fail(`entry ${key} terminal dimensions mismatch`);
|
|
439
|
+
if (mode === "ascii-no-color" ? /\x1b\[/.test(ansi) : !/\x1b\[[0-9;]*(?:3[0-9]|38;)/.test(ansi))
|
|
440
|
+
fail(`entry ${key} ANSI mode/color mismatch`);
|
|
441
|
+
const metadata = await readJson(path.join(root, key, "metadata.json"), `metadata ${key}`);
|
|
442
|
+
exactKeys(
|
|
443
|
+
metadata,
|
|
444
|
+
[
|
|
445
|
+
"schema_version",
|
|
446
|
+
"entry_key",
|
|
447
|
+
"fixture_revision",
|
|
448
|
+
"capture_timestamp",
|
|
449
|
+
"command_or_replay_source",
|
|
450
|
+
"fixture_source",
|
|
451
|
+
"terminal",
|
|
452
|
+
"render_mode",
|
|
453
|
+
"ansi_mode",
|
|
454
|
+
"source_revision",
|
|
455
|
+
"output_revision",
|
|
456
|
+
"state",
|
|
457
|
+
"provenance",
|
|
458
|
+
"cjk_phrase_boundaries",
|
|
459
|
+
],
|
|
460
|
+
`metadata ${key}`,
|
|
461
|
+
);
|
|
462
|
+
const terminal = object(metadata.terminal, `metadata ${key} terminal`);
|
|
463
|
+
exactKeys(
|
|
464
|
+
terminal,
|
|
465
|
+
["id", "columns", "rows", "font_rendering_assumptions", "wrapping_truncation_policy"],
|
|
466
|
+
`metadata ${key} terminal`,
|
|
467
|
+
);
|
|
468
|
+
const stateEvidence = object(metadata.state, `metadata ${key} state`);
|
|
469
|
+
const metaProvenance = object(metadata.provenance, `metadata ${key} provenance`);
|
|
470
|
+
if (
|
|
471
|
+
metadata.schema_version !== 2 ||
|
|
472
|
+
metadata.entry_key !== key ||
|
|
473
|
+
metadata.fixture_revision !== "sticky-viewport-showcase-v2" ||
|
|
474
|
+
metadata.capture_timestamp !== TIMESTAMP ||
|
|
475
|
+
metadata.command_or_replay_source !== COMMAND ||
|
|
476
|
+
metadata.fixture_source !== FIXTURE ||
|
|
477
|
+
metadata.render_mode !== mode ||
|
|
478
|
+
metadata.ansi_mode !== (mode === "unicode-color") ||
|
|
479
|
+
metadata.source_revision !== "production-tui-virtual-terminal-v2" ||
|
|
480
|
+
terminal.id !== id ||
|
|
481
|
+
terminal.columns !== columns ||
|
|
482
|
+
terminal.rows !== rows ||
|
|
483
|
+
terminal.font_rendering_assumptions !== FONT_RENDERING_ASSUMPTIONS ||
|
|
484
|
+
terminal.wrapping_truncation_policy !== WRAPPING_TRUNCATION_POLICY ||
|
|
485
|
+
metaProvenance.capture_mode !== provenance.capture_mode ||
|
|
486
|
+
metaProvenance.live_pty !== false ||
|
|
487
|
+
metaProvenance.network !== false ||
|
|
488
|
+
metaProvenance.fixed_clock !== true ||
|
|
489
|
+
metaProvenance.author_identity !== provenance.author_identity ||
|
|
490
|
+
metaProvenance.executor_identity !== provenance.executor_identity ||
|
|
491
|
+
stateEvidence.composer_visible !== true
|
|
492
|
+
)
|
|
493
|
+
fail(`metadata schema mismatch for ${key}`);
|
|
494
|
+
if (stateEvidence.transcript_capacity !== transcriptCapacity(text))
|
|
495
|
+
fail(`capacity metadata/frame mismatch for ${key}`);
|
|
496
|
+
validateOracle(key, text, metadata, stateEvidence);
|
|
497
|
+
if (state === "capacity-zero" && transcriptCapacity(text) !== 0)
|
|
498
|
+
fail(`zero capacity frame invariant mismatch for ${key}`);
|
|
499
|
+
if (state === "capacity-one" && transcriptCapacity(text) !== 1)
|
|
500
|
+
fail(`one capacity frame invariant mismatch for ${key}`);
|
|
501
|
+
if (state === "capacity-many" && transcriptCapacity(text) < 2)
|
|
502
|
+
fail(`many capacity frame invariant mismatch for ${key}`);
|
|
503
|
+
if (state === "narrow-cjk") {
|
|
504
|
+
strings(metadata.cjk_phrase_boundaries, CJK, "narrow CJK boundaries");
|
|
505
|
+
if (CJK.some(boundary => !text.includes(boundary))) fail("narrow CJK visible terminal evidence missing");
|
|
506
|
+
} else strings(metadata.cjk_phrase_boundaries, [], `non-narrow CJK boundaries for ${key}`);
|
|
507
|
+
}
|
|
508
|
+
const required = new Set([
|
|
509
|
+
"manifest.json",
|
|
510
|
+
"review-input.json",
|
|
511
|
+
...KEYS.flatMap(key => PAYLOADS.map(file => `${key}/${file}`)),
|
|
512
|
+
...(requireIndependentReview ? ["independent-review.json"] : []),
|
|
513
|
+
]);
|
|
514
|
+
for (const file of await allFiles(root)) if (!required.has(file)) fail(`unexpected file ${file}`);
|
|
515
|
+
const reviewInput = await readJson(path.join(root, "review-input.json"), "review input");
|
|
516
|
+
if (
|
|
517
|
+
reviewInput.schema_version !== 2 ||
|
|
518
|
+
reviewInput.manifest_sha256 !== hash(manifestText) ||
|
|
519
|
+
reviewInput.command_or_replay_source !== COMMAND ||
|
|
520
|
+
reviewInput.capture_timestamp !== TIMESTAMP ||
|
|
521
|
+
reviewInput.fixture_source !== FIXTURE ||
|
|
522
|
+
reviewInput.fixed_clock !== true ||
|
|
523
|
+
reviewInput.live_pty !== false ||
|
|
524
|
+
reviewInput.network !== false ||
|
|
525
|
+
reviewInput.author_identity !== provenance.author_identity ||
|
|
526
|
+
reviewInput.executor_identity !== provenance.executor_identity
|
|
527
|
+
)
|
|
528
|
+
fail("review input manifest binding mismatch");
|
|
529
|
+
strings(reviewInput.expected_keys, KEYS, "review input expected_keys");
|
|
530
|
+
strings(reviewInput.required_artifacts, PAYLOADS, "review input required_artifacts");
|
|
531
|
+
const reviewHostMatrix = object(reviewInput.host_matrix, "review input host matrix");
|
|
532
|
+
if (
|
|
533
|
+
reviewInput.acceptance_version !== ACCEPTANCE_VERSION ||
|
|
534
|
+
reviewInput.design_version !== DESIGN_VERSION ||
|
|
535
|
+
reviewHostMatrix.capture_host !== HOST_MATRIX.capture_host ||
|
|
536
|
+
reviewHostMatrix.live_pty !== HOST_MATRIX.live_pty ||
|
|
537
|
+
reviewHostMatrix.network !== HOST_MATRIX.network
|
|
538
|
+
)
|
|
539
|
+
fail("review input acceptance, design, or host matrix mismatch");
|
|
540
|
+
const narrow = object(reviewInput.narrow_cjk, "review input narrow CJK");
|
|
541
|
+
if (narrow.entry_key !== "narrow-cjk/48x10/unicode-color") fail("review input narrow CJK mismatch");
|
|
542
|
+
strings(narrow.phrase_boundaries, CJK, "review input narrow CJK boundaries");
|
|
543
|
+
if (requireIndependentReview) {
|
|
544
|
+
const review = await readJson(path.join(root, "independent-review.json"), "independent review");
|
|
545
|
+
exactKeys(review, INDEPENDENT_REVIEW_KEYS, "independent review");
|
|
546
|
+
const reviewer = review.reviewer_identity;
|
|
547
|
+
const canonicalReviewer = typeof reviewer === "string" ? reviewer.trim() : "";
|
|
548
|
+
const canonicalAuthor = (provenance.author_identity as string).trim();
|
|
549
|
+
const canonicalExecutor = (provenance.executor_identity as string).trim();
|
|
550
|
+
const defects = array(review.defects, "independent review defects");
|
|
551
|
+
if (
|
|
552
|
+
review.schema_version !== 2 ||
|
|
553
|
+
review.manifest_sha256 !== hash(manifestText) ||
|
|
554
|
+
review.fixture_revision !== "sticky-viewport-showcase-v2" ||
|
|
555
|
+
review.expected_entry_count !== 20 ||
|
|
556
|
+
review.observed_entry_count !== 20 ||
|
|
557
|
+
review.final !== "accept" ||
|
|
558
|
+
review.reviewer_role !== "independent-terminal-reviewer" ||
|
|
559
|
+
typeof reviewer !== "string" ||
|
|
560
|
+
!canonicalReviewer ||
|
|
561
|
+
reviewer !== canonicalReviewer ||
|
|
562
|
+
canonicalReviewer === canonicalAuthor ||
|
|
563
|
+
canonicalReviewer === canonicalExecutor ||
|
|
564
|
+
review.artifact_decision !== "accept" ||
|
|
565
|
+
review.cjk_semantic_line_breaks !== "accept" ||
|
|
566
|
+
review.host_matrix !== "accept"
|
|
567
|
+
)
|
|
568
|
+
fail("independent review schema or decision mismatch");
|
|
569
|
+
for (const defect of defects) {
|
|
570
|
+
const item = object(defect, "independent review defect");
|
|
571
|
+
exactKeys(item, INDEPENDENT_REVIEW_DEFECT_KEYS, "independent review defect");
|
|
572
|
+
if (
|
|
573
|
+
typeof item.description !== "string" ||
|
|
574
|
+
!item.description.trim() ||
|
|
575
|
+
item.description !== item.description.trim() ||
|
|
576
|
+
item.accepted !== true
|
|
577
|
+
)
|
|
578
|
+
fail("independent review defect mismatch");
|
|
579
|
+
}
|
|
580
|
+
strings(review.checked_keys, KEYS, "independent review checked_keys");
|
|
581
|
+
const results = array(review.per_key_results, "independent review per-key results");
|
|
582
|
+
if (results.length !== KEYS.length) fail("independent review per-key results must contain exactly 20 entries");
|
|
583
|
+
for (let index = 0; index < KEYS.length; index += 1) {
|
|
584
|
+
const result = object(results[index], `independent review result ${index}`);
|
|
585
|
+
exactKeys(result, INDEPENDENT_REVIEW_RESULT_KEYS, `independent review result ${index}`);
|
|
586
|
+
if (
|
|
587
|
+
result.key !== KEYS[index] ||
|
|
588
|
+
result.result !== "accept" ||
|
|
589
|
+
typeof result.notes !== "string" ||
|
|
590
|
+
!result.notes.trim()
|
|
591
|
+
)
|
|
592
|
+
fail("independent review per-key result mismatch");
|
|
593
|
+
const checks = object(result.artifact_checks, "independent review per-key artifact checks");
|
|
594
|
+
exactKeys(checks, ARTIFACT_CHECK_KEYS, "independent review per-key artifact checks");
|
|
595
|
+
for (const [artifact, expected] of Object.entries(ARTIFACT_CHECKS))
|
|
596
|
+
if (checks[artifact] !== expected) fail("independent review per-key artifact checks missing");
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function main() {
|
|
601
|
+
const args = process.argv.slice(2);
|
|
602
|
+
const required = args.includes("--require-independent-review");
|
|
603
|
+
const rest = args.filter(value => value !== "--require-independent-review");
|
|
604
|
+
if (rest.length !== 2 || rest[0] !== "--root")
|
|
605
|
+
throw new Error(
|
|
606
|
+
"Usage: bun packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts --root <root> [--require-independent-review]",
|
|
607
|
+
);
|
|
608
|
+
await verifyStickyViewportShowcase(rest[1]!, required);
|
|
609
|
+
process.stdout.write("Sticky viewport evidence verified\n");
|
|
610
|
+
}
|
|
611
|
+
if (import.meta.main) await main();
|
package/src/capability/index.ts
CHANGED
|
@@ -259,10 +259,18 @@ export function initializeWithSettings(activeSettings: Settings): void {
|
|
|
259
259
|
for (const id of disabled) disabledProviders.add(id);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
function assertDisabledProvidersWritable(activeSettings: Settings): void {
|
|
263
|
+
if (!activeSettings.canWriteDurableConfig()) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
"Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
262
269
|
/**
|
|
263
270
|
* Persist current disabled providers to settings.
|
|
264
271
|
*/
|
|
265
272
|
function persistDisabledProviders(activeSettings: Settings, providers: ReadonlySet<string>): void {
|
|
273
|
+
assertDisabledProvidersWritable(activeSettings);
|
|
266
274
|
activeSettings.set("disabledProviders", Array.from(providers));
|
|
267
275
|
}
|
|
268
276
|
|
package/src/cli/args.ts
CHANGED
|
@@ -205,17 +205,23 @@ export function parseArgs(args: string[]): Args {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
result.tools = validTools;
|
|
208
|
-
} else if (arg === "--thinking"
|
|
208
|
+
} else if (arg === "--thinking") {
|
|
209
|
+
// Match --credential / --mcp-config: a missing value or a following flag is
|
|
210
|
+
// a usage error, not a silent no-op / accidental consumption of `-p`.
|
|
211
|
+
const next = args[i + 1];
|
|
212
|
+
if (!next || next.startsWith("-")) {
|
|
213
|
+
throw new CliParseError(`--thinking requires <level> (${THINKING_EFFORTS.join(", ")})`);
|
|
214
|
+
}
|
|
209
215
|
const rawThinking = args[++i];
|
|
210
216
|
const thinking = parseEffort(rawThinking);
|
|
211
|
-
if (thinking
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
level:
|
|
216
|
-
|
|
217
|
-
});
|
|
217
|
+
if (thinking === undefined) {
|
|
218
|
+
// Fail closed: a silent ignore left users believing `ultra` (or any typo)
|
|
219
|
+
// was applied. Help / Flags.options advertise the real Effort enum.
|
|
220
|
+
throw new CliParseError(
|
|
221
|
+
`Invalid --thinking level "${rawThinking}". Expected one of: ${THINKING_EFFORTS.join(", ")}`,
|
|
222
|
+
);
|
|
218
223
|
}
|
|
224
|
+
result.thinking = thinking;
|
|
219
225
|
} else if (arg === "--print" || arg === "-p") {
|
|
220
226
|
result.print = true;
|
|
221
227
|
} else if (arg === "--export" && i + 1 < args.length) {
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -84,7 +84,7 @@ Environment Variables:
|
|
|
84
84
|
GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
|
|
85
85
|
GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
|
|
86
86
|
GJC_TMUX_PROFILE - Apply GJC tmux scroll/mouse/clipboard profile to --tmux sessions (set 0/off to skip)
|
|
87
|
-
GJC_MOUSE -
|
|
87
|
+
GJC_MOUSE - Apply tmux copy-mode mouse capture in --tmux sessions (set 0/off to skip)
|
|
88
88
|
|
|
89
89
|
For complete environment variable reference, see:
|
|
90
90
|
docs/environment-variables.md
|