@quireco/cli 0.0.11 → 0.0.13
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/dist/{cli-CIBEsC75.mjs → cli-DIvpEXlX.mjs} +901 -85
- package/dist/index.mjs +1 -1
- package/dist/quire.mjs +6 -2
- package/package.json +1 -1
|
@@ -8,10 +8,10 @@ import { exec, execFile, execFileSync, spawn } from "node:child_process";
|
|
|
8
8
|
import pc from "picocolors";
|
|
9
9
|
import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
10
10
|
import { convertMessages } from "@earendil-works/pi-ai/openai-completions";
|
|
11
|
-
import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
11
|
+
import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
12
12
|
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
14
|
-
import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
14
|
+
import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { customAlphabet } from "nanoid";
|
|
17
17
|
import { Type } from "@sinclair/typebox";
|
|
@@ -671,8 +671,17 @@ async function createInvestigationModelSessionDependencies({ runId, store = auth
|
|
|
671
671
|
fetchFn
|
|
672
672
|
});
|
|
673
673
|
}
|
|
674
|
-
async function
|
|
675
|
-
const
|
|
674
|
+
async function detectLocalInvestigationModelSource(options = {}) {
|
|
675
|
+
const localModel = await createLocalModelSessionDependencies(options.localAuthStorage, options.env);
|
|
676
|
+
if (localModel === null || localModel.source === "quire_credits") return null;
|
|
677
|
+
return {
|
|
678
|
+
source: localModel.source,
|
|
679
|
+
provider: localModel.model.provider,
|
|
680
|
+
modelId: localModel.model.id
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
async function createLocalModelSessionDependencies(localAuthStorage, env = process.env) {
|
|
684
|
+
const localRegistries = (localAuthStorage === void 0 ? [AuthStorage.create(readQuireModelAuthPath(env)), AuthStorage.create()] : [localAuthStorage]).map((authStorage) => ({
|
|
676
685
|
authStorage,
|
|
677
686
|
modelRegistry: ModelRegistry.create(authStorage)
|
|
678
687
|
}));
|
|
@@ -690,8 +699,8 @@ async function createLocalModelSessionDependencies(localAuthStorage) {
|
|
|
690
699
|
}
|
|
691
700
|
return null;
|
|
692
701
|
}
|
|
693
|
-
function readQuireModelAuthPath() {
|
|
694
|
-
return defaultModelAuthPath();
|
|
702
|
+
function readQuireModelAuthPath(env = process.env) {
|
|
703
|
+
return defaultModelAuthPath(env);
|
|
695
704
|
}
|
|
696
705
|
function quireModelDefinition() {
|
|
697
706
|
return {
|
|
@@ -1375,21 +1384,28 @@ const FINDING_RELATIONSHIPS_TO_REQUEST = [
|
|
|
1375
1384
|
];
|
|
1376
1385
|
function createRunDir(repoPath, options = {}) {
|
|
1377
1386
|
const workspaceKey = workspaceKeyForRepoPath(resolve(repoPath));
|
|
1378
|
-
const runsRoot
|
|
1379
|
-
|
|
1387
|
+
const { runsRoot, runId, root, temporaryRunsRoot } = allocateRunRoot({
|
|
1388
|
+
runsRoot: resolve(options.runsRoot ?? defaultRunsRoot()),
|
|
1389
|
+
workspaceKey,
|
|
1380
1390
|
runId: options.runId,
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const paths = runDirPaths(root);
|
|
1384
|
-
mkdirSync(paths.evidence, {
|
|
1385
|
-
recursive: true,
|
|
1386
|
-
mode: 448
|
|
1391
|
+
runIdFactory: options.createRunId ?? createRunId,
|
|
1392
|
+
allowTemporaryFallback: options.runsRoot === void 0 && (process.env.QUIRE_RUNS_DIR?.trim().length ?? 0) === 0
|
|
1387
1393
|
});
|
|
1394
|
+
const paths = runDirPaths(root);
|
|
1395
|
+
try {
|
|
1396
|
+
mkdirSync(paths.evidence, {
|
|
1397
|
+
recursive: true,
|
|
1398
|
+
mode: 448
|
|
1399
|
+
});
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
throw writableRunsRootError(error, runsRoot);
|
|
1402
|
+
}
|
|
1388
1403
|
return {
|
|
1389
1404
|
runId,
|
|
1390
1405
|
workspaceKey,
|
|
1391
1406
|
runsRoot,
|
|
1392
1407
|
root,
|
|
1408
|
+
...temporaryRunsRoot ? { temporaryRunsRoot: true } : {},
|
|
1393
1409
|
paths
|
|
1394
1410
|
};
|
|
1395
1411
|
}
|
|
@@ -1416,6 +1432,9 @@ function runPlanPath(runPath) {
|
|
|
1416
1432
|
function runHandoffPath(runPath) {
|
|
1417
1433
|
return join(resolve(runPath), "handoff.md");
|
|
1418
1434
|
}
|
|
1435
|
+
function runPendingHandoffPath(runPath) {
|
|
1436
|
+
return join(resolve(runPath), ".pending-handoff.json");
|
|
1437
|
+
}
|
|
1419
1438
|
function runDirPaths(root) {
|
|
1420
1439
|
const evidence = join(root, "evidence");
|
|
1421
1440
|
return {
|
|
@@ -1432,6 +1451,51 @@ function runDirPaths(root) {
|
|
|
1432
1451
|
harnessSessions: join(root, ".harness-sessions")
|
|
1433
1452
|
};
|
|
1434
1453
|
}
|
|
1454
|
+
function allocateRunRoot(input) {
|
|
1455
|
+
try {
|
|
1456
|
+
return {
|
|
1457
|
+
...createRunRoot(input.runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
|
|
1458
|
+
runsRoot: input.runsRoot,
|
|
1459
|
+
temporaryRunsRoot: false
|
|
1460
|
+
};
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
if (!isWriteAccessError$2(error)) throw error;
|
|
1463
|
+
if (!input.allowTemporaryFallback) throw writableRunsRootError(error, input.runsRoot);
|
|
1464
|
+
const runsRoot = mkdtempSync(join(tmpdir(), "quire-runs-"));
|
|
1465
|
+
return {
|
|
1466
|
+
...createRunRoot(runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
|
|
1467
|
+
runsRoot,
|
|
1468
|
+
temporaryRunsRoot: true
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
function createRunRoot(runsRoot, workspaceKey, runId, runIdFactory) {
|
|
1473
|
+
return runId === void 0 ? createUniqueRunRoot(runsRoot, workspaceKey, runIdFactory) : createNamedRunRoot(runsRoot, workspaceKey, runId);
|
|
1474
|
+
}
|
|
1475
|
+
function createNamedRunRoot(runsRoot, workspaceKey, runId) {
|
|
1476
|
+
const root = join(runsRoot, workspaceKey, runId);
|
|
1477
|
+
mkdirSync(join(root, "evidence"), {
|
|
1478
|
+
recursive: true,
|
|
1479
|
+
mode: 448
|
|
1480
|
+
});
|
|
1481
|
+
return {
|
|
1482
|
+
runId,
|
|
1483
|
+
root
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
function writableRunsRootError(error, runsRoot) {
|
|
1487
|
+
if (!isWriteAccessError$2(error)) return error;
|
|
1488
|
+
return new CliError$1([
|
|
1489
|
+
`Quire could not write run data at ${runsRoot}: ${error.message}`,
|
|
1490
|
+
"In sandboxed coding-agent environments, retry with a temporary run directory:",
|
|
1491
|
+
" quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
|
|
1492
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
|
|
1493
|
+
"Use the same QUIRE_RUNS_DIR value for follow-up `quire wait`, `quire status`, `quire watch`, or `quire cancel` commands."
|
|
1494
|
+
].join("\n"), ExitCode.InvalidInput);
|
|
1495
|
+
}
|
|
1496
|
+
function isWriteAccessError$2(error) {
|
|
1497
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
1498
|
+
}
|
|
1435
1499
|
function defaultRunsRoot(env = process.env) {
|
|
1436
1500
|
return defaultRunsRoot$1(env);
|
|
1437
1501
|
}
|
|
@@ -1483,6 +1547,20 @@ function writeRunFinalHandoff(runDir, final) {
|
|
|
1483
1547
|
finalResult: final.result
|
|
1484
1548
|
});
|
|
1485
1549
|
}
|
|
1550
|
+
function writeRunPendingHandoff(runDir, handoff) {
|
|
1551
|
+
const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1552
|
+
writeFileAtomically(runPendingHandoffPath(runDir.root), `${stableJson({
|
|
1553
|
+
version: 1,
|
|
1554
|
+
writtenAt,
|
|
1555
|
+
handoff
|
|
1556
|
+
})}\n`, 384);
|
|
1557
|
+
writeFileAtomically(runDir.paths.handoff, `${handoff.handoffMarkdown.trimEnd()}\n`, 384);
|
|
1558
|
+
}
|
|
1559
|
+
function clearRunPendingHandoff(runDir) {
|
|
1560
|
+
try {
|
|
1561
|
+
unlinkSync(runPendingHandoffPath(runDir.root));
|
|
1562
|
+
} catch {}
|
|
1563
|
+
}
|
|
1486
1564
|
function patchRunStatusJson(runDir, patch) {
|
|
1487
1565
|
if (!existsSync(runDir.paths.status)) return;
|
|
1488
1566
|
let existing;
|
|
@@ -1752,18 +1830,32 @@ function readFreshRunStatus(runPath) {
|
|
|
1752
1830
|
if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
|
|
1753
1831
|
return updateRunStatus(runPath, {
|
|
1754
1832
|
status: "stale",
|
|
1755
|
-
error: "Worker process exited before marking the run as running."
|
|
1833
|
+
error: staleWorkerError(runPath, "Worker process exited before marking the run as running.")
|
|
1756
1834
|
});
|
|
1757
1835
|
}
|
|
1758
1836
|
if ((status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid)) {
|
|
1759
1837
|
if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
|
|
1760
1838
|
return updateRunStatus(runPath, {
|
|
1761
1839
|
status: status.status === "canceling" ? "canceled" : "stale",
|
|
1762
|
-
error: status.status === "canceling" ? void 0 : "Worker process is no longer running."
|
|
1840
|
+
error: status.status === "canceling" ? void 0 : staleWorkerError(runPath, "Worker process is no longer running.")
|
|
1763
1841
|
});
|
|
1764
1842
|
}
|
|
1765
1843
|
return status;
|
|
1766
1844
|
}
|
|
1845
|
+
function staleWorkerError(runPath, message) {
|
|
1846
|
+
const workerLogPath = join(runPath, "worker.log");
|
|
1847
|
+
if (!existsSync(workerLogPath)) return message;
|
|
1848
|
+
const tail = readWorkerLogTail$1(workerLogPath);
|
|
1849
|
+
if (tail.length === 0) return `${message}\n\nWorker log: ${workerLogPath} (empty)`;
|
|
1850
|
+
return `${message}\n\nWorker log: ${workerLogPath}\n\nLast worker output:\n${tail}`;
|
|
1851
|
+
}
|
|
1852
|
+
function readWorkerLogTail$1(workerLogPath) {
|
|
1853
|
+
try {
|
|
1854
|
+
return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
|
|
1855
|
+
} catch {
|
|
1856
|
+
return "";
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1767
1859
|
function readRequiredRunStatus(runPath) {
|
|
1768
1860
|
const status = readRunStatus(runPath);
|
|
1769
1861
|
if (status === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
|
|
@@ -1799,10 +1891,27 @@ function writeStatus(filePath, status) {
|
|
|
1799
1891
|
mode: 448
|
|
1800
1892
|
});
|
|
1801
1893
|
const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.status.tmp`);
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1894
|
+
try {
|
|
1895
|
+
writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
|
|
1896
|
+
fsyncFile(tempPath);
|
|
1897
|
+
renameSync(tempPath, filePath);
|
|
1898
|
+
fsyncDirectory(dirname(filePath));
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
throw writableRunStatusError(error, status.runPath);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function writableRunStatusError(error, runPath) {
|
|
1904
|
+
if (!isWriteAccessError$1(error)) return error;
|
|
1905
|
+
return new CliError$1([
|
|
1906
|
+
`Quire could not update run status at ${runPath}: ${error.message}`,
|
|
1907
|
+
"In sandboxed coding-agent environments, put Quire run data in a temporary directory and reuse it for every lifecycle command:",
|
|
1908
|
+
" quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
|
|
1909
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
|
|
1910
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire wait <run-id> --json"
|
|
1911
|
+
].join("\n"), ExitCode.InvalidInput);
|
|
1912
|
+
}
|
|
1913
|
+
function isWriteAccessError$1(error) {
|
|
1914
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
1806
1915
|
}
|
|
1807
1916
|
function fsyncFile(filePath) {
|
|
1808
1917
|
const file = openSync(filePath, "r");
|
|
@@ -2801,6 +2910,7 @@ function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadat
|
|
|
2801
2910
|
result: materializedResult
|
|
2802
2911
|
});
|
|
2803
2912
|
writeHandoffArtifact(runDir, materializedHandoff);
|
|
2913
|
+
clearRunPendingHandoff(runDir);
|
|
2804
2914
|
return {
|
|
2805
2915
|
result: materializedResult,
|
|
2806
2916
|
metadata: nextMetadata
|
|
@@ -45315,6 +45425,23 @@ const writeHandoffToolDefinition = {
|
|
|
45315
45425
|
};
|
|
45316
45426
|
}
|
|
45317
45427
|
};
|
|
45428
|
+
function makeWriteHandoffTool(options = {}) {
|
|
45429
|
+
return defineTool({
|
|
45430
|
+
...writeHandoffToolDefinition,
|
|
45431
|
+
async execute(_toolCallId, params, _signal) {
|
|
45432
|
+
const handoff = normalizeWriteHandoffInput(params);
|
|
45433
|
+
await options.onHandoff?.(handoff);
|
|
45434
|
+
return {
|
|
45435
|
+
content: [{
|
|
45436
|
+
type: "text",
|
|
45437
|
+
text: JSON.stringify(handoff, null, 2)
|
|
45438
|
+
}],
|
|
45439
|
+
details: handoff,
|
|
45440
|
+
terminate: true
|
|
45441
|
+
};
|
|
45442
|
+
}
|
|
45443
|
+
});
|
|
45444
|
+
}
|
|
45318
45445
|
function normalizeWriteHandoffInput(params) {
|
|
45319
45446
|
const { status, ...rest } = params;
|
|
45320
45447
|
return {
|
|
@@ -45327,7 +45454,7 @@ function normalizeHandoffStatus(status) {
|
|
|
45327
45454
|
if (status === "no_issue_found" || status === "not_a_bug" || status === "verified_no_bug_found" || status === "verified_no_issue_found") return "not_reproduced";
|
|
45328
45455
|
return status;
|
|
45329
45456
|
}
|
|
45330
|
-
|
|
45457
|
+
makeWriteHandoffTool();
|
|
45331
45458
|
//#endregion
|
|
45332
45459
|
//#region src/utils/schema.ts
|
|
45333
45460
|
function stringEnumSchema(values) {
|
|
@@ -46153,7 +46280,11 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46153
46280
|
followup,
|
|
46154
46281
|
skillPack,
|
|
46155
46282
|
mcpGateway,
|
|
46156
|
-
intent
|
|
46283
|
+
intent,
|
|
46284
|
+
onHandoff: (handoff) => {
|
|
46285
|
+
emittedHandoff ??= handoff;
|
|
46286
|
+
writeRunPendingHandoff(runDir, handoff);
|
|
46287
|
+
}
|
|
46157
46288
|
});
|
|
46158
46289
|
const { session, logAgentProgress, registeredTargetTools } = runtime;
|
|
46159
46290
|
onResolvedModel?.({
|
|
@@ -46161,18 +46292,31 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46161
46292
|
provider: runtime.model.provider,
|
|
46162
46293
|
modelId: runtime.model.id
|
|
46163
46294
|
});
|
|
46295
|
+
let rejectCurrentPromptOnModelError;
|
|
46164
46296
|
const unsubscribe = session.subscribe((event) => {
|
|
46165
46297
|
touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
|
|
46166
46298
|
logAgentProgress(event);
|
|
46299
|
+
const sessionError = modelErrorFromSessionEvent(event);
|
|
46300
|
+
if (sessionError !== void 0) rejectCurrentPromptOnModelError?.(sessionError);
|
|
46167
46301
|
emittedHandoff ??= extractWriteHandoff(event);
|
|
46168
46302
|
rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
|
|
46169
46303
|
const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
|
|
46170
46304
|
if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
|
|
46171
46305
|
});
|
|
46172
46306
|
try {
|
|
46173
|
-
|
|
46174
|
-
|
|
46307
|
+
try {
|
|
46308
|
+
await promptOrModelError(session.prompt(runBrief, { expandPromptTemplates: false }), (reject) => {
|
|
46309
|
+
rejectCurrentPromptOnModelError = reject;
|
|
46310
|
+
});
|
|
46311
|
+
if (emittedHandoff === void 0) await promptOrModelError(session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false }), (reject) => {
|
|
46312
|
+
rejectCurrentPromptOnModelError = reject;
|
|
46313
|
+
});
|
|
46314
|
+
} catch (error) {
|
|
46315
|
+
if (emittedHandoff === void 0) throw error;
|
|
46316
|
+
progress?.("Recovered terminal write_handoff after the agent session stopped.");
|
|
46317
|
+
}
|
|
46175
46318
|
} finally {
|
|
46319
|
+
rejectCurrentPromptOnModelError = void 0;
|
|
46176
46320
|
runtime.writeSessionState();
|
|
46177
46321
|
unsubscribe();
|
|
46178
46322
|
runtime.dispose();
|
|
@@ -46182,7 +46326,23 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46182
46326
|
runDir
|
|
46183
46327
|
});
|
|
46184
46328
|
}
|
|
46185
|
-
async function
|
|
46329
|
+
async function promptOrModelError(prompt, setRejectCurrentPromptOnModelError) {
|
|
46330
|
+
try {
|
|
46331
|
+
return await Promise.race([prompt, new Promise((_resolve, reject) => {
|
|
46332
|
+
setRejectCurrentPromptOnModelError(reject);
|
|
46333
|
+
})]);
|
|
46334
|
+
} finally {
|
|
46335
|
+
setRejectCurrentPromptOnModelError(void 0);
|
|
46336
|
+
}
|
|
46337
|
+
}
|
|
46338
|
+
function modelErrorFromSessionEvent(event) {
|
|
46339
|
+
if (!isRecord$14(event) || event.type !== "message" || !isRecord$14(event.message)) return;
|
|
46340
|
+
const message = event.message;
|
|
46341
|
+
if (message.role !== "assistant" || message.stopReason !== "error") return;
|
|
46342
|
+
const errorMessage = typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0 ? message.errorMessage.trim() : "model request failed";
|
|
46343
|
+
return /* @__PURE__ */ new Error(`Quire model request failed: ${errorMessage}`);
|
|
46344
|
+
}
|
|
46345
|
+
async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway, onHandoff }) {
|
|
46186
46346
|
const logAgentProgress = createAgentProgressLogger({
|
|
46187
46347
|
runDir,
|
|
46188
46348
|
write: progress
|
|
@@ -46212,6 +46372,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
|
|
|
46212
46372
|
gateway: mcpGateway,
|
|
46213
46373
|
runId: runDir.runId
|
|
46214
46374
|
});
|
|
46375
|
+
const writeHandoffTool = makeWriteHandoffTool({ onHandoff });
|
|
46215
46376
|
const registeredTargetTools = [...browserTool === void 0 ? [] : [{
|
|
46216
46377
|
toolName: "agent-browser",
|
|
46217
46378
|
target: "web"
|
|
@@ -48183,6 +48344,8 @@ function runPath(runDir) {
|
|
|
48183
48344
|
}
|
|
48184
48345
|
//#endregion
|
|
48185
48346
|
//#region src/pipeline/background-investigation.ts
|
|
48347
|
+
const WORKER_BOOTSTRAP_TIMEOUT_MS = 2e3;
|
|
48348
|
+
const WORKER_LOG_FILE = "worker.log";
|
|
48186
48349
|
async function startInvestigationRun(options) {
|
|
48187
48350
|
assertHasInvestigationInput(options.query, options.inputText);
|
|
48188
48351
|
const repoPath = resolve(options.cwd ?? process.cwd());
|
|
@@ -48238,6 +48401,8 @@ async function startInvestigationRun(options) {
|
|
|
48238
48401
|
runId: runDir.runId,
|
|
48239
48402
|
workspaceKey: runDir.workspaceKey,
|
|
48240
48403
|
runPath: runDir.root,
|
|
48404
|
+
runsRoot: runDir.runsRoot,
|
|
48405
|
+
temporaryRunsRoot: runDir.temporaryRunsRoot === true,
|
|
48241
48406
|
pid: worker.pid
|
|
48242
48407
|
});
|
|
48243
48408
|
}
|
|
@@ -48315,6 +48480,8 @@ function workerStartedBrowserSessionIds(runDir) {
|
|
|
48315
48480
|
function spawnInvestigationWorker({ runPath }) {
|
|
48316
48481
|
const entryPoint = process.argv[1];
|
|
48317
48482
|
if (entryPoint === void 0 || entryPoint.length === 0) throw new CliError$1("Unable to locate Quire CLI entrypoint for background worker.", ExitCode.InternalError);
|
|
48483
|
+
const workerLogPath = `${runPath}/${WORKER_LOG_FILE}`;
|
|
48484
|
+
const workerLogFd = openSync(workerLogPath, "a", 384);
|
|
48318
48485
|
const child = spawn(process.execPath, [
|
|
48319
48486
|
...process.execArgv,
|
|
48320
48487
|
entryPoint,
|
|
@@ -48327,23 +48494,89 @@ function spawnInvestigationWorker({ runPath }) {
|
|
|
48327
48494
|
env: process.env,
|
|
48328
48495
|
stdio: [
|
|
48329
48496
|
"ignore",
|
|
48330
|
-
|
|
48331
|
-
|
|
48497
|
+
workerLogFd,
|
|
48498
|
+
workerLogFd
|
|
48332
48499
|
]
|
|
48333
48500
|
});
|
|
48334
|
-
|
|
48335
|
-
return
|
|
48501
|
+
closeSync(workerLogFd);
|
|
48502
|
+
return waitForWorkerBootstrap({
|
|
48503
|
+
child,
|
|
48504
|
+
runPath,
|
|
48505
|
+
workerLogPath
|
|
48506
|
+
}).finally(() => {
|
|
48507
|
+
child.unref();
|
|
48508
|
+
});
|
|
48509
|
+
}
|
|
48510
|
+
async function waitForWorkerBootstrap({ child, runPath, workerLogPath }) {
|
|
48511
|
+
const exit = new Promise((resolveExit) => {
|
|
48512
|
+
child.once("exit", (code, signal) => resolveExit({
|
|
48513
|
+
code,
|
|
48514
|
+
signal
|
|
48515
|
+
}));
|
|
48516
|
+
});
|
|
48517
|
+
const timeout = new Promise((resolveTimeout) => {
|
|
48518
|
+
setTimeout(() => resolveTimeout("timeout"), WORKER_BOOTSTRAP_TIMEOUT_MS).unref();
|
|
48519
|
+
});
|
|
48520
|
+
const started = waitForRunToLeaveQueued(runPath);
|
|
48521
|
+
const result = await Promise.race([
|
|
48522
|
+
exit,
|
|
48523
|
+
timeout,
|
|
48524
|
+
started.promise
|
|
48525
|
+
]);
|
|
48526
|
+
started.stop();
|
|
48527
|
+
if (result === "started") return { pid: child.pid };
|
|
48528
|
+
const status = readRunStatus(runPath);
|
|
48529
|
+
if (result !== "timeout" && status?.status === "queued") throw new CliError$1(formatEarlyWorkerExitError({
|
|
48530
|
+
workerLogPath,
|
|
48531
|
+
code: result.code,
|
|
48532
|
+
signal: result.signal
|
|
48533
|
+
}), ExitCode.InternalError);
|
|
48534
|
+
return { pid: child.pid };
|
|
48535
|
+
}
|
|
48536
|
+
function waitForRunToLeaveQueued(runPath) {
|
|
48537
|
+
let stopped = false;
|
|
48538
|
+
return {
|
|
48539
|
+
promise: new Promise((resolveStarted) => {
|
|
48540
|
+
const checkStatus = () => {
|
|
48541
|
+
if (stopped) return;
|
|
48542
|
+
const status = readRunStatus(runPath);
|
|
48543
|
+
if (status !== void 0 && status.status !== "queued") {
|
|
48544
|
+
resolveStarted("started");
|
|
48545
|
+
return;
|
|
48546
|
+
}
|
|
48547
|
+
setTimeout(checkStatus, 25).unref();
|
|
48548
|
+
};
|
|
48549
|
+
checkStatus();
|
|
48550
|
+
}),
|
|
48551
|
+
stop: () => {
|
|
48552
|
+
stopped = true;
|
|
48553
|
+
}
|
|
48554
|
+
};
|
|
48555
|
+
}
|
|
48556
|
+
function formatEarlyWorkerExitError({ workerLogPath, code, signal }) {
|
|
48557
|
+
const exit = signal === null ? `exit code ${code ?? "unknown"}` : `signal ${signal}`;
|
|
48558
|
+
const tail = readWorkerLogTail(workerLogPath);
|
|
48559
|
+
return `Worker process exited before marking the run as running (${exit}).\n\n${tail.length === 0 ? `Worker log: ${workerLogPath} (empty)` : `Worker log: ${workerLogPath}\n\nLast worker output:\n${tail}`}`;
|
|
48560
|
+
}
|
|
48561
|
+
function readWorkerLogTail(workerLogPath) {
|
|
48562
|
+
try {
|
|
48563
|
+
return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
|
|
48564
|
+
} catch {
|
|
48565
|
+
return "";
|
|
48566
|
+
}
|
|
48336
48567
|
}
|
|
48337
48568
|
function runHandle(input) {
|
|
48569
|
+
const commandPrefix = input.temporaryRunsRoot ? `QUIRE_RUNS_DIR=${shellQuote(input.runsRoot)} ` : "";
|
|
48338
48570
|
return {
|
|
48339
48571
|
status: "queued",
|
|
48340
48572
|
runId: input.runId,
|
|
48341
48573
|
workspaceKey: input.workspaceKey,
|
|
48342
48574
|
runPath: toJsonPath(input.runPath),
|
|
48343
48575
|
...input.pid === void 0 ? {} : { pid: input.pid },
|
|
48344
|
-
statusCommand:
|
|
48345
|
-
|
|
48346
|
-
|
|
48576
|
+
statusCommand: `${commandPrefix}quire status ${shellQuote(input.runId)} --json`,
|
|
48577
|
+
waitCommand: `${commandPrefix}quire wait ${shellQuote(input.runId)} --json`,
|
|
48578
|
+
watchCommand: `${commandPrefix}quire watch ${shellQuote(input.runId)}`,
|
|
48579
|
+
cancelCommand: `${commandPrefix}quire cancel ${shellQuote(input.runId)}`
|
|
48347
48580
|
};
|
|
48348
48581
|
}
|
|
48349
48582
|
function readWorkerRequest(runPath) {
|
|
@@ -48528,6 +48761,38 @@ const watchCommand = defineCommand({
|
|
|
48528
48761
|
await watchRun(readRequiredString(args.run, "run"));
|
|
48529
48762
|
}
|
|
48530
48763
|
});
|
|
48764
|
+
const waitCommand = defineCommand({
|
|
48765
|
+
meta: {
|
|
48766
|
+
name: "wait",
|
|
48767
|
+
description: "Wait for a Quire investigation run to reach a terminal status."
|
|
48768
|
+
},
|
|
48769
|
+
args: {
|
|
48770
|
+
run: {
|
|
48771
|
+
type: "positional",
|
|
48772
|
+
description: "Run id or run directory.",
|
|
48773
|
+
required: true
|
|
48774
|
+
},
|
|
48775
|
+
json: {
|
|
48776
|
+
type: "boolean",
|
|
48777
|
+
description: "Emit terminal status JSON."
|
|
48778
|
+
},
|
|
48779
|
+
"timeout-ms": {
|
|
48780
|
+
type: "string",
|
|
48781
|
+
description: "Maximum time to wait in milliseconds. Defaults to 600000."
|
|
48782
|
+
},
|
|
48783
|
+
"interval-ms": {
|
|
48784
|
+
type: "string",
|
|
48785
|
+
description: "Polling interval in milliseconds. Defaults to 5000."
|
|
48786
|
+
}
|
|
48787
|
+
},
|
|
48788
|
+
async run({ args }) {
|
|
48789
|
+
await waitRun(readRequiredString(args.run, "run"), {
|
|
48790
|
+
json: args.json === true,
|
|
48791
|
+
timeoutMs: readPositiveIntegerOption(args["timeout-ms"], "--timeout-ms") ?? 6e5,
|
|
48792
|
+
intervalMs: readPositiveIntegerOption(args["interval-ms"], "--interval-ms") ?? 5e3
|
|
48793
|
+
});
|
|
48794
|
+
}
|
|
48795
|
+
});
|
|
48531
48796
|
const cancelCommand = defineCommand({
|
|
48532
48797
|
meta: {
|
|
48533
48798
|
name: "cancel",
|
|
@@ -48653,6 +48918,28 @@ async function watchRun(run, options = {}) {
|
|
|
48653
48918
|
if (sigintHandler !== void 0) process.off("SIGINT", sigintHandler);
|
|
48654
48919
|
}
|
|
48655
48920
|
}
|
|
48921
|
+
async function waitRun(run, options = {}) {
|
|
48922
|
+
const stdout = options.io?.stdout ?? process.stdout;
|
|
48923
|
+
const runPath = resolveRunPath(run, options);
|
|
48924
|
+
const sleep = options.sleep ?? defaultSleep$1;
|
|
48925
|
+
const intervalMs = options.intervalMs ?? 5e3;
|
|
48926
|
+
const timeoutMs = options.timeoutMs ?? 6e5;
|
|
48927
|
+
const now = options.now ?? Date.now;
|
|
48928
|
+
const deadline = now() + timeoutMs;
|
|
48929
|
+
while (true) {
|
|
48930
|
+
const status = withSyncStatus(readFreshRunStatus(runPath));
|
|
48931
|
+
if (isTerminalRunStatus(status.status)) {
|
|
48932
|
+
if (options.json === true) writeJson(stdout, status);
|
|
48933
|
+
else {
|
|
48934
|
+
writeLine(stdout, renderFinalStatus(status));
|
|
48935
|
+
writeHandoffSummary(stdout, runDirFromStatus(status).paths.handoff);
|
|
48936
|
+
}
|
|
48937
|
+
return status;
|
|
48938
|
+
}
|
|
48939
|
+
if (now() >= deadline) throw new CliError$1(`Timed out waiting for run ${status.runId} to finish; latest status is ${status.status}. Use \`quire status ${status.runId} --json\` or \`quire watch ${status.runId}\` to inspect progress.`, ExitCode.InvalidInput);
|
|
48940
|
+
await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
|
|
48941
|
+
}
|
|
48942
|
+
}
|
|
48656
48943
|
async function cancelRun(run, options = {}) {
|
|
48657
48944
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
48658
48945
|
const runPath = resolveRunPath(run, options);
|
|
@@ -48713,9 +49000,15 @@ async function syncRun(run, options = {}) {
|
|
|
48713
49000
|
function withSyncStatus(status) {
|
|
48714
49001
|
const sync = readInvestigationSyncStatus(runDirFromStatus(status));
|
|
48715
49002
|
const { sync: _storedSync, ...baseStatus } = status;
|
|
48716
|
-
|
|
49003
|
+
if (sync === void 0) return baseStatus;
|
|
49004
|
+
return sync.caseUrl === void 0 ? {
|
|
48717
49005
|
...baseStatus,
|
|
48718
49006
|
sync
|
|
49007
|
+
} : {
|
|
49008
|
+
...baseStatus,
|
|
49009
|
+
sync,
|
|
49010
|
+
webUrl: sync.caseUrl,
|
|
49011
|
+
caseUrl: sync.caseUrl
|
|
48719
49012
|
};
|
|
48720
49013
|
}
|
|
48721
49014
|
function signalRunProcess(pid, signal) {
|
|
@@ -48738,6 +49031,13 @@ function statusColor(status) {
|
|
|
48738
49031
|
function formatNumber(value) {
|
|
48739
49032
|
return new Intl.NumberFormat("en-US").format(value);
|
|
48740
49033
|
}
|
|
49034
|
+
function readPositiveIntegerOption(value, name) {
|
|
49035
|
+
if (value === void 0 || value === null || value === false) return;
|
|
49036
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
|
|
49037
|
+
const parsed = Number(value);
|
|
49038
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
|
|
49039
|
+
return parsed;
|
|
49040
|
+
}
|
|
48741
49041
|
function defaultSleep$1(ms) {
|
|
48742
49042
|
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
48743
49043
|
}
|
|
@@ -48797,6 +49097,7 @@ const investigationCommandSchema = {
|
|
|
48797
49097
|
"workspaceKey",
|
|
48798
49098
|
"runPath",
|
|
48799
49099
|
"statusCommand",
|
|
49100
|
+
"waitCommand",
|
|
48800
49101
|
"watchCommand",
|
|
48801
49102
|
"cancelCommand"
|
|
48802
49103
|
],
|
|
@@ -48810,6 +49111,7 @@ const investigationCommandSchema = {
|
|
|
48810
49111
|
optional: true
|
|
48811
49112
|
},
|
|
48812
49113
|
statusCommand: { type: "string" },
|
|
49114
|
+
waitCommand: { type: "string" },
|
|
48813
49115
|
watchCommand: { type: "string" },
|
|
48814
49116
|
cancelCommand: { type: "string" }
|
|
48815
49117
|
}
|
|
@@ -48817,6 +49119,7 @@ const investigationCommandSchema = {
|
|
|
48817
49119
|
followUpCommands: {
|
|
48818
49120
|
continue: "quire continue <run-id> \"additional context or instruction\"",
|
|
48819
49121
|
status: "quire status <run-id> --json",
|
|
49122
|
+
wait: "quire wait <run-id> --json",
|
|
48820
49123
|
watch: "quire watch <run-id>",
|
|
48821
49124
|
cancel: "quire cancel <run-id>"
|
|
48822
49125
|
},
|
|
@@ -48851,6 +49154,7 @@ const investigationCommandSchema = {
|
|
|
48851
49154
|
"quire \"verify checkout before I approve this PR\"",
|
|
48852
49155
|
"printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
|
|
48853
49156
|
"quire status run_9x4mdq7p2h8kc6nv4apz --json",
|
|
49157
|
+
"quire wait run_9x4mdq7p2h8kc6nv4apz --json",
|
|
48854
49158
|
"quire watch run_9x4mdq7p2h8kc6nv4apz"
|
|
48855
49159
|
]
|
|
48856
49160
|
};
|
|
@@ -48938,7 +49242,9 @@ async function runInvestigate(options) {
|
|
|
48938
49242
|
});
|
|
48939
49243
|
if (options.detach === true) log.message([
|
|
48940
49244
|
`${color.label("Run")}: ${color.path(handle.runPath)}`,
|
|
49245
|
+
...handle.webUrl === void 0 ? [] : [`${color.label("Case")}: ${handle.webUrl}`],
|
|
48941
49246
|
`${color.label("Status")}: ${color.command(handle.statusCommand)}`,
|
|
49247
|
+
`${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
|
|
48942
49248
|
`${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
|
|
48943
49249
|
`${color.label("Cancel")}: ${color.command(handle.cancelCommand)}`
|
|
48944
49250
|
], {
|
|
@@ -48949,15 +49255,16 @@ async function runInvestigate(options) {
|
|
|
48949
49255
|
else log.message([
|
|
48950
49256
|
`${color.label("Run")}: ${color.path(handle.runPath)}`,
|
|
48951
49257
|
`${color.label("Status")}: ${color.command(handle.statusCommand)}`,
|
|
49258
|
+
`${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
|
|
48952
49259
|
`${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
|
|
48953
|
-
`This run continues in the background. You can safely exit; use ${color.command(handle.statusCommand)} for the current state or ${color.command(handle.watchCommand)} to follow progress logs.`
|
|
49260
|
+
`This run continues in the background. You can safely exit; use ${color.command(handle.waitCommand)} for the terminal handoff, ${color.command(handle.statusCommand)} for the current state, or ${color.command(handle.watchCommand)} to follow progress logs.`
|
|
48954
49261
|
], {
|
|
48955
49262
|
output: stdout,
|
|
48956
49263
|
spacing: 0,
|
|
48957
49264
|
withGuide: true
|
|
48958
49265
|
});
|
|
48959
49266
|
}
|
|
48960
|
-
if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.
|
|
49267
|
+
if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runPath, {
|
|
48961
49268
|
cwd: options.cwd,
|
|
48962
49269
|
runsRoot: options.runsRoot,
|
|
48963
49270
|
io: { stdout }
|
|
@@ -49098,11 +49405,13 @@ function pluralize(count, singular, plural) {
|
|
|
49098
49405
|
//#region src/doctor/checks.ts
|
|
49099
49406
|
const execFileAsync = promisify(execFile);
|
|
49100
49407
|
function createDoctorContext(overrides = {}) {
|
|
49408
|
+
const env = overrides.env ?? process.env;
|
|
49101
49409
|
return {
|
|
49102
49410
|
cwd: resolve(overrides.cwd ?? process.cwd()),
|
|
49103
|
-
runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
|
|
49411
|
+
runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot(env)),
|
|
49412
|
+
runsRootExplicit: overrides.runsRootExplicit ?? (overrides.runsRoot !== void 0 || (env.QUIRE_RUNS_DIR?.trim().length ?? 0) > 0),
|
|
49104
49413
|
authStore: overrides.authStore ?? authStore,
|
|
49105
|
-
env
|
|
49414
|
+
env,
|
|
49106
49415
|
fetchFn: overrides.fetchFn ?? fetch,
|
|
49107
49416
|
execFn: overrides.execFn ?? defaultExecFn,
|
|
49108
49417
|
probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
|
|
@@ -49204,6 +49513,13 @@ async function checkAuthValid(context, auth) {
|
|
|
49204
49513
|
}
|
|
49205
49514
|
}
|
|
49206
49515
|
async function checkAuthBroker(context, auth) {
|
|
49516
|
+
const localModelSource = await detectLocalInvestigationModelSource({ env: context.env });
|
|
49517
|
+
if (localModelSource !== null) return {
|
|
49518
|
+
id: "auth.broker",
|
|
49519
|
+
section: "identity",
|
|
49520
|
+
severity: "pass",
|
|
49521
|
+
message: `Local model provider connected (${formatLocalModelSource(localModelSource.source)}; ${localModelSource.provider}/${localModelSource.modelId}).`
|
|
49522
|
+
};
|
|
49207
49523
|
try {
|
|
49208
49524
|
return brokerToCheckResult(await fetchModelSourceBrokerStatus({
|
|
49209
49525
|
apiBaseUrl: auth.apiBaseUrl,
|
|
@@ -49220,6 +49536,9 @@ async function checkAuthBroker(context, auth) {
|
|
|
49220
49536
|
};
|
|
49221
49537
|
}
|
|
49222
49538
|
}
|
|
49539
|
+
function formatLocalModelSource(source) {
|
|
49540
|
+
return source === "local_openai_codex" ? "ChatGPT/Codex" : "GitHub Copilot";
|
|
49541
|
+
}
|
|
49223
49542
|
function brokerToCheckResult(status) {
|
|
49224
49543
|
if (status.requiredNextAction !== null) return {
|
|
49225
49544
|
id: "auth.broker",
|
|
@@ -49294,6 +49613,22 @@ async function checkRunsRoot(context) {
|
|
|
49294
49613
|
message: `Runs root writable (${context.runsRoot}).`
|
|
49295
49614
|
};
|
|
49296
49615
|
} catch (error) {
|
|
49616
|
+
if (!context.runsRootExplicit && isWriteAccessError(error)) {
|
|
49617
|
+
const fallbackRunsRoot = await mkdtemp(join(tmpdir(), "quire-runs-"));
|
|
49618
|
+
await mkdir(fallbackRunsRoot, {
|
|
49619
|
+
recursive: true,
|
|
49620
|
+
mode: 448
|
|
49621
|
+
});
|
|
49622
|
+
await access(fallbackRunsRoot, constants.W_OK);
|
|
49623
|
+
context.runsRoot = fallbackRunsRoot;
|
|
49624
|
+
return {
|
|
49625
|
+
id: "runs.rootWritable",
|
|
49626
|
+
section: "runs",
|
|
49627
|
+
severity: "warn",
|
|
49628
|
+
message: `Default runs root not writable (${toMessage(error)}); using temporary runs root ${fallbackRunsRoot}.`,
|
|
49629
|
+
fix: { command: `Set QUIRE_RUNS_DIR=${fallbackRunsRoot} for follow-up Quire commands in this shell.` }
|
|
49630
|
+
};
|
|
49631
|
+
}
|
|
49297
49632
|
return {
|
|
49298
49633
|
id: "runs.rootWritable",
|
|
49299
49634
|
section: "runs",
|
|
@@ -49338,6 +49673,9 @@ function checkStuckRuns(context) {
|
|
|
49338
49673
|
};
|
|
49339
49674
|
}
|
|
49340
49675
|
}
|
|
49676
|
+
function isWriteAccessError(error) {
|
|
49677
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
49678
|
+
}
|
|
49341
49679
|
function formatUser$2(user) {
|
|
49342
49680
|
if (user.email !== void 0 && user.email !== null && user.email.length > 0) return user.email;
|
|
49343
49681
|
if (user.name !== void 0 && user.name !== null && user.name.length > 0) return user.name;
|
|
@@ -49648,6 +49986,7 @@ const logoutCommand = defineCommand({
|
|
|
49648
49986
|
async function runLogout(options) {
|
|
49649
49987
|
const store = options.store ?? authStore;
|
|
49650
49988
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
49989
|
+
const env = options.env ?? process.env;
|
|
49651
49990
|
const existing = await store.get();
|
|
49652
49991
|
await store.clear();
|
|
49653
49992
|
if (existing === null) log.info("Already logged out.", {
|
|
@@ -49658,48 +49997,379 @@ async function runLogout(options) {
|
|
|
49658
49997
|
output: stdout,
|
|
49659
49998
|
spacing: 0
|
|
49660
49999
|
});
|
|
50000
|
+
if (env["QUIRE_API_TOKEN"]?.trim()) log.warn(`${QUIRE_API_TOKEN_ENV} is still set, so Quire commands may still authenticate.`, {
|
|
50001
|
+
output: stdout,
|
|
50002
|
+
spacing: 0
|
|
50003
|
+
});
|
|
49661
50004
|
}
|
|
49662
50005
|
//#endregion
|
|
49663
50006
|
//#region src/commands/setup.ts
|
|
49664
|
-
const
|
|
49665
|
-
const
|
|
49666
|
-
const
|
|
50007
|
+
const DEFAULT_PUBLIC_BASE_URL = "https://quire.sh";
|
|
50008
|
+
const SETUP_URL_PLACEHOLDER = "__QUIRE_SETUP_URL__";
|
|
50009
|
+
const VERIFICATION_SKILL_URL_PLACEHOLDER = "__QUIRE_VERIFICATION_SKILL_URL__";
|
|
50010
|
+
const AGENT_SETUP_PROMPT = `Help me set up Quire for this repo. Use \`quire\` if available, otherwise \`npx @quireco/cli@latest\`. Load and follow ${SETUP_URL_PLACEHOLDER}. Guide any login/model setup with me, create the repo runbook, and run one setup smoke only if Quire doctor is healthy. Report the run link/evidence or exact blocker.`;
|
|
50011
|
+
const QUIRE_VERIFY_SKILL_MARKDOWN = `---
|
|
50012
|
+
name: verify
|
|
50013
|
+
description: Verify claims, code changes, bug fixes, workflows, and release readiness with independent evidence. Use when asked to verify, QA, smoke test, prove a fix, or check non-trivial work before completion.
|
|
50014
|
+
---
|
|
50015
|
+
|
|
50016
|
+
# Verify
|
|
50017
|
+
|
|
50018
|
+
Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50019
|
+
|
|
50020
|
+
Use this skill to prove whether a claim, code change, bug fix, test result, workflow, or release/readiness assertion is actually correct. Quire is the independent read-only QA/verification agent for this workflow. Quire does not implement fixes. Quire reports pass, fail, blocked, or partial results honestly with evidence.
|
|
50021
|
+
|
|
50022
|
+
## When to use this skill
|
|
50023
|
+
|
|
50024
|
+
Use this skill when the user asks to verify, QA, smoke test, validate, prove a fix, check a workflow, confirm a release claim, ask Quire, run Quire, or get a Quire run id/verdict/handoff.
|
|
49667
50025
|
|
|
49668
|
-
|
|
50026
|
+
Also use Quire before declaring non-trivial coding work complete when independent verification would materially increase confidence, especially for UI/workflow changes, bug fixes, failing or flaky tests, auth, security, permissions, billing, data loss, migrations, concurrency, integrations, or release-readiness work.
|
|
49669
50027
|
|
|
49670
|
-
|
|
49671
|
-
|
|
50028
|
+
Do not use Quire for every tiny task. Usually skip independent verification for purely conversational answers, simple docs/formatting edits, trivial mechanical changes where local checks are enough, or repeated verification of the same unchanged diff. Exception: if the user explicitly asks for Quire or independent verification, use Quire anyway.
|
|
50029
|
+
|
|
50030
|
+
## Hard requirement
|
|
50031
|
+
|
|
50032
|
+
If the caller explicitly asks for Quire or independent verification, do not substitute your own direct checks, web fetches, curl commands, tests, browser checks, or manual reasoning and claim that is equivalent. You may do basic preparation, but Quire itself must be invoked.
|
|
50033
|
+
|
|
50034
|
+
Required path: if this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result. Do not decide the task is simple enough to run \`quire\` directly from the parent session when a native \`quire\` subagent is available. Use CLI fallback only when no native \`quire\` subagent is available.
|
|
50035
|
+
|
|
50036
|
+
If the caller asks whether the Quire agent/subagent path was used, answer from your actual invocation path. If you used a native \`quire\` child task/subagent, report that the native Quire subagent path was used even when \`quire wait\` returns metadata for an older run whose internal harness was \`pi\`, Codex, or another agent. Treat old run metadata as separate historical context, not as evidence that the current adapter path was not used.
|
|
50037
|
+
|
|
50038
|
+
When communicating Quire results to the user, produce a compact verification report like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, known non-regressions, synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\`, report/handoff markdown path or excerpt, and useful artifacts such as screenshots, recordings, traces, console/network output, or other evidence.
|
|
50039
|
+
|
|
50040
|
+
## How to invoke Quire
|
|
50041
|
+
|
|
50042
|
+
Preferred order:
|
|
50043
|
+
|
|
50044
|
+
1. If this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result. This is mandatory when available.
|
|
50045
|
+
2. If no native Quire subagent is available, run the CLI directly.
|
|
50046
|
+
|
|
50047
|
+
CLI fallback:
|
|
50048
|
+
|
|
50049
|
+
\`\`\`bash
|
|
50050
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50051
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50052
|
+
quire wait "$run_id" --json
|
|
50053
|
+
\`\`\`
|
|
50054
|
+
|
|
50055
|
+
If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result. Wait for and return the handoff with:
|
|
50056
|
+
|
|
50057
|
+
\`\`\`bash
|
|
50058
|
+
quire wait "<run-id>" --json
|
|
50059
|
+
\`\`\`
|
|
50060
|
+
|
|
50061
|
+
The JSON output includes \`webUrl\` / \`caseUrl\` when the run has synced to Quire's web app. Use that link as the shareable evidence URL in user-facing summaries.
|
|
50062
|
+
|
|
50063
|
+
Omit \`--url\` when there is no URL and include the target context in the natural-language request. Use \`quire wait\` as the waiting primitive. Do not manually poll unless \`quire wait\` is unavailable.
|
|
50064
|
+
|
|
50065
|
+
If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50066
|
+
|
|
50067
|
+
\`\`\`bash
|
|
50068
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50069
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50070
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50071
|
+
\`\`\`
|
|
50072
|
+
|
|
50073
|
+
## Verification brief template
|
|
50074
|
+
|
|
50075
|
+
- Claim/change to verify:
|
|
50076
|
+
- Relevant files/paths:
|
|
50077
|
+
- Commands already run:
|
|
50078
|
+
- Expected behavior:
|
|
50079
|
+
- Known risks or edge cases:
|
|
50080
|
+
- Constraints: read-only verification; do not edit code.
|
|
50081
|
+
|
|
50082
|
+
Return Quire's terminal handoff with:
|
|
50083
|
+
|
|
50084
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50085
|
+
- Quire run id
|
|
50086
|
+
- Quire web URL when present
|
|
50087
|
+
- What Quire verified, including checks run and pass/fail/blocked counts when available
|
|
50088
|
+
- Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
|
|
50089
|
+
- Known non-regressions or pre-existing issues, when relevant
|
|
50090
|
+
- Follow-up actions for the implementation agent
|
|
50091
|
+
`;
|
|
50092
|
+
const QUIRE_DEBUG_SKILL_MARKDOWN = `---
|
|
50093
|
+
name: debug
|
|
50094
|
+
description: Diagnose bugs, regressions, flaky behavior, and broken workflows with a reproduce-first loop. Use when asked to debug, diagnose, investigate a failure, reproduce a bug, or triage a regression.
|
|
50095
|
+
---
|
|
50096
|
+
|
|
50097
|
+
# Debug
|
|
50098
|
+
|
|
50099
|
+
Use this skill for disciplined diagnosis: reproduce the failure, minimize the loop, form hypotheses, instrument only what distinguishes them, fix, and regression-test. Quire is the independent evidence collector for this workflow when a fresh reproduction, browser/API/mobile check, or external verification handoff would materially improve confidence.
|
|
50100
|
+
|
|
50101
|
+
## When to use Quire during diagnosis
|
|
50102
|
+
|
|
50103
|
+
Use the native \`quire\` subagent when you need an independent read-only investigator to:
|
|
50104
|
+
|
|
50105
|
+
- reproduce a reported bug or flaky workflow;
|
|
50106
|
+
- verify a suspected fix against the real app, browser, API, CLI, or mobile surface;
|
|
50107
|
+
- gather evidence such as screenshots, recordings, console output, terminal logs, or a concise handoff;
|
|
50108
|
+
- check whether a failure is environment/setup-blocked versus a product bug;
|
|
50109
|
+
- produce a run id and terminal verdict that another agent can use in an implementation/verification loop.
|
|
50110
|
+
|
|
50111
|
+
Do not outsource implementation to Quire. Quire should not edit code. The parent/debugging agent owns hypotheses, code changes, and regression tests.
|
|
50112
|
+
|
|
50113
|
+
## Recommended invocation
|
|
50114
|
+
|
|
50115
|
+
1. Build or identify the best reproduction loop you can from the report.
|
|
50116
|
+
2. If this harness exposes a native subagent named \`quire\`, delegate a concise reproduction or verification brief to that subagent and wait for its handoff. This is mandatory when available.
|
|
50117
|
+
3. If no native \`quire\` subagent is available, use the Quire CLI fallback from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50118
|
+
4. Use Quire's result as evidence, not as a substitute for understanding the code path. When communicating the result, surface the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or the handoff markdown when they help the user review the diagnosis. If Quire reports blocked, preserve that honestly and fix the setup or ask for the missing environment.
|
|
50119
|
+
|
|
50120
|
+
## Quire brief template
|
|
50121
|
+
|
|
50122
|
+
- Failure or regression to reproduce:
|
|
50123
|
+
- User-visible symptom:
|
|
50124
|
+
- Target URL, command, app, or environment:
|
|
50125
|
+
- Steps already tried:
|
|
50126
|
+
- Expected behavior:
|
|
50127
|
+
- Actual behavior:
|
|
50128
|
+
- Evidence to capture:
|
|
50129
|
+
- Constraints: read-only investigation; do not edit code.
|
|
50130
|
+
|
|
50131
|
+
## CLI fallback
|
|
50132
|
+
|
|
50133
|
+
Use CLI fallback only if the native \`quire\` subagent is unavailable. Prefer detached JSON mode and wait for the terminal handoff:
|
|
50134
|
+
|
|
50135
|
+
\`\`\`bash
|
|
50136
|
+
run_json=$(quire run --detach --json --url "<url>" "<reproduction or verification request>")
|
|
50137
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50138
|
+
quire wait "$run_id" --json
|
|
50139
|
+
\`\`\`
|
|
50140
|
+
|
|
50141
|
+
If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result:
|
|
50142
|
+
|
|
50143
|
+
\`\`\`bash
|
|
50144
|
+
quire wait "<run-id>" --json
|
|
50145
|
+
\`\`\`
|
|
50146
|
+
|
|
50147
|
+
For more CLI details, sandbox recovery, and handoff interpretation, follow ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50148
|
+
`;
|
|
50149
|
+
const QUIRE_AGENT_MARKDOWN = `---
|
|
49672
50150
|
name: quire
|
|
49673
|
-
description:
|
|
50151
|
+
description: "Run Quire, the independent read-only QA/verification/triage agent. Use when the user explicitly asks for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
|
|
50152
|
+
color: green
|
|
50153
|
+
model: inherit
|
|
49674
50154
|
---
|
|
49675
50155
|
|
|
49676
|
-
|
|
50156
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
50157
|
+
|
|
50158
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
49677
50159
|
|
|
49678
|
-
|
|
50160
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
49679
50161
|
|
|
49680
|
-
|
|
50162
|
+
## Workflow
|
|
50163
|
+
|
|
50164
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50165
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50166
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50167
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50168
|
+
|
|
50169
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50170
|
+
|
|
50171
|
+
Prefer detached JSON mode for agent handoffs:
|
|
50172
|
+
|
|
50173
|
+
\`\`\`bash
|
|
50174
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50175
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50176
|
+
\`\`\`
|
|
50177
|
+
|
|
50178
|
+
If there is no URL, omit \`--url\` and put target context in the natural-language request.
|
|
50179
|
+
|
|
50180
|
+
5. Wait patiently until terminal status:
|
|
50181
|
+
|
|
50182
|
+
\`\`\`bash
|
|
50183
|
+
quire wait "$run_id" --json
|
|
50184
|
+
\`\`\`
|
|
50185
|
+
|
|
50186
|
+
If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50187
|
+
|
|
50188
|
+
\`\`\`bash
|
|
50189
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50190
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50191
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50192
|
+
\`\`\`
|
|
50193
|
+
|
|
50194
|
+
Terminal statuses are \`completed\`, \`failed\`, \`canceled\`, and \`stale\`. Quire can take several minutes while it inspects context, opens browsers, runs shell checks, captures evidence, and writes a handoff.
|
|
50195
|
+
|
|
50196
|
+
6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
|
|
50197
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50198
|
+
8. If Quire cannot start because auth, model provider, wallet, browser, mobile, or target setup is missing, report setup-blocked with the exact reason and the next command Quire suggested.
|
|
50199
|
+
|
|
50200
|
+
## Return format
|
|
50201
|
+
|
|
50202
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50203
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50204
|
+
- Quire run id
|
|
50205
|
+
- Quire web URL when present
|
|
50206
|
+
- What Quire verified, including checks run and pass/fail/blocked counts when available
|
|
50207
|
+
- Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
|
|
50208
|
+
- Known non-regressions or pre-existing issues, when relevant
|
|
50209
|
+
- Follow-up actions for the implementation agent
|
|
49681
50210
|
`;
|
|
49682
|
-
const
|
|
49683
|
-
|
|
49684
|
-
|
|
49685
|
-
|
|
49686
|
-
|
|
49687
|
-
|
|
49688
|
-
|
|
49689
|
-
|
|
49690
|
-
|
|
49691
|
-
|
|
49692
|
-
|
|
49693
|
-
|
|
49694
|
-
|
|
49695
|
-
|
|
49696
|
-
|
|
49697
|
-
|
|
49698
|
-
|
|
49699
|
-
|
|
49700
|
-
|
|
49701
|
-
|
|
49702
|
-
|
|
50211
|
+
const QUIRE_CURSOR_MARKDOWN = QUIRE_AGENT_MARKDOWN.replace("model: inherit\n---", "model: inherit\nreadonly: true\n---");
|
|
50212
|
+
const QUIRE_CODEX_TOML = `name = "quire"
|
|
50213
|
+
description = "Run Quire, the independent read-only QA/verification/triage agent. Use when explicitly asked for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
|
|
50214
|
+
|
|
50215
|
+
developer_instructions = """
|
|
50216
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
50217
|
+
|
|
50218
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
50219
|
+
|
|
50220
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
50221
|
+
|
|
50222
|
+
Workflow:
|
|
50223
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50224
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50225
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50226
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50227
|
+
|
|
50228
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50229
|
+
|
|
50230
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50231
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50232
|
+
quire wait "$run_id" --json
|
|
50233
|
+
|
|
50234
|
+
5. If Quire reports EROFS, EACCES, or another write error under ~/.local/share/quire/runs, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50235
|
+
|
|
50236
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50237
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50238
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50239
|
+
|
|
50240
|
+
6. Return finalHandoff.handoffMarkdown from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
|
|
50241
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50242
|
+
|
|
50243
|
+
Return:
|
|
50244
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50245
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50246
|
+
- Quire run id
|
|
50247
|
+
- Quire web URL when present
|
|
50248
|
+
- What Quire verified, including checks run and pass/fail/blocked counts when available
|
|
50249
|
+
- Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
|
|
50250
|
+
- Known non-regressions or pre-existing issues, when relevant
|
|
50251
|
+
- Follow-up actions for the implementation agent
|
|
50252
|
+
"""
|
|
50253
|
+
`;
|
|
50254
|
+
const QUIRE_OPENCODE_MARKDOWN = `---
|
|
50255
|
+
description: Native @quire subagent. If the user mentions @quire, the parent must invoke the Task tool's quire subagent and must not run Bash/CLI directly in the parent session. For existing run IDs, this subagent uses quire wait <run-id> --json, not quire status. If asked whether the agent/subagent path was used, answer yes for this native child invocation; old run harness metadata is separate historical context.
|
|
50256
|
+
mode: subagent
|
|
50257
|
+
permission:
|
|
50258
|
+
edit: deny
|
|
50259
|
+
---
|
|
50260
|
+
|
|
50261
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
50262
|
+
|
|
50263
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
50264
|
+
|
|
50265
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
50266
|
+
|
|
50267
|
+
## Workflow
|
|
50268
|
+
|
|
50269
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50270
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50271
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50272
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50273
|
+
|
|
50274
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50275
|
+
|
|
50276
|
+
\`\`\`bash
|
|
50277
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50278
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50279
|
+
quire wait "$run_id" --json
|
|
50280
|
+
\`\`\`
|
|
50281
|
+
|
|
50282
|
+
5. If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50283
|
+
|
|
50284
|
+
\`\`\`bash
|
|
50285
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50286
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50287
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50288
|
+
\`\`\`
|
|
50289
|
+
|
|
50290
|
+
6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Format the result like a QA test run summary: verdict, checks run, pass/fail/blocked counts when available, report path or excerpt, screenshots, recordings, traces, console/network output, known non-regressions, and follow-up actions.
|
|
50291
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50292
|
+
|
|
50293
|
+
## Return format
|
|
50294
|
+
|
|
50295
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50296
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50297
|
+
- Quire run id
|
|
50298
|
+
- Quire web URL when present
|
|
50299
|
+
- What Quire verified, including checks run and pass/fail/blocked counts when available
|
|
50300
|
+
- Key evidence, artifacts, report, screenshots, recordings, traces, or handoff excerpt
|
|
50301
|
+
- Known non-regressions or pre-existing issues, when relevant
|
|
50302
|
+
- Follow-up actions for the implementation agent
|
|
50303
|
+
`;
|
|
50304
|
+
const SKILL_INSTALL_TARGETS = [
|
|
50305
|
+
{
|
|
50306
|
+
id: "agents",
|
|
50307
|
+
label: "Universal",
|
|
50308
|
+
skillPaths: [{
|
|
50309
|
+
path: ".agents/skills/verify/SKILL.md",
|
|
50310
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50311
|
+
}, {
|
|
50312
|
+
path: ".agents/skills/debug/SKILL.md",
|
|
50313
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50314
|
+
}],
|
|
50315
|
+
agentPath: ".agents/agents/quire.md",
|
|
50316
|
+
codexAgentPath: ".codex/agents/quire.toml",
|
|
50317
|
+
opencodeAgentPath: ".config/opencode/agents/quire.md",
|
|
50318
|
+
piAgentPath: ".pi/agent/agents/quire.md",
|
|
50319
|
+
hint: "~/.agents/skills/{verify,debug}/SKILL.md · plus Codex/OpenCode/Pi Quire subagents",
|
|
50320
|
+
aliases: [
|
|
50321
|
+
"agents",
|
|
50322
|
+
"agent-skills",
|
|
50323
|
+
"pi",
|
|
50324
|
+
"opencode",
|
|
50325
|
+
"amp",
|
|
50326
|
+
"codex",
|
|
50327
|
+
"other"
|
|
50328
|
+
]
|
|
50329
|
+
},
|
|
50330
|
+
{
|
|
50331
|
+
id: "claude",
|
|
50332
|
+
label: "Claude Code",
|
|
50333
|
+
skillPaths: [{
|
|
50334
|
+
path: ".claude/skills/verify/SKILL.md",
|
|
50335
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50336
|
+
}, {
|
|
50337
|
+
path: ".claude/skills/debug/SKILL.md",
|
|
50338
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50339
|
+
}],
|
|
50340
|
+
agentPath: ".claude/agents/quire.md",
|
|
50341
|
+
hint: "~/.claude/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
|
|
50342
|
+
aliases: ["claude", "claude-code"]
|
|
50343
|
+
},
|
|
50344
|
+
{
|
|
50345
|
+
id: "cursor",
|
|
50346
|
+
label: "Cursor",
|
|
50347
|
+
skillPaths: [{
|
|
50348
|
+
path: ".cursor/skills/verify/SKILL.md",
|
|
50349
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50350
|
+
}, {
|
|
50351
|
+
path: ".cursor/skills/debug/SKILL.md",
|
|
50352
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50353
|
+
}],
|
|
50354
|
+
agentPath: ".cursor/agents/quire.md",
|
|
50355
|
+
agentContents: QUIRE_CURSOR_MARKDOWN,
|
|
50356
|
+
hint: "~/.cursor/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
|
|
50357
|
+
aliases: ["cursor", "cursor-cli"]
|
|
50358
|
+
}
|
|
50359
|
+
];
|
|
50360
|
+
const LEGACY_QUIRE_VERIFIER_PATHS = [
|
|
50361
|
+
".agents/agents/quire-verifier.md",
|
|
50362
|
+
".agents/skills/quire-verifier/SKILL.md",
|
|
50363
|
+
".claude/agents/quire-verifier.md",
|
|
50364
|
+
".codex/agents/quire-verifier.toml",
|
|
50365
|
+
".cursor/agents/quire-verifier.md",
|
|
50366
|
+
".config/opencode/agents/quire-verifier.md"
|
|
50367
|
+
];
|
|
50368
|
+
const LEGACY_QUIRE_SKILL_PATHS = [
|
|
50369
|
+
".agents/skills/quire/SKILL.md",
|
|
50370
|
+
".claude/skills/quire/SKILL.md",
|
|
50371
|
+
".cursor/skills/quire/SKILL.md"
|
|
50372
|
+
];
|
|
49703
50373
|
const setupCommand = defineCommand({
|
|
49704
50374
|
meta: {
|
|
49705
50375
|
name: "setup",
|
|
@@ -49708,7 +50378,7 @@ const setupCommand = defineCommand({
|
|
|
49708
50378
|
args: {
|
|
49709
50379
|
skill: {
|
|
49710
50380
|
type: "string",
|
|
49711
|
-
description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
|
|
50381
|
+
description: "Comma-separated local agent skill targets to install: agents, claude, cursor, all, none.",
|
|
49712
50382
|
valueHint: "targets"
|
|
49713
50383
|
},
|
|
49714
50384
|
"no-skill": {
|
|
@@ -49721,37 +50391,76 @@ const setupCommand = defineCommand({
|
|
|
49721
50391
|
}
|
|
49722
50392
|
},
|
|
49723
50393
|
async run({ args }) {
|
|
50394
|
+
const noSkill = args["no-skill"] === true || args.noSkill === true;
|
|
50395
|
+
const noInteractive = args["no-interactive"] === true || args.noInteractive === true || args.interactive === false;
|
|
49724
50396
|
await runSetup({
|
|
49725
|
-
skill:
|
|
49726
|
-
interactive:
|
|
50397
|
+
skill: noSkill ? false : args.skill,
|
|
50398
|
+
interactive: !noInteractive
|
|
49727
50399
|
});
|
|
49728
50400
|
}
|
|
49729
50401
|
});
|
|
49730
50402
|
async function runSetup(options = {}) {
|
|
49731
50403
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
50404
|
+
const env = options.env ?? process.env;
|
|
50405
|
+
const interactive = options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true);
|
|
50406
|
+
const authStore$1 = options.authStore ?? authStore;
|
|
50407
|
+
const urls = setupUrls(env);
|
|
49732
50408
|
const selectedTargets = await selectSkillInstallTargets({
|
|
49733
50409
|
skill: options.skill,
|
|
49734
|
-
interactive
|
|
50410
|
+
interactive
|
|
49735
50411
|
});
|
|
49736
|
-
const
|
|
50412
|
+
const installedAgentFiles = await installQuireAgentFiles(options.homeDir ?? homedir(), selectedTargets, urls);
|
|
49737
50413
|
log.step("Set up Quire for this workspace", {
|
|
49738
50414
|
output: stdout,
|
|
49739
50415
|
spacing: 0
|
|
49740
50416
|
});
|
|
49741
|
-
log.message([`Open ${color.info(
|
|
50417
|
+
log.message([`Open ${color.info(urls.setupUrl)} to load the Quire setup skill, or copy this prompt into your coding agent like Claude Code, Cursor, Codex, Pi, or Amp:`], {
|
|
49742
50418
|
output: stdout,
|
|
49743
50419
|
spacing: 0,
|
|
49744
50420
|
withGuide: true
|
|
49745
50421
|
});
|
|
49746
50422
|
writeLine(stdout);
|
|
49747
50423
|
writeLine(stdout, color.label("--- copy prompt ---"));
|
|
49748
|
-
writeLine(stdout, AGENT_SETUP_PROMPT);
|
|
50424
|
+
writeLine(stdout, renderSetupTemplate(AGENT_SETUP_PROMPT, urls));
|
|
49749
50425
|
writeLine(stdout, color.label("--- end prompt ---"));
|
|
49750
|
-
if (
|
|
50426
|
+
if (installedAgentFiles.length > 0) {
|
|
49751
50427
|
writeLine(stdout);
|
|
49752
|
-
writeLine(stdout, color.label("Installed local Quire
|
|
49753
|
-
for (const path of
|
|
50428
|
+
writeLine(stdout, color.label("Installed local Quire agent files:"));
|
|
50429
|
+
for (const path of installedAgentFiles) writeLine(stdout, ` ${color.success("✓")} ${path}`);
|
|
50430
|
+
}
|
|
50431
|
+
const authConfigured = await completeInteractiveLogin({
|
|
50432
|
+
interactive,
|
|
50433
|
+
env,
|
|
50434
|
+
store: authStore$1,
|
|
50435
|
+
login: options.login ?? runLogin,
|
|
50436
|
+
io: options.io
|
|
50437
|
+
});
|
|
50438
|
+
writeLine(stdout);
|
|
50439
|
+
writeLine(stdout, color.label("Next steps:"));
|
|
50440
|
+
writeLine(stdout, ` 1. Give the copy prompt above to your coding agent so it creates ${color.path(".quire/runbook.md")}.`);
|
|
50441
|
+
if (authConfigured) writeLine(stdout, ` 2. Run ${color.command("quire doctor")} to confirm this repo is ready.`);
|
|
50442
|
+
else writeLine(stdout, ` 2. Authenticate Quire with ${color.command("quire login")} or ${color.command("QUIRE_API_TOKEN")} if this machine is not already signed in.`);
|
|
50443
|
+
writeLine(stdout, ` 3. Ask your coding agent to run a small Quire smoke verification once ${color.path(".quire/runbook.md")} and auth are ready.`);
|
|
50444
|
+
}
|
|
50445
|
+
async function completeInteractiveLogin(options) {
|
|
50446
|
+
if (!options.interactive) return false;
|
|
50447
|
+
const stdout = options.io?.stdout ?? process.stdout;
|
|
50448
|
+
if (await resolveAuthCredentials({
|
|
50449
|
+
store: options.store,
|
|
50450
|
+
env: options.env
|
|
50451
|
+
}) !== null) {
|
|
50452
|
+
log.success("Quire authentication is already configured.", {
|
|
50453
|
+
output: stdout,
|
|
50454
|
+
spacing: 0
|
|
50455
|
+
});
|
|
50456
|
+
return true;
|
|
49754
50457
|
}
|
|
50458
|
+
await options.login({
|
|
50459
|
+
apiBaseUrl: readApiBaseUrl(options.env.QUIRE_API_URL),
|
|
50460
|
+
store: options.store,
|
|
50461
|
+
io: options.io
|
|
50462
|
+
});
|
|
50463
|
+
return true;
|
|
49755
50464
|
}
|
|
49756
50465
|
async function selectSkillInstallTargets(options) {
|
|
49757
50466
|
if (options.skill === false || options.skill === "none") return [];
|
|
@@ -49764,7 +50473,11 @@ async function selectSkillInstallTargets(options) {
|
|
|
49764
50473
|
value: target.id,
|
|
49765
50474
|
hint: target.hint
|
|
49766
50475
|
})),
|
|
49767
|
-
initialValues: [
|
|
50476
|
+
initialValues: [
|
|
50477
|
+
"agents",
|
|
50478
|
+
"claude",
|
|
50479
|
+
"cursor"
|
|
50480
|
+
],
|
|
49768
50481
|
required: false
|
|
49769
50482
|
});
|
|
49770
50483
|
if (isCancel(selected) || selected.length === 0) return [];
|
|
@@ -49780,19 +50493,121 @@ function resolveSkillTargets(value) {
|
|
|
49780
50493
|
}
|
|
49781
50494
|
return [...selected.values()];
|
|
49782
50495
|
}
|
|
49783
|
-
|
|
50496
|
+
function setupUrls(env) {
|
|
50497
|
+
const baseUrl = publicBaseUrl(env);
|
|
50498
|
+
return {
|
|
50499
|
+
setupUrl: new URL("/agent-setup", baseUrl).toString(),
|
|
50500
|
+
verificationSkillUrl: new URL("/agent-verification", baseUrl).toString()
|
|
50501
|
+
};
|
|
50502
|
+
}
|
|
50503
|
+
function publicBaseUrl(env) {
|
|
50504
|
+
const configured = env.QUIRE_SKILL_BASE_URL ?? env.QUIRE_PUBLIC_URL ?? env.QUIRE_API_URL;
|
|
50505
|
+
if (configured === void 0 || configured.trim().length === 0) return DEFAULT_PUBLIC_BASE_URL;
|
|
50506
|
+
return configured;
|
|
50507
|
+
}
|
|
50508
|
+
function renderSetupTemplate(contents, urls) {
|
|
50509
|
+
return contents.replaceAll(SETUP_URL_PLACEHOLDER, urls.setupUrl).replaceAll(VERIFICATION_SKILL_URL_PLACEHOLDER, urls.verificationSkillUrl);
|
|
50510
|
+
}
|
|
50511
|
+
async function installQuireAgentFiles(homeDir, targets, urls) {
|
|
49784
50512
|
const installed = [];
|
|
50513
|
+
if (targets.length === 0) return installed;
|
|
50514
|
+
await removeLegacyQuireVerifierFiles(homeDir);
|
|
50515
|
+
await removeLegacyQuireSkillFiles(homeDir);
|
|
49785
50516
|
await Promise.all(targets.map(async (target) => {
|
|
49786
|
-
const
|
|
49787
|
-
|
|
50517
|
+
for (const skill of target.skillPaths) {
|
|
50518
|
+
const skillPath = join(homeDir, skill.path);
|
|
50519
|
+
await mkdir(dirname(skillPath), {
|
|
50520
|
+
recursive: true,
|
|
50521
|
+
mode: 448
|
|
50522
|
+
});
|
|
50523
|
+
await writeFile(skillPath, renderSetupTemplate(skill.contents, urls), { mode: 384 });
|
|
50524
|
+
installed.push(skillPath);
|
|
50525
|
+
}
|
|
50526
|
+
const agentPath = join(homeDir, target.agentPath);
|
|
50527
|
+
await mkdir(dirname(agentPath), {
|
|
49788
50528
|
recursive: true,
|
|
49789
50529
|
mode: 448
|
|
49790
50530
|
});
|
|
49791
|
-
await writeFile(
|
|
49792
|
-
installed.push(
|
|
50531
|
+
await writeFile(agentPath, renderSetupTemplate(target.agentContents ?? QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
|
|
50532
|
+
installed.push(agentPath);
|
|
50533
|
+
if (target.codexAgentPath !== void 0) {
|
|
50534
|
+
const codexAgentPath = join(homeDir, target.codexAgentPath);
|
|
50535
|
+
await mkdir(dirname(codexAgentPath), {
|
|
50536
|
+
recursive: true,
|
|
50537
|
+
mode: 448
|
|
50538
|
+
});
|
|
50539
|
+
await writeFile(codexAgentPath, renderSetupTemplate(QUIRE_CODEX_TOML, urls), { mode: 384 });
|
|
50540
|
+
installed.push(codexAgentPath);
|
|
50541
|
+
}
|
|
50542
|
+
if (target.opencodeAgentPath !== void 0) {
|
|
50543
|
+
const opencodeAgentPath = join(homeDir, target.opencodeAgentPath);
|
|
50544
|
+
await mkdir(dirname(opencodeAgentPath), {
|
|
50545
|
+
recursive: true,
|
|
50546
|
+
mode: 448
|
|
50547
|
+
});
|
|
50548
|
+
await writeFile(opencodeAgentPath, renderSetupTemplate(QUIRE_OPENCODE_MARKDOWN, urls), { mode: 384 });
|
|
50549
|
+
installed.push(opencodeAgentPath);
|
|
50550
|
+
}
|
|
50551
|
+
if (target.piAgentPath !== void 0) {
|
|
50552
|
+
const piAgentPath = join(homeDir, target.piAgentPath);
|
|
50553
|
+
await mkdir(dirname(piAgentPath), {
|
|
50554
|
+
recursive: true,
|
|
50555
|
+
mode: 448
|
|
50556
|
+
});
|
|
50557
|
+
await writeFile(piAgentPath, renderSetupTemplate(QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
|
|
50558
|
+
installed.push(piAgentPath);
|
|
50559
|
+
}
|
|
49793
50560
|
}));
|
|
49794
50561
|
return installed.sort();
|
|
49795
50562
|
}
|
|
50563
|
+
async function removeLegacyQuireVerifierFiles(homeDir) {
|
|
50564
|
+
await Promise.all(LEGACY_QUIRE_VERIFIER_PATHS.map(async (legacyPath) => {
|
|
50565
|
+
const path = join(homeDir, legacyPath);
|
|
50566
|
+
try {
|
|
50567
|
+
if (isLegacyQuireVerifierFile(await readFile(path, "utf8"))) {
|
|
50568
|
+
await rm(path, { force: true });
|
|
50569
|
+
await removeEmptyDirectory(dirname(path));
|
|
50570
|
+
}
|
|
50571
|
+
} catch (error) {
|
|
50572
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
50573
|
+
await removeEmptyDirectory(dirname(path));
|
|
50574
|
+
return;
|
|
50575
|
+
}
|
|
50576
|
+
throw error;
|
|
50577
|
+
}
|
|
50578
|
+
}));
|
|
50579
|
+
}
|
|
50580
|
+
async function removeLegacyQuireSkillFiles(homeDir) {
|
|
50581
|
+
await Promise.all(LEGACY_QUIRE_SKILL_PATHS.map(async (legacyPath) => {
|
|
50582
|
+
const path = join(homeDir, legacyPath);
|
|
50583
|
+
try {
|
|
50584
|
+
if (isLegacyQuireSkillFile(await readFile(path, "utf8"))) {
|
|
50585
|
+
await rm(path, { force: true });
|
|
50586
|
+
await removeEmptyDirectory(dirname(path));
|
|
50587
|
+
}
|
|
50588
|
+
} catch (error) {
|
|
50589
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
50590
|
+
await removeEmptyDirectory(dirname(path));
|
|
50591
|
+
return;
|
|
50592
|
+
}
|
|
50593
|
+
throw error;
|
|
50594
|
+
}
|
|
50595
|
+
}));
|
|
50596
|
+
}
|
|
50597
|
+
async function removeEmptyDirectory(path) {
|
|
50598
|
+
try {
|
|
50599
|
+
await rmdir(path);
|
|
50600
|
+
} catch (error) {
|
|
50601
|
+
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTEMPTY" || error.code === "EEXIST" || error.code === "EISDIR")) return;
|
|
50602
|
+
throw error;
|
|
50603
|
+
}
|
|
50604
|
+
}
|
|
50605
|
+
function isLegacyQuireVerifierFile(contents) {
|
|
50606
|
+
return contents.includes("Quire") && contents.includes("quire wait") && (contents.includes("quire-verifier") || contents.includes("Quire verifier"));
|
|
50607
|
+
}
|
|
50608
|
+
function isLegacyQuireSkillFile(contents) {
|
|
50609
|
+
return contents.includes("name: quire") && contents.includes("/agent-verification") && contents.includes("native subagent named `quire`");
|
|
50610
|
+
}
|
|
49796
50611
|
//#endregion
|
|
49797
50612
|
//#region src/commands/whoami.ts
|
|
49798
50613
|
const whoamiCommand = defineCommand({
|
|
@@ -49943,7 +50758,7 @@ function formatWalletBalance(value) {
|
|
|
49943
50758
|
}
|
|
49944
50759
|
//#endregion
|
|
49945
50760
|
//#region package.json
|
|
49946
|
-
var version$1 = "0.0.
|
|
50761
|
+
var version$1 = "0.0.13";
|
|
49947
50762
|
//#endregion
|
|
49948
50763
|
//#region src/cli.ts
|
|
49949
50764
|
const subCommands = {
|
|
@@ -49974,6 +50789,7 @@ const subCommands = {
|
|
|
49974
50789
|
setup: setupCommand,
|
|
49975
50790
|
status: statusCommand,
|
|
49976
50791
|
sync: syncCommand,
|
|
50792
|
+
wait: waitCommand,
|
|
49977
50793
|
watch: watchCommand
|
|
49978
50794
|
};
|
|
49979
50795
|
const subCommandAliases = new Set(["me", "resume"]);
|