@haystackeditor/cli 0.15.27 → 0.15.29
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 +13 -20
- package/dist/assets/hooks/agent-context/detect.ts +7 -7
- package/dist/assets/hooks/agent-context/parsers/claude.ts +1 -1
- package/dist/assets/hooks/scripts/commit-msg.sh +1 -3
- package/dist/assets/hooks/scripts/post-commit.sh +1 -3
- package/dist/assets/hooks/scripts/pre-push.sh +1 -4
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
- package/dist/assets/telemetry/browser-runtime.js +668 -43
- package/dist/assets/telemetry/runtime.cjs +62 -68
- package/dist/commands/design-verify.d.ts +11 -3
- package/dist/commands/design-verify.js +147 -4
- package/dist/commands/hooks.d.ts +1 -5
- package/dist/commands/hooks.js +10 -112
- package/dist/commands/install-session-hooks.d.ts +2 -2
- package/dist/commands/install-session-hooks.js +71 -21
- package/dist/commands/mcp.js +1 -34
- package/dist/commands/pr.js +2 -7
- package/dist/commands/setup.d.ts +1 -2
- package/dist/commands/setup.js +18 -272
- package/dist/commands/submit.js +1 -2
- package/dist/commands/verify-hosted-mcp.js +12 -2
- package/dist/commands/verify-hosted-reproducibility.d.ts +1 -0
- package/dist/commands/verify-hosted-reproducibility.js +4 -1
- package/dist/commands/verify-hosted.d.ts +20 -0
- package/dist/commands/verify-hosted.js +119 -11
- package/dist/commands/verify-precompute.d.ts +19 -0
- package/dist/commands/verify-precompute.js +328 -0
- package/dist/index.js +45 -44
- package/dist/schema.d.ts +1 -2
- package/dist/schema.js +1 -2
- package/dist/triage/prompts.d.ts +0 -7
- package/dist/triage/prompts.js +0 -145
- package/dist/triage/runner.js +3 -22
- package/dist/triage/types.d.ts +1 -8
- package/dist/types.d.ts +8 -8
- package/dist/types.js +1 -1
- package/dist/utils/design-verifier-api.d.ts +71 -0
- package/dist/utils/design-verifier-api.js +96 -0
- package/dist/utils/design-verifier-result.d.ts +7 -2
- package/dist/utils/design-verifier-result.js +67 -21
- package/dist/utils/git.d.ts +5 -1
- package/dist/utils/git.js +19 -9
- package/dist/utils/github-api.js +11 -14
- package/dist/utils/hooks.d.ts +1 -15
- package/dist/utils/hooks.js +1 -127
- package/dist/utils/telemetry.d.ts +2 -0
- package/dist/utils/telemetry.js +81 -8
- package/package.json +2 -1
- package/schemas/cloud-verifier.v1.json +69 -3
- package/schemas/{pr.v2.json → pr.v3.json} +3 -12
- package/dist/commands/traces.d.ts +0 -48
- package/dist/commands/traces.js +0 -92
- package/dist/triage/traces.d.ts +0 -20
- package/dist/triage/traces.js +0 -311
- package/schemas/traces.v1.json +0 -53
package/dist/utils/hooks.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utilities for haystack hooks install/status
|
|
3
|
-
*
|
|
4
|
-
* Handles Entire CLI binary download, hook file copying, and git configuration.
|
|
2
|
+
* Utilities for haystack hooks install/status commands.
|
|
5
3
|
*/
|
|
6
4
|
import * as fs from 'fs/promises';
|
|
7
5
|
import { existsSync } from 'fs';
|
|
8
6
|
import * as path from 'path';
|
|
9
|
-
import * as os from 'os';
|
|
10
7
|
import { execSync } from 'child_process';
|
|
11
8
|
import { fileURLToPath } from 'url';
|
|
12
9
|
// ============================================================================
|
|
13
10
|
// Constants
|
|
14
11
|
// ============================================================================
|
|
15
|
-
export const ENTIRE_DEFAULT_VERSION = '0.4.8';
|
|
16
|
-
export const ENTIRE_GITHUB_REPO = 'entireio/cli';
|
|
17
|
-
export const HAYSTACK_BIN_DIR = path.join(os.homedir(), '.haystack', 'bin');
|
|
18
|
-
export const ENTIRE_BIN_PATH = path.join(HAYSTACK_BIN_DIR, 'entire');
|
|
19
12
|
const HOOK_SCRIPTS = [
|
|
20
13
|
'pre-commit',
|
|
21
14
|
'commit-msg',
|
|
@@ -24,25 +17,6 @@ const HOOK_SCRIPTS = [
|
|
|
24
17
|
'prepare-commit-msg',
|
|
25
18
|
];
|
|
26
19
|
// ============================================================================
|
|
27
|
-
// Platform detection
|
|
28
|
-
// ============================================================================
|
|
29
|
-
const OS_MAP = {
|
|
30
|
-
darwin: 'darwin',
|
|
31
|
-
linux: 'linux',
|
|
32
|
-
};
|
|
33
|
-
const ARCH_MAP = {
|
|
34
|
-
arm64: 'arm64',
|
|
35
|
-
x64: 'amd64',
|
|
36
|
-
};
|
|
37
|
-
export function getPlatformAssetName() {
|
|
38
|
-
const osName = OS_MAP[process.platform];
|
|
39
|
-
const archName = ARCH_MAP[process.arch];
|
|
40
|
-
if (!osName || !archName) {
|
|
41
|
-
throw new Error(`Unsupported platform: ${process.platform}-${process.arch}. Entire CLI supports macOS and Linux (amd64/arm64).`);
|
|
42
|
-
}
|
|
43
|
-
return `entire_${osName}_${archName}.tar.gz`;
|
|
44
|
-
}
|
|
45
|
-
// ============================================================================
|
|
46
20
|
// Git repo detection
|
|
47
21
|
// ============================================================================
|
|
48
22
|
export function findGitRoot() {
|
|
@@ -57,86 +31,6 @@ export function findGitRoot() {
|
|
|
57
31
|
}
|
|
58
32
|
}
|
|
59
33
|
// ============================================================================
|
|
60
|
-
// Entire binary management
|
|
61
|
-
// ============================================================================
|
|
62
|
-
export async function getInstalledEntireVersion() {
|
|
63
|
-
if (!existsSync(ENTIRE_BIN_PATH))
|
|
64
|
-
return null;
|
|
65
|
-
try {
|
|
66
|
-
const output = execSync(`"${ENTIRE_BIN_PATH}" --version`, {
|
|
67
|
-
encoding: 'utf-8',
|
|
68
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
|
-
}).trim();
|
|
70
|
-
// Output is like "entire version 0.4.8" or just "0.4.8"
|
|
71
|
-
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
72
|
-
return match ? match[1] : output;
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
export async function getLatestEntireVersion() {
|
|
79
|
-
const response = await fetch(`https://api.github.com/repos/${ENTIRE_GITHUB_REPO}/releases/latest`, {
|
|
80
|
-
headers: {
|
|
81
|
-
'User-Agent': 'Haystack-CLI',
|
|
82
|
-
Accept: 'application/vnd.github.v3+json',
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
throw new Error(`Failed to fetch latest Entire release: ${response.status} ${response.statusText}`);
|
|
87
|
-
}
|
|
88
|
-
const data = (await response.json());
|
|
89
|
-
// tag_name is like "v0.4.8"
|
|
90
|
-
return data.tag_name.replace(/^v/, '');
|
|
91
|
-
}
|
|
92
|
-
export async function downloadEntireBinary(version) {
|
|
93
|
-
const assetName = getPlatformAssetName();
|
|
94
|
-
const tag = version.startsWith('v') ? version : `v${version}`;
|
|
95
|
-
// Fetch release to get asset download URL
|
|
96
|
-
const releaseResponse = await fetch(`https://api.github.com/repos/${ENTIRE_GITHUB_REPO}/releases/tags/${tag}`, {
|
|
97
|
-
headers: {
|
|
98
|
-
'User-Agent': 'Haystack-CLI',
|
|
99
|
-
Accept: 'application/vnd.github.v3+json',
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
if (!releaseResponse.ok) {
|
|
103
|
-
throw new Error(`Failed to fetch Entire release ${tag}: ${releaseResponse.status}`);
|
|
104
|
-
}
|
|
105
|
-
const release = (await releaseResponse.json());
|
|
106
|
-
const asset = release.assets.find((a) => a.name === assetName);
|
|
107
|
-
if (!asset) {
|
|
108
|
-
throw new Error(`No binary found for ${assetName} in Entire release ${tag}. Available: ${release.assets.map((a) => a.name).join(', ')}`);
|
|
109
|
-
}
|
|
110
|
-
// Download the tar.gz
|
|
111
|
-
const downloadResponse = await fetch(asset.browser_download_url, {
|
|
112
|
-
headers: { 'User-Agent': 'Haystack-CLI' },
|
|
113
|
-
});
|
|
114
|
-
if (!downloadResponse.ok) {
|
|
115
|
-
throw new Error(`Failed to download ${assetName}: ${downloadResponse.status}`);
|
|
116
|
-
}
|
|
117
|
-
const buffer = Buffer.from(await downloadResponse.arrayBuffer());
|
|
118
|
-
// Create temp dir, extract, and move binary
|
|
119
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'haystack-entire-'));
|
|
120
|
-
const tarPath = path.join(tmpDir, assetName);
|
|
121
|
-
try {
|
|
122
|
-
await fs.writeFile(tarPath, buffer);
|
|
123
|
-
execSync(`tar xzf "${tarPath}" -C "${tmpDir}"`, { stdio: 'pipe' });
|
|
124
|
-
// Ensure bin dir exists
|
|
125
|
-
await fs.mkdir(HAYSTACK_BIN_DIR, { recursive: true });
|
|
126
|
-
// Move the binary
|
|
127
|
-
const extractedBinary = path.join(tmpDir, 'entire');
|
|
128
|
-
if (!existsSync(extractedBinary)) {
|
|
129
|
-
throw new Error('Entire binary not found in extracted archive');
|
|
130
|
-
}
|
|
131
|
-
await fs.copyFile(extractedBinary, ENTIRE_BIN_PATH);
|
|
132
|
-
await fs.chmod(ENTIRE_BIN_PATH, 0o755);
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
// Clean up temp dir
|
|
136
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// ============================================================================
|
|
140
34
|
// Asset directory resolution
|
|
141
35
|
// ============================================================================
|
|
142
36
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -188,26 +82,6 @@ export async function copyLlmRulesTemplate(repoRoot) {
|
|
|
188
82
|
await fs.copyFile(src, dest);
|
|
189
83
|
return true;
|
|
190
84
|
}
|
|
191
|
-
/** Create .entire/settings.json and .entire/.gitignore */
|
|
192
|
-
export async function createEntireConfig(repoRoot) {
|
|
193
|
-
const entireDir = path.join(repoRoot, '.entire');
|
|
194
|
-
await fs.mkdir(entireDir, { recursive: true });
|
|
195
|
-
await fs.writeFile(path.join(entireDir, 'settings.json'), JSON.stringify({ enabled: true, telemetry: false }, null, 2) + '\n', 'utf-8');
|
|
196
|
-
await fs.writeFile(path.join(entireDir, '.gitignore'), 'tmp/\nsettings.local.json\nmetadata/\nlogs/\n', 'utf-8');
|
|
197
|
-
}
|
|
198
|
-
/** Add .entire/metadata/ to repo .gitignore if not already present */
|
|
199
|
-
export async function updateGitignore(repoRoot) {
|
|
200
|
-
const gitignorePath = path.join(repoRoot, '.gitignore');
|
|
201
|
-
let content = '';
|
|
202
|
-
if (existsSync(gitignorePath)) {
|
|
203
|
-
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
204
|
-
}
|
|
205
|
-
if (content.includes('.entire/metadata'))
|
|
206
|
-
return false;
|
|
207
|
-
const addition = '\n# Entire CLI local session data\n.entire/metadata/\n';
|
|
208
|
-
await fs.writeFile(gitignorePath, content + addition, 'utf-8');
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
85
|
// ============================================================================
|
|
212
86
|
// Helpers
|
|
213
87
|
// ============================================================================
|
|
@@ -15,3 +15,5 @@ export declare function trackError(errorType: string, metadata?: Record<string,
|
|
|
15
15
|
* Track a setup event (non-error). Fire-and-forget.
|
|
16
16
|
*/
|
|
17
17
|
export declare function trackSetupEvent(step: string, metadata?: Record<string, unknown>): void;
|
|
18
|
+
/** Track one bounded front-half precompute result per CLI invocation. */
|
|
19
|
+
export declare function trackPrecomputeEvent(outcome: string, wallMs: number, timeoutMs: number): void;
|
package/dist/utils/telemetry.js
CHANGED
|
@@ -11,23 +11,64 @@ import { readFileSync } from 'node:fs';
|
|
|
11
11
|
import { dirname, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
import * as os from 'os';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
14
15
|
const POSTHOG_HOST = 'https://us.i.posthog.com';
|
|
15
16
|
const POSTHOG_API_KEY = process.env.HAYSTACK_POSTHOG_KEY || 'phc_7wHrCoMlcwuftLe6N38hPb9wKHb4OK0wNw9ayeRySad';
|
|
16
17
|
const CURRENT_FILE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const PACKAGE_JSON_PATH = resolve(CURRENT_FILE_DIR, '../../package.json');
|
|
19
|
+
const SAFE_ENUM = /^[A-Za-z0-9 _.:/-]{1,100}$/;
|
|
20
|
+
const SAFE_STRING_KEYS = new Set([
|
|
21
|
+
'checker',
|
|
22
|
+
'cli',
|
|
23
|
+
'during',
|
|
24
|
+
'kind',
|
|
25
|
+
'operation',
|
|
26
|
+
'outcome',
|
|
27
|
+
'rate_limit_limit',
|
|
28
|
+
'rate_limit_reset',
|
|
29
|
+
'rate_limit_resource',
|
|
30
|
+
]);
|
|
31
|
+
const SAFE_NUMBER_KEYS = new Set([
|
|
32
|
+
'configured',
|
|
33
|
+
'exit_code',
|
|
34
|
+
'max_turns',
|
|
35
|
+
'status',
|
|
36
|
+
'timeout_ms',
|
|
37
|
+
'total',
|
|
38
|
+
'wall_ms',
|
|
39
|
+
]);
|
|
40
|
+
function telemetryDisabled() {
|
|
41
|
+
return process.env.DO_NOT_TRACK === '1' || process.env.HAYSTACK_TELEMETRY_DISABLED === '1';
|
|
42
|
+
}
|
|
43
|
+
function minimizeMetadata(metadata) {
|
|
44
|
+
const minimized = {};
|
|
45
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
46
|
+
if (typeof value === 'string' && SAFE_STRING_KEYS.has(key) && SAFE_ENUM.test(value)) {
|
|
47
|
+
minimized[key] = value;
|
|
48
|
+
}
|
|
49
|
+
else if (typeof value === 'number' && Number.isFinite(value) && SAFE_NUMBER_KEYS.has(key)) {
|
|
50
|
+
minimized[key] = value;
|
|
51
|
+
}
|
|
52
|
+
else if (typeof value === 'boolean') {
|
|
53
|
+
minimized[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return minimized;
|
|
57
|
+
}
|
|
18
58
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
59
|
+
* Pseudonymous stable machine identifier for PostHog distinct_id.
|
|
60
|
+
* The local username and hostname are never transmitted.
|
|
21
61
|
* Never throws — falls back to hostname-only or 'anonymous'.
|
|
22
62
|
*/
|
|
23
63
|
function getDistinctId() {
|
|
24
64
|
try {
|
|
25
65
|
const hostname = os.hostname();
|
|
26
66
|
try {
|
|
27
|
-
|
|
67
|
+
const localMachine = `${os.userInfo().username}\0${hostname}`;
|
|
68
|
+
return `cli:${createHash('sha256').update(localMachine).digest('hex').slice(0, 24)}`;
|
|
28
69
|
}
|
|
29
70
|
catch {
|
|
30
|
-
return `cli:${hostname}`;
|
|
71
|
+
return `cli:${createHash('sha256').update(hostname).digest('hex').slice(0, 24)}`;
|
|
31
72
|
}
|
|
32
73
|
}
|
|
33
74
|
catch {
|
|
@@ -39,7 +80,7 @@ function getDistinctId() {
|
|
|
39
80
|
*/
|
|
40
81
|
export function trackError(errorType, metadata) {
|
|
41
82
|
try {
|
|
42
|
-
if (typeof globalThis.fetch !== 'function')
|
|
83
|
+
if (telemetryDisabled() || typeof globalThis.fetch !== 'function')
|
|
43
84
|
return;
|
|
44
85
|
const event = {
|
|
45
86
|
api_key: POSTHOG_API_KEY,
|
|
@@ -52,7 +93,8 @@ export function trackError(errorType, metadata) {
|
|
|
52
93
|
arch: process.arch,
|
|
53
94
|
node_version: process.version,
|
|
54
95
|
timestamp: new Date().toISOString(),
|
|
55
|
-
...metadata,
|
|
96
|
+
...minimizeMetadata(metadata),
|
|
97
|
+
$ip: null,
|
|
56
98
|
},
|
|
57
99
|
timestamp: new Date().toISOString(),
|
|
58
100
|
};
|
|
@@ -72,7 +114,7 @@ export function trackError(errorType, metadata) {
|
|
|
72
114
|
*/
|
|
73
115
|
export function trackSetupEvent(step, metadata) {
|
|
74
116
|
try {
|
|
75
|
-
if (typeof globalThis.fetch !== 'function')
|
|
117
|
+
if (telemetryDisabled() || typeof globalThis.fetch !== 'function')
|
|
76
118
|
return;
|
|
77
119
|
const event = {
|
|
78
120
|
api_key: POSTHOG_API_KEY,
|
|
@@ -84,7 +126,8 @@ export function trackSetupEvent(step, metadata) {
|
|
|
84
126
|
os: process.platform,
|
|
85
127
|
arch: process.arch,
|
|
86
128
|
timestamp: new Date().toISOString(),
|
|
87
|
-
...metadata,
|
|
129
|
+
...minimizeMetadata(metadata),
|
|
130
|
+
$ip: null,
|
|
88
131
|
},
|
|
89
132
|
timestamp: new Date().toISOString(),
|
|
90
133
|
};
|
|
@@ -98,6 +141,36 @@ export function trackSetupEvent(step, metadata) {
|
|
|
98
141
|
// Telemetry must never crash the CLI
|
|
99
142
|
}
|
|
100
143
|
}
|
|
144
|
+
/** Track one bounded front-half precompute result per CLI invocation. */
|
|
145
|
+
export function trackPrecomputeEvent(outcome, wallMs, timeoutMs) {
|
|
146
|
+
try {
|
|
147
|
+
if (telemetryDisabled() || typeof globalThis.fetch !== 'function')
|
|
148
|
+
return;
|
|
149
|
+
const event = {
|
|
150
|
+
api_key: POSTHOG_API_KEY,
|
|
151
|
+
distinct_id: getDistinctId(),
|
|
152
|
+
event: 'cli_verify_precompute',
|
|
153
|
+
properties: {
|
|
154
|
+
cli_version: getCLIVersion(),
|
|
155
|
+
os: process.platform,
|
|
156
|
+
arch: process.arch,
|
|
157
|
+
timestamp: new Date().toISOString(),
|
|
158
|
+
...minimizeMetadata({ outcome, wall_ms: wallMs }),
|
|
159
|
+
$ip: null,
|
|
160
|
+
},
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
};
|
|
163
|
+
globalThis.fetch(`${POSTHOG_HOST}/capture/`, {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: { 'Content-Type': 'application/json' },
|
|
166
|
+
body: JSON.stringify(event),
|
|
167
|
+
signal: AbortSignal.timeout(Math.max(1, timeoutMs)),
|
|
168
|
+
}).catch(() => { });
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Telemetry must never crash or delay the CLI.
|
|
172
|
+
}
|
|
173
|
+
}
|
|
101
174
|
function getCLIVersion() {
|
|
102
175
|
try {
|
|
103
176
|
const packageJson = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haystackeditor/cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.29",
|
|
4
4
|
"description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
13
|
"benchmark:telemetry": "pnpm build && node scripts/benchmark-telemetry-overhead.mjs",
|
|
14
14
|
"benchmark:telemetry:corpus": "pnpm build && node scripts/benchmark-open-source-telemetry.mjs",
|
|
15
|
+
"benchmark:telemetry:corpus:guard": "pnpm build && node scripts/benchmark-open-source-telemetry.mjs --quick --require-budget",
|
|
15
16
|
"check:schemas": "node scripts/check-schemas.mjs",
|
|
16
17
|
"release": "node scripts/release.mjs",
|
|
17
18
|
"prepublishOnly": "node scripts/no-direct-publish.mjs",
|
|
@@ -67,6 +67,61 @@
|
|
|
67
67
|
},
|
|
68
68
|
"error": {
|
|
69
69
|
"type": "string"
|
|
70
|
+
},
|
|
71
|
+
"result": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"required": [
|
|
75
|
+
"verdict",
|
|
76
|
+
"summary",
|
|
77
|
+
"casesTotal",
|
|
78
|
+
"casesFailed",
|
|
79
|
+
"regressions",
|
|
80
|
+
"outputLocation",
|
|
81
|
+
"wallSeconds"
|
|
82
|
+
],
|
|
83
|
+
"properties": {
|
|
84
|
+
"verdict": {
|
|
85
|
+
"type": "string"
|
|
86
|
+
},
|
|
87
|
+
"summary": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"casesTotal": {
|
|
91
|
+
"type": "integer",
|
|
92
|
+
"minimum": 0
|
|
93
|
+
},
|
|
94
|
+
"casesFailed": {
|
|
95
|
+
"type": "integer",
|
|
96
|
+
"minimum": 0
|
|
97
|
+
},
|
|
98
|
+
"regressions": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"additionalProperties": false,
|
|
103
|
+
"required": [
|
|
104
|
+
"caseId",
|
|
105
|
+
"title"
|
|
106
|
+
],
|
|
107
|
+
"properties": {
|
|
108
|
+
"caseId": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
},
|
|
111
|
+
"title": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"outputLocation": {
|
|
118
|
+
"type": "string"
|
|
119
|
+
},
|
|
120
|
+
"wallSeconds": {
|
|
121
|
+
"type": "number",
|
|
122
|
+
"minimum": 0
|
|
123
|
+
}
|
|
124
|
+
}
|
|
70
125
|
}
|
|
71
126
|
},
|
|
72
127
|
"allOf": [
|
|
@@ -86,9 +141,20 @@
|
|
|
86
141
|
"repository",
|
|
87
142
|
"base_commit",
|
|
88
143
|
"head_commit",
|
|
89
|
-
"created_at"
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
"created_at"
|
|
145
|
+
],
|
|
146
|
+
"anyOf": [
|
|
147
|
+
{
|
|
148
|
+
"required": [
|
|
149
|
+
"completed_at",
|
|
150
|
+
"output"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"required": [
|
|
155
|
+
"result"
|
|
156
|
+
]
|
|
157
|
+
}
|
|
92
158
|
]
|
|
93
159
|
}
|
|
94
160
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://docs.haystackeditor.com/cli/schemas/pr.
|
|
3
|
+
"$id": "https://docs.haystackeditor.com/cli/schemas/pr.v3.json",
|
|
4
4
|
"title": "haystack pr get <ref> --json",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": ["schema_version", "ref", "title", "head_sha", "triage", "merge"
|
|
7
|
+
"required": ["schema_version", "ref", "title", "head_sha", "triage", "merge"],
|
|
8
8
|
"properties": {
|
|
9
|
-
"schema_version": { "type": "string", "const": "
|
|
9
|
+
"schema_version": { "type": "string", "const": "3.0.0" },
|
|
10
10
|
"ref": { "type": "string" },
|
|
11
11
|
"title": { "type": "string" },
|
|
12
12
|
"head_sha": { "type": "string" },
|
|
@@ -31,15 +31,6 @@
|
|
|
31
31
|
"action": { "type": ["string", "null"] }
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
|
-
"traces": {
|
|
35
|
-
"type": "object",
|
|
36
|
-
"additionalProperties": false,
|
|
37
|
-
"required": ["available", "count"],
|
|
38
|
-
"properties": {
|
|
39
|
-
"available": { "type": "boolean" },
|
|
40
|
-
"count": { "type": "integer", "minimum": 0 }
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
34
|
"fixer": {
|
|
44
35
|
"type": "object",
|
|
45
36
|
"additionalProperties": false,
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export interface TraceGetOptions {
|
|
2
|
-
json?: boolean;
|
|
3
|
-
cursor?: string;
|
|
4
|
-
limit?: string;
|
|
5
|
-
}
|
|
6
|
-
export declare function fetchTracesListPayload(ref: string): Promise<{
|
|
7
|
-
operation: string;
|
|
8
|
-
ref: string;
|
|
9
|
-
head_sha: string;
|
|
10
|
-
checkpoints: {
|
|
11
|
-
checkpoint_id: string;
|
|
12
|
-
commit_sha: string;
|
|
13
|
-
commit_message: string;
|
|
14
|
-
timestamp: string | null;
|
|
15
|
-
summary: string;
|
|
16
|
-
user_prompt: string | null;
|
|
17
|
-
files_touched: string[];
|
|
18
|
-
branch: string | null;
|
|
19
|
-
}[];
|
|
20
|
-
} & {
|
|
21
|
-
schema_version: string;
|
|
22
|
-
}>;
|
|
23
|
-
export declare function tracesListCommand(ref: string, options: {
|
|
24
|
-
json?: boolean;
|
|
25
|
-
}): Promise<void>;
|
|
26
|
-
export declare function fetchTracesGetPayload(ref: string, checkpointId: string, options: Pick<TraceGetOptions, 'cursor' | 'limit'>): Promise<{
|
|
27
|
-
operation: string;
|
|
28
|
-
ref: string;
|
|
29
|
-
head_sha: string;
|
|
30
|
-
checkpoint_id: string;
|
|
31
|
-
checkpoint: {
|
|
32
|
-
checkpoint_id: string;
|
|
33
|
-
commit_sha: string;
|
|
34
|
-
commit_message: string;
|
|
35
|
-
timestamp: string | null;
|
|
36
|
-
summary: string;
|
|
37
|
-
user_prompt: string | null;
|
|
38
|
-
files_touched: string[];
|
|
39
|
-
branch: string | null;
|
|
40
|
-
} | null;
|
|
41
|
-
chunks: string[];
|
|
42
|
-
total_chunks: number;
|
|
43
|
-
total_chars: number;
|
|
44
|
-
next_cursor: string | null;
|
|
45
|
-
} & {
|
|
46
|
-
schema_version: string;
|
|
47
|
-
}>;
|
|
48
|
-
export declare function tracesGetCommand(ref: string, checkpointId: string, options: TraceGetOptions): Promise<void>;
|
package/dist/commands/traces.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { loadToken } from '../utils/auth.js';
|
|
3
|
-
import { fetchAnalysisArtifact, fetchGitHubJson } from '../utils/haystack-api.js';
|
|
4
|
-
import { withSchema } from '../schema.js';
|
|
5
|
-
import { parsePrRef } from '../utils/pr-ref.js';
|
|
6
|
-
function publicCheckpoint(trace) {
|
|
7
|
-
return {
|
|
8
|
-
checkpoint_id: trace.checkpointId,
|
|
9
|
-
commit_sha: trace.commitSha,
|
|
10
|
-
commit_message: trace.commitMessage,
|
|
11
|
-
timestamp: trace.timestamp ?? null,
|
|
12
|
-
summary: trace.overviewSummary,
|
|
13
|
-
user_prompt: trace.userPrompt ?? null,
|
|
14
|
-
files_touched: trace.filesTouched ?? [],
|
|
15
|
-
branch: trace.branch ?? null,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
async function tokenAndHead(ref) {
|
|
19
|
-
const pr = parsePrRef(ref, 'traces list');
|
|
20
|
-
const token = await loadToken({ owner: pr.owner, repo: pr.repo });
|
|
21
|
-
if (!token)
|
|
22
|
-
throw new Error('Not authenticated. Run `haystack login` first.');
|
|
23
|
-
const meta = await fetchGitHubJson(`/repos/${pr.owner}/${pr.repo}/pulls/${pr.prNumber}`, token);
|
|
24
|
-
return { pr, token, headSha: meta.head.sha, ref: `${pr.owner}/${pr.repo}#${pr.prNumber}` };
|
|
25
|
-
}
|
|
26
|
-
export async function fetchTracesListPayload(ref) {
|
|
27
|
-
const { pr, token, headSha, ref: normalizedRef } = await tokenAndHead(ref);
|
|
28
|
-
const overview = await fetchAnalysisArtifact(pr.owner, pr.repo, pr.prNumber, 'entire_traces.json', token);
|
|
29
|
-
return withSchema('traces', {
|
|
30
|
-
operation: 'list',
|
|
31
|
-
ref: normalizedRef,
|
|
32
|
-
head_sha: headSha,
|
|
33
|
-
checkpoints: (overview?.traces ?? []).map(publicCheckpoint),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
export async function tracesListCommand(ref, options) {
|
|
37
|
-
const payload = await fetchTracesListPayload(ref);
|
|
38
|
-
if (options.json) {
|
|
39
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (payload.checkpoints.length === 0) {
|
|
43
|
-
console.log(chalk.dim('\nNo retained customer traces for this PR.\n'));
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
console.log(chalk.bold('\nCustomer traces\n'));
|
|
47
|
-
for (const trace of payload.checkpoints) {
|
|
48
|
-
console.log(`${chalk.cyan(trace.checkpoint_id)} ${trace.commit_message}`);
|
|
49
|
-
console.log(` ${trace.summary}\n`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export async function fetchTracesGetPayload(ref, checkpointId, options) {
|
|
53
|
-
const { pr, token, headSha, ref: normalizedRef } = await tokenAndHead(ref);
|
|
54
|
-
const [trace, overview] = await Promise.all([
|
|
55
|
-
fetchAnalysisArtifact(pr.owner, pr.repo, pr.prNumber, `entire_trace_chunks/${encodeURIComponent(checkpointId)}.json`, token),
|
|
56
|
-
fetchAnalysisArtifact(pr.owner, pr.repo, pr.prNumber, 'entire_traces.json', token),
|
|
57
|
-
]);
|
|
58
|
-
if (!trace)
|
|
59
|
-
throw new Error(`Trace checkpoint not found: ${checkpointId}`);
|
|
60
|
-
if (trace.checkpointId !== checkpointId)
|
|
61
|
-
throw new Error('Trace checkpoint provenance mismatch');
|
|
62
|
-
const cursor = Math.max(0, Number.parseInt(options.cursor ?? '0', 10) || 0);
|
|
63
|
-
const limit = Math.min(100, Math.max(1, Number.parseInt(options.limit ?? '20', 10) || 20));
|
|
64
|
-
const chunks = trace.chunks.slice(cursor, cursor + limit);
|
|
65
|
-
const next = cursor + chunks.length < trace.chunks.length ? String(cursor + chunks.length) : null;
|
|
66
|
-
return withSchema('traces', {
|
|
67
|
-
operation: 'get',
|
|
68
|
-
ref: normalizedRef,
|
|
69
|
-
head_sha: headSha,
|
|
70
|
-
checkpoint_id: trace.checkpointId,
|
|
71
|
-
checkpoint: (() => {
|
|
72
|
-
const item = overview?.traces?.find((candidate) => candidate.checkpointId === trace.checkpointId);
|
|
73
|
-
return item ? publicCheckpoint(item) : null;
|
|
74
|
-
})(),
|
|
75
|
-
chunks,
|
|
76
|
-
total_chunks: trace.chunks.length,
|
|
77
|
-
total_chars: trace.totalChars,
|
|
78
|
-
next_cursor: next,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
export async function tracesGetCommand(ref, checkpointId, options) {
|
|
82
|
-
const payload = await fetchTracesGetPayload(ref, checkpointId, options);
|
|
83
|
-
const chunks = payload.chunks;
|
|
84
|
-
const next = payload.next_cursor;
|
|
85
|
-
if (options.json) {
|
|
86
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
console.log(chunks.join('\n\n---\n\n'));
|
|
90
|
-
if (next)
|
|
91
|
-
console.log(chalk.dim(`\nMore available: --cursor ${next}\n`));
|
|
92
|
-
}
|
package/dist/triage/traces.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local Entire CLI trace discovery for intent drift analysis.
|
|
3
|
-
*
|
|
4
|
-
* Finds .entire/metadata/ session files whose modified files
|
|
5
|
-
* overlap with the current diff's changed files.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Find local Entire CLI trace files relevant to the current diff.
|
|
9
|
-
*
|
|
10
|
-
* Scans .entire/metadata/ for session directories containing full.jsonl,
|
|
11
|
-
* then checks if any files modified in those sessions overlap with the
|
|
12
|
-
* files changed in the current diff.
|
|
13
|
-
*
|
|
14
|
-
* Returns paths to condensed files containing only user messages (not the
|
|
15
|
-
* raw multi-MB traces). This keeps the intent-drift sub-agent focused on
|
|
16
|
-
* user intent without burning its context window on tool outputs.
|
|
17
|
-
*
|
|
18
|
-
* @returns Array of absolute paths to condensed user-messages files
|
|
19
|
-
*/
|
|
20
|
-
export declare function findRelevantTraces(gitRoot: string, baseBranch: string): string[];
|