orizu 0.5.18 → 0.5.20
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/README.md +23 -0
- package/dist/diff-comments-cli.js +68 -0
- package/dist/gepa-engine-dispatch.js +261 -0
- package/dist/gepa-python-command.js +26 -0
- package/dist/gepa-python-paths.js +39 -0
- package/dist/help.js +4 -4
- package/dist/index.js +37 -63
- package/dist/markdown-report-input.js +15 -1
- package/dist/prompt-length-wire.js +30 -0
- package/dist/prompt-report-cli.js +2 -2
- package/dist/prompt-summary-table.js +37 -0
- package/dist/report-markdown-blankness.js +6 -0
- package/dist/report-replacement-warning.js +31 -0
- package/package.json +2 -2
- package/scripts/build-cli-dist.mjs +30 -0
- package/scripts/orizu-source-checkout.d.mts +1 -0
- package/scripts/orizu-source-checkout.mjs +10 -0
- package/scripts/vendor-gepa-python.mjs +187 -4
- package/src/diff-comments-cli.ts +125 -0
- package/src/gepa-engine-dispatch.ts +300 -0
- package/src/gepa-python-command.ts +31 -0
- package/src/gepa-python-paths.ts +53 -0
- package/src/help.ts +4 -4
- package/src/index.ts +46 -69
- package/src/markdown-report-input.ts +19 -1
- package/src/prompt-length-wire.ts +65 -0
- package/src/prompt-report-cli.ts +2 -2
- package/src/prompt-summary-table.ts +57 -0
- package/src/report-markdown-blankness.ts +6 -0
- package/src/report-replacement-warning.ts +35 -0
- package/vendor/gepa-python/LICENSE +7 -0
- package/vendor/gepa-python/manifest.json +107 -0
- package/vendor/gepa-python/src/gepa/__init__.py +22 -0
- package/vendor/gepa-python/src/gepa/adapters/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/__init__.py +26 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/confidence_adapter.py +618 -0
- package/vendor/gepa-python/src/gepa/adapters/confidence_adapter/scoring.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/default_adapter/default_adapter.py +206 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/dspy_adapter.py +525 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_adapter/instruction_proposal.py +500 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/__init__.py +6 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- package/vendor/gepa-python/src/gepa/adapters/dspy_full_program_adapter/full_program_adapter.py +269 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/__init__.py +56 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- package/vendor/gepa-python/src/gepa/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/__init__.py +11 -0
- package/vendor/gepa-python/src/gepa/adapters/langchain_adapter/langchain_adapter.py +261 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/__init__.py +37 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_adapter.py +693 -0
- package/vendor/gepa-python/src/gepa/adapters/mcp_adapter/mcp_client.py +364 -0
- package/vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py +879 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- package/vendor/gepa-python/src/gepa/api.py +468 -0
- package/vendor/gepa-python/src/gepa/core/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/core/adapter.py +227 -0
- package/vendor/gepa-python/src/gepa/core/callbacks.py +551 -0
- package/vendor/gepa-python/src/gepa/core/data_loader.py +74 -0
- package/vendor/gepa-python/src/gepa/core/engine.py +1025 -0
- package/vendor/gepa-python/src/gepa/core/result.py +271 -0
- package/vendor/gepa-python/src/gepa/core/state.py +706 -0
- package/vendor/gepa-python/src/gepa/examples/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/examples/aime.py +24 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/eval_default.py +111 -0
- package/vendor/gepa-python/src/gepa/examples/anymaths-bench/train_anymaths.py +177 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/__init__.py +4 -0
- package/vendor/gepa-python/src/gepa/examples/mcp_adapter/mcp_optimization_example.py +456 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/__init__.py +9 -0
- package/vendor/gepa-python/src/gepa/examples/rag_adapter/rag_optimization.py +824 -0
- package/vendor/gepa-python/src/gepa/examples/terminal-bench/train_terminus.py +161 -0
- package/vendor/gepa-python/src/gepa/gepa_utils.py +117 -0
- package/vendor/gepa-python/src/gepa/gskill/__init__.py +1 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/cost_tracker.py +194 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code.py +1117 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/claude_code_skills.py +613 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/evaluate/mini_swe_agent.py +440 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/experiment_logger.py +367 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_fitness_fn.py +209 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/swe_harness.py +352 -0
- package/vendor/gepa-python/src/gepa/gskill/gskill/train_optimize_anything.py +830 -0
- package/vendor/gepa-python/src/gepa/image.py +104 -0
- package/vendor/gepa-python/src/gepa/lm.py +261 -0
- package/vendor/gepa-python/src/gepa/logging/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/logging/experiment_tracker.py +457 -0
- package/vendor/gepa-python/src/gepa/logging/logger.py +75 -0
- package/vendor/gepa-python/src/gepa/logging/utils.py +130 -0
- package/vendor/gepa-python/src/gepa/optimize_anything.py +1701 -0
- package/vendor/gepa-python/src/gepa/proposer/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/base.py +53 -0
- package/vendor/gepa-python/src/gepa/proposer/merge.py +403 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/base.py +64 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflection_lm.py +178 -0
- package/vendor/gepa-python/src/gepa/proposer/reflective_mutation/reflective_mutation.py +641 -0
- package/vendor/gepa-python/src/gepa/py.typed +0 -0
- package/vendor/gepa-python/src/gepa/strategies/__init__.py +0 -0
- package/vendor/gepa-python/src/gepa/strategies/acceptance.py +66 -0
- package/vendor/gepa-python/src/gepa/strategies/batch_sampler.py +103 -0
- package/vendor/gepa-python/src/gepa/strategies/candidate_selector.py +82 -0
- package/vendor/gepa-python/src/gepa/strategies/component_selector.py +36 -0
- package/vendor/gepa-python/src/gepa/strategies/eval_policy.py +64 -0
- package/vendor/gepa-python/src/gepa/strategies/instruction_proposal.py +153 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_sampling.py +128 -0
- package/vendor/gepa-python/src/gepa/strategies/proposal_selection.py +84 -0
- package/vendor/gepa-python/src/gepa/utils/__init__.py +63 -0
- package/vendor/gepa-python/src/gepa/utils/code_execution.py +626 -0
- package/vendor/gepa-python/src/gepa/utils/stdio_capture.py +145 -0
- package/vendor/gepa-python/src/gepa/utils/stop_condition.py +230 -0
- package/vendor/gepa-python/src/gepa/visualization.py +359 -0
- package/vendor/orizu-gepa/pyproject.toml +18 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +8 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__main__.py +5 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +132 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +652 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/cli.py +7 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +177 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/preflight.py +64 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +106 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +643 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +38 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +202 -0
- package/vendor/orizu-gepa-python/pyproject.toml +1 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +49 -0
- package/vendor/skills/orizu-cli/SKILL.md +1 -1
- package/vendor/skills/orizu-cli/references/cli-reference.md +10 -0
- package/vendor/skills/orizu-cli/references/optimization-reports.md +1 -1
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +1 -1
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +23 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { basename } from 'path';
|
|
3
|
+
import { hasCanonicalReportMarkdownContent } from './report-markdown-blankness.js';
|
|
3
4
|
export const MARKDOWN_REPORT_MAX_BYTES = 2 * 1024 * 1024;
|
|
4
5
|
function getArg(args, name) {
|
|
5
6
|
const index = args.indexOf(name);
|
|
@@ -17,6 +18,11 @@ function rejectDashPrefixedOptionValue(name, value) {
|
|
|
17
18
|
throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
21
|
+
function rejectMissingOptionValue(args, name) {
|
|
22
|
+
if (args.some((arg, index) => arg === name && index + 1 >= args.length)) {
|
|
23
|
+
throw new Error(`${name} requires a value`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
20
26
|
function readSourceFile(pathArg) {
|
|
21
27
|
const expandedPath = expandHomePath(pathArg);
|
|
22
28
|
try {
|
|
@@ -31,6 +37,14 @@ function readSourceFile(pathArg) {
|
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
export function readMarkdownReportInput(args, reportLabel) {
|
|
40
|
+
if (args.some(arg => arg.startsWith('--report='))) {
|
|
41
|
+
throw new Error('use --report <markdown|@file> with a space, not =');
|
|
42
|
+
}
|
|
43
|
+
if (args.some(arg => arg.startsWith('--report-file='))) {
|
|
44
|
+
throw new Error('use --report-file <path> with a space, not =');
|
|
45
|
+
}
|
|
46
|
+
rejectMissingOptionValue(args, '--report');
|
|
47
|
+
rejectMissingOptionValue(args, '--report-file');
|
|
34
48
|
const report = getArg(args, '--report');
|
|
35
49
|
const reportFile = getArg(args, '--report-file');
|
|
36
50
|
rejectDashPrefixedOptionValue('--report', report);
|
|
@@ -57,7 +71,7 @@ export function readMarkdownReportInput(args, reportLabel) {
|
|
|
57
71
|
markdown = report || '';
|
|
58
72
|
sourceName = 'inline';
|
|
59
73
|
}
|
|
60
|
-
if (!markdown
|
|
74
|
+
if (!hasCanonicalReportMarkdownContent(markdown)) {
|
|
61
75
|
throw new Error(`${reportLabel} report markdown must not be blank`);
|
|
62
76
|
}
|
|
63
77
|
if (Buffer.byteLength(markdown, 'utf8') > MARKDOWN_REPORT_MAX_BYTES) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const CLI_LENGTH_STATS_FIELDS = [
|
|
2
|
+
'tokens',
|
|
3
|
+
'lines',
|
|
4
|
+
'chars',
|
|
5
|
+
'words',
|
|
6
|
+
];
|
|
7
|
+
export const CLI_LENGTH_DELTA_SPLIT_UNAVAILABLE_REASONS = [
|
|
8
|
+
'diff_degraded_size_limit',
|
|
9
|
+
'diff_degraded_cell_limit',
|
|
10
|
+
];
|
|
11
|
+
export const CLI_LENGTH_MEASUREMENT_UNAVAILABLE_REASONS = [
|
|
12
|
+
'length_measurement_size_limit',
|
|
13
|
+
'length_measurement_failed',
|
|
14
|
+
'body_resolution_failed',
|
|
15
|
+
'measurement_cap_exceeded',
|
|
16
|
+
'enrichment_failed',
|
|
17
|
+
'bodies_unavailable_event_cap',
|
|
18
|
+
];
|
|
19
|
+
export function formatFiniteLengthCount(value, signed = false) {
|
|
20
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
21
|
+
return null;
|
|
22
|
+
if (!signed)
|
|
23
|
+
return value.toLocaleString('en-US');
|
|
24
|
+
const formatted = Math.abs(value).toLocaleString('en-US');
|
|
25
|
+
if (value > 0)
|
|
26
|
+
return `+${formatted}`;
|
|
27
|
+
if (value < 0)
|
|
28
|
+
return `-${formatted}`;
|
|
29
|
+
return formatted;
|
|
30
|
+
}
|
|
@@ -27,7 +27,7 @@ export async function promptReportCommand(args, context) {
|
|
|
27
27
|
rejectDashPrefixedOptionValue(name, value);
|
|
28
28
|
}
|
|
29
29
|
if (!prompt) {
|
|
30
|
-
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
|
|
30
|
+
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> [--project <team/project>] ' +
|
|
31
31
|
'(--version <n> | --version-id <id>) ' +
|
|
32
32
|
'(--report <markdown|@file> | --report-file <path>) [--json]');
|
|
33
33
|
}
|
|
@@ -39,7 +39,7 @@ export async function promptReportCommand(args, context) {
|
|
|
39
39
|
}
|
|
40
40
|
const report = readMarkdownReportInput(args, 'Prompt');
|
|
41
41
|
if (!report) {
|
|
42
|
-
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
|
|
42
|
+
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> [--project <team/project>] ' +
|
|
43
43
|
'(--version <n> | --version-id <id>) ' +
|
|
44
44
|
'(--report <markdown|@file> | --report-file <path>) [--json]');
|
|
45
45
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
2
|
+
import { formatFiniteLengthCount, } from './prompt-length-wire.js';
|
|
3
|
+
export function printPromptSummaryTable(items, emptyMessage, printLine) {
|
|
4
|
+
if (items.length === 0) {
|
|
5
|
+
printLine(emptyMessage);
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const rows = items.map(item => ({
|
|
9
|
+
id: sanitizeTerminalText(item.id),
|
|
10
|
+
name: sanitizeTerminalText(item.name),
|
|
11
|
+
role: sanitizeTerminalText(item.role),
|
|
12
|
+
status: sanitizeTerminalText(item.status || 'active'),
|
|
13
|
+
tokens: item.lengthStats == null
|
|
14
|
+
? '—'
|
|
15
|
+
: (() => {
|
|
16
|
+
const count = formatFiniteLengthCount(item.lengthStats.tokens);
|
|
17
|
+
return count === null ? '—' : `~${count}`;
|
|
18
|
+
})(),
|
|
19
|
+
lines: item.lengthStats == null
|
|
20
|
+
? '—'
|
|
21
|
+
: formatFiniteLengthCount(item.lengthStats.lines) ?? '—',
|
|
22
|
+
chars: item.lengthStats == null
|
|
23
|
+
? '—'
|
|
24
|
+
: formatFiniteLengthCount(item.lengthStats.chars) ?? '—',
|
|
25
|
+
words: item.lengthStats == null
|
|
26
|
+
? '—'
|
|
27
|
+
: formatFiniteLengthCount(item.lengthStats.words) ?? '—',
|
|
28
|
+
}));
|
|
29
|
+
const headers = ['ID', 'NAME', 'ROLE', 'STATUS', 'TOKENS', 'LINES', 'CHARS', 'WORDS'];
|
|
30
|
+
const keys = ['id', 'name', 'role', 'status', 'tokens', 'lines', 'chars', 'words'];
|
|
31
|
+
const widths = keys.map((key, index) => Math.max(headers[index].length, ...rows.map(row => row[key].length)));
|
|
32
|
+
printLine(headers.map((header, index) => header.padEnd(widths[index])).join(' '));
|
|
33
|
+
printLine(widths.map(width => '-'.repeat(width)).join(' '));
|
|
34
|
+
rows.forEach(row => {
|
|
35
|
+
printLine(keys.map((key, index) => row[key].padEnd(widths[index])).join(' '));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// JavaScript's \s is broader than the database locale's POSIX [:space:]
|
|
2
|
+
// predicate. These four code points are content in the measured PostgreSQL
|
|
3
|
+
// boundary, so retain them explicitly when validating CLI report input.
|
|
4
|
+
export function hasCanonicalReportMarkdownContent(markdown) {
|
|
5
|
+
return /(?:[^\s]|[\u00a0\u2007\u202f\ufeff])/u.test(markdown);
|
|
6
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ALI-1452: a shared, human-readable warning for a replaced prompt-version
|
|
3
|
+
* report on a push/draft response.
|
|
4
|
+
*
|
|
5
|
+
* Re-pushing an unchanged prompt with a report deliberately replaces the
|
|
6
|
+
* version's existing report (reports are correctable; versions are immutable).
|
|
7
|
+
* The server discloses that replacement as `report_replaced:
|
|
8
|
+
* { source_name, updated_at } | null`; this turns a non-null disclosure into
|
|
9
|
+
* the stderr warning line the push commands print. JSON mode prints nothing —
|
|
10
|
+
* the payload already carries `report_replaced`.
|
|
11
|
+
*/
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A one-line warning when a push response discloses that it replaced an
|
|
17
|
+
* existing report, or null when nothing was replaced (or no report was sent).
|
|
18
|
+
*/
|
|
19
|
+
export function reportReplacementWarning(data) {
|
|
20
|
+
const replaced = data.report_replaced;
|
|
21
|
+
if (!isRecord(replaced))
|
|
22
|
+
return null;
|
|
23
|
+
const sourceName = typeof replaced.source_name === 'string'
|
|
24
|
+
? replaced.source_name
|
|
25
|
+
: 'unknown source';
|
|
26
|
+
const updatedAt = typeof replaced.updated_at === 'string'
|
|
27
|
+
? replaced.updated_at
|
|
28
|
+
: 'unknown time';
|
|
29
|
+
return ('Warning: replaced the existing report for this version ' +
|
|
30
|
+
`(${sourceName}, last updated ${updatedAt})`);
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orizu",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.20",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc -p tsconfig.json",
|
|
19
|
-
"prepack": "node scripts/vendor-gepa-python.mjs && node scripts/vendor-orizu-skill.mjs",
|
|
19
|
+
"prepack": "node scripts/build-cli-dist.mjs && node scripts/vendor-gepa-python.mjs && node scripts/vendor-orizu-skill.mjs",
|
|
20
20
|
"postpack": "node scripts/vendor-gepa-python.mjs --clean && node scripts/vendor-orizu-skill.mjs --clean"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { dirname, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
import { isOrizuSourceCheckout } from './orizu-source-checkout.mjs'
|
|
7
|
+
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const cliRoot = resolve(scriptDir, '..')
|
|
10
|
+
const repoRoot = resolve(cliRoot, '..', '..')
|
|
11
|
+
const packedEntrypoint = resolve(cliRoot, 'dist', 'index.js')
|
|
12
|
+
|
|
13
|
+
// A source checkout has the compiler and must always rebuild before packing so
|
|
14
|
+
// ignored dist cannot carry an old CLI. A published tarball has no dev compiler;
|
|
15
|
+
// it may be repacked only when it already carries the built entrypoint.
|
|
16
|
+
if (!isOrizuSourceCheckout(resolve(repoRoot, 'package.json'))) {
|
|
17
|
+
if (!existsSync(packedEntrypoint)) {
|
|
18
|
+
throw new Error('Unable to pack CLI: published package is missing dist/index.js')
|
|
19
|
+
}
|
|
20
|
+
process.exit(0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
24
|
+
// Delete old compiler output first: npm pack must never retain a module whose
|
|
25
|
+
// TypeScript source was renamed or removed since the previous build.
|
|
26
|
+
rmSync(resolve(cliRoot, 'dist'), { recursive: true, force: true })
|
|
27
|
+
const result = spawnSync(npm, ['run', 'build'], { cwd: cliRoot, stdio: 'inherit' })
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`Unable to build CLI dist before packing: ${result.error?.message || 'npm run build failed'}`)
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function isOrizuSourceCheckout(packageJsonPath: string): boolean
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
/** True only for the Orizu monorepo, never merely for a consumer Git checkout. */
|
|
4
|
+
export function isOrizuSourceCheckout(packageJsonPath) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(readFileSync(packageJsonPath, 'utf8')).name === 'orizu'
|
|
7
|
+
} catch {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
2
4
|
import { dirname, resolve } from 'node:path'
|
|
3
5
|
import { fileURLToPath } from 'node:url'
|
|
4
6
|
|
|
@@ -7,32 +9,194 @@ const cliRoot = resolve(scriptDir, '..')
|
|
|
7
9
|
const repoRoot = resolve(cliRoot, '..', '..')
|
|
8
10
|
const sourceRoot = resolve(repoRoot, 'packages', 'orizu-gepa-python')
|
|
9
11
|
const vendorRoot = resolve(cliRoot, 'vendor', 'orizu-gepa-python')
|
|
12
|
+
const connectorSourceRoot = resolve(repoRoot, 'packages', 'orizu-gepa')
|
|
13
|
+
const connectorVendorRoot = resolve(cliRoot, 'vendor', 'orizu-gepa')
|
|
14
|
+
const officialArchive = resolve(cliRoot, 'gepa-python-source.zip')
|
|
15
|
+
const officialVendorRoot = resolve(cliRoot, 'vendor', 'gepa-python')
|
|
10
16
|
const shouldClean = process.argv.includes('--clean')
|
|
17
|
+
const shouldRefresh = process.argv.includes('--refresh')
|
|
18
|
+
const archiveArgumentIndex = process.argv.indexOf('--archive')
|
|
19
|
+
const suppliedArchive = archiveArgumentIndex === -1
|
|
20
|
+
? null
|
|
21
|
+
: process.argv[archiveArgumentIndex + 1]
|
|
11
22
|
const sourceSrc = resolve(sourceRoot, 'src')
|
|
12
23
|
const sourcePyproject = resolve(sourceRoot, 'pyproject.toml')
|
|
13
24
|
const sourceManifest = resolve(sourceRoot, 'manifest.json')
|
|
14
25
|
const vendoredSrc = resolve(vendorRoot, 'src')
|
|
15
26
|
const vendoredPyproject = resolve(vendorRoot, 'pyproject.toml')
|
|
16
27
|
const vendoredManifest = resolve(vendorRoot, 'manifest.json')
|
|
28
|
+
const officialVendoredManifest = resolve(officialVendorRoot, 'manifest.json')
|
|
29
|
+
const officialVendoredGepa = resolve(officialVendorRoot, 'src', 'gepa', '__init__.py')
|
|
30
|
+
const GEPA_VERSION = '0.1.4'
|
|
31
|
+
const GEPA_RELEASE_COMMIT = '8b0ce6cd99a234f6b74daf37558a2ac0ce18f975'
|
|
32
|
+
const GEPA_WHEEL_FILENAME = 'gepa-0.1.4-py3-none-any.whl'
|
|
33
|
+
const GEPA_WHEEL_SHA256 = '12b971039599625c156d2231f6d72a29c31a22e9c237689459b5f1a3c353f532'
|
|
17
34
|
const hasSourcePackage = existsSync(sourceSrc)
|
|
18
35
|
&& existsSync(sourcePyproject)
|
|
19
36
|
&& existsSync(sourceManifest)
|
|
37
|
+
const hasConnectorSourcePackage = existsSync(resolve(connectorSourceRoot, 'src'))
|
|
38
|
+
&& existsSync(resolve(connectorSourceRoot, 'pyproject.toml'))
|
|
20
39
|
const hasVendoredPackage = existsSync(vendoredSrc)
|
|
21
40
|
&& existsSync(vendoredPyproject)
|
|
22
41
|
&& existsSync(vendoredManifest)
|
|
42
|
+
const hasVendoredConnectorPackage = existsSync(resolve(connectorVendorRoot, 'src', 'orizu_gepa_connector', '__main__.py'))
|
|
43
|
+
&& existsSync(resolve(connectorVendorRoot, 'pyproject.toml'))
|
|
44
|
+
const hasOfficialVendoredPackage = existsSync(officialVendoredManifest)
|
|
45
|
+
&& existsSync(officialVendoredGepa)
|
|
46
|
+
|
|
47
|
+
function sha256File(path) {
|
|
48
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertPinnedGepaArchive(path) {
|
|
52
|
+
if (!existsSync(path)) {
|
|
53
|
+
throw new Error(`Unable to vendor official GEPA: source archive not found at ${path}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const actualSha256 = sha256File(path)
|
|
57
|
+
if (actualSha256 !== GEPA_WHEEL_SHA256) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Official GEPA source archive sha256 mismatch: expected ${GEPA_WHEEL_SHA256}, got ${actualSha256}`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function collectFileHashes(root, prefix = '') {
|
|
65
|
+
const files = {}
|
|
66
|
+
for (const entry of readdirSync(root).sort()) {
|
|
67
|
+
const path = resolve(root, entry)
|
|
68
|
+
const relativePath = prefix ? `${prefix}/${entry}` : entry
|
|
69
|
+
if (statSync(path).isDirectory()) {
|
|
70
|
+
Object.assign(files, collectFileHashes(path, relativePath))
|
|
71
|
+
} else {
|
|
72
|
+
files[relativePath] = sha256File(path)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return files
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function assertOfficialVendoredManifest() {
|
|
79
|
+
let manifest
|
|
80
|
+
try {
|
|
81
|
+
manifest = JSON.parse(readFileSync(officialVendoredManifest, 'utf8'))
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new Error(`Official GEPA vendored manifest is unreadable: ${error.message}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!manifest.files || typeof manifest.files !== 'object' || Array.isArray(manifest.files)) {
|
|
87
|
+
throw new Error('Official GEPA vendored manifest is missing file hashes')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const actualFiles = collectFileHashes(officialVendorRoot)
|
|
91
|
+
delete actualFiles['manifest.json']
|
|
92
|
+
const expectedEntries = Object.entries(manifest.files).sort(([left], [right]) => left.localeCompare(right))
|
|
93
|
+
const actualEntries = Object.entries(actualFiles).sort(([left], [right]) => left.localeCompare(right))
|
|
94
|
+
if (expectedEntries.length !== actualEntries.length) {
|
|
95
|
+
throw new Error('Official GEPA vendored manifest file count does not match extracted files')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let index = 0; index < expectedEntries.length; index += 1) {
|
|
99
|
+
const [expectedPath, expectedSha256] = expectedEntries[index]
|
|
100
|
+
const [actualPath, actualSha256] = actualEntries[index]
|
|
101
|
+
if (expectedPath !== actualPath || expectedSha256 !== actualSha256) {
|
|
102
|
+
throw new Error(`Official GEPA vendored manifest mismatch at ${expectedPath}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractPinnedGepa(archivePath) {
|
|
108
|
+
const extractor = String.raw`
|
|
109
|
+
from pathlib import Path, PurePosixPath
|
|
110
|
+
import shutil
|
|
111
|
+
import sys
|
|
112
|
+
import zipfile
|
|
113
|
+
|
|
114
|
+
archive_path = Path(sys.argv[1])
|
|
115
|
+
destination_root = Path(sys.argv[2])
|
|
116
|
+
license_member = 'gepa-0.1.4.dist-info/licenses/LICENSE'
|
|
117
|
+
|
|
118
|
+
with zipfile.ZipFile(archive_path) as archive:
|
|
119
|
+
for member in archive.infolist():
|
|
120
|
+
if member.is_dir():
|
|
121
|
+
continue
|
|
122
|
+
if member.filename.startswith('gepa/'):
|
|
123
|
+
relative_path = PurePosixPath('src') / PurePosixPath(member.filename)
|
|
124
|
+
elif member.filename == license_member:
|
|
125
|
+
relative_path = PurePosixPath('LICENSE')
|
|
126
|
+
else:
|
|
127
|
+
continue
|
|
128
|
+
if relative_path.is_absolute() or '..' in relative_path.parts:
|
|
129
|
+
raise ValueError(f'unsafe GEPA wheel member: {member.filename}')
|
|
130
|
+
destination = destination_root.joinpath(*relative_path.parts)
|
|
131
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
with archive.open(member) as source, destination.open('wb') as output:
|
|
133
|
+
shutil.copyfileobj(source, output)
|
|
134
|
+
`
|
|
135
|
+
const result = spawnSync('python3', ['-c', extractor, archivePath, officialVendorRoot], {
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
})
|
|
138
|
+
if (result.status !== 0) {
|
|
139
|
+
const details = result.error?.message || result.stderr || result.stdout || 'no subprocess output'
|
|
140
|
+
throw new Error(`Unable to extract verified official GEPA wheel; python3 is required: ${details}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function vendorOfficialGepa(archivePath) {
|
|
145
|
+
assertPinnedGepaArchive(archivePath)
|
|
146
|
+
rmSync(officialVendorRoot, { recursive: true, force: true })
|
|
147
|
+
mkdirSync(officialVendorRoot, { recursive: true })
|
|
148
|
+
extractPinnedGepa(archivePath)
|
|
149
|
+
|
|
150
|
+
const files = collectFileHashes(officialVendorRoot)
|
|
151
|
+
writeFileSync(officialVendoredManifest, `${JSON.stringify({
|
|
152
|
+
schemaVersion: 'gepa-vendor.v1',
|
|
153
|
+
upstream: {
|
|
154
|
+
package: 'gepa',
|
|
155
|
+
version: GEPA_VERSION,
|
|
156
|
+
repositoryCommit: GEPA_RELEASE_COMMIT,
|
|
157
|
+
wheel: {
|
|
158
|
+
filename: GEPA_WHEEL_FILENAME,
|
|
159
|
+
sha256: GEPA_WHEEL_SHA256,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
files,
|
|
163
|
+
}, null, 2)}\n`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (archiveArgumentIndex !== -1 && (!suppliedArchive || suppliedArchive.startsWith('--'))) {
|
|
167
|
+
throw new Error('Expected a source archive path after --archive')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (shouldRefresh) {
|
|
171
|
+
if (!suppliedArchive) {
|
|
172
|
+
throw new Error('Refreshing official GEPA requires --archive <verified-wheel-path>')
|
|
173
|
+
}
|
|
174
|
+
assertPinnedGepaArchive(suppliedArchive)
|
|
175
|
+
cpSync(suppliedArchive, officialArchive)
|
|
176
|
+
}
|
|
23
177
|
|
|
24
178
|
if (shouldClean) {
|
|
25
179
|
if (hasSourcePackage) {
|
|
26
180
|
rmSync(vendorRoot, { recursive: true, force: true })
|
|
27
181
|
}
|
|
182
|
+
if (hasConnectorSourcePackage) {
|
|
183
|
+
rmSync(connectorVendorRoot, { recursive: true, force: true })
|
|
184
|
+
}
|
|
185
|
+
if (hasSourcePackage || hasConnectorSourcePackage) {
|
|
186
|
+
rmSync(officialVendorRoot, { recursive: true, force: true })
|
|
187
|
+
}
|
|
28
188
|
process.exit(0)
|
|
29
189
|
}
|
|
30
190
|
|
|
31
|
-
if (!hasSourcePackage) {
|
|
32
|
-
if (hasVendoredPackage) {
|
|
191
|
+
if (!hasSourcePackage || !hasConnectorSourcePackage) {
|
|
192
|
+
if (hasVendoredPackage && hasVendoredConnectorPackage && hasOfficialVendoredPackage) {
|
|
193
|
+
assertOfficialVendoredManifest()
|
|
33
194
|
process.exit(0)
|
|
34
195
|
}
|
|
35
|
-
|
|
196
|
+
if (hasVendoredPackage || hasVendoredConnectorPackage || hasOfficialVendoredPackage) {
|
|
197
|
+
throw new Error('Unable to vendor GEPA packages: vendored legacy, connector, or official package is incomplete')
|
|
198
|
+
}
|
|
199
|
+
throw new Error(`Unable to vendor GEPA packages: source package not found at ${sourceRoot} or ${connectorSourceRoot}`)
|
|
36
200
|
}
|
|
37
201
|
|
|
38
202
|
rmSync(vendorRoot, { recursive: true, force: true })
|
|
@@ -52,3 +216,22 @@ cpSync(sourceSrc, resolve(vendorRoot, 'src'), {
|
|
|
52
216
|
&& !normalizedPath.endsWith('/.DS_Store')
|
|
53
217
|
},
|
|
54
218
|
})
|
|
219
|
+
|
|
220
|
+
rmSync(connectorVendorRoot, { recursive: true, force: true })
|
|
221
|
+
mkdirSync(connectorVendorRoot, { recursive: true })
|
|
222
|
+
cpSync(resolve(connectorSourceRoot, 'pyproject.toml'), resolve(connectorVendorRoot, 'pyproject.toml'))
|
|
223
|
+
cpSync(resolve(connectorSourceRoot, 'src'), resolve(connectorVendorRoot, 'src'), {
|
|
224
|
+
recursive: true,
|
|
225
|
+
filter: sourcePath => {
|
|
226
|
+
const normalizedPath = sourcePath.replace(/\\/g, '/')
|
|
227
|
+
return !normalizedPath.endsWith('/__pycache__')
|
|
228
|
+
&& !normalizedPath.includes('/__pycache__/')
|
|
229
|
+
&& !normalizedPath.endsWith('.pyc')
|
|
230
|
+
&& !normalizedPath.endsWith('.pyo')
|
|
231
|
+
&& !normalizedPath.endsWith('/.pytest_cache')
|
|
232
|
+
&& !normalizedPath.includes('/.pytest_cache/')
|
|
233
|
+
&& !normalizedPath.endsWith('/.DS_Store')
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
vendorOfficialGepa(suppliedArchive ?? officialArchive)
|
package/src/diff-comments-cli.ts
CHANGED
|
@@ -3,6 +3,13 @@ import {
|
|
|
3
3
|
isJsonErrorResponsePayload,
|
|
4
4
|
} from './error-response.js'
|
|
5
5
|
import { sanitizeHumanInlineText } from './json-response.js'
|
|
6
|
+
import {
|
|
7
|
+
formatFiniteLengthCount,
|
|
8
|
+
type CliLengthDelta,
|
|
9
|
+
type CliLengthMeasurementUnavailableReason,
|
|
10
|
+
type CliLengthStats,
|
|
11
|
+
type CliLengthStatsPair,
|
|
12
|
+
} from './prompt-length-wire.js'
|
|
6
13
|
|
|
7
14
|
interface DiffCommentAuthor {
|
|
8
15
|
name?: string
|
|
@@ -42,6 +49,13 @@ interface DiffCommentPayloadPairContext {
|
|
|
42
49
|
diff: string | null
|
|
43
50
|
bodies: { from: string; to: string } | null
|
|
44
51
|
degraded: { reason: DiffCommentContextUnavailableReason } | null
|
|
52
|
+
lengthStats: CliLengthStatsPair | null
|
|
53
|
+
lengthDelta: (
|
|
54
|
+
Partial<Record<keyof Omit<CliLengthDelta, 'splitUnavailableReason'>, unknown>> & {
|
|
55
|
+
splitUnavailableReason?: unknown
|
|
56
|
+
}
|
|
57
|
+
) | null
|
|
58
|
+
lengthDeltaUnavailableReason?: CliLengthMeasurementUnavailableReason
|
|
45
59
|
comments: DiffCommentPayloadComment[]
|
|
46
60
|
}
|
|
47
61
|
|
|
@@ -66,6 +80,12 @@ interface DiffCommentsPayload {
|
|
|
66
80
|
pairs: DiffCommentPayloadPair[]
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
interface RenderableLengthMetricDelta {
|
|
84
|
+
removed: number | null
|
|
85
|
+
added: number | null
|
|
86
|
+
net: number | null
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
export interface DiffCommentsCliContext {
|
|
70
90
|
getArg: (name: string) => string | null
|
|
71
91
|
rejectDashPrefixedOptionValue: (name: string, value: string | null) => void
|
|
@@ -143,6 +163,110 @@ function humanLineNumber(
|
|
|
143
163
|
: sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(line + 1))
|
|
144
164
|
}
|
|
145
165
|
|
|
166
|
+
function formatStats(stats: CliLengthStats): string {
|
|
167
|
+
const tokens = formatFiniteLengthCount(stats.tokens)
|
|
168
|
+
return `${tokens === null ? '—' : `~${tokens}`} tokens · ` +
|
|
169
|
+
`${formatFiniteLengthCount(stats.lines) ?? '—'} lines · ` +
|
|
170
|
+
`${formatFiniteLengthCount(stats.chars) ?? '—'} chars · ` +
|
|
171
|
+
`${formatFiniteLengthCount(stats.words) ?? '—'} words`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function formatDeltaValue(
|
|
175
|
+
value: number | null,
|
|
176
|
+
metric: keyof Omit<CliLengthDelta, 'splitUnavailableReason'>,
|
|
177
|
+
signed = false
|
|
178
|
+
): string {
|
|
179
|
+
const formatted = formatFiniteLengthCount(value, signed)
|
|
180
|
+
if (formatted === null) return '—'
|
|
181
|
+
return metric === 'tokens' ? `~${formatted}` : formatted
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isLengthMetricDelta(
|
|
185
|
+
value: unknown
|
|
186
|
+
): value is RenderableLengthMetricDelta {
|
|
187
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
const delta = value as Record<string, unknown>
|
|
191
|
+
const isNullableFiniteNumber = (candidate: unknown) =>
|
|
192
|
+
candidate === null || (
|
|
193
|
+
typeof candidate === 'number' && Number.isFinite(candidate)
|
|
194
|
+
)
|
|
195
|
+
return isNullableFiniteNumber(delta.removed) &&
|
|
196
|
+
isNullableFiniteNumber(delta.added) &&
|
|
197
|
+
isNullableFiniteNumber(delta.net)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function printLengthMeasurements(
|
|
201
|
+
ctx: DiffCommentsCliContext,
|
|
202
|
+
pair: DiffCommentPayloadPair
|
|
203
|
+
) {
|
|
204
|
+
function unavailableSuffix(reason: unknown): string {
|
|
205
|
+
if (typeof reason !== 'string' || reason.length === 0) return ''
|
|
206
|
+
return ` (${sanitizeHumanInlineText(ctx.sanitizeTerminalText, reason)})`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!pair.lengthStats && !pair.lengthDelta) {
|
|
210
|
+
ctx.printLine(
|
|
211
|
+
` Length: unavailable${unavailableSuffix(
|
|
212
|
+
pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason
|
|
213
|
+
)}`
|
|
214
|
+
)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (pair.lengthStats) {
|
|
219
|
+
ctx.printLine(pair.lengthStats.from
|
|
220
|
+
? ` From: ${formatStats(pair.lengthStats.from)}`
|
|
221
|
+
: ` From: unavailable${unavailableSuffix(
|
|
222
|
+
pair.lengthStats.fromUnavailableReason
|
|
223
|
+
)}`)
|
|
224
|
+
ctx.printLine(pair.lengthStats.to
|
|
225
|
+
? ` To: ${formatStats(pair.lengthStats.to)}`
|
|
226
|
+
: ` To: unavailable${unavailableSuffix(
|
|
227
|
+
pair.lengthStats.toUnavailableReason
|
|
228
|
+
)}`)
|
|
229
|
+
}
|
|
230
|
+
if (!pair.lengthDelta) {
|
|
231
|
+
ctx.printLine(
|
|
232
|
+
` Length delta: unavailable${unavailableSuffix(
|
|
233
|
+
pair.lengthDeltaUnavailableReason ?? pair.degraded?.reason
|
|
234
|
+
)}`
|
|
235
|
+
)
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const splitUnavailableReason = pair.lengthDelta.splitUnavailableReason
|
|
240
|
+
if (
|
|
241
|
+
typeof splitUnavailableReason === 'string' &&
|
|
242
|
+
splitUnavailableReason.length > 0
|
|
243
|
+
) {
|
|
244
|
+
ctx.printLine(
|
|
245
|
+
` Split unavailable: ${sanitizeHumanInlineText(
|
|
246
|
+
ctx.sanitizeTerminalText,
|
|
247
|
+
splitUnavailableReason
|
|
248
|
+
)}`
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
for (const [metric, label] of [
|
|
252
|
+
['tokens', 'Tokens'],
|
|
253
|
+
['lines', 'Lines'],
|
|
254
|
+
['chars', 'Chars'],
|
|
255
|
+
['words', 'Words'],
|
|
256
|
+
] as const) {
|
|
257
|
+
const delta = pair.lengthDelta[metric]
|
|
258
|
+
if (!isLengthMetricDelta(delta)) {
|
|
259
|
+
ctx.printLine(` ${label}: unavailable`)
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
ctx.printLine(
|
|
263
|
+
` ${label}: removed ${formatDeltaValue(delta.removed, metric)} · ` +
|
|
264
|
+
`added ${formatDeltaValue(delta.added, metric)} · ` +
|
|
265
|
+
`net ${formatDeltaValue(delta.net, metric, true)}`
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
146
270
|
function buildParams(ctx: DiffCommentsCliContext): URLSearchParams {
|
|
147
271
|
const runId = ctx.getArg('--run')
|
|
148
272
|
const promptId = ctx.getArg('--prompt')
|
|
@@ -210,6 +334,7 @@ function printDiffComments(ctx: DiffCommentsCliContext, payload: DiffCommentsPay
|
|
|
210
334
|
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, from)} → ` +
|
|
211
335
|
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, to)}`
|
|
212
336
|
)
|
|
337
|
+
printLengthMeasurements(ctx, pair)
|
|
213
338
|
if (pair.degraded) {
|
|
214
339
|
ctx.printLine(
|
|
215
340
|
` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.degraded.reason)}`
|