orizu 0.5.17 → 0.5.18
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 +15 -0
- package/dist/archive-artifacts-cli.js +1 -3
- package/dist/artifact-pull.js +1 -3
- package/dist/diff-comments-cli.js +68 -32
- package/dist/error-response.js +17 -17
- package/dist/github-setup.js +3 -11
- package/dist/help.js +22 -2
- package/dist/index.js +40 -84
- package/dist/json-response.js +21 -0
- package/dist/markdown-report-input.js +67 -0
- package/dist/optimization-export-cli.js +30 -0
- package/dist/optimizations-list-cli.js +1 -3
- package/dist/prompt-draft-push.js +7 -5
- package/dist/prompt-report-cli.js +86 -0
- package/dist/scorer-draft-push.js +1 -22
- package/dist/scores-accept-cli.js +1 -3
- package/dist/zip-draft-push.js +1 -24
- package/package.json +1 -1
- package/src/archive-artifacts-cli.ts +1 -7
- package/src/artifact-pull.ts +1 -4
- package/src/diff-comments-cli.ts +86 -37
- package/src/error-response.ts +23 -21
- package/src/github-setup.ts +3 -12
- package/src/help.ts +22 -2
- package/src/index.ts +43 -106
- package/src/json-response.ts +37 -0
- package/src/markdown-report-input.ts +80 -0
- package/src/optimization-export-cli.ts +72 -0
- package/src/optimizations-list-cli.ts +1 -4
- package/src/prompt-draft-push.ts +8 -6
- package/src/prompt-report-cli.ts +135 -0
- package/src/scorer-draft-push.ts +1 -6
- package/src/scores-accept-cli.ts +1 -4
- package/src/zip-draft-push.ts +1 -6
- package/vendor/skills/orizu-cli/SKILL.md +1 -0
- package/vendor/skills/orizu-cli/references/cli-reference.md +5 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseJsonResponse, sanitizeHumanInlineText, } from './json-response.js';
|
|
2
|
+
export function printDiffCommentsSuppressionMarker(ctx, data) {
|
|
3
|
+
const reason = data.diffCommentsSuppressedReason;
|
|
4
|
+
if (typeof reason !== 'string' || reason.length === 0) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
ctx.printLine(`Diff comments suppressed: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, reason)}`);
|
|
8
|
+
}
|
|
9
|
+
export async function exportOptimizationRunCommand(ctx) {
|
|
10
|
+
const runId = ctx.getPositionalArg(2) || ctx.getArg('--run-id');
|
|
11
|
+
const outPathArg = ctx.getArg('--out');
|
|
12
|
+
if (!runId) {
|
|
13
|
+
throw new Error('Usage: orizu optimizations export <run-id> [--out <path>] [--json]');
|
|
14
|
+
}
|
|
15
|
+
const response = await ctx.authedFetch(`/api/cli/optimization-runs/${encodeURIComponent(runId)}/export`);
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error(`Failed to export optimization run: ${await response.text()}`);
|
|
18
|
+
}
|
|
19
|
+
const data = await parseJsonResponse(response, 'Optimization export');
|
|
20
|
+
if (ctx.hasJsonFlag() && !outPathArg) {
|
|
21
|
+
ctx.printJson(data);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const filename = outPathArg
|
|
25
|
+
? ctx.expandHomePath(outPathArg)
|
|
26
|
+
: `${runId}.optimization.json`;
|
|
27
|
+
ctx.writeTextFileEnsuringDir(filename, `${JSON.stringify(data, null, 2)}\n`);
|
|
28
|
+
ctx.printLine(`Saved optimization export to ${ctx.sanitizeTerminalText(filename)}`);
|
|
29
|
+
printDiffCommentsSuppressionMarker(ctx, data);
|
|
30
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { authedFetch } from './http.js';
|
|
14
14
|
import { extractErrorMessage } from './error-response.js';
|
|
15
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
15
16
|
// ALI-1175: best scores here derive from agent-reported score runs — nothing is
|
|
16
17
|
// server-attested yet. Both the table footer AND the --json payload carry this
|
|
17
18
|
// signal so no consumer, human or machine, mistakes the number for attested
|
|
@@ -25,9 +26,6 @@ function argValue(args, flag) {
|
|
|
25
26
|
}
|
|
26
27
|
return args[index + 1];
|
|
27
28
|
}
|
|
28
|
-
function sanitizeTerminalText(value) {
|
|
29
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
30
|
-
}
|
|
31
29
|
// Shared-parser behavior (artifact-pull.ts pattern): a non-JSON or malformed
|
|
32
30
|
// body becomes a bounded, sanitized error — never a raw JSON.parse throw.
|
|
33
31
|
async function parseJsonPayload(response, context) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { authedFetch } from './http.js';
|
|
2
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
2
3
|
import { appendRegistrationTagWarning } from './registration-tag-warning.js';
|
|
3
4
|
/**
|
|
4
5
|
* ADR-007 P4 (ALI-1074): the session-scoped, commit-first prompt push.
|
|
@@ -18,11 +19,6 @@ import { appendRegistrationTagWarning } from './registration-tag-warning.js';
|
|
|
18
19
|
* Lives outside index.ts per the CLI line ratchet (ALI-976).
|
|
19
20
|
*/
|
|
20
21
|
export const PROMPT_PRIMARY_GIT_FILENAME = 'prompt.md';
|
|
21
|
-
// Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
|
|
22
|
-
// its own copy to avoid importing the CLI entrypoint).
|
|
23
|
-
function sanitizeTerminalText(value) {
|
|
24
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
25
|
-
}
|
|
26
22
|
/**
|
|
27
23
|
* Build the git file set for a prompt-version draft: the primary text at the
|
|
28
24
|
* canonical prompt.md plus every sidecar at its own relative path. A sidecar
|
|
@@ -74,6 +70,12 @@ export async function pushPromptDraft(input) {
|
|
|
74
70
|
parentVersionId: input.parentVersionId,
|
|
75
71
|
createdBy: manifest.provenance || { kind: 'human-edit' },
|
|
76
72
|
files,
|
|
73
|
+
...(input.report
|
|
74
|
+
? {
|
|
75
|
+
reportMarkdown: input.report.markdown,
|
|
76
|
+
reportSourceName: input.report.sourceName,
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
77
79
|
}),
|
|
78
80
|
});
|
|
79
81
|
if (!response.ok) {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { extractErrorMessage } from './error-response.js';
|
|
2
|
+
import { authedFetch } from './http.js';
|
|
3
|
+
import { parseJsonResponse, sanitizeTerminalText } from './json-response.js';
|
|
4
|
+
import { readMarkdownReportInput } from './markdown-report-input.js';
|
|
5
|
+
const IDENTICAL_PROMPT_VERSION_ERROR = 'Prompt version is identical to the current prompt version.';
|
|
6
|
+
function getArg(args, name) {
|
|
7
|
+
const index = args.indexOf(name);
|
|
8
|
+
if (index === -1 || index + 1 >= args.length)
|
|
9
|
+
return null;
|
|
10
|
+
return args[index + 1];
|
|
11
|
+
}
|
|
12
|
+
function rejectDashPrefixedOptionValue(name, value) {
|
|
13
|
+
if (value?.startsWith('-')) {
|
|
14
|
+
throw new Error(`Invalid value for ${name}: option values cannot start with a dash`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function promptReportCommand(args, context) {
|
|
18
|
+
const prompt = getArg(args, '--prompt');
|
|
19
|
+
const projectArg = getArg(args, '--project');
|
|
20
|
+
const version = getArg(args, '--version');
|
|
21
|
+
const versionId = getArg(args, '--version-id');
|
|
22
|
+
for (const [name, value] of [
|
|
23
|
+
['--prompt', prompt],
|
|
24
|
+
['--project', projectArg],
|
|
25
|
+
['--version-id', versionId],
|
|
26
|
+
]) {
|
|
27
|
+
rejectDashPrefixedOptionValue(name, value);
|
|
28
|
+
}
|
|
29
|
+
if (!prompt) {
|
|
30
|
+
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
|
|
31
|
+
'(--version <n> | --version-id <id>) ' +
|
|
32
|
+
'(--report <markdown|@file> | --report-file <path>) [--json]');
|
|
33
|
+
}
|
|
34
|
+
if (Boolean(version) === Boolean(versionId)) {
|
|
35
|
+
throw new Error('Exactly one of --version or --version-id is required');
|
|
36
|
+
}
|
|
37
|
+
if (version && (!/^[1-9]\d*$/.test(version) || !Number.isSafeInteger(Number(version)))) {
|
|
38
|
+
throw new Error('--version must be a positive integer');
|
|
39
|
+
}
|
|
40
|
+
const report = readMarkdownReportInput(args, 'Prompt');
|
|
41
|
+
if (!report) {
|
|
42
|
+
throw new Error('Usage: orizu prompts report set --prompt <id-or-name> --project <team/project> ' +
|
|
43
|
+
'(--version <n> | --version-id <id>) ' +
|
|
44
|
+
'(--report <markdown|@file> | --report-file <path>) [--json]');
|
|
45
|
+
}
|
|
46
|
+
const project = projectArg || await context.resolveProjectSlug(null);
|
|
47
|
+
const query = new URLSearchParams({ project });
|
|
48
|
+
if (version)
|
|
49
|
+
query.set('version', version);
|
|
50
|
+
else
|
|
51
|
+
query.set('versionId', versionId);
|
|
52
|
+
const fetcher = context.fetcher ?? authedFetch;
|
|
53
|
+
const response = await fetcher(`/api/cli/prompts/${encodeURIComponent(prompt)}/report?${query.toString()}`, {
|
|
54
|
+
method: 'PATCH',
|
|
55
|
+
headers: { 'Content-Type': 'application/json' },
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
reportMarkdown: report.markdown,
|
|
58
|
+
reportSourceName: report.sourceName,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`Failed to update prompt report: ${await extractErrorMessage(response)}`);
|
|
63
|
+
}
|
|
64
|
+
const data = await parseJsonResponse(response, 'Prompt report update');
|
|
65
|
+
if (context.json) {
|
|
66
|
+
context.printJson(data);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const versionLabel = data.version
|
|
70
|
+
? data.version.versionNumber === null
|
|
71
|
+
? data.version.id
|
|
72
|
+
: `v${data.version.versionNumber}`
|
|
73
|
+
: data.promptVersionId || versionId || `v${version}`;
|
|
74
|
+
const promptName = data.prompt?.name || prompt;
|
|
75
|
+
const status = data.version?.status || 'updated';
|
|
76
|
+
context.printLine(`Uploaded prompt report for ${sanitizeTerminalText(promptName)} ` +
|
|
77
|
+
`(${sanitizeTerminalText(versionLabel)}) [${sanitizeTerminalText(status)}]`);
|
|
78
|
+
}
|
|
79
|
+
export async function promptPushErrorMessage(response, kind, hasReport) {
|
|
80
|
+
const message = await extractErrorMessage(response);
|
|
81
|
+
if (hasReport && response.status === 409 && message === IDENTICAL_PROMPT_VERSION_ERROR) {
|
|
82
|
+
return (`${IDENTICAL_PROMPT_VERSION_ERROR} ` +
|
|
83
|
+
'Attach the report to the existing version with `orizu prompts report set`.');
|
|
84
|
+
}
|
|
85
|
+
return `Failed to push ${kind}: ${message}`;
|
|
86
|
+
}
|
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
import { authedFetch } from './http.js';
|
|
2
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
2
3
|
import { appendRegistrationTagWarning } from './registration-tag-warning.js';
|
|
3
|
-
/**
|
|
4
|
-
* ADR-007 P5 (ALI-1074): the session-scoped, commit-first scorer push.
|
|
5
|
-
*
|
|
6
|
-
* `orizu scorers register --session <id>` creates the version through
|
|
7
|
-
* POST /api/cli/scorers/drafts: the server commits the scorer's canonical
|
|
8
|
-
* `manifest.json` to the session's `orizu/session-*` branch (one Git Data API
|
|
9
|
-
* commit) and then writes a DRAFT row pinning
|
|
10
|
-
* repo_path/content_sha/commit_sha. The draft seals — and gets its version
|
|
11
|
-
* number — when the session branch merges via `orizu session finish` +
|
|
12
|
-
* promotion-manifest apply.
|
|
13
|
-
*
|
|
14
|
-
* The git filename is ALWAYS `manifest.json`: the server read side
|
|
15
|
-
* (lib/artifact-git-read.ts SCORER_MANIFEST_FILENAMES) prefers that name.
|
|
16
|
-
*
|
|
17
|
-
* Lives outside index.ts per the CLI line ratchet (ALI-976); mirrors
|
|
18
|
-
* prompt-draft-push.ts.
|
|
19
|
-
*/
|
|
20
|
-
// Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
|
|
21
|
-
// its own copy to avoid importing the CLI entrypoint).
|
|
22
|
-
function sanitizeTerminalText(value) {
|
|
23
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
24
|
-
}
|
|
25
4
|
/** The full `orizu scorers register` command (moved out of index.ts for the
|
|
26
5
|
* CLI line ratchet): --session takes the ADR-007 P5 commit-first draft path,
|
|
27
6
|
* sessionless stays on the legacy create endpoint. */
|
|
@@ -11,9 +11,7 @@
|
|
|
11
11
|
* own argument parsing and printing via injected io.
|
|
12
12
|
*/
|
|
13
13
|
import { authedFetch } from './http.js';
|
|
14
|
-
|
|
15
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
16
|
-
}
|
|
14
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
17
15
|
function argValue(args, flag) {
|
|
18
16
|
const index = args.indexOf(flag);
|
|
19
17
|
if (index === -1 || index + 1 >= args.length || args[index + 1].startsWith('--')) {
|
package/dist/zip-draft-push.js
CHANGED
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
import { basename } from 'path';
|
|
2
2
|
import { zipDirectoryToBase64 } from './artifact-archive.js';
|
|
3
3
|
import { authedFetch } from './http.js';
|
|
4
|
+
import { sanitizeTerminalText } from './json-response.js';
|
|
4
5
|
import { appendRegistrationTagWarning } from './registration-tag-warning.js';
|
|
5
|
-
/**
|
|
6
|
-
* ADR-007 P5 (ALI-1074): the session-scoped, commit-first runner/optimizer
|
|
7
|
-
* push.
|
|
8
|
-
*
|
|
9
|
-
* `orizu runners|optimizers push --session <id>` sends the SAME deterministic
|
|
10
|
-
* zip as the legacy push to POST /api/cli/{runners|optimizers}/drafts: the
|
|
11
|
-
* server unpacks the zip's file set and commits it to the session's
|
|
12
|
-
* `orizu/session-*` branch (one Git Data API commit; binary entries travel
|
|
13
|
-
* base64), keeps uploading the zip to storage (dual-write until ALI-1046),
|
|
14
|
-
* and writes a DRAFT row pinning repo_path/content_sha/commit_sha. The draft
|
|
15
|
-
* seals — and gets its version number — when the session branch merges via
|
|
16
|
-
* `orizu session finish` + promotion-manifest apply.
|
|
17
|
-
*
|
|
18
|
-
* Labels cannot ride a draft push (`--label` with `--session` is an error at
|
|
19
|
-
* the command layer): labels address sealed versions only.
|
|
20
|
-
*
|
|
21
|
-
* Lives outside index.ts per the CLI line ratchet (ALI-976); mirrors
|
|
22
|
-
* prompt-draft-push.ts.
|
|
23
|
-
*/
|
|
24
|
-
// Mirrors index.ts `sanitizeTerminalText` (precedent: artifact-pull.ts keeps
|
|
25
|
-
// its own copy to avoid importing the CLI entrypoint).
|
|
26
|
-
function sanitizeTerminalText(value) {
|
|
27
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
|
|
28
|
-
}
|
|
29
6
|
/** The full `orizu runners|optimizers push` command (moved out of index.ts
|
|
30
7
|
* for the CLI line ratchet): --session takes the ADR-007 P5 commit-first
|
|
31
8
|
* draft path, sessionless stays on the legacy zip-only endpoint. */
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { authedFetch } from './http.js'
|
|
10
10
|
import { extractErrorMessage } from './error-response.js'
|
|
11
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
11
12
|
|
|
12
13
|
type ArchiveStatus = 'active' | 'archived' | 'all'
|
|
13
14
|
type ArchiveAction = 'archive' | 'restore'
|
|
@@ -109,13 +110,6 @@ function argValue(args: readonly string[], flag: string): string | null {
|
|
|
109
110
|
return args[index + 1]
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
function sanitizeTerminalText(value: unknown): string {
|
|
113
|
-
return String(value).replace(
|
|
114
|
-
/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g,
|
|
115
|
-
''
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
113
|
function columnWidths(headers: string[], rows: string[][]): number[] {
|
|
120
114
|
return headers.map((header, index) =>
|
|
121
115
|
Math.max(header.length, ...rows.map(row => row[index]?.length || 0))
|
package/src/artifact-pull.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { tmpdir } from 'os'
|
|
|
18
18
|
import { dirname, join } from 'path'
|
|
19
19
|
|
|
20
20
|
import { authedFetch } from './http.js'
|
|
21
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
21
22
|
|
|
22
23
|
export type PullableArtifactKind = 'runner' | 'optimizer'
|
|
23
24
|
|
|
@@ -31,10 +32,6 @@ export interface PullArtifactIo {
|
|
|
31
32
|
// Shared with the runners exec download path in index.ts.
|
|
32
33
|
export const ARTIFACT_MAX_BYTES = 25 * 1024 * 1024
|
|
33
34
|
|
|
34
|
-
function sanitizeTerminalText(value: unknown): string {
|
|
35
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
35
|
function expandHomePath(path: string): string {
|
|
39
36
|
if (path.startsWith('~/')) {
|
|
40
37
|
const home = process.env.HOME || ''
|
package/src/diff-comments-cli.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractErrorMessage,
|
|
3
|
+
isJsonErrorResponsePayload,
|
|
4
|
+
} from './error-response.js'
|
|
5
|
+
import { sanitizeHumanInlineText } from './json-response.js'
|
|
6
|
+
|
|
1
7
|
interface DiffCommentAuthor {
|
|
2
8
|
name?: string
|
|
3
9
|
person?: { name?: string }
|
|
@@ -5,6 +11,7 @@ interface DiffCommentAuthor {
|
|
|
5
11
|
|
|
6
12
|
type DiffCommentContextUnavailableReason =
|
|
7
13
|
| 'diff_degraded_size_limit'
|
|
14
|
+
| 'diff_degraded_cell_limit'
|
|
8
15
|
| 'bodies_unavailable_event_cap'
|
|
9
16
|
| 'body_unresolvable'
|
|
10
17
|
| 'anchor_out_of_range'
|
|
@@ -25,8 +32,8 @@ interface DiffCommentPayloadComment {
|
|
|
25
32
|
anchor: { side: 'old' | 'new'; line: number }
|
|
26
33
|
context: {
|
|
27
34
|
lineText: string
|
|
28
|
-
lineOp: 'add' | 'del' | 'context'
|
|
29
|
-
hunk: { lines: DiffCommentContextLine[] }
|
|
35
|
+
lineOp: 'add' | 'del' | 'context' | null
|
|
36
|
+
hunk: { lines: DiffCommentContextLine[] } | null
|
|
30
37
|
} | null
|
|
31
38
|
contextUnavailableReason?: DiffCommentContextUnavailableReason
|
|
32
39
|
}
|
|
@@ -71,6 +78,7 @@ export interface DiffCommentsCliContext {
|
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
const DETAIL_LEVELS = ['hunk', 'diff', 'full'] as const
|
|
81
|
+
const CONTEXT_UNAVAILABLE_WITHOUT_REASON = ' Context unavailable (no reason supplied)'
|
|
74
82
|
|
|
75
83
|
function isCandidatePair(
|
|
76
84
|
pair: DiffCommentPayloadPair
|
|
@@ -92,18 +100,47 @@ function sanitizeHumanText(
|
|
|
92
100
|
return ctx.sanitizeTerminalText(value).replaceAll('\r', '')
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
103
|
+
// Quote untrusted multiline blocks so none of their rows can impersonate a
|
|
104
|
+
// genuine hunk row, whose marker begins immediately after the four-space indent.
|
|
105
|
+
function printHumanTextBlock(ctx: DiffCommentsCliContext, value: unknown): void {
|
|
106
|
+
for (const line of sanitizeHumanText(ctx, value).split('\n')) {
|
|
107
|
+
ctx.printLine(` │ ${line}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function hasExplicitlyEmptyJsonError(response: Response, rawBody: string): boolean {
|
|
112
|
+
try {
|
|
113
|
+
const payload: unknown = JSON.parse(rawBody)
|
|
114
|
+
return isJsonErrorResponsePayload(response, payload) && payload.error.length === 0
|
|
115
|
+
} catch {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function extractDiffCommentsErrorMessage(response: Response): Promise<string> {
|
|
121
|
+
const statusFallback = `HTTP ${response.status}`
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const rawBody = await response.text()
|
|
125
|
+
if (hasExplicitlyEmptyJsonError(response, rawBody)) {
|
|
126
|
+
return statusFallback
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const copiedResponse = new Response(rawBody, { headers: response.headers })
|
|
130
|
+
const extractedMessage = await extractErrorMessage(copiedResponse)
|
|
131
|
+
return extractedMessage.trim() ? extractedMessage : statusFallback
|
|
132
|
+
} catch {
|
|
133
|
+
return statusFallback
|
|
134
|
+
}
|
|
100
135
|
}
|
|
101
136
|
|
|
102
137
|
function humanLineNumber(
|
|
103
138
|
ctx: DiffCommentsCliContext,
|
|
104
139
|
line: number | null
|
|
105
140
|
): string {
|
|
106
|
-
return line === null
|
|
141
|
+
return line === null
|
|
142
|
+
? '-'
|
|
143
|
+
: sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(line + 1))
|
|
107
144
|
}
|
|
108
145
|
|
|
109
146
|
function buildParams(ctx: DiffCommentsCliContext): URLSearchParams {
|
|
@@ -123,9 +160,15 @@ function buildParams(ctx: DiffCommentsCliContext): URLSearchParams {
|
|
|
123
160
|
ctx.rejectDashPrefixedOptionValue(name, value)
|
|
124
161
|
}
|
|
125
162
|
|
|
163
|
+
if (runId === '' || promptId === '') {
|
|
164
|
+
throw new Error(usage())
|
|
165
|
+
}
|
|
126
166
|
if (Number(Boolean(runId)) + Number(Boolean(promptId)) !== 1) {
|
|
127
167
|
throw new Error(usage())
|
|
128
168
|
}
|
|
169
|
+
if (from === '' || to === '') {
|
|
170
|
+
throw new Error('--from and --to must not be empty')
|
|
171
|
+
}
|
|
129
172
|
if (Boolean(from) !== Boolean(to)) {
|
|
130
173
|
throw new Error('--from and --to must be provided together')
|
|
131
174
|
}
|
|
@@ -164,16 +207,17 @@ function printDiffComments(ctx: DiffCommentsCliContext, payload: DiffCommentsPay
|
|
|
164
207
|
ctx.printLine('')
|
|
165
208
|
ctx.printLine(
|
|
166
209
|
`${hasCandidateIds ? 'Candidates' : 'Versions'} ` +
|
|
167
|
-
`${sanitizeHumanInlineText(ctx, from)} →
|
|
210
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, from)} → ` +
|
|
211
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, to)}`
|
|
168
212
|
)
|
|
169
213
|
if (pair.degraded) {
|
|
170
|
-
ctx.printLine(
|
|
214
|
+
ctx.printLine(
|
|
215
|
+
` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, pair.degraded.reason)}`
|
|
216
|
+
)
|
|
171
217
|
}
|
|
172
218
|
if (pair.diff !== null) {
|
|
173
219
|
ctx.printLine(' Diff:')
|
|
174
|
-
|
|
175
|
-
ctx.printLine(` ${line}`)
|
|
176
|
-
}
|
|
220
|
+
printHumanTextBlock(ctx, pair.diff)
|
|
177
221
|
}
|
|
178
222
|
if (pair.bodies !== null) {
|
|
179
223
|
for (const [label, body] of [
|
|
@@ -181,29 +225,40 @@ function printDiffComments(ctx: DiffCommentsCliContext, payload: DiffCommentsPay
|
|
|
181
225
|
['To body', pair.bodies.to],
|
|
182
226
|
] as const) {
|
|
183
227
|
ctx.printLine(` ${label}:`)
|
|
184
|
-
|
|
185
|
-
ctx.printLine(` ${line}`)
|
|
186
|
-
}
|
|
228
|
+
printHumanTextBlock(ctx, body)
|
|
187
229
|
}
|
|
188
230
|
}
|
|
189
231
|
|
|
190
232
|
for (const comment of pair.comments) {
|
|
191
233
|
const authorName = comment.author?.person?.name || comment.author?.name || 'Unknown'
|
|
192
234
|
ctx.printLine(
|
|
193
|
-
` ${sanitizeHumanInlineText(ctx, comment.id)} · ` +
|
|
194
|
-
`${sanitizeHumanInlineText(ctx, authorName)} · ` +
|
|
195
|
-
`${sanitizeHumanInlineText(ctx, comment.createdAt)} · ` +
|
|
196
|
-
`${sanitizeHumanInlineText(ctx, comment.anchor.side)} line ` +
|
|
197
|
-
`${sanitizeHumanInlineText(ctx, String(comment.anchor.line + 1))}`
|
|
235
|
+
` ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.id)} · ` +
|
|
236
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, authorName)} · ` +
|
|
237
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.createdAt)} · ` +
|
|
238
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.anchor.side)} line ` +
|
|
239
|
+
`${sanitizeHumanInlineText(ctx.sanitizeTerminalText, String(comment.anchor.line + 1))}`
|
|
198
240
|
)
|
|
199
|
-
|
|
200
|
-
ctx.printLine(` ${line}`)
|
|
201
|
-
}
|
|
241
|
+
printHumanTextBlock(ctx, comment.body)
|
|
202
242
|
|
|
243
|
+
if (comment.contextUnavailableReason) {
|
|
244
|
+
ctx.printLine(
|
|
245
|
+
` Context unavailable: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.contextUnavailableReason)}`
|
|
246
|
+
)
|
|
247
|
+
if (comment.context) {
|
|
248
|
+
ctx.printLine(
|
|
249
|
+
` Anchored line: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.context.lineText)}`
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
203
254
|
if (!comment.context) {
|
|
255
|
+
ctx.printLine(CONTEXT_UNAVAILABLE_WITHOUT_REASON)
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
if (!comment.context.hunk) {
|
|
259
|
+
ctx.printLine(CONTEXT_UNAVAILABLE_WITHOUT_REASON)
|
|
204
260
|
ctx.printLine(
|
|
205
|
-
`
|
|
206
|
-
`${sanitizeHumanText(ctx, comment.contextUnavailableReason ?? 'body_unresolvable')}`
|
|
261
|
+
` Anchored line: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, comment.context.lineText)}`
|
|
207
262
|
)
|
|
208
263
|
continue
|
|
209
264
|
}
|
|
@@ -217,7 +272,7 @@ function printDiffComments(ctx: DiffCommentsCliContext, payload: DiffCommentsPay
|
|
|
217
272
|
ctx.printLine(
|
|
218
273
|
` ${marker} old:${humanLineNumber(ctx, line.oldLine)} ` +
|
|
219
274
|
`new:${humanLineNumber(ctx, line.newLine)} | ` +
|
|
220
|
-
`${prefix}${
|
|
275
|
+
`${prefix}${sanitizeHumanInlineText(ctx.sanitizeTerminalText, line.text)}`
|
|
221
276
|
)
|
|
222
277
|
}
|
|
223
278
|
}
|
|
@@ -228,16 +283,10 @@ export async function diffCommentsCommand(ctx: DiffCommentsCliContext) {
|
|
|
228
283
|
const params = buildParams(ctx)
|
|
229
284
|
const response = await ctx.authedFetch(`/api/cli/diff-comments?${params.toString()}`)
|
|
230
285
|
if (!response.ok) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
message = sanitizeHumanInlineText(ctx, payload.error)
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
// Keep the status-only error for non-JSON responses.
|
|
239
|
-
}
|
|
240
|
-
throw new Error(`Failed to fetch diff comments: ${message}`)
|
|
286
|
+
const message = await extractDiffCommentsErrorMessage(response)
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Failed to fetch diff comments: ${sanitizeHumanInlineText(ctx.sanitizeTerminalText, message)}`
|
|
289
|
+
)
|
|
241
290
|
}
|
|
242
291
|
|
|
243
292
|
const payload = await ctx.parseJsonResponse<DiffCommentsPayload>(response, 'Diff comments')
|
package/src/error-response.ts
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
2
|
+
|
|
1
3
|
interface ErrorResponsePayload {
|
|
2
4
|
error?: string
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
const ERROR_BODY_PREVIEW_LIMIT = 180
|
|
6
8
|
|
|
9
|
+
export function isJsonErrorResponsePayload(
|
|
10
|
+
response: Response,
|
|
11
|
+
payload: unknown
|
|
12
|
+
): payload is ErrorResponsePayload & { error: string } {
|
|
13
|
+
const contentType = response.headers.get('content-type') || ''
|
|
14
|
+
return (
|
|
15
|
+
contentType.includes('application/json') &&
|
|
16
|
+
payload !== null &&
|
|
17
|
+
typeof payload === 'object' &&
|
|
18
|
+
!Array.isArray(payload) &&
|
|
19
|
+
typeof (payload as ErrorResponsePayload).error === 'string'
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
function boundedTerminalPreview(value: unknown): string {
|
|
8
|
-
return
|
|
9
|
-
.replace(
|
|
10
|
-
/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g,
|
|
11
|
-
''
|
|
12
|
-
)
|
|
13
|
-
.slice(0, ERROR_BODY_PREVIEW_LIMIT)
|
|
24
|
+
return sanitizeTerminalText(value).slice(0, ERROR_BODY_PREVIEW_LIMIT)
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
export async function extractErrorMessage(response: Response): Promise<string> {
|
|
17
|
-
const contentType = response.headers.get('content-type') || ''
|
|
18
28
|
const rawBody = await response.text()
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
payload &&
|
|
25
|
-
typeof payload === 'object' &&
|
|
26
|
-
!Array.isArray(payload) &&
|
|
27
|
-
typeof payload.error === 'string' &&
|
|
28
|
-
payload.error.length > 0
|
|
29
|
-
) {
|
|
30
|
-
return boundedTerminalPreview(payload.error)
|
|
31
|
-
}
|
|
32
|
-
} catch {
|
|
33
|
-
// Fall through to raw body
|
|
30
|
+
try {
|
|
31
|
+
const payload: unknown = JSON.parse(rawBody)
|
|
32
|
+
if (isJsonErrorResponsePayload(response, payload) && payload.error.length > 0) {
|
|
33
|
+
return boundedTerminalPreview(payload.error)
|
|
34
34
|
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Fall through to raw body
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
return boundedTerminalPreview(rawBody)
|
package/src/github-setup.ts
CHANGED
|
@@ -19,20 +19,11 @@ import { existsSync, mkdirSync } from 'fs'
|
|
|
19
19
|
import { join } from 'path'
|
|
20
20
|
|
|
21
21
|
import { authedFetch } from './http.js'
|
|
22
|
+
import { sanitizeTerminalText } from './json-response.js'
|
|
22
23
|
import { assertWorkspaceDirUsable, workspaceExists } from './workspace.js'
|
|
23
24
|
|
|
24
25
|
export type SetupFetcher = (path: string, init?: RequestInit) => Promise<Response>
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
* Strip terminal control characters from server-supplied strings before they
|
|
28
|
-
* are printed (mirrors index.ts `sanitizeTerminalText`), so a hostile or
|
|
29
|
-
* mangled provisioning `warnings[]`/`defaultBranch` value can't inject escape
|
|
30
|
-
* sequences into the user's terminal.
|
|
31
|
-
*/
|
|
32
|
-
function sanitizeServerText(value: unknown): string {
|
|
33
|
-
return String(value).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
export interface GithubLinkIo {
|
|
37
28
|
print: (line: string) => void
|
|
38
29
|
fetcher?: SetupFetcher
|
|
@@ -349,12 +340,12 @@ export async function runHostedAttach(
|
|
|
349
340
|
if (data.rulesetApplied === true) {
|
|
350
341
|
io.print(
|
|
351
342
|
` Default-branch protection applied (ruleset "orizu-default-branch-protection"${
|
|
352
|
-
data.defaultBranch ? ` on ${
|
|
343
|
+
data.defaultBranch ? ` on ${sanitizeTerminalText(data.defaultBranch)}` : ''
|
|
353
344
|
}).`
|
|
354
345
|
)
|
|
355
346
|
}
|
|
356
347
|
for (const warning of data.warnings ?? []) {
|
|
357
|
-
io.print(` ⚠ ${
|
|
348
|
+
io.print(` ⚠ ${sanitizeTerminalText(warning)}`)
|
|
358
349
|
}
|
|
359
350
|
}
|
|
360
351
|
|
package/src/help.ts
CHANGED
|
@@ -362,10 +362,30 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
362
362
|
},
|
|
363
363
|
{
|
|
364
364
|
path: ['prompts', 'push'],
|
|
365
|
-
usage: 'orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--json]',
|
|
365
|
+
usage: 'orizu prompts push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--report-file <path>] [--json]',
|
|
366
366
|
summary: 'Push a local prompt artifact directory as a new version (with --session: a commit-first git draft).',
|
|
367
367
|
group: 'Prompts and judges',
|
|
368
368
|
},
|
|
369
|
+
{
|
|
370
|
+
path: ['prompts', 'report', 'set'],
|
|
371
|
+
usage: 'orizu prompts report set --prompt <id-or-name> --project <team/project> (--version <n> | --version-id <id>) (--report <markdown|@file> | --report-file <path>) [--json]',
|
|
372
|
+
summary: 'Upload or replace the Markdown report for one explicit prompt version.',
|
|
373
|
+
group: 'Prompts and judges',
|
|
374
|
+
aliases: [['prompts', 'report', 'upload']],
|
|
375
|
+
options: [
|
|
376
|
+
{ name: '--prompt <id-or-name>', help: 'Prompt ID or name.', required: true },
|
|
377
|
+
{ name: '--project <team/project>', help: 'Project slug containing the prompt.', required: true },
|
|
378
|
+
{ name: '--version <n>', help: 'Positive prompt version number.' },
|
|
379
|
+
{ name: '--version-id <id>', help: 'Explicit prompt version ID.' },
|
|
380
|
+
{ name: '--report <markdown|@file>', help: 'Inline Markdown or an @file reference.' },
|
|
381
|
+
{ name: '--report-file <path>', help: 'Markdown file to upload.' },
|
|
382
|
+
{ name: '--json', help: 'Emit the updated prompt report payload.' },
|
|
383
|
+
],
|
|
384
|
+
examples: [
|
|
385
|
+
'orizu prompts report set --prompt Generator --project core/evals --version 3 --report-file ./report.md',
|
|
386
|
+
'orizu prompts report upload --prompt Generator --project core/evals --version-id <id> --report @./report.md',
|
|
387
|
+
],
|
|
388
|
+
},
|
|
369
389
|
{
|
|
370
390
|
path: ['prompts', 'labels', 'set'],
|
|
371
391
|
usage: 'orizu prompts labels set <prompt-name> <label> --version <version-id> [--project <team/project>] [--json]',
|
|
@@ -398,7 +418,7 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
398
418
|
},
|
|
399
419
|
{
|
|
400
420
|
path: ['judges', 'push'],
|
|
401
|
-
usage: 'orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--json]',
|
|
421
|
+
usage: 'orizu judges push <dir> [--runner-version <id>] [--project <team/project>] [--parent <version-id>] [--session <session-id>] [--report-file <path>] [--json]',
|
|
402
422
|
summary: 'Push a local judge artifact directory as a new version (with --session: a commit-first git draft).',
|
|
403
423
|
group: 'Prompts and judges',
|
|
404
424
|
},
|