@gajae-code/coding-agent 0.16.1 → 0.16.4
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 +70 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/cli/rows.d.ts +7 -0
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -2
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/dist/types/utils/external-editor.d.ts +5 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +94 -6
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/acp/acp-agent.ts +29 -14
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/acp/adapter.ts +1 -1
- package/src/sdk/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +44 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/rows.ts +47 -2
- package/src/sdk/cli/session-cli.ts +47 -23
- package/src/sdk/host/events.ts +25 -2
- package/src/sdk/host/host.ts +4 -1
- package/src/sdk/host/session-runtime.test.ts +36 -25
- package/src/sdk/host/session-runtime.ts +279 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +60 -37
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -30
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +50 -21
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -2
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +9 -2
- package/src/utils/external-editor.ts +16 -2
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
renderStatusLineCustomEditorShowcase,
|
|
5
|
+
STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES,
|
|
6
|
+
type StatusLineCustomEditorShowcaseEntry,
|
|
7
|
+
} from "../test/fixtures/tui/status-line-custom-editor-showcase";
|
|
8
|
+
import { ansiToHtml } from "./capture-sticky-viewport-showcase";
|
|
9
|
+
|
|
10
|
+
const CANONICAL_COMMAND =
|
|
11
|
+
"bun packages/coding-agent/scripts/capture-status-line-custom-editor-showcase.ts --out .gjc/qa/status-line-custom-editor-<run>";
|
|
12
|
+
const CAPTURE_TOOL_VERSION = "status-line-custom-editor-showcase-v1";
|
|
13
|
+
const SOURCE_PATHS = [
|
|
14
|
+
"packages/coding-agent/src/modes/components/settings-selector.ts",
|
|
15
|
+
"packages/coding-agent/src/modes/components/tool-status-header.ts",
|
|
16
|
+
"packages/coding-agent/src/modes/controllers/selector-controller.ts",
|
|
17
|
+
"packages/tui/src/components/settings-list.ts",
|
|
18
|
+
"packages/coding-agent/test/fixtures/tui/status-line-custom-editor-showcase.ts",
|
|
19
|
+
"packages/coding-agent/scripts/verify-status-line-custom-editor-showcase.ts",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
interface ArtifactFile {
|
|
23
|
+
path: string;
|
|
24
|
+
sha256: string;
|
|
25
|
+
byte_length: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ManifestEntry {
|
|
29
|
+
key: string;
|
|
30
|
+
stateId: string;
|
|
31
|
+
viewport: { columns: number; rows: number };
|
|
32
|
+
renderMode: string;
|
|
33
|
+
simulatedStatusbarRows: number;
|
|
34
|
+
files: ArtifactFile[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function usage(): never {
|
|
38
|
+
throw new Error(`Usage: ${CANONICAL_COMMAND}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function outputPath(args: string[]): string {
|
|
42
|
+
if (args.length !== 2 || args[0] !== "--out" || !args[1]) usage();
|
|
43
|
+
return args[1];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function json(value: unknown): string {
|
|
47
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sha256(value: string): string {
|
|
51
|
+
return new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function commandOutput(command: string[]): string | undefined {
|
|
55
|
+
const result = Bun.spawnSync({ cmd: command, stdout: "pipe", stderr: "pipe" });
|
|
56
|
+
if (result.exitCode !== 0) return undefined;
|
|
57
|
+
const output = result.stdout.toString().trim();
|
|
58
|
+
return output || undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function sourceHash(): Promise<string> {
|
|
62
|
+
const entries = await Promise.all(
|
|
63
|
+
SOURCE_PATHS.map(async sourcePath => `${sourcePath}:${sha256(await Bun.file(sourcePath).text())}`),
|
|
64
|
+
);
|
|
65
|
+
return `sha256:${sha256(entries.join("\n"))}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function entryKey(entry: StatusLineCustomEditorShowcaseEntry): string {
|
|
69
|
+
return `${entry.stateId}/${entry.columns}x${entry.rows}/${entry.renderMode}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function simulatedStatusbarRows(text: string): number {
|
|
73
|
+
const lines = Bun.stripANSI(text).split("\n");
|
|
74
|
+
const heading = lines.findIndex(line => line.includes("Simulated statusbar"));
|
|
75
|
+
if (heading < 0) return 0;
|
|
76
|
+
let end = heading + 1;
|
|
77
|
+
while (end < lines.length && lines[end]?.trim() && !lines[end]?.includes("Warning:")) end++;
|
|
78
|
+
return Math.max(0, end - heading - 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function writeArtifact(root: string, relativePath: string, content: string): Promise<ArtifactFile> {
|
|
82
|
+
const fullPath = path.join(root, relativePath);
|
|
83
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
84
|
+
await Bun.write(fullPath, content);
|
|
85
|
+
return { path: relativePath, sha256: sha256(content), byte_length: Buffer.byteLength(content) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main(): Promise<void> {
|
|
89
|
+
const root = outputPath(process.argv.slice(2));
|
|
90
|
+
const sourceHead = commandOutput(["git", "rev-parse", "HEAD"]);
|
|
91
|
+
if (!sourceHead) throw new Error("Could not resolve source HEAD");
|
|
92
|
+
const captureActor = commandOutput(["gh", "api", "user", "--jq", ".login"]) ?? "unauthenticated";
|
|
93
|
+
const digest = await sourceHash();
|
|
94
|
+
const independentReviewPath = path.join(root, "independent-review.json");
|
|
95
|
+
const existingIndependentReview = (await Bun.file(independentReviewPath).exists())
|
|
96
|
+
? await Bun.file(independentReviewPath).text()
|
|
97
|
+
: undefined;
|
|
98
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
99
|
+
await fs.mkdir(root, { recursive: true });
|
|
100
|
+
if (existingIndependentReview !== undefined) {
|
|
101
|
+
await Bun.write(independentReviewPath, existingIndependentReview);
|
|
102
|
+
}
|
|
103
|
+
const entries: ManifestEntry[] = [];
|
|
104
|
+
for (const entry of STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES) {
|
|
105
|
+
const key = entryKey(entry);
|
|
106
|
+
const dir = key.replace(/[^a-zA-Z0-9._/-]/g, "_");
|
|
107
|
+
const terminalAnsi = await renderStatusLineCustomEditorShowcase(entry);
|
|
108
|
+
const renderedRows = terminalAnsi.split("\n").length;
|
|
109
|
+
if (renderedRows > entry.rows) {
|
|
110
|
+
throw new Error(`${key}: rendered ${renderedRows} rows for a ${entry.rows}-row viewport`);
|
|
111
|
+
}
|
|
112
|
+
const statusbarRows = simulatedStatusbarRows(terminalAnsi);
|
|
113
|
+
const terminalPlain = Bun.stripANSI(terminalAnsi);
|
|
114
|
+
const html = ansiToHtml(terminalAnsi);
|
|
115
|
+
const metadata = json({
|
|
116
|
+
tool: CAPTURE_TOOL_VERSION,
|
|
117
|
+
command: CANONICAL_COMMAND,
|
|
118
|
+
stateId: entry.stateId,
|
|
119
|
+
viewport: { columns: entry.columns, rows: entry.rows },
|
|
120
|
+
renderMode: entry.renderMode,
|
|
121
|
+
fixedClock: "2026-01-01T00:00:00.000Z",
|
|
122
|
+
simulatedStatusbarRows: statusbarRows,
|
|
123
|
+
wrappingPolicy: statusbarRows > 2 ? "two-row" : "single-row",
|
|
124
|
+
});
|
|
125
|
+
const files = [
|
|
126
|
+
await writeArtifact(root, `${dir}/terminal.txt`, terminalPlain),
|
|
127
|
+
await writeArtifact(root, `${dir}/terminal-ansi.txt`, terminalAnsi),
|
|
128
|
+
await writeArtifact(root, `${dir}/terminal.html`, html),
|
|
129
|
+
await writeArtifact(root, `${dir}/metadata.json`, metadata),
|
|
130
|
+
];
|
|
131
|
+
entries.push({
|
|
132
|
+
key,
|
|
133
|
+
stateId: entry.stateId,
|
|
134
|
+
viewport: { columns: entry.columns, rows: entry.rows },
|
|
135
|
+
renderMode: entry.renderMode,
|
|
136
|
+
simulatedStatusbarRows: statusbarRows,
|
|
137
|
+
files,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
await writeArtifact(
|
|
141
|
+
root,
|
|
142
|
+
"manifest.json",
|
|
143
|
+
json({ tool: CAPTURE_TOOL_VERSION, sourceHead, sourceHash: digest, captureActor, entries }),
|
|
144
|
+
);
|
|
145
|
+
process.stdout.write(`Captured ${entries.length} status-line custom editor showcase entries at ${root}\n`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
void main().catch(error => {
|
|
149
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
@@ -11,6 +11,8 @@ const inventoryPath = process.env.GJC_SDK_OPERATION_INVENTORY
|
|
|
11
11
|
|
|
12
12
|
/** Reviewed seams deliberately excluded from the public SDK operation surface. */
|
|
13
13
|
const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
14
|
+
"agent_session:reloadSkills":
|
|
15
|
+
"internal marketplace lifecycle refresh for the session skill catalog; not an independent SDK ingress operation",
|
|
14
16
|
"agent_session:getTerminalTurnEpoch":
|
|
15
17
|
"internal terminal-abort bus seam (epoch only), threaded via terminalAbortSeams; not a user-facing SDK control seam",
|
|
16
18
|
"agent_session:cancelPendingPreflightForTerminalAbort":
|
|
@@ -119,6 +121,13 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
119
121
|
"test-only parked terminal flush seam, not a user-facing SDK control seam",
|
|
120
122
|
"agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
|
|
121
123
|
"test-only cancellation seam, not a user-facing SDK control seam",
|
|
124
|
+
"agent_session:awaitDisposeCompletion": "test-only disposal completion seam, not a user-facing SDK control seam",
|
|
125
|
+
"agent_session:setDisposeTimeoutForTests": "test-only disposal timeout seam, not a user-facing SDK control seam",
|
|
126
|
+
"agent_session:trackPostPromptTaskForTests": "test-only post-prompt task seam, not a user-facing SDK control seam",
|
|
127
|
+
"agent_session:requestWorkerIntegrationForTests":
|
|
128
|
+
"test-only worker integration seam, not a user-facing SDK control seam",
|
|
129
|
+
"agent_session:runWithPromptAdmissionForTests":
|
|
130
|
+
"test-only prompt admission seam, not a user-facing SDK control seam",
|
|
122
131
|
"agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
|
|
123
132
|
"agent_session:emitNotice": "internal accessor/plumbing, not a user-facing control seam",
|
|
124
133
|
"agent_session:subscribe": "internal accessor/plumbing, not a user-facing control seam",
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ansiToHtml } from "./capture-sticky-viewport-showcase";
|
|
4
|
+
|
|
5
|
+
const CAPTURE_TOOL_VERSION = "status-line-custom-editor-showcase-v1";
|
|
6
|
+
const CANONICAL_COMMAND =
|
|
7
|
+
"bun packages/coding-agent/scripts/capture-status-line-custom-editor-showcase.ts --out .gjc/qa/status-line-custom-editor-<run>";
|
|
8
|
+
const FIXED_CLOCK = "2026-01-01T00:00:00.000Z";
|
|
9
|
+
|
|
10
|
+
const UNICODE_STATES = [
|
|
11
|
+
"root-statusbar",
|
|
12
|
+
"picked-origin-slot",
|
|
13
|
+
"palette-exact-insert",
|
|
14
|
+
"separator-control",
|
|
15
|
+
"separator-choice-focused",
|
|
16
|
+
"separator-choice-applied",
|
|
17
|
+
"option-boolean-choice-focused",
|
|
18
|
+
"option-enum-choice-focused",
|
|
19
|
+
"option-numeric-choice-focused",
|
|
20
|
+
"option-choice-applied",
|
|
21
|
+
"exit-restored",
|
|
22
|
+
"confirm-persisted",
|
|
23
|
+
"overflow-two-row-warning",
|
|
24
|
+
] as const;
|
|
25
|
+
const ASCII_STATES = [
|
|
26
|
+
"root-statusbar",
|
|
27
|
+
"picked-origin-slot",
|
|
28
|
+
"separator-choice-focused",
|
|
29
|
+
"option-boolean-choice-focused",
|
|
30
|
+
"option-enum-choice-focused",
|
|
31
|
+
"option-numeric-choice-focused",
|
|
32
|
+
] as const;
|
|
33
|
+
const EXPECTED_KEYS = new Set([
|
|
34
|
+
...UNICODE_STATES.map(state => `${state}/80x32/unicode-color`),
|
|
35
|
+
"overflow-two-row-warning/48x40/unicode-color",
|
|
36
|
+
"narrow-cjk/48x40/unicode-color",
|
|
37
|
+
...ASCII_STATES.map(state => `${state}/80x32/ascii-no-color`),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function expectedWrappingPolicy(statusbarRows: number): string {
|
|
41
|
+
return statusbarRows > 2 ? "two-row" : "single-row";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ManifestFile {
|
|
45
|
+
tool: string;
|
|
46
|
+
sourceHead: string;
|
|
47
|
+
sourceHash: string;
|
|
48
|
+
captureActor: string;
|
|
49
|
+
entries: Array<{
|
|
50
|
+
key: string;
|
|
51
|
+
stateId: string;
|
|
52
|
+
viewport: { columns: number; rows: number };
|
|
53
|
+
renderMode: string;
|
|
54
|
+
simulatedStatusbarRows: number;
|
|
55
|
+
files: Array<{ path: string; sha256: string; byte_length: number }>;
|
|
56
|
+
}>;
|
|
57
|
+
independentReview?: {
|
|
58
|
+
reviewer: string;
|
|
59
|
+
reviewerAssociation: "OWNER" | "MEMBER" | "COLLABORATOR";
|
|
60
|
+
verdict: "approved" | "rejected";
|
|
61
|
+
evidence: string;
|
|
62
|
+
manifestSha256?: string;
|
|
63
|
+
sourceHash?: string;
|
|
64
|
+
headSha?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sha256(value: string): string {
|
|
69
|
+
return new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function measuredStatusbarRows(plain: string): number {
|
|
73
|
+
const lines = plain.split("\n");
|
|
74
|
+
const heading = lines.findIndex(line => line.includes("Simulated statusbar"));
|
|
75
|
+
if (heading < 0) return 0;
|
|
76
|
+
let end = heading + 1;
|
|
77
|
+
while (end < lines.length && lines[end]?.trim() && !lines[end]?.includes("Warning:")) end++;
|
|
78
|
+
return Math.max(0, end - heading - 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseArgs(args: string[]): { root: string; requireIndependentReview: boolean } {
|
|
82
|
+
let root = "";
|
|
83
|
+
let requireIndependentReview = false;
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
const arg = args[i];
|
|
86
|
+
if (arg === "--root") root = args[++i] ?? "";
|
|
87
|
+
else if (arg === "--require-independent-review") requireIndependentReview = true;
|
|
88
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
89
|
+
}
|
|
90
|
+
if (!root) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Usage: bun packages/coding-agent/scripts/verify-status-line-custom-editor-showcase.ts --root <dir> [--require-independent-review]",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return { root, requireIndependentReview };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function readText(root: string, relativePath: string): Promise<string> {
|
|
99
|
+
const fullPath = path.resolve(root, relativePath);
|
|
100
|
+
const resolvedRoot = path.resolve(root);
|
|
101
|
+
if (!fullPath.startsWith(`${resolvedRoot}${path.sep}`)) throw new Error(`Artifact escapes root: ${relativePath}`);
|
|
102
|
+
return await Bun.file(fullPath).text();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function tryReadReview(root: string): Promise<ManifestFile["independentReview"] | undefined> {
|
|
106
|
+
const reviewPath = path.join(root, "independent-review.json");
|
|
107
|
+
if (!(await Bun.file(reviewPath).exists())) return undefined;
|
|
108
|
+
return JSON.parse(await Bun.file(reviewPath).text()) as ManifestFile["independentReview"];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function collectRelativeFiles(root: string, dir = ""): Promise<string[]> {
|
|
112
|
+
const fullDir = path.join(root, dir);
|
|
113
|
+
const entries = await fs.readdir(fullDir, { withFileTypes: true });
|
|
114
|
+
const files: string[] = [];
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const relative = dir ? `${dir}/${entry.name}` : entry.name;
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
files.push(...(await collectRelativeFiles(root, relative)));
|
|
119
|
+
} else if (entry.isFile()) {
|
|
120
|
+
files.push(relative);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return files;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function requireWitness(entry: ManifestFile["entries"][number], plain: string): void {
|
|
127
|
+
if (entry.stateId === "exit-restored" || entry.stateId === "confirm-persisted") {
|
|
128
|
+
if (!plain.includes("Status Line")) throw new Error(`${entry.key}: missing returned settings witness`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const baseWitnesses = [
|
|
132
|
+
"Status Line Custom Editor",
|
|
133
|
+
"Simulated statusbar",
|
|
134
|
+
"Hidden segment palette",
|
|
135
|
+
"Visible choices",
|
|
136
|
+
];
|
|
137
|
+
for (const witness of baseWitnesses) {
|
|
138
|
+
if (!plain.includes(witness)) throw new Error(`${entry.key}: missing witness ${witness}`);
|
|
139
|
+
}
|
|
140
|
+
if (entry.stateId.includes("choice-focused") && !plain.includes("Choices:")) {
|
|
141
|
+
throw new Error(`${entry.key}: missing choice panel witness`);
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
entry.stateId === "picked-origin-slot" &&
|
|
145
|
+
(!plain.includes("Selected:") || plain.includes("Floating ghost") || plain.includes("Origin "))
|
|
146
|
+
) {
|
|
147
|
+
throw new Error(`${entry.key}: missing selected witness or stale ghost/origin text present`);
|
|
148
|
+
}
|
|
149
|
+
if (entry.stateId.includes("overflow") && !plain.includes("Warning: statusbar wrapped to 2 rows")) {
|
|
150
|
+
throw new Error(`${entry.key}: missing overflow warning`);
|
|
151
|
+
}
|
|
152
|
+
if (entry.stateId === "narrow-cjk" && !/[\u3131-\uD7A3\u3040-\u30FF\u3400-\u9FFF]/.test(plain)) {
|
|
153
|
+
throw new Error(`${entry.key}: missing CJK width witness`);
|
|
154
|
+
}
|
|
155
|
+
if (plain.includes("Move left:") || plain.includes("Move right:") || plain.includes("Segment: gajae")) {
|
|
156
|
+
throw new Error(`${entry.key}: legacy row witness present`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function main(): Promise<void> {
|
|
161
|
+
const { root, requireIndependentReview } = parseArgs(process.argv.slice(2));
|
|
162
|
+
const manifestText = await Bun.file(path.join(root, "manifest.json")).text();
|
|
163
|
+
const manifest = JSON.parse(manifestText) as ManifestFile;
|
|
164
|
+
const manifestSha256 = sha256(manifestText);
|
|
165
|
+
if (manifest.tool !== CAPTURE_TOOL_VERSION) throw new Error(`Unexpected tool version: ${manifest.tool}`);
|
|
166
|
+
if (!/^[0-9a-f]{40}$/.test(manifest.sourceHead)) throw new Error("Manifest source HEAD is not a full commit SHA");
|
|
167
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(manifest.sourceHash)) throw new Error("Manifest source hash is invalid");
|
|
168
|
+
const actualKeys = new Set(manifest.entries.map(entry => entry.key));
|
|
169
|
+
if (manifest.entries.length !== actualKeys.size) throw new Error("Duplicate showcase entries found");
|
|
170
|
+
if (manifest.entries.length !== EXPECTED_KEYS.size) throw new Error("Unexpected showcase entry count");
|
|
171
|
+
for (const key of EXPECTED_KEYS) if (!actualKeys.has(key)) throw new Error(`Missing showcase key: ${key}`);
|
|
172
|
+
for (const key of actualKeys) if (!EXPECTED_KEYS.has(key)) throw new Error(`Unexpected showcase key: ${key}`);
|
|
173
|
+
if (requireIndependentReview) {
|
|
174
|
+
const review = manifest.independentReview ?? (await tryReadReview(root));
|
|
175
|
+
if (
|
|
176
|
+
review?.verdict !== "approved" ||
|
|
177
|
+
!review.reviewer?.trim() ||
|
|
178
|
+
review.reviewer === manifest.captureActor ||
|
|
179
|
+
!(["OWNER", "MEMBER", "COLLABORATOR"] as const).includes(review.reviewerAssociation) ||
|
|
180
|
+
!/^https:\/\/github\.com\//.test(review.evidence) ||
|
|
181
|
+
review.manifestSha256 !== manifestSha256 ||
|
|
182
|
+
review.sourceHash !== manifest.sourceHash ||
|
|
183
|
+
review.headSha !== manifest.sourceHead
|
|
184
|
+
) {
|
|
185
|
+
throw new Error("Missing approved independent review in manifest");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const artifactPaths = new Set<string>();
|
|
189
|
+
const expectedRootFiles = new Set(["manifest.json"]);
|
|
190
|
+
if (await Bun.file(path.join(root, "independent-review.json")).exists())
|
|
191
|
+
expectedRootFiles.add("independent-review.json");
|
|
192
|
+
for (const entry of manifest.entries) {
|
|
193
|
+
if (entry.key !== `${entry.stateId}/${entry.viewport.columns}x${entry.viewport.rows}/${entry.renderMode}`) {
|
|
194
|
+
throw new Error(`${entry.key}: key does not match manifest fields`);
|
|
195
|
+
}
|
|
196
|
+
const expectedDirectory = entry.key.replace(/[^a-zA-Z0-9._/-]/g, "_");
|
|
197
|
+
const names = new Set(entry.files.map(file => path.basename(file.path)));
|
|
198
|
+
if (names.size !== 4 || entry.files.length !== 4)
|
|
199
|
+
throw new Error(`${entry.key}: artifact membership is not exact`);
|
|
200
|
+
for (const required of ["terminal.txt", "terminal-ansi.txt", "terminal.html", "metadata.json"]) {
|
|
201
|
+
if (!names.has(required)) throw new Error(`${entry.key}: missing ${required}`);
|
|
202
|
+
}
|
|
203
|
+
for (const file of entry.files) {
|
|
204
|
+
if (!file.path.startsWith(`${expectedDirectory}/`)) {
|
|
205
|
+
throw new Error(`${entry.key}: artifact path is not bound to canonical entry directory`);
|
|
206
|
+
}
|
|
207
|
+
if (artifactPaths.has(file.path)) throw new Error(`${entry.key}: duplicate artifact path ${file.path}`);
|
|
208
|
+
artifactPaths.add(file.path);
|
|
209
|
+
const content = await readText(root, file.path);
|
|
210
|
+
if (sha256(content) !== file.sha256) throw new Error(`${entry.key}: sha mismatch for ${file.path}`);
|
|
211
|
+
if (Buffer.byteLength(content) !== file.byte_length) {
|
|
212
|
+
throw new Error(`${entry.key}: byte length mismatch for ${file.path}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const plainPath = entry.files.find(file => path.basename(file.path) === "terminal.txt")?.path;
|
|
216
|
+
const ansiPath = entry.files.find(file => path.basename(file.path) === "terminal-ansi.txt")?.path;
|
|
217
|
+
const htmlPath = entry.files.find(file => path.basename(file.path) === "terminal.html")?.path;
|
|
218
|
+
const metadataPath = entry.files.find(file => path.basename(file.path) === "metadata.json")?.path;
|
|
219
|
+
if (!plainPath || !ansiPath || !htmlPath || !metadataPath) throw new Error(`${entry.key}: incomplete file list`);
|
|
220
|
+
const plain = await readText(root, plainPath);
|
|
221
|
+
const ansi = await readText(root, ansiPath);
|
|
222
|
+
const html = await readText(root, htmlPath);
|
|
223
|
+
if (Bun.stripANSI(ansi) !== plain) throw new Error(`${entry.key}: stripped ANSI differs from plain text`);
|
|
224
|
+
if (html !== ansiToHtml(ansi)) throw new Error(`${entry.key}: HTML artifact is not canonical ANSI conversion`);
|
|
225
|
+
if (plain.split("\n").length > entry.viewport.rows) {
|
|
226
|
+
throw new Error(`${entry.key}: terminal row count exceeds declared viewport`);
|
|
227
|
+
}
|
|
228
|
+
for (const line of plain.split("\n")) {
|
|
229
|
+
if (Bun.stringWidth(line) > entry.viewport.columns) {
|
|
230
|
+
throw new Error(`${entry.key}: terminal line exceeds declared viewport width`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (entry.renderMode === "ascii-no-color") {
|
|
234
|
+
if (/\x1b\[[0-9;]*m/.test(ansi)) throw new Error(`${entry.key}: ascii-no-color artifact contains ANSI`);
|
|
235
|
+
if (/[^\x09\x0a\x0d\x20-\x7e]/.test(plain)) {
|
|
236
|
+
throw new Error(`${entry.key}: ascii-no-color artifact contains non-ASCII glyphs`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const metadata = JSON.parse(await readText(root, metadataPath)) as {
|
|
240
|
+
tool?: string;
|
|
241
|
+
command?: string;
|
|
242
|
+
stateId?: string;
|
|
243
|
+
viewport?: { columns?: number; rows?: number };
|
|
244
|
+
renderMode?: string;
|
|
245
|
+
fixedClock?: string;
|
|
246
|
+
simulatedStatusbarRows?: number;
|
|
247
|
+
wrappingPolicy?: string;
|
|
248
|
+
};
|
|
249
|
+
const actualStatusbarRows = measuredStatusbarRows(plain);
|
|
250
|
+
if (
|
|
251
|
+
metadata.tool !== CAPTURE_TOOL_VERSION ||
|
|
252
|
+
metadata.command !== CANONICAL_COMMAND ||
|
|
253
|
+
metadata.stateId !== entry.stateId ||
|
|
254
|
+
metadata.viewport?.columns !== entry.viewport.columns ||
|
|
255
|
+
metadata.viewport?.rows !== entry.viewport.rows ||
|
|
256
|
+
metadata.renderMode !== entry.renderMode ||
|
|
257
|
+
metadata.fixedClock !== FIXED_CLOCK ||
|
|
258
|
+
entry.simulatedStatusbarRows !== actualStatusbarRows ||
|
|
259
|
+
metadata.simulatedStatusbarRows !== actualStatusbarRows ||
|
|
260
|
+
metadata.wrappingPolicy !== expectedWrappingPolicy(actualStatusbarRows)
|
|
261
|
+
) {
|
|
262
|
+
throw new Error(`${entry.key}: metadata does not match manifest`);
|
|
263
|
+
}
|
|
264
|
+
requireWitness(entry, plain);
|
|
265
|
+
if (entry.stateId === "palette-exact-insert" && (plain.includes("Selected:") || plain.includes("{gajae}"))) {
|
|
266
|
+
throw new Error(`${entry.key}: exact insertion state still has floating/hidden segment evidence`);
|
|
267
|
+
}
|
|
268
|
+
if (entry.stateId === "palette-exact-insert" && !plain.includes("left model / path /")) {
|
|
269
|
+
throw new Error(`${entry.key}: exact insertion state missing ordered layout witness`);
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
entry.stateId === "exit-restored" &&
|
|
273
|
+
!plain.includes("left=model,path,git right=session_name,jobs separator=slash")
|
|
274
|
+
) {
|
|
275
|
+
throw new Error(`${entry.key}: exit-restored state missing exact restored preview witness`);
|
|
276
|
+
}
|
|
277
|
+
if (
|
|
278
|
+
entry.stateId === "confirm-persisted" &&
|
|
279
|
+
!plain.includes("left=path,git right=session_name,jobs separator=slash")
|
|
280
|
+
) {
|
|
281
|
+
throw new Error(`${entry.key}: confirm-persisted state missing exact persisted preview witness`);
|
|
282
|
+
}
|
|
283
|
+
if (entry.stateId === "separator-choice-applied" && !plain.includes("[Pipe]")) {
|
|
284
|
+
throw new Error(`${entry.key}: separator applied state missing Pipe witness`);
|
|
285
|
+
}
|
|
286
|
+
if (entry.stateId === "option-choice-applied" && !plain.includes("[40]")) {
|
|
287
|
+
throw new Error(`${entry.key}: option applied state missing numeric option witness`);
|
|
288
|
+
}
|
|
289
|
+
if (entry.stateId.includes("narrow") && !plain.includes("Focused target:")) {
|
|
290
|
+
throw new Error(`${entry.key}: narrow state missing focused target witness`);
|
|
291
|
+
}
|
|
292
|
+
if (entry.stateId === "overflow-two-row-warning" && actualStatusbarRows !== 4) {
|
|
293
|
+
throw new Error(`${entry.key}: expected two actual and two slot statusbar rows`);
|
|
294
|
+
}
|
|
295
|
+
if (entry.stateId === "narrow-cjk" && !plain.includes("상태줄 경계 · 意味の境界 · 语义边界")) {
|
|
296
|
+
throw new Error(`${entry.key}: missing exact mixed-width semantic witness`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const filePath of await collectRelativeFiles(root)) {
|
|
300
|
+
if (expectedRootFiles.has(filePath)) continue;
|
|
301
|
+
if (!artifactPaths.has(filePath)) throw new Error(`Unmanifested artifact file: ${filePath}`);
|
|
302
|
+
}
|
|
303
|
+
process.stdout.write(`Verified ${manifest.entries.length} status-line custom editor showcase entries at ${root}\n`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
void main().catch(error => {
|
|
307
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|
package/src/cli/args.ts
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
* CLI argument parsing
|
|
3
3
|
*/
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
6
6
|
import { logger } from "@gajae-code/utils";
|
|
7
7
|
import { CliParseError } from "@gajae-code/utils/cli";
|
|
8
|
-
import {
|
|
8
|
+
import { parseThinkingLevel } from "../thinking";
|
|
9
9
|
import { BUILTIN_TOOLS } from "../tools";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type ConsumerLaunchFlagName,
|
|
12
|
+
LAUNCH_PARSE_FLAGS,
|
|
13
|
+
launchFlagIsOwnedBy,
|
|
14
|
+
ROOT_THINKING_LEVELS,
|
|
15
|
+
type RootThinkingLevel,
|
|
16
|
+
} from "./root-flags";
|
|
11
17
|
|
|
12
18
|
const ROOT_FLAG_TOKENS = new Set<string>();
|
|
13
19
|
for (const [name, descriptor] of Object.entries(LAUNCH_PARSE_FLAGS)) {
|
|
@@ -55,7 +61,7 @@ export interface Args {
|
|
|
55
61
|
mcpConfig?: string;
|
|
56
62
|
/** Opt a standalone session out of conventional MCP autoload (mutually exclusive with --mcp-config). */
|
|
57
63
|
noMcp?: boolean;
|
|
58
|
-
thinking?:
|
|
64
|
+
thinking?: RootThinkingLevel;
|
|
59
65
|
continue?: boolean;
|
|
60
66
|
resume?: string | true;
|
|
61
67
|
help?: boolean;
|
|
@@ -138,6 +144,7 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
|
|
|
138
144
|
unknownFlags: new Map(),
|
|
139
145
|
};
|
|
140
146
|
const consumerFlags = new Map<ConsumerLaunchFlagName, string>();
|
|
147
|
+
let hasThinkingFlag = false;
|
|
141
148
|
|
|
142
149
|
for (let i = 0; i < args.length; i++) {
|
|
143
150
|
let arg = args[i];
|
|
@@ -344,19 +351,23 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
|
|
|
344
351
|
}
|
|
345
352
|
result.tools = validTools;
|
|
346
353
|
} else if (arg === "--thinking") {
|
|
354
|
+
if (hasThinkingFlag) {
|
|
355
|
+
throw new CliParseError("--thinking can only be specified once");
|
|
356
|
+
}
|
|
357
|
+
hasThinkingFlag = true;
|
|
347
358
|
// Match --credential / --mcp-config: a missing value or a following flag is
|
|
348
359
|
// a usage error, not a silent no-op / accidental consumption of `-p`.
|
|
349
360
|
const next = args[i + 1];
|
|
350
361
|
if (!next || next.startsWith("-")) {
|
|
351
|
-
throw new CliParseError(`--thinking requires <level> (${
|
|
362
|
+
throw new CliParseError(`--thinking requires <level> (${ROOT_THINKING_LEVELS.join(", ")})`);
|
|
352
363
|
}
|
|
353
364
|
const rawThinking = args[++i];
|
|
354
|
-
const thinking =
|
|
355
|
-
if (thinking === undefined) {
|
|
365
|
+
const thinking = parseThinkingLevel(rawThinking);
|
|
366
|
+
if (thinking === undefined || thinking === ThinkingLevel.Inherit) {
|
|
356
367
|
// Fail closed: a silent ignore left users believing `ultra` (or any typo)
|
|
357
|
-
// was applied. Help / Flags.options advertise the
|
|
368
|
+
// was applied. Help / Flags.options advertise the root thinking levels.
|
|
358
369
|
throw new CliParseError(
|
|
359
|
-
`Invalid --thinking level "${rawThinking}". Expected one of: ${
|
|
370
|
+
`Invalid --thinking level "${rawThinking}". Expected one of: ${ROOT_THINKING_LEVELS.join(", ")}`,
|
|
360
371
|
);
|
|
361
372
|
}
|
|
362
373
|
result.thinking = thinking;
|
package/src/cli/root-flags.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { THINKING_EFFORTS } from "@gajae-code/ai/core";
|
|
1
2
|
import { type FlagDescriptor, Flags } from "@gajae-code/utils/cli";
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
+
export const ROOT_THINKING_LEVELS = ["off", ...THINKING_EFFORTS] as const;
|
|
5
|
+
export type RootThinkingLevel = (typeof ROOT_THINKING_LEVELS)[number];
|
|
4
6
|
|
|
5
7
|
/** Public launch flags shared by root help, completion, and the launch command. */
|
|
6
8
|
export const ROOT_LAUNCH_FLAGS = {
|
|
@@ -63,8 +65,8 @@ export const ROOT_LAUNCH_FLAGS = {
|
|
|
63
65
|
tmux: Flags.boolean({ description: "Launch interactive startup inside tmux" }),
|
|
64
66
|
tools: Flags.string({ description: "Comma-separated list of tools to enable (default: all)" }),
|
|
65
67
|
thinking: Flags.string({
|
|
66
|
-
description: `Set thinking level: ${
|
|
67
|
-
options:
|
|
68
|
+
description: `Set thinking level: ${ROOT_THINKING_LEVELS.join(", ")}`,
|
|
69
|
+
options: ROOT_THINKING_LEVELS,
|
|
68
70
|
}),
|
|
69
71
|
"no-rules": Flags.boolean({ description: "Disable rules discovery and loading" }),
|
|
70
72
|
export: Flags.string({ description: "Export session file to HTML and exit" }),
|
|
@@ -359,6 +359,7 @@ export const TIER_MAP_SKIP_LIST = {
|
|
|
359
359
|
"nvidia/poolside/laguna-xs-2.1": { rationale: "post-rebase catalog addition from dev; not yet curated" },
|
|
360
360
|
"nvidia/thinkingmachines/inkling": { rationale: "post-rebase catalog addition from dev; not yet curated" },
|
|
361
361
|
"nvidia/upstage/solar-10.7b-instruct": { rationale: "post-rebase catalog addition from dev; not yet curated" },
|
|
362
|
+
"openai-codex/gpt-6-astra": { rationale: "GPT-6 Astra catalog addition (2026-09-03 release); not yet curated" },
|
|
362
363
|
"openai-codex/gpt-daybreak-blue-latest": { rationale: "post-rebase catalog addition from dev; not yet curated" },
|
|
363
364
|
"opencode-go/deepseek-v4-flash-vision-exp": {
|
|
364
365
|
rationale: "post-rebase catalog addition from dev; not yet curated",
|