looply-cli 0.5.18 → 0.5.19
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/.src-hash +1 -1
- package/dist/branch-delivery-wiring.d.ts.map +1 -1
- package/dist/branch-delivery-wiring.js +5 -1
- package/dist/branch-delivery-wiring.js.map +1 -1
- package/dist/branch-delivery.d.ts +4 -1
- package/dist/branch-delivery.d.ts.map +1 -1
- package/dist/branch-delivery.js +9 -0
- package/dist/branch-delivery.js.map +1 -1
- package/dist/bug-repro.d.ts +234 -0
- package/dist/bug-repro.d.ts.map +1 -0
- package/dist/bug-repro.js +533 -0
- package/dist/bug-repro.js.map +1 -0
- package/dist/pursuit-ending.d.ts.map +1 -1
- package/dist/pursuit-ending.js +3 -1
- package/dist/pursuit-ending.js.map +1 -1
- package/dist/run-cmd.d.ts.map +1 -1
- package/dist/run-cmd.js +72 -5
- package/dist/run-cmd.js.map +1 -1
- package/package.json +2 -2
package/dist/run-cmd.js
CHANGED
|
@@ -73,6 +73,7 @@ const run_base_2 = require("./run-base");
|
|
|
73
73
|
const round_records_1 = require("./round-records");
|
|
74
74
|
const goal_gap_1 = require("./goal-gap");
|
|
75
75
|
const visual_proof_run_1 = require("./visual-proof-run");
|
|
76
|
+
const bug_repro_1 = require("./bug-repro");
|
|
76
77
|
const goal_adaptive_1 = require("./goal-adaptive");
|
|
77
78
|
const pending_action_1 = require("./pending-action");
|
|
78
79
|
const approval_resolution_1 = require("./approval-resolution");
|
|
@@ -2875,6 +2876,9 @@ async function runLoop(opts) {
|
|
|
2875
2876
|
const compileReaders = (0, citation_readers_1.nodeCitationReaders)(cwd, stateDir);
|
|
2876
2877
|
const compileProvider = (0, provider_identity_1.providerRecordFor)(resolved.spec, resolved.spec.command === agent_exec_1.DEFAULT_AGENT.command ? (0, provider_discovery_1.claudeExecutable)(resolved.spec.executable) : null);
|
|
2877
2878
|
const compiles = new compile_store_1.CompileStore(stateDir, ledger_store_1.nodeLedgerFs);
|
|
2879
|
+
// Per PROJECT, like the goal mirror: the reproduction a Start recorded is the one every later Start reads.
|
|
2880
|
+
const reproStore = new bug_repro_1.ReproStore(projectStateDirPath);
|
|
2881
|
+
const reproFor = (id, version) => reproStore.read(id, version);
|
|
2878
2882
|
const recordCompile = async (forGoalId, forGoalVersion, treeSha, outcome, checked) => {
|
|
2879
2883
|
try {
|
|
2880
2884
|
if (!compiles.isOpen())
|
|
@@ -2930,17 +2934,62 @@ async function runLoop(opts) {
|
|
|
2930
2934
|
/** The first compile's authority answer, kept so its reason is the acted-on one. */
|
|
2931
2935
|
let stillOurs = { ok: true };
|
|
2932
2936
|
let seamFailure = null;
|
|
2937
|
+
/** A bug that did not show: the one question for the ticket, asked before any plan (`bug-repro.ts`). */
|
|
2938
|
+
let reproAsks = null;
|
|
2933
2939
|
try {
|
|
2934
|
-
|
|
2940
|
+
/**
|
|
2941
|
+
* BUG TICKETS ARE REPRODUCTION-FIRST (`bug-repro.ts`). Before a bug goal's first plan, a test
|
|
2942
|
+
* is written and run red on the base tree, and its text travels into the plan and every round.
|
|
2943
|
+
* A bug that does not show asks one question instead of spending a fix attempt — only when the
|
|
2944
|
+
* reproduction happened in THIS Start, so a resumed goal is never asked the same thing twice.
|
|
2945
|
+
*/
|
|
2946
|
+
if (verdict.compile && scope !== null && binding.kind === 'bound' && (0, bug_repro_1.isBugGoal)(binding.goal.text)) {
|
|
2947
|
+
let repro = reproStore.read(binding.goal.id, binding.goal.version);
|
|
2948
|
+
if (repro === null) {
|
|
2949
|
+
log('repro : this is a bug — writing a test that shows it before planning the fix');
|
|
2950
|
+
repro = await (0, bug_repro_1.reproduceBug)({
|
|
2951
|
+
goalId: binding.goal.id,
|
|
2952
|
+
goalVersion: binding.goal.version,
|
|
2953
|
+
goalText: binding.goal.text,
|
|
2954
|
+
projectName: config.project.name,
|
|
2955
|
+
gates: config.gates,
|
|
2956
|
+
}, {
|
|
2957
|
+
invoke: agent,
|
|
2958
|
+
run: (0, untrusted_gate_1.makeUntrustedGateRunner)(cwd),
|
|
2959
|
+
git: { changedEntries: () => git.changedEntries(), headSha: () => git.headSha(), discard: () => git.discard() },
|
|
2960
|
+
readFile: (0, bug_repro_1.checkoutReader)(cwd),
|
|
2961
|
+
download: (0, bug_repro_1.nodeAttachmentDownloader)(cwd),
|
|
2962
|
+
now: () => new Date(),
|
|
2963
|
+
log,
|
|
2964
|
+
});
|
|
2965
|
+
try {
|
|
2966
|
+
reproStore.write(repro);
|
|
2967
|
+
}
|
|
2968
|
+
catch (e) {
|
|
2969
|
+
log(`repro : the record could not be written — ${e.message}`);
|
|
2970
|
+
}
|
|
2971
|
+
log(repro.outcome === 'reproduced'
|
|
2972
|
+
? `repro : reproduced — "${repro.test}" fails on the base tree (exit ${repro.failedBefore?.exitCode ?? '?'})`
|
|
2973
|
+
: `repro : ${repro.outcome} — ${repro.because ?? ''}`);
|
|
2974
|
+
if (repro.outcome === 'question')
|
|
2975
|
+
reproAsks = (0, bug_repro_1.reproQuestion)(repro);
|
|
2976
|
+
}
|
|
2977
|
+
else if (repro.outcome === 'question') {
|
|
2978
|
+
log('repro : a question was already asked for this goal; going on without a reproduction');
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
if (reproAsks === null && verdict.compile && scope !== null && binding.kind === 'bound') {
|
|
2935
2982
|
compiledThisRun = true;
|
|
2936
2983
|
log(`compile : ${binding.goal.text}`);
|
|
2937
2984
|
// Read before the compile: the compiler is told not to change anything, and every check of its
|
|
2938
2985
|
// answer (the first and any re-plan) is made against the same committed tree.
|
|
2939
2986
|
const compiledAt = await git.headTreeSha().catch(() => null);
|
|
2987
|
+
// A reproduced bug's test rides with the goal's words: the plan's first job is to make it pass.
|
|
2988
|
+
const planText = (0, bug_repro_1.withReproduction)(binding.goal.text, reproFor(binding.goal.id, binding.goal.version));
|
|
2940
2989
|
const recovered = await (0, plan_recovery_1.compileWithRecovery)({
|
|
2941
2990
|
goalId: binding.goal.id,
|
|
2942
2991
|
goalVersion: binding.goal.version,
|
|
2943
|
-
goalText:
|
|
2992
|
+
goalText: planText,
|
|
2944
2993
|
projectId,
|
|
2945
2994
|
projectName: config.project.name,
|
|
2946
2995
|
/**
|
|
@@ -3291,7 +3340,7 @@ async function runLoop(opts) {
|
|
|
3291
3340
|
adaptive = await (0, goal_adaptive_1.decideAdaptive)({
|
|
3292
3341
|
goalId: scope.goalId,
|
|
3293
3342
|
goalVersion: bound.version,
|
|
3294
|
-
goalText: bound.text,
|
|
3343
|
+
goalText: (0, bug_repro_1.withReproduction)(bound.text, reproFor(scope.goalId, bound.version)),
|
|
3295
3344
|
projectId,
|
|
3296
3345
|
projectName: config.project.name,
|
|
3297
3346
|
currentRevision: revision,
|
|
@@ -3337,7 +3386,18 @@ async function runLoop(opts) {
|
|
|
3337
3386
|
...(config.run.baseBranch === undefined ? {} : { configuredBase: config.run.baseBranch }),
|
|
3338
3387
|
log,
|
|
3339
3388
|
});
|
|
3340
|
-
|
|
3389
|
+
// A reproduced bug is met only when its test passes at this tree, run by the machine (`bug-repro.ts`).
|
|
3390
|
+
const gap = await (0, goal_gap_1.evaluateGoalGap)(screenshots.length > 0 ? { ...i, screenshots } : i, { invoke: planAgent });
|
|
3391
|
+
return (0, bug_repro_1.evaluateWithReproduction)(gap, reproFor(goalId, bound.version), {
|
|
3392
|
+
run: (0, untrusted_gate_1.makeUntrustedGateRunner)(cwd),
|
|
3393
|
+
readFile: (0, bug_repro_1.checkoutReader)(cwd),
|
|
3394
|
+
headSha: () => git.headSha(),
|
|
3395
|
+
changedPaths: () => git.changedPaths(),
|
|
3396
|
+
discard: () => git.discard(),
|
|
3397
|
+
store: reproStore,
|
|
3398
|
+
now: () => new Date(),
|
|
3399
|
+
log,
|
|
3400
|
+
});
|
|
3341
3401
|
},
|
|
3342
3402
|
// The same recovery as the first plan: re-plan refused steps, read an empty answer as
|
|
3343
3403
|
// an instruction, and fall back to one direct step before this revision is empty.
|
|
@@ -3598,6 +3658,8 @@ async function runLoop(opts) {
|
|
|
3598
3658
|
* of that block and the ending is written there, before any of them.
|
|
3599
3659
|
*/
|
|
3600
3660
|
const nothingPickableBecause = () => {
|
|
3661
|
+
if (reproAsks !== null)
|
|
3662
|
+
return reproAsks;
|
|
3601
3663
|
if (seamFailure !== null)
|
|
3602
3664
|
return `could not check this goal — ${seamFailure}`;
|
|
3603
3665
|
const said = adaptive === null ? null : (0, goal_adaptive_1.describeAdaptive)(adaptive, attestedNow(), coverage);
|
|
@@ -3700,6 +3762,11 @@ async function runLoop(opts) {
|
|
|
3700
3762
|
// disk. That is safe — the write is one whole-file rename and the next
|
|
3701
3763
|
// Start converges on the same rows — but the message should not claim more
|
|
3702
3764
|
// than it knows, so it says what happened rather than what it hopes.
|
|
3765
|
+
// The bug did not show: one precise question on the ticket, before any fix is attempted.
|
|
3766
|
+
if (reproAsks !== null) {
|
|
3767
|
+
await releaseHold();
|
|
3768
|
+
throw new RunRefused(reproAsks, 'human_required');
|
|
3769
|
+
}
|
|
3703
3770
|
if (seamFailure !== null) {
|
|
3704
3771
|
await releaseHold();
|
|
3705
3772
|
throw new Error(`could not check this goal — ${seamFailure}`);
|
|
@@ -3951,7 +4018,7 @@ async function runLoop(opts) {
|
|
|
3951
4018
|
* bound to — a second read could straddle an amendment and show a
|
|
3952
4019
|
* goal this work was never authorised against.
|
|
3953
4020
|
*/
|
|
3954
|
-
binding.kind === 'bound' ? binding.goal.text : undefined,
|
|
4021
|
+
binding.kind === 'bound' ? (0, bug_repro_1.withReproduction)(binding.goal.text, reproFor(binding.goal.id, binding.goal.version)) : undefined,
|
|
3955
4022
|
// The other repositories this change spans, resolved once for the run.
|
|
3956
4023
|
span),
|
|
3957
4024
|
timeoutMs: AGENT_TIMEOUT_MS,
|