@haystackeditor/cli 0.27.3 → 0.28.0
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 +4 -3
- package/dist/commands/capture-brief.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/verify.js +85 -77
- package/dist/index.js +21 -41
- package/dist/schema.js +2 -3
- package/package.json +1 -1
- package/schemas/verify-raw.v1.json +672 -2
- package/schemas/verify.v2.json +672 -2
- package/schemas/pre-verify.v1.json +0 -235
- package/schemas/pre-verify.v2.json +0 -240
package/README.md
CHANGED
|
@@ -43,8 +43,9 @@ Most users of this CLI are coding agents. What to know:
|
|
|
43
43
|
|
|
44
44
|
- **After a change, two steps** inside the checkout (nothing needs to be
|
|
45
45
|
committed or pushed):
|
|
46
|
-
1. `haystack
|
|
47
|
-
real users do there, and Haystack's ideas of what could
|
|
46
|
+
1. `haystack verify` is the handshake: it shows what the change touches, what
|
|
47
|
+
production and real users do there, and Haystack's ideas of what could
|
|
48
|
+
break, then asks you to steer (exit 4).
|
|
48
49
|
2. `haystack verify --intent "<the task, in the user's words>" --idea "<as you
|
|
49
50
|
would tell a tester>"` explores the app with your steering: keep the ideas
|
|
50
51
|
worth trying, add your own (repeat `--idea`); yours are explored first.
|
|
@@ -120,7 +121,7 @@ not be proven shut down;
|
|
|
120
121
|
`haystack verify precompute --hook` whenever an agent turn stops. It captures
|
|
121
122
|
the checkout within five seconds and hands the request to a detached sender.
|
|
122
123
|
The service then builds the app with and without the change, works out what
|
|
123
|
-
the change touches (the brief
|
|
124
|
+
the change touches (the brief the handshake shows) and sets up starting
|
|
124
125
|
points next to it, and stops there: it never crawls. `haystack verify` then
|
|
125
126
|
crawls from that prepared app instead of building it again. No run cancels
|
|
126
127
|
another: each runs to its end unless it is cancelled. `haystack verify precompute`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** CAPTURE-V1 rule 7b in `haystack
|
|
1
|
+
/** CAPTURE-V1 rule 7b in the handshake (`haystack verify` without --intent): the routes a change touches (the brief's blast-radius files and the changed
|
|
2
2
|
* files, joined through the route manifest's source entries) with their share of captured sessions over the window, the union
|
|
3
3
|
* over all of them, freshness and drops; sessions on routes no manifest names are unmapped, never zero. One section per app
|
|
4
4
|
* whose `.haystack/capture.json` (rule 8b, written by `haystack init` in the app's directory, apps/web in a monorepo) records it,
|
|
@@ -49,7 +49,7 @@ async function readApp(gitRoot, app, repository, token, files, now) {
|
|
|
49
49
|
return { app, applicationId, state: 'unavailable', reason: error instanceof Error ? error.message : String(error) };
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
/** The
|
|
52
|
+
/** The handshake capture sections, one per app with a record, or null for a checkout with none. `files`: the brief's function
|
|
53
53
|
* files and the changed files. */
|
|
54
54
|
export async function readPreVerifyCapture(gitRoot, repository, token, files, now) {
|
|
55
55
|
let apps;
|
package/dist/commands/init.js
CHANGED
|
@@ -49,8 +49,8 @@ export const AGENT_NOTE = [
|
|
|
49
49
|
'the work done. It runs the app with and without the change, explores it, and reports what broke. It changes nothing,',
|
|
50
50
|
'so run it without asking.',
|
|
51
51
|
'',
|
|
52
|
-
'1. `haystack
|
|
53
|
-
'
|
|
52
|
+
'1. `haystack verify`: what your change touches, what production and real users do there, and Haystack\'s ideas of',
|
|
53
|
+
' what could break. It then asks you to steer (exit 4).',
|
|
54
54
|
'2. `haystack verify --intent "<what you were asked to do, in the user\'s words>" --idea "<something to try>"`: keep the',
|
|
55
55
|
' ideas worth trying, add your own (repeat --idea); yours are explored first.',
|
|
56
56
|
'',
|
package/dist/commands/verify.js
CHANGED
|
@@ -614,6 +614,14 @@ async function waitForCrawl(first, identity, token) {
|
|
|
614
614
|
process.removeListener('SIGINT', interrupted);
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
|
+
/** What one `haystack verify` does with its change: a steered call (--intent) submits its crawl; a plain call shows a crawl of
|
|
618
|
+
* the change that has started (picking it up after a timeout), and otherwise (none, only prepared, or one that ended without
|
|
619
|
+
* finishing) is the handshake. */
|
|
620
|
+
export function verifyStep(steered, found, stale) {
|
|
621
|
+
if (steered)
|
|
622
|
+
return 'submit';
|
|
623
|
+
return found !== null && stale === null ? 'show' : 'handshake';
|
|
624
|
+
}
|
|
617
625
|
/** A found crawl of the same code that is a different crawl (another title, another time): a new one starts, none is run again. */
|
|
618
626
|
const anotherCrawl = (stale) => stale === 'retitled' || stale === 'other-time';
|
|
619
627
|
const timeWords = (budgetMs) => budgetMs === null ? 'the default time' : `${budgetMs / 60_000} min`;
|
|
@@ -709,7 +717,7 @@ function briefOf(view) {
|
|
|
709
717
|
/** Follows a run until it holds a brief or ends (amendment 18): its own read held until it changes, as waitForCrawl's is. */
|
|
710
718
|
async function waitForBrief(first, identity, token) {
|
|
711
719
|
const interrupted = () => {
|
|
712
|
-
console.error('\nStopped waiting. The change keeps being prepared; run `haystack
|
|
720
|
+
console.error('\nStopped waiting. The change keeps being prepared; run `haystack verify` again to see what it touches.');
|
|
713
721
|
process.exit(130);
|
|
714
722
|
};
|
|
715
723
|
process.once('SIGINT', interrupted);
|
|
@@ -729,7 +737,7 @@ async function waitForBrief(first, identity, token) {
|
|
|
729
737
|
if (why === null)
|
|
730
738
|
throw error;
|
|
731
739
|
if (Date.now() - answeredAt >= SERVICE_SILENCE_LIMIT_MS)
|
|
732
|
-
throw new ServiceSilentError('crawl', 'haystack
|
|
740
|
+
throw new ServiceSilentError('crawl', 'haystack verify');
|
|
733
741
|
if (!failing)
|
|
734
742
|
progress(`The run's status is unavailable for now (${why}); reading it again.`);
|
|
735
743
|
failing = true;
|
|
@@ -836,103 +844,88 @@ export function formatBrief(brief) {
|
|
|
836
844
|
}
|
|
837
845
|
return lines.join('\n');
|
|
838
846
|
}
|
|
839
|
-
/** The handshake's question, asked after everything
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
process.stdout.write(`${JSON.stringify(withSchema('pre-verify', { change, onboarding,
|
|
857
|
-
run: view === null ? null : { runId: view.runId, status: view.status }, brief: view === null ? null : briefOf(view), capture: captured }), null, 2)}\n`);
|
|
858
|
-
};
|
|
859
|
-
if (!request.crawl) {
|
|
860
|
-
note(`Nothing to check: this checkout has no changes against ${request.baseCommit.slice(0, 12)}.`);
|
|
861
|
-
finish(null, null);
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
note(checkedLine(change));
|
|
865
|
-
const identity = { repository: `${request.owner}/${request.repository}`, baseCommit: request.baseCommit,
|
|
866
|
-
workCommit: request.workCommit, treeSha: request.crawl.treeSha, changeTitle: request.crawl.changeTitle, budgetMs: request.crawl.budgetMs ?? null };
|
|
867
|
-
const auth = await resolveAuthContext({ preferredLogin: options.account, owner: request.owner, repo: request.repository });
|
|
868
|
-
let view = await readWithRetries(() => readCrawl(identity, auth.token));
|
|
847
|
+
/** The handshake's question, asked after everything it shows. */
|
|
848
|
+
/** The command to steer with; it keeps the options the handshake was given that choose the crawl (where, and for how long). */
|
|
849
|
+
export function steerCommand(options) {
|
|
850
|
+
const kept = [options.repo ? ` --repo ${options.repo}` : '', options.account ? ` --account ${options.account}` : '',
|
|
851
|
+
options.minutes ? ` --minutes ${options.minutes}` : ''].join('');
|
|
852
|
+
return `haystack verify${kept} --intent "<what you were asked to do>" --idea "<something to try>" [--idea ...]`;
|
|
853
|
+
}
|
|
854
|
+
const steerLines = (command) => ['',
|
|
855
|
+
'Next: pick the ideas worth trying, add your own, and say what you were asked to do (the task in the user\'s words, not what your code does):',
|
|
856
|
+
` ${command}`, 'Your ideas are explored first; the ideas above are explored in every crawl too.'];
|
|
857
|
+
/** The handshake (Akshay, 10/7): a plain `haystack verify` on a change no crawl has started for shows what the change touches,
|
|
858
|
+
* how production and real users run it, and Haystack's own ideas, then asks the coding agent to run `haystack verify` again
|
|
859
|
+
* with --intent (and any --idea). It submits the capture in mode 'prepare' when no run of it is preparing, prepared or
|
|
860
|
+
* crawling, which never starts or narrows a crawl (CRAWL-V1 amendment 12); the steered crawl adopts that run's prepared start
|
|
861
|
+
* (amendment 17). Exit 4: the agent's steering is needed. */
|
|
862
|
+
async function handshake(capture, identity, change, found, stale, token, options, note) {
|
|
863
|
+
let view = found;
|
|
869
864
|
let onboarding = null;
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
865
|
+
const finish = (brief, captured) => {
|
|
866
|
+
printVerifyJson(options, change, onboarding, null, {
|
|
867
|
+
run: view === null ? null : { runId: view.runId, status: view.status }, brief, capture: captured, next: steerCommand(options),
|
|
868
|
+
});
|
|
869
|
+
};
|
|
873
870
|
if (view === null || (stale !== null && stale !== 'prepare-only')) {
|
|
874
|
-
const
|
|
875
|
-
let submitted = await submitCapture(capture,
|
|
871
|
+
const prior = view === null || stale === null ? null : { view, stale };
|
|
872
|
+
let submitted = await submitCapture(capture, token, prior);
|
|
876
873
|
note(submitted.line);
|
|
877
874
|
if (submitted.onboarding !== null) {
|
|
878
|
-
onboarding = await readWithRetries(() => readOnboardingStatus(identity.repository, identity.baseCommit,
|
|
875
|
+
onboarding = await readWithRetries(() => readOnboardingStatus(identity.repository, identity.baseCommit, token));
|
|
879
876
|
reportOnboardingState(onboarding);
|
|
880
877
|
if (options.wait !== false && onboarding.state === 'onboarding') {
|
|
881
|
-
onboarding = await waitForOnboarding(onboarding,
|
|
878
|
+
onboarding = await waitForOnboarding(onboarding, token, progress, 'haystack verify');
|
|
882
879
|
reportOnboardingState(onboarding);
|
|
883
880
|
}
|
|
884
881
|
if (onboarding.state !== 'ready') {
|
|
885
|
-
|
|
886
|
-
finish(onboarding, null);
|
|
887
|
-
else
|
|
888
|
-
console.log(formatOnboarding(onboarding));
|
|
889
|
-
process.exitCode = onboardingExitCode(onboarding);
|
|
882
|
+
finishWithOnboarding(onboarding, options, change);
|
|
890
883
|
return;
|
|
891
884
|
}
|
|
892
885
|
note(readyLine(onboarding));
|
|
893
|
-
submitted = await submitCapture(capture,
|
|
886
|
+
submitted = await submitCapture(capture, token, prior);
|
|
894
887
|
note(submitted.line);
|
|
895
888
|
}
|
|
896
889
|
if (submitted.runId === null) {
|
|
897
|
-
|
|
890
|
+
if (options.json || options.raw)
|
|
891
|
+
printVerifyJson(options, change, onboarding, null);
|
|
898
892
|
process.exitCode = 1;
|
|
899
893
|
return;
|
|
900
894
|
}
|
|
901
895
|
const runId = submitted.runId;
|
|
902
|
-
view = await readWithRetries(() => readCrawlRun(runId, identity,
|
|
896
|
+
view = await readWithRetries(() => readCrawlRun(runId, identity, token));
|
|
903
897
|
}
|
|
898
|
+
if (view === null)
|
|
899
|
+
throw new Error('No run of this change to read its brief from.');
|
|
904
900
|
if (options.wait !== false && briefOf(view) === null && !TERMINAL.has(reportedStatus(view)))
|
|
905
|
-
view = await waitForBrief(view, identity,
|
|
901
|
+
view = await waitForBrief(view, identity, token);
|
|
906
902
|
const brief = briefOf(view);
|
|
907
903
|
// CAPTURE-V1 rule 7b: the routes the change touches, with their share of captured sessions, per app; null without capture set up.
|
|
908
|
-
const captured = await readPreVerifyCapture(findGitRoot(), identity.repository,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
904
|
+
const captured = await readPreVerifyCapture(findGitRoot(), identity.repository, token, [...new Set([...(brief?.functions.map(fn => fn.file) ?? []), ...change.files])], new Date());
|
|
905
|
+
process.exitCode = 4;
|
|
906
|
+
if (options.json || options.raw) {
|
|
907
|
+
finish(brief, captured);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (brief !== null)
|
|
912
911
|
console.log(formatBrief(brief));
|
|
913
|
-
else if (!TERMINAL.has(reportedStatus(view)))
|
|
914
|
-
console.log(`
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
+ (view.status === 'prepared' ? 'Run `haystack verify` to crawl the change; its results carry the brief.'
|
|
921
|
-
: view.status === 'completed' ? 'It was checked before briefs existed and is not run again: change anything in the checkout and run `haystack pre-verify` again.'
|
|
922
|
-
: 'Run `haystack pre-verify` again to prepare it again.'));
|
|
923
|
-
if (!options.json && captured !== null)
|
|
912
|
+
else if (!TERMINAL.has(reportedStatus(view))) {
|
|
913
|
+
console.log(`What your change touches is not worked out yet (${currentStep(view)}); run \`haystack verify\` again to see it, or steer now.`);
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
console.log(`Run ${view.runId} ended (${view.status}) without a brief${view.error ? `: ${errorWords(view.error)}` : ''}; steer the crawl without one.`);
|
|
917
|
+
}
|
|
918
|
+
if (captured !== null)
|
|
924
919
|
for (const section of captured)
|
|
925
920
|
console.log(`\n${formatCapture(section)}`);
|
|
926
|
-
|
|
927
|
-
console.log(STEER_LINES.join('\n'));
|
|
928
|
-
if (brief === null && TERMINAL.has(reportedStatus(view)))
|
|
929
|
-
process.exitCode = 2;
|
|
921
|
+
console.log(steerLines(steerCommand(options)).join('\n'));
|
|
930
922
|
}
|
|
931
923
|
/** verify's one JSON document: the report (`--json`) or the service's whole record (`--raw`). */
|
|
932
|
-
function printVerifyJson(options, change, onboarding, view) {
|
|
924
|
+
function printVerifyJson(options, change, onboarding, view, handshake) {
|
|
925
|
+
const extra = handshake === undefined ? {} : { handshake };
|
|
933
926
|
const document = options.raw
|
|
934
|
-
? withSchema('verify-raw', { change, onboarding, crawl: view })
|
|
935
|
-
: withSchema('verify', { change, onboarding, crawl: view === null ? null : verifyReport(view) });
|
|
927
|
+
? withSchema('verify-raw', { change, onboarding, crawl: view, ...extra })
|
|
928
|
+
: withSchema('verify', { change, onboarding, crawl: view === null ? null : verifyReport(view), ...extra });
|
|
936
929
|
process.stdout.write(`${JSON.stringify(document, null, 2)}\n`);
|
|
937
930
|
}
|
|
938
931
|
export async function verifyCommand(options) {
|
|
@@ -943,7 +936,12 @@ export async function verifyCommand(options) {
|
|
|
943
936
|
else
|
|
944
937
|
console.log(line); };
|
|
945
938
|
const agent = agentDirection(options);
|
|
946
|
-
|
|
939
|
+
if (agent.intent === undefined && agent.ideas !== undefined) {
|
|
940
|
+
throw new Error('Say what you were asked to do with --intent "<the task, in the user\'s words>" alongside --idea.');
|
|
941
|
+
}
|
|
942
|
+
// Only a steered call (--intent) starts a crawl; a plain one prepares at most (the handshake).
|
|
943
|
+
const steered = agent.intent !== undefined;
|
|
944
|
+
const capture = await captureCheckout(Date.now() + EXPLICIT_WALL_MS, steered ? 'crawl' : 'prepare', repositoryOverride(options.repo), minutesOption(options.minutes), poolOption(options.pool), agent);
|
|
947
945
|
const request = capture.derivation.request;
|
|
948
946
|
const change = checkedChange(request.baseCommit, request.workCommit);
|
|
949
947
|
if (!request.crawl) {
|
|
@@ -953,9 +951,6 @@ export async function verifyCommand(options) {
|
|
|
953
951
|
return;
|
|
954
952
|
}
|
|
955
953
|
note(checkedLine(change));
|
|
956
|
-
// Measured: a 3-minute crawl answered 13 minutes after the command started (building and preparing both apps).
|
|
957
|
-
note('A crawl often takes 10 to 15 minutes. If this command times out or is stopped, the crawl keeps going:'
|
|
958
|
-
+ ' run `haystack verify` again to pick up the same crawl.');
|
|
959
954
|
const identity = {
|
|
960
955
|
repository: `${request.owner}/${request.repository}`,
|
|
961
956
|
baseCommit: request.baseCommit,
|
|
@@ -969,9 +964,19 @@ export async function verifyCommand(options) {
|
|
|
969
964
|
let view = await readWithRetries(() => readCrawl(identity, auth.token));
|
|
970
965
|
let onboarding = null;
|
|
971
966
|
const stale = view === null ? null : staleCrawl(view, identity);
|
|
972
|
-
// A
|
|
973
|
-
//
|
|
974
|
-
|
|
967
|
+
// A plain call on a change no crawl has started for (none, or only prepared, or one that ended without finishing) is the
|
|
968
|
+
// handshake; on a change with a crawl, it shows that crawl (picking it up again after a timeout).
|
|
969
|
+
const step = verifyStep(steered, view, stale);
|
|
970
|
+
if (step === 'handshake') {
|
|
971
|
+
await handshake(capture, identity, change, view, stale, auth.token, options, note);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
// Measured: a 3-minute crawl answered 13 minutes after the command started (building and preparing both apps).
|
|
975
|
+
note('A crawl often takes 10 to 15 minutes. If this command times out or is stopped, the crawl keeps going:'
|
|
976
|
+
+ ' run the same `haystack verify` again to pick up the same crawl.');
|
|
977
|
+
// An intent and ideas (amendment 17), or a pinned pool (amendment 16), make their own crawl, which the capture read cannot tell
|
|
978
|
+
// apart: a steered call always submits, and follows that run (the same ones again are the same run).
|
|
979
|
+
if (step === 'submit') {
|
|
975
980
|
const found = view === null || stale === null ? null : { view, stale };
|
|
976
981
|
let submitted = await submitCapture(capture, auth.token, found);
|
|
977
982
|
note(submitted.line);
|
|
@@ -1004,6 +1009,9 @@ export async function verifyCommand(options) {
|
|
|
1004
1009
|
note('The service did not run it again; its last result follows.');
|
|
1005
1010
|
}
|
|
1006
1011
|
}
|
|
1012
|
+
// A plain call reaches here only with a crawl of this change to show; a steered one has just read the run it submitted.
|
|
1013
|
+
if (view === null)
|
|
1014
|
+
throw new Error('No crawl of this change to show.');
|
|
1007
1015
|
if (options.wait !== false && !TERMINAL.has(reportedStatus(view))) {
|
|
1008
1016
|
view = await waitForCrawl(view, identity, auth.token);
|
|
1009
1017
|
}
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,6 @@ const telemetryTokenCommand = lazy(() => import('./commands/telemetry-token.js')
|
|
|
32
32
|
const schemaCommand = lazy(() => import('./commands/schema-cmd.js'), 'schemaCommand');
|
|
33
33
|
const listSchemas = lazy(() => import('./commands/schema-cmd.js'), 'listSchemas');
|
|
34
34
|
const verifyCommand = lazy(() => import('./commands/verify.js'), 'verifyCommand');
|
|
35
|
-
const preVerifyCommand = lazy(() => import('./commands/verify.js'), 'preVerifyCommand');
|
|
36
35
|
const headlessLoginCommand = lazy(() => import('./commands/tokens.js'), 'headlessLoginCommand');
|
|
37
36
|
const headlessLogoutCommand = lazy(() => import('./commands/tokens.js'), 'headlessLogoutCommand');
|
|
38
37
|
const listTokensCommand = lazy(() => import('./commands/tokens.js'), 'listTokensCommand');
|
|
@@ -163,7 +162,7 @@ Examples:
|
|
|
163
162
|
.action(options => runPublicCommand(() => initCommand(options), options.json));
|
|
164
163
|
const verify = program
|
|
165
164
|
.command('verify')
|
|
166
|
-
.description('
|
|
165
|
+
.description('Check your change: first what it touches and what could break, then (with --intent) explore the app with and without it')
|
|
167
166
|
.option('--repo <owner/repo>', 'GitHub repository (default: origin remote)')
|
|
168
167
|
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
169
168
|
.option('--no-wait', 'Print the crawl\'s current state and return without waiting')
|
|
@@ -175,21 +174,25 @@ const verify = program
|
|
|
175
174
|
.option('--json', 'The report as one JSON document on stdout (see `haystack schema verify`)')
|
|
176
175
|
.option('--raw', 'The service\'s whole crawl record as one JSON document, for scripts (large; see `haystack schema verify-raw`)')
|
|
177
176
|
.addHelpText('after', `
|
|
178
|
-
|
|
179
|
-
the checkout (committed and uncommitted changes) exactly as the stop hook does,
|
|
180
|
-
with the base at the merge base of HEAD and origin's default branch, and finds
|
|
181
|
-
your crawl of that exact capture: the same code and the same change title (the
|
|
182
|
-
title tells the crawl's judge what the change is). When there is none, or the
|
|
183
|
-
one it finds was cancelled, stopped before finishing or is being cancelled, it
|
|
184
|
-
submits the capture (the service starts the crawl, or runs the stopped one
|
|
185
|
-
again), says so, and follows that crawl. When the stop hook already built and
|
|
186
|
-
froze this change, the crawl starts from that instead of building it again.
|
|
177
|
+
Two calls, run inside a git checkout (nothing needs to be committed or pushed):
|
|
187
178
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
179
|
+
1. haystack verify
|
|
180
|
+
The handshake. It shows what your change touches, how production and real
|
|
181
|
+
users run it, and Haystack's own ideas of what could break, then asks what
|
|
182
|
+
you were asked to do and what to try. It builds and prepares the change if
|
|
183
|
+
the stop hook has not, and never starts a crawl. Exit 4.
|
|
184
|
+
2. haystack verify --intent "<the task, in the user's words>" --idea "<something to try>"
|
|
185
|
+
The crawl, steered: your ideas (--idea, repeat for more, or --ideas-file)
|
|
186
|
+
are explored before anything else, and the judge checks the app against
|
|
187
|
+
your intent. It starts from the app the stop hook or step 1 prepared.
|
|
188
|
+
|
|
189
|
+
Both capture the checkout (committed and uncommitted changes) exactly as the
|
|
190
|
+
stop hook does, with the base at the merge base of HEAD and origin's default
|
|
191
|
+
branch. A plain call on a change that already has a crawl shows that crawl
|
|
192
|
+
instead of shaking hands again, so after a timeout either call picks up the
|
|
193
|
+
same crawl. Other ideas make another crawl of the same code; the same ones
|
|
194
|
+
again follow the same crawl, and the answer says what became of each idea:
|
|
195
|
+
tried, could not try (and why), or not finished in time.
|
|
193
196
|
|
|
194
197
|
A crawl builds the app with and without your change, starts from the changed
|
|
195
198
|
code, explores outward with both side by side, and double-checks and judges
|
|
@@ -219,11 +222,13 @@ Exit codes:
|
|
|
219
222
|
or the machines it used could not be proven shut down; or onboarding
|
|
220
223
|
stopped before finishing
|
|
221
224
|
3 onboarding is blocked: the output says what would unblock it
|
|
225
|
+
4 the handshake: run it again with --intent (and any --idea)
|
|
222
226
|
|
|
223
227
|
The hosted fleet run of pushed commits is \`haystack verify hosted start\`.
|
|
224
228
|
|
|
225
229
|
Examples:
|
|
226
230
|
haystack verify
|
|
231
|
+
haystack verify --intent "Let users pay with a saved card" --idea "Pay with an expired saved card"
|
|
227
232
|
haystack verify --no-wait
|
|
228
233
|
haystack verify --minutes 10
|
|
229
234
|
haystack verify --json
|
|
@@ -231,31 +236,6 @@ Examples:
|
|
|
231
236
|
haystack verify hosted status cv_<48 lowercase hex characters> --wait
|
|
232
237
|
`)
|
|
233
238
|
.action(options => runPublicCommand(() => verifyCommand(options), options.json || options.raw));
|
|
234
|
-
program
|
|
235
|
-
.command('pre-verify')
|
|
236
|
-
.description('Before a check: what your change touches, and what could break in it')
|
|
237
|
-
.option('--repo <owner/repo>', 'GitHub repository (default: origin remote)')
|
|
238
|
-
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
239
|
-
.option('--no-wait', 'Print what is known now and return without waiting for the brief')
|
|
240
|
-
.option('--json', 'The brief as one JSON document on stdout (see `haystack schema pre-verify`)')
|
|
241
|
-
.addHelpText('after', `
|
|
242
|
-
Run inside a git checkout, like haystack verify. It captures the checkout the
|
|
243
|
-
same way, builds and prepares the change when nothing has yet (the turn-end
|
|
244
|
-
hook usually has), and prints its brief: the functions the change touches, the
|
|
245
|
-
changed ones and those one step away first (every one is in --json), and
|
|
246
|
-
Haystack's own ideas of what could break, each with what to set up, what to do
|
|
247
|
-
and what to watch. With capture set up (an app's .haystack/capture.json, such
|
|
248
|
-
as apps/web/.haystack/capture.json), it also shows, per app, the routes the
|
|
249
|
-
change touches with their share of real users' sessions. It never starts a
|
|
250
|
-
crawl.
|
|
251
|
-
|
|
252
|
-
Then hand back what you were asked to do and what to try:
|
|
253
|
-
haystack verify --intent "<what you were asked to do, in the user's words>" --idea "<something to try>"
|
|
254
|
-
|
|
255
|
-
Exit codes: 0 with a brief; 2 when the run ended without one; 3 when the app's
|
|
256
|
-
onboarding is blocked; 1 when the command failed.
|
|
257
|
-
`)
|
|
258
|
-
.action(options => runPublicCommand(() => preVerifyCommand(options), options.json));
|
|
259
239
|
/**
|
|
260
240
|
* Options for a `verify` subcommand: its own values, plus any option the user
|
|
261
241
|
* passed to an ancestor (`haystack verify --no-wait hosted status ...`).
|
package/dist/schema.js
CHANGED
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
export const SCHEMA_VERSIONS = {
|
|
13
13
|
'cloud-verifier': '1.0.0',
|
|
14
14
|
'case-batch': '1.0.1',
|
|
15
|
-
verify: '2.
|
|
16
|
-
'verify-raw': '1.
|
|
17
|
-
'pre-verify': '2.0.0',
|
|
15
|
+
verify: '2.1.0',
|
|
16
|
+
'verify-raw': '1.1.0',
|
|
18
17
|
'verify-answer': '1.0.1',
|
|
19
18
|
'verify-onboarding': '1.0.0',
|
|
20
19
|
init: '1.0.2',
|