@haystackeditor/cli 0.15.27 → 0.15.28
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 +651 -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.d.ts +13 -0
- package/dist/commands/verify-hosted.js +49 -1
- package/dist/commands/verify-precompute.d.ts +19 -0
- package/dist/commands/verify-precompute.js +328 -0
- package/dist/index.js +37 -38
- 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
|
@@ -51,6 +51,7 @@ const safeBufferByteLength = Buffer.byteLength;
|
|
|
51
51
|
const safeObjectKeys = Object.keys;
|
|
52
52
|
const safeObjectEntries = Object.entries;
|
|
53
53
|
const safeObjectCreate = Object.create;
|
|
54
|
+
const safeObjectFreeze = Object.freeze;
|
|
54
55
|
const safeGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
55
56
|
const safeGetPrototypeOf = Object.getPrototypeOf;
|
|
56
57
|
const safeHasOwn = Function.call.bind(Object.prototype.hasOwnProperty);
|
|
@@ -95,7 +96,7 @@ const safeResponseStatus = typeof Response === 'function'
|
|
|
95
96
|
: null;
|
|
96
97
|
const safeClearInterval = clearInterval;
|
|
97
98
|
const SafeError = Error;
|
|
98
|
-
const nativeErrorPrototypes =
|
|
99
|
+
const nativeErrorPrototypes = safeObjectFreeze({
|
|
99
100
|
type: TypeError.prototype,
|
|
100
101
|
range: RangeError.prototype,
|
|
101
102
|
syntax: SyntaxError.prototype,
|
|
@@ -173,10 +174,12 @@ function responseStatus(response) {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
const RUNTIME_VERSION = 1;
|
|
176
|
-
const SAMPLER_VERSION =
|
|
177
|
+
const SAMPLER_VERSION = 4;
|
|
177
178
|
const DEFAULT_FLUSH_INTERVAL_MS = 60_000;
|
|
178
179
|
const DEFAULT_SITE_OBSERVATIONS = 8;
|
|
179
180
|
const MAX_SITE_OBSERVATIONS = 4_096;
|
|
181
|
+
const DEFAULT_PROCESS_PROBE_OBSERVATIONS = 4_096;
|
|
182
|
+
const MAX_PROCESS_PROBE_OBSERVATIONS = 65_536;
|
|
180
183
|
const MAX_SAFE_COUNT = Number.MAX_SAFE_INTEGER;
|
|
181
184
|
const MAX_COUNTER_KEYS = 50_000;
|
|
182
185
|
const MAX_EXECUTION_FAMILIES = 64;
|
|
@@ -195,7 +198,7 @@ const MAX_CONSECUTIVE_FAILURES = 3;
|
|
|
195
198
|
const CONTROL_GLOBAL = '__haystackTelemetryControlV1';
|
|
196
199
|
const CONTROL_CHANNEL = '__HAYSTACK_TELEMETRY_CONTROL_CHANNEL__';
|
|
197
200
|
const RUNTIME_INTEGRITY = '__HAYSTACK_TELEMETRY_RUNTIME_INTEGRITY__';
|
|
198
|
-
const SUPPRESSED_ACTION =
|
|
201
|
+
const SUPPRESSED_ACTION = safeObjectFreeze({ suppressed: true });
|
|
199
202
|
|
|
200
203
|
let installed = false;
|
|
201
204
|
let activeControl = null;
|
|
@@ -401,7 +404,7 @@ function loadInstrumentation() {
|
|
|
401
404
|
const manifest = safeJsonParse(readFileSync(join(__dirname, 'instrumentation.json'), 'utf8'));
|
|
402
405
|
if (manifest.schema_version !== 'haystack-node-telemetry-instrumentation-v3' ||
|
|
403
406
|
!safeArrayIsArray(manifest.sites)) return { sites: new SafeMap(), entrySourcePath: null };
|
|
404
|
-
const siteEntries = safeArrayMap(manifest.sites, site => [site.probe_key,
|
|
407
|
+
const siteEntries = safeArrayMap(manifest.sites, site => [site.probe_key, safeObjectFreeze({
|
|
405
408
|
probeKey: site.probe_key,
|
|
406
409
|
siteId: site.site_id,
|
|
407
410
|
sourcePath: site.source_path,
|
|
@@ -461,7 +464,6 @@ function installRuntime(options = {}) {
|
|
|
461
464
|
metadata,
|
|
462
465
|
calls: 0,
|
|
463
466
|
saturated: false,
|
|
464
|
-
omitted: 0,
|
|
465
467
|
});
|
|
466
468
|
}
|
|
467
469
|
if (safeMapSize(sitesByProbe) === 0) return { enabled: false, reason: 'missing-instrumentation-manifest' };
|
|
@@ -481,6 +483,15 @@ function installRuntime(options = {}) {
|
|
|
481
483
|
requestedSiteObservations >= 1 && requestedSiteObservations <= MAX_SITE_OBSERVATIONS
|
|
482
484
|
? requestedSiteObservations
|
|
483
485
|
: DEFAULT_SITE_OBSERVATIONS;
|
|
486
|
+
const requestedProcessProbeObservations = safeNumberParseInt(
|
|
487
|
+
process.env.HAYSTACK_TELEMETRY_PROCESS_PROBE_OBSERVATIONS ?? '',
|
|
488
|
+
10,
|
|
489
|
+
);
|
|
490
|
+
const processProbeObservationLimit = safeNumberIsSafeInteger(requestedProcessProbeObservations) &&
|
|
491
|
+
requestedProcessProbeObservations >= 1 &&
|
|
492
|
+
requestedProcessProbeObservations <= MAX_PROCESS_PROBE_OBSERVATIONS
|
|
493
|
+
? requestedProcessProbeObservations
|
|
494
|
+
: DEFAULT_PROCESS_PROBE_OBSERVATIONS;
|
|
484
495
|
const requestedProcessSampleRate = safeNumberParseInt(
|
|
485
496
|
process.env.HAYSTACK_TELEMETRY_PROCESS_SAMPLE_RATE ?? '',
|
|
486
497
|
10,
|
|
@@ -499,6 +510,8 @@ function installRuntime(options = {}) {
|
|
|
499
510
|
: DEFAULT_ACTION_SAMPLE_RATE;
|
|
500
511
|
|
|
501
512
|
let enabled = true;
|
|
513
|
+
let collecting = true;
|
|
514
|
+
let processProbeObservations = 0;
|
|
502
515
|
let sequence = 0;
|
|
503
516
|
let consecutiveFailures = 0;
|
|
504
517
|
let currentShape = new SafeMap();
|
|
@@ -509,7 +522,6 @@ function installRuntime(options = {}) {
|
|
|
509
522
|
let sampledActions = 0;
|
|
510
523
|
let unsampledActions = 0;
|
|
511
524
|
let currentOmissions = new SafeMap();
|
|
512
|
-
let saturatedProbeOmissions = 0;
|
|
513
525
|
let pending = null;
|
|
514
526
|
let pendingQueue = [];
|
|
515
527
|
let flushInFlight = null;
|
|
@@ -743,11 +755,15 @@ function installRuntime(options = {}) {
|
|
|
743
755
|
}
|
|
744
756
|
|
|
745
757
|
function sampleWeight(state) {
|
|
746
|
-
if (
|
|
747
|
-
|
|
748
|
-
|
|
758
|
+
if (processProbeObservations >= processProbeObservationLimit) {
|
|
759
|
+
// Attribute the one observation that exhausted the process-wide budget.
|
|
760
|
+
// Later probes are deliberately not inspected or counted: the sampler
|
|
761
|
+
// version declares that this is a bounded, lossy collection mode.
|
|
762
|
+
recordOmission(state.metadata.probeKey, 'probeObservations');
|
|
763
|
+
collecting = false;
|
|
749
764
|
return -2;
|
|
750
765
|
}
|
|
766
|
+
processProbeObservations++;
|
|
751
767
|
const calls = ++state.calls;
|
|
752
768
|
if (calls > siteObservationLimit) {
|
|
753
769
|
state.saturated = true;
|
|
@@ -766,7 +782,11 @@ function installRuntime(options = {}) {
|
|
|
766
782
|
}
|
|
767
783
|
|
|
768
784
|
function observeState(state, value, fields) {
|
|
769
|
-
|
|
785
|
+
// Saturation is deliberately lossy: the first over-limit observation
|
|
786
|
+
// records an explicit saturation marker, then this probe becomes a
|
|
787
|
+
// constant-time pass-through. Counting every later omission caused severe
|
|
788
|
+
// amplification in customer hot loops while adding no new shape evidence.
|
|
789
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
770
790
|
try {
|
|
771
791
|
const site = state.metadata.probeKey;
|
|
772
792
|
const action = actionForProbe();
|
|
@@ -877,7 +897,7 @@ function installRuntime(options = {}) {
|
|
|
877
897
|
}
|
|
878
898
|
|
|
879
899
|
function branchState(state, value) {
|
|
880
|
-
if (!enabled) return value;
|
|
900
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
881
901
|
try {
|
|
882
902
|
const site = state.metadata.probeKey;
|
|
883
903
|
const action = actionForProbe();
|
|
@@ -909,7 +929,7 @@ function installRuntime(options = {}) {
|
|
|
909
929
|
}
|
|
910
930
|
|
|
911
931
|
function thrownState(state, value) {
|
|
912
|
-
if (!enabled) return value;
|
|
932
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
913
933
|
try {
|
|
914
934
|
const site = state.metadata.probeKey;
|
|
915
935
|
const action = actionForProbe();
|
|
@@ -939,7 +959,7 @@ function installRuntime(options = {}) {
|
|
|
939
959
|
if (pending || pendingQueue.length > 0 || (safeMapSize(currentShape) === 0 &&
|
|
940
960
|
safeMapSize(currentBranch) === 0 && safeMapSize(currentExecutions) === 0 &&
|
|
941
961
|
omittedExecutionFamilyObservations === 0 && sampledActions === 0 && unsampledActions === 0 &&
|
|
942
|
-
safeMapSize(currentOmissions) === 0
|
|
962
|
+
safeMapSize(currentOmissions) === 0)) return;
|
|
943
963
|
const shape = objectFromMap(currentShape);
|
|
944
964
|
const branchCounters = objectFromMap(currentBranch);
|
|
945
965
|
currentShape = new SafeMap();
|
|
@@ -958,25 +978,6 @@ function installRuntime(options = {}) {
|
|
|
958
978
|
sampledActions = 0;
|
|
959
979
|
unsampledActions = 0;
|
|
960
980
|
omittedExecutionFamilyObservations = 0;
|
|
961
|
-
if (saturatedProbeOmissions > 0) {
|
|
962
|
-
const countEntries = mapEntriesArray(probeStates);
|
|
963
|
-
for (let index = 0; index < countEntries.length; index++) {
|
|
964
|
-
const site = countEntries[index][0];
|
|
965
|
-
const state = countEntries[index][1];
|
|
966
|
-
if (!state || state.omitted <= 0) continue;
|
|
967
|
-
const current = safeMapGet(currentOmissions, site) || {
|
|
968
|
-
counterObservations: 0,
|
|
969
|
-
probeObservations: 0,
|
|
970
|
-
};
|
|
971
|
-
current.probeObservations = safeMathMin(
|
|
972
|
-
MAX_SAFE_COUNT,
|
|
973
|
-
current.probeObservations + state.omitted,
|
|
974
|
-
);
|
|
975
|
-
safeMapSet(currentOmissions, site, current);
|
|
976
|
-
state.omitted = 0;
|
|
977
|
-
}
|
|
978
|
-
saturatedProbeOmissions = 0;
|
|
979
|
-
}
|
|
980
981
|
const omittedEntries = safeArraySort(
|
|
981
982
|
mapEntriesArray(currentOmissions),
|
|
982
983
|
(leftEntry, rightEntry) => compareStrings(leftEntry[0], rightEntry[0]),
|
|
@@ -1389,7 +1390,7 @@ function installRuntime(options = {}) {
|
|
|
1389
1390
|
void flush('before-exit');
|
|
1390
1391
|
});
|
|
1391
1392
|
|
|
1392
|
-
const control =
|
|
1393
|
+
const control = safeObjectFreeze({
|
|
1393
1394
|
enabled: true,
|
|
1394
1395
|
runtimeVersion: RUNTIME_VERSION,
|
|
1395
1396
|
buildId,
|
|
@@ -1409,55 +1410,48 @@ function installRuntime(options = {}) {
|
|
|
1409
1410
|
if (name === '__hstValue') {
|
|
1410
1411
|
const fields = state.metadata.allowedFields;
|
|
1411
1412
|
return (value) => {
|
|
1412
|
-
if (!enabled) return value;
|
|
1413
|
-
if (state.saturated) {
|
|
1414
|
-
const action = actionForProbe();
|
|
1415
|
-
if (action?.suppressed) return value;
|
|
1416
|
-
const executionValue = state.metadata.probeKind === 'parameter' ||
|
|
1417
|
-
state.metadata.probeKind === 'return';
|
|
1418
|
-
if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
|
|
1419
|
-
if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
|
|
1420
|
-
if (executionValue) {
|
|
1421
|
-
const skippedEvents = value !== null && typeof value === 'object' && !safeArrayIsArray(value)
|
|
1422
|
-
? 1 + safeMathMin(fields.length, 48)
|
|
1423
|
-
: 1;
|
|
1424
|
-
omitExecutionEvents(action, skippedEvents);
|
|
1425
|
-
}
|
|
1426
|
-
return value;
|
|
1427
|
-
}
|
|
1413
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
1428
1414
|
return observeState(state, value, fields);
|
|
1429
1415
|
};
|
|
1430
1416
|
}
|
|
1431
1417
|
if (name === '__hstBranch') return (value) => {
|
|
1432
|
-
if (!enabled) return value;
|
|
1433
|
-
if (state.saturated) {
|
|
1434
|
-
const action = actionForProbe();
|
|
1435
|
-
if (action?.suppressed) return value;
|
|
1436
|
-
if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
|
|
1437
|
-
if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
|
|
1438
|
-
omitExecutionEvents(action);
|
|
1439
|
-
return value;
|
|
1440
|
-
}
|
|
1418
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
1441
1419
|
return branchState(state, value);
|
|
1442
1420
|
};
|
|
1443
1421
|
if (name === '__hstThrow') return (value) => {
|
|
1444
|
-
if (!enabled) return value;
|
|
1445
|
-
if (state.saturated) {
|
|
1446
|
-
const action = actionForProbe();
|
|
1447
|
-
if (action?.suppressed) return value;
|
|
1448
|
-
if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
|
|
1449
|
-
if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
|
|
1450
|
-
omitExecutionEvents(action);
|
|
1451
|
-
return value;
|
|
1452
|
-
}
|
|
1422
|
+
if (!enabled || !collecting || state.saturated) return value;
|
|
1453
1423
|
return thrownState(state, value);
|
|
1454
1424
|
};
|
|
1455
1425
|
return null;
|
|
1456
1426
|
},
|
|
1427
|
+
snapshotCurrentAction() {
|
|
1428
|
+
try {
|
|
1429
|
+
const action = currentAction();
|
|
1430
|
+
if (!action || action.actionKind !== 'http-server') return null;
|
|
1431
|
+
return safeObjectFreeze({
|
|
1432
|
+
actionKind: action.actionKind,
|
|
1433
|
+
truncated: action.truncated,
|
|
1434
|
+
omittedEvents: action.omittedEvents,
|
|
1435
|
+
events: safeObjectFreeze(safeArrayMap(action.events, event => safeObjectFreeze({
|
|
1436
|
+
siteId: event.siteId,
|
|
1437
|
+
kind: event.kind,
|
|
1438
|
+
scope: event.scope,
|
|
1439
|
+
outcome: event.outcome,
|
|
1440
|
+
observations: event.observations,
|
|
1441
|
+
}))),
|
|
1442
|
+
});
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
debug('current action snapshot failed open', error);
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1457
1448
|
status() {
|
|
1458
1449
|
return {
|
|
1459
1450
|
enabled,
|
|
1451
|
+
collecting,
|
|
1460
1452
|
sequence,
|
|
1453
|
+
processProbeObservations,
|
|
1454
|
+
processProbeObservationLimit,
|
|
1461
1455
|
pending: pending !== null || pendingQueue.length > 0,
|
|
1462
1456
|
shapeKeys: safeMapSize(currentShape),
|
|
1463
1457
|
branchKeys: safeMapSize(currentBranch),
|
|
@@ -1496,7 +1490,7 @@ function install(options = {}) {
|
|
|
1496
1490
|
}
|
|
1497
1491
|
}
|
|
1498
1492
|
|
|
1499
|
-
const publicRuntime =
|
|
1493
|
+
const publicRuntime = safeObjectFreeze({ install, selectProbe, safeSelectProbe, safeSelectSiteProbe });
|
|
1500
1494
|
// Keep the exported capability immutable. The generated CommonJS entry locks
|
|
1501
1495
|
// its cache slot after genuine evaluation; the ESM wrapper evaluates this
|
|
1502
1496
|
// source outside CommonJS's public cache.
|
|
@@ -6,18 +6,26 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Unlike `haystack review` (finding-oriented analysis of the diff), design
|
|
8
8
|
* verification executes the change: it infers intent from the diff alone,
|
|
9
|
-
* designs pre-registered behavioral test cases, runs
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* designs pre-registered behavioral test cases, runs control-before, before,
|
|
10
|
+
* and after in isolated sandboxes, and reports evidence-backed anomaly
|
|
11
|
+
* candidates outside the registered intent.
|
|
12
12
|
*
|
|
13
13
|
* The command waits by default and returns an agent-facing behavioral result.
|
|
14
14
|
* --no-wait preserves the asynchronous trigger-only path.
|
|
15
15
|
*/
|
|
16
|
+
import { type DesignVerifierUniverseRole } from '../utils/design-verifier-api.js';
|
|
16
17
|
export interface DesignVerifyOptions {
|
|
17
18
|
json?: boolean;
|
|
18
19
|
wait?: boolean;
|
|
19
20
|
run?: string;
|
|
20
21
|
pollInterval?: string;
|
|
21
22
|
timeout?: string;
|
|
23
|
+
case?: string;
|
|
24
|
+
universe?: DesignVerifierUniverseRole;
|
|
25
|
+
replay?: boolean;
|
|
26
|
+
execJson?: string;
|
|
27
|
+
cwd?: string;
|
|
28
|
+
commandTimeout?: string;
|
|
29
|
+
action?: string;
|
|
22
30
|
}
|
|
23
31
|
export declare function designVerifyCommand(identifier: string, options: DesignVerifyOptions): Promise<void>;
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Unlike `haystack review` (finding-oriented analysis of the diff), design
|
|
8
8
|
* verification executes the change: it infers intent from the diff alone,
|
|
9
|
-
* designs pre-registered behavioral test cases, runs
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* designs pre-registered behavioral test cases, runs control-before, before,
|
|
10
|
+
* and after in isolated sandboxes, and reports evidence-backed anomaly
|
|
11
|
+
* candidates outside the registered intent.
|
|
12
12
|
*
|
|
13
13
|
* The command waits by default and returns an agent-facing behavioral result.
|
|
14
14
|
* --no-wait preserves the asynchronous trigger-only path.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import chalk from 'chalk';
|
|
17
17
|
import { loadToken } from '../utils/auth.js';
|
|
18
18
|
import { parsePrRef } from '../utils/pr-ref.js';
|
|
19
|
-
import { triggerDesignVerifier, waitForDesignVerifier } from '../utils/design-verifier-api.js';
|
|
19
|
+
import { getDesignVerifierUniverseAction, startDesignVerifierUniverseAction, triggerDesignVerifier, waitForDesignVerifier, waitForDesignVerifierUniverseAction, } from '../utils/design-verifier-api.js';
|
|
20
20
|
import { buildDesignVerifierAgentReport, designVerifierStageLabel, formatDesignVerifierAgentReport, } from '../utils/design-verifier-result.js';
|
|
21
21
|
function emit(json, payload, human) {
|
|
22
22
|
if (json) {
|
|
@@ -26,6 +26,138 @@ function emit(json, payload, human) {
|
|
|
26
26
|
human();
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
function parseExecArgv(source) {
|
|
30
|
+
let value;
|
|
31
|
+
try {
|
|
32
|
+
value = JSON.parse(source);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`--exec-json is not valid JSON: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(value) || value.length === 0 ||
|
|
38
|
+
value.some(argument => typeof argument !== 'string')) {
|
|
39
|
+
throw new Error('--exec-json must be a non-empty JSON array of string arguments');
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function terminalSafe(value) {
|
|
44
|
+
// Preserve ordinary whitespace while removing C0/C1 controls, including
|
|
45
|
+
// ESC/OSC introducers. JSON output retains the server-returned strings.
|
|
46
|
+
let safe = '';
|
|
47
|
+
for (const character of value) {
|
|
48
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
49
|
+
const ordinaryWhitespace = codePoint === 9 || codePoint === 10 || codePoint === 13;
|
|
50
|
+
if ((codePoint < 32 && !ordinaryWhitespace) ||
|
|
51
|
+
(codePoint >= 127 && codePoint <= 159))
|
|
52
|
+
continue;
|
|
53
|
+
safe += character;
|
|
54
|
+
}
|
|
55
|
+
return safe;
|
|
56
|
+
}
|
|
57
|
+
function printUniverseAction(action) {
|
|
58
|
+
const result = action.result;
|
|
59
|
+
if (action.status === 'queued' || action.status === 'running') {
|
|
60
|
+
console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} is ${action.status}; action ${action.actionId}`));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (action.status === 'failed') {
|
|
64
|
+
console.error(chalk.red(`Universe ${action.action} failed: ${action.error || 'unknown error'}`));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!result) {
|
|
68
|
+
console.error(chalk.red(`Universe ${action.action} completed without a result.`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} (${result.durationMs}ms, exit ${result.exitCode ?? 'unknown'})`));
|
|
72
|
+
if (result.stdout) {
|
|
73
|
+
const stdout = terminalSafe(result.stdout);
|
|
74
|
+
process.stdout.write(stdout.endsWith('\n') ? stdout : `${stdout}\n`);
|
|
75
|
+
}
|
|
76
|
+
if (result.stderr) {
|
|
77
|
+
const stderr = terminalSafe(result.stderr);
|
|
78
|
+
process.stderr.write(stderr.endsWith('\n') ? stderr : `${stderr}\n`);
|
|
79
|
+
}
|
|
80
|
+
if (result.stdoutOmittedBytes || result.stderrOmittedBytes) {
|
|
81
|
+
console.error(chalk.dim(`Output truncated: ${result.stdoutOmittedBytes} stdout bytes, ${result.stderrOmittedBytes} stderr bytes omitted.`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds) {
|
|
85
|
+
const requested = Boolean(options.action || options.case || options.universe ||
|
|
86
|
+
options.replay || options.execJson || options.cwd || options.commandTimeout);
|
|
87
|
+
if (!requested)
|
|
88
|
+
return false;
|
|
89
|
+
if (!options.run)
|
|
90
|
+
throw new Error('universe exploration requires --run <run-id>');
|
|
91
|
+
let actionId = options.action;
|
|
92
|
+
if (actionId) {
|
|
93
|
+
if (options.case || options.universe || options.replay || options.execJson ||
|
|
94
|
+
options.cwd || options.commandTimeout) {
|
|
95
|
+
throw new Error('--action cannot be combined with a new universe command');
|
|
96
|
+
}
|
|
97
|
+
if (options.wait === false) {
|
|
98
|
+
const loaded = await getDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token);
|
|
99
|
+
if (loaded.status !== 'ok')
|
|
100
|
+
throw new Error(loaded.message);
|
|
101
|
+
emit(options.json, { ok: true, pr: prLabel, ...loaded.action }, () => printUniverseAction(loaded.action));
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
if (!options.case || !/^case-\d{5}$/.test(options.case)) {
|
|
107
|
+
throw new Error('a canonical --case case-00000 is required');
|
|
108
|
+
}
|
|
109
|
+
if (!options.universe || !['control-before', 'before', 'after'].includes(options.universe)) {
|
|
110
|
+
throw new Error('--universe must be control-before, before, or after');
|
|
111
|
+
}
|
|
112
|
+
if (Boolean(options.replay) === Boolean(options.execJson)) {
|
|
113
|
+
throw new Error('choose exactly one of --replay or --exec-json');
|
|
114
|
+
}
|
|
115
|
+
if (options.replay && (options.cwd || options.commandTimeout)) {
|
|
116
|
+
throw new Error('--cwd and --command-timeout apply only to --exec-json');
|
|
117
|
+
}
|
|
118
|
+
const commandTimeoutMs = options.commandTimeout === undefined
|
|
119
|
+
? undefined
|
|
120
|
+
: Number(options.commandTimeout) * 1000;
|
|
121
|
+
if (commandTimeoutMs !== undefined &&
|
|
122
|
+
!(Number.isInteger(commandTimeoutMs) && commandTimeoutMs >= 1_000 && commandTimeoutMs <= 300_000)) {
|
|
123
|
+
throw new Error('--command-timeout must be between 1 and 300 seconds');
|
|
124
|
+
}
|
|
125
|
+
const started = await startDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, token, {
|
|
126
|
+
caseId: options.case,
|
|
127
|
+
universe: options.universe,
|
|
128
|
+
action: options.replay ? 'replay' : 'exec',
|
|
129
|
+
...(options.execJson ? { argv: parseExecArgv(options.execJson) } : {}),
|
|
130
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
131
|
+
...(commandTimeoutMs === undefined ? {} : { timeoutMs: commandTimeoutMs }),
|
|
132
|
+
});
|
|
133
|
+
if (started.status !== 'ok')
|
|
134
|
+
throw new Error(started.message);
|
|
135
|
+
actionId = started.action.actionId;
|
|
136
|
+
if (options.wait === false) {
|
|
137
|
+
emit(options.json, { ok: true, pr: prLabel, ...started.action }, () => {
|
|
138
|
+
console.log(chalk.green('✓ Universe action queued'));
|
|
139
|
+
console.log(chalk.dim(` Action: ${actionId}`));
|
|
140
|
+
console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`));
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const waited = await waitForDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token, { intervalMs: pollIntervalSeconds * 1000, timeoutMs: timeoutSeconds * 1000 });
|
|
146
|
+
if (waited.status === 'timeout') {
|
|
147
|
+
throw new Error(`Timed out waiting; resume with: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`);
|
|
148
|
+
}
|
|
149
|
+
if (waited.status === 'error')
|
|
150
|
+
throw new Error(waited.message);
|
|
151
|
+
emit(options.json, {
|
|
152
|
+
ok: waited.action.status === 'completed' && waited.action.result?.exitCode === 0,
|
|
153
|
+
pr: prLabel,
|
|
154
|
+
...waited.action,
|
|
155
|
+
}, () => printUniverseAction(waited.action));
|
|
156
|
+
process.exitCode = waited.action.status === 'failed'
|
|
157
|
+
? 2
|
|
158
|
+
: waited.action.result?.exitCode === 0 ? 0 : 1;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
29
161
|
export async function designVerifyCommand(identifier, options) {
|
|
30
162
|
let pr;
|
|
31
163
|
try {
|
|
@@ -73,6 +205,17 @@ export async function designVerifyCommand(identifier, options) {
|
|
|
73
205
|
process.exitCode = 2;
|
|
74
206
|
return;
|
|
75
207
|
}
|
|
208
|
+
try {
|
|
209
|
+
if (await runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds))
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
emit(options.json, { ok: false, pr: prLabel, error: error.message }, () => {
|
|
214
|
+
console.error(chalk.red(error.message));
|
|
215
|
+
});
|
|
216
|
+
process.exitCode = 2;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
76
219
|
if (!options.json) {
|
|
77
220
|
console.log(chalk.dim(options.run
|
|
78
221
|
? `\nResuming design verification ${options.run}...`
|
package/dist/commands/hooks.d.ts
CHANGED
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
* haystack hooks - Install and manage git hooks for AI agent quality checks
|
|
3
3
|
*
|
|
4
4
|
* Installs:
|
|
5
|
-
* - Entire CLI binary (session tracking, powered by https://entire.dev)
|
|
6
5
|
* - Pre-commit hook (agent detection, truncation checking, LLM rules review)
|
|
7
|
-
* - Git hooks for
|
|
6
|
+
* - Git hooks for Haystack quality checks and workflow guidance
|
|
8
7
|
*/
|
|
9
8
|
interface HooksInstallOptions {
|
|
10
|
-
version?: string;
|
|
11
9
|
force?: boolean;
|
|
12
|
-
skipEntire?: boolean;
|
|
13
10
|
}
|
|
14
11
|
export declare function hooksInstall(options: HooksInstallOptions): Promise<void>;
|
|
15
12
|
export declare function hooksStatus(): Promise<void>;
|
|
16
|
-
export declare function hooksUpdate(): Promise<void>;
|
|
17
13
|
export {};
|