@renzrollon/interlock 1.0.2 → 1.1.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -1
- package/README.md +70 -301
- package/bin/interlock +20 -19
- package/lib/evals-triage.mjs +38 -2
- package/lib/limits.mjs +19 -8
- package/lib/merge-lanes.mjs +45 -2
- package/lib/plan-fingerprint.mjs +8 -7
- package/lib/receipt.mjs +14 -4
- package/lib/resume-card.mjs +319 -0
- package/lib/run.mjs +209 -24
- package/lib/waves.mjs +248 -83
- package/package.json +1 -1
- package/shared/TOOL-ECONOMY.md +8 -1
package/lib/run.mjs
CHANGED
|
@@ -52,6 +52,7 @@ import { planRemediation } from './remediate.mjs'
|
|
|
52
52
|
import { classifySurface } from './surface.mjs'
|
|
53
53
|
import { buildChecklist } from './conformance.mjs'
|
|
54
54
|
import { record as recordAutonomy } from './autonomy.mjs'
|
|
55
|
+
import { writeResumeCard } from './resume-card.mjs'
|
|
55
56
|
import {
|
|
56
57
|
applyReplan,
|
|
57
58
|
batchOutcomes,
|
|
@@ -1449,7 +1450,8 @@ function logSpawns(ctx, manifest, step) {
|
|
|
1449
1450
|
// No run identity yet — the classify step runs before there is a wave state,
|
|
1450
1451
|
// so nothing is owed. The same rule `logVerifyJudgement` follows: a call with
|
|
1451
1452
|
// no runId is not part of a logged run, and logging is not what should fail it.
|
|
1452
|
-
if (!manifest.runId) return
|
|
1453
|
+
if (!manifest.runId) return null
|
|
1454
|
+
let failure = null
|
|
1453
1455
|
for (const s of step.spawns || []) {
|
|
1454
1456
|
if (s.kind === 'implementer') continue // logged by wave-state, at the lane
|
|
1455
1457
|
const written = appendRunLogEvent(ctx.root, {
|
|
@@ -1460,13 +1462,18 @@ function logSpawns(ctx, manifest, step) {
|
|
|
1460
1462
|
model: s.model || undefined,
|
|
1461
1463
|
kind: s.kind
|
|
1462
1464
|
})
|
|
1463
|
-
if (!written.written)
|
|
1465
|
+
if (!written.written) {
|
|
1466
|
+
ctx.warn(`agent-spawn not recorded: ${written.reason}`)
|
|
1467
|
+
// The first failure is the one that opened the gap; the rest of the loop
|
|
1468
|
+
// still runs so the warnings a reader gets are unchanged.
|
|
1469
|
+
if (!failure) failure = `agent-spawn ${s.label}: ${written.reason}`
|
|
1470
|
+
}
|
|
1464
1471
|
}
|
|
1465
|
-
return
|
|
1472
|
+
return failure
|
|
1466
1473
|
}
|
|
1467
1474
|
|
|
1468
1475
|
function logCliExit(ctx, manifest, command, exitCode) {
|
|
1469
|
-
if (!manifest.runId) return
|
|
1476
|
+
if (!manifest.runId) return null
|
|
1470
1477
|
const written = appendRunLogEvent(ctx.root, {
|
|
1471
1478
|
runId: manifest.runId,
|
|
1472
1479
|
change: manifest.change,
|
|
@@ -1474,9 +1481,46 @@ function logCliExit(ctx, manifest, command, exitCode) {
|
|
|
1474
1481
|
command,
|
|
1475
1482
|
exitCode
|
|
1476
1483
|
})
|
|
1477
|
-
if (
|
|
1484
|
+
if (written.written) return null
|
|
1485
|
+
ctx.warn(`cli-exit not recorded: ${written.reason}`)
|
|
1486
|
+
return `cli-exit ${command}: ${written.reason}`
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* The halt a failed run-trajectory append owes the run.
|
|
1491
|
+
*
|
|
1492
|
+
* `.claude/ship/runs` is `fatal: true` in `lib/doctor.mjs`'s STATE_DIRS, and
|
|
1493
|
+
* `wave-state` / `verify judge` in `bin/interlock` already `process.exit(1)`
|
|
1494
|
+
* when their append does not land. The live `interlock run` path used to warn
|
|
1495
|
+
* and walk on — into the task tick and the commit — so a run nobody could
|
|
1496
|
+
* reconstruct still ticked boxes and shipped. A warning is not a gate.
|
|
1497
|
+
*
|
|
1498
|
+
* Caller-side by construction: fatality is a policy of the SITE, not of the
|
|
1499
|
+
* shared writer, which is why `appendRunLogEvent` still just reports. The
|
|
1500
|
+
* outcome corpus and review metrics keep their deliberately different
|
|
1501
|
+
* semantics — a failed write there is reported and never touches the exit code.
|
|
1502
|
+
*
|
|
1503
|
+
* `site` names where the gap is, so the halt reason is enough to find it.
|
|
1504
|
+
*/
|
|
1505
|
+
function trajectoryHaltStep(site, reason, extra = {}) {
|
|
1506
|
+
return haltStep(`trajectory append failed: ${site}: ${reason}`, extra)
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* The reason to halt with when `logWaveMutation` failed.
|
|
1511
|
+
*
|
|
1512
|
+
* It returns the filesystem's own reason where it has one. The fallback covers
|
|
1513
|
+
* an injected or older dep that reports only the boolean — better a named site
|
|
1514
|
+
* with a vague cause than a halt that says nothing at all.
|
|
1515
|
+
*/
|
|
1516
|
+
function waveAppendReason(outcome) {
|
|
1517
|
+
const reason = outcome && typeof outcome.reason === 'string' ? outcome.reason.trim() : ''
|
|
1518
|
+
return reason || 'the wave-action or its paired cli-exit did not land'
|
|
1478
1519
|
}
|
|
1479
1520
|
|
|
1521
|
+
/** Same, for `logAgentSpawns` — it returns a boolean and reports its reason to stderr. */
|
|
1522
|
+
const LANE_SPAWN_APPEND_FAILED = 'an implementer lane spawn did not land'
|
|
1523
|
+
|
|
1480
1524
|
/** The shared tree's HEAD. Never throws: an unreadable base is null, never a guess. */
|
|
1481
1525
|
function headCommit(ctx) {
|
|
1482
1526
|
return ctx.deps.headCommit(ctx.root)
|
|
@@ -1597,10 +1641,25 @@ export function claimDerivedBanner() {
|
|
|
1597
1641
|
* three tasks has told us nothing about the third — and "nothing" read as
|
|
1598
1642
|
* success is how a task silently ships unimplemented. `not-attempted` is the one
|
|
1599
1643
|
* outcome that is neither: it is reported, ticked nowhere, and counted nowhere.
|
|
1644
|
+
*
|
|
1645
|
+
* A result that is absent entirely is a lost agent — a crash, a timeout, or a
|
|
1646
|
+
* briefing the agent never acknowledged — not an agent that filed a bad report.
|
|
1647
|
+
* On a multi-task lane that costs one failure, charged to the first task, and
|
|
1648
|
+
* the rest are `not-attempted`. Charging every task would halt a chain on one
|
|
1649
|
+
* miss. A single-task lane is one failure either way, and stays that.
|
|
1600
1650
|
*/
|
|
1601
1651
|
export function laneOutcomes(lane, result) {
|
|
1602
1652
|
const failAll = error => lane.map(t => ({ id: t.id, outcome: 'failed', error, handoff: null }))
|
|
1603
|
-
if (!result)
|
|
1653
|
+
if (!result) {
|
|
1654
|
+
if (lane.length >= 2) {
|
|
1655
|
+
return lane.map((t, i) =>
|
|
1656
|
+
i === 0
|
|
1657
|
+
? { id: t.id, outcome: 'failed', error: 'agent returned no result', handoff: null }
|
|
1658
|
+
: { id: t.id, outcome: 'not-attempted' }
|
|
1659
|
+
)
|
|
1660
|
+
}
|
|
1661
|
+
return failAll('agent returned no result')
|
|
1662
|
+
}
|
|
1604
1663
|
|
|
1605
1664
|
if (lane.length === 1) {
|
|
1606
1665
|
const [only] = lane
|
|
@@ -1771,15 +1830,29 @@ function adoptPlan(ctx, manifest, plan) {
|
|
|
1771
1830
|
// anything, and `run-log check` does not require it.
|
|
1772
1831
|
sessionId: hostSessionId(manifest, ctx.env) || undefined
|
|
1773
1832
|
})
|
|
1774
|
-
if (!started.written)
|
|
1833
|
+
if (!started.written) {
|
|
1834
|
+
ctx.warn(`run-start not recorded: ${started.reason}`)
|
|
1835
|
+
return trajectoryHaltStep('run-start', started.reason)
|
|
1836
|
+
}
|
|
1775
1837
|
|
|
1776
|
-
const
|
|
1838
|
+
const created = ctx.deps.logWaveMutation(root, {
|
|
1777
1839
|
source: 'create',
|
|
1778
1840
|
change: manifest.change,
|
|
1779
1841
|
state
|
|
1780
1842
|
})
|
|
1781
|
-
|
|
1782
|
-
|
|
1843
|
+
const { step, ok } = created
|
|
1844
|
+
if (!ok) {
|
|
1845
|
+
ctx.warn('the wave-state trajectory append failed on run start')
|
|
1846
|
+
return trajectoryHaltStep('wave-state create', waveAppendReason(created))
|
|
1847
|
+
}
|
|
1848
|
+
// The lane spawns, which `logSpawns` deliberately skips (they are logged at
|
|
1849
|
+
// the lane, by wave-state). Its verdict is read for the same reason every
|
|
1850
|
+
// other append's is: `wave-state next` exits 1 on this exact boolean, and the
|
|
1851
|
+
// live path owes the trajectory the same fatality.
|
|
1852
|
+
if (ctx.deps.logAgentSpawns(root, { state, change: manifest.change, step, source: 'next' }) === false) {
|
|
1853
|
+
ctx.warn('an implementer agent-spawn was not recorded on run start')
|
|
1854
|
+
return trajectoryHaltStep('agent-spawn', LANE_SPAWN_APPEND_FAILED)
|
|
1855
|
+
}
|
|
1783
1856
|
return decorate(ctx, step, manifest)
|
|
1784
1857
|
}
|
|
1785
1858
|
|
|
@@ -2103,18 +2176,43 @@ export function runRecordBatch(ctx, { results }) {
|
|
|
2103
2176
|
changedPaths: ctx.deps.observedChangedPaths(root)
|
|
2104
2177
|
})
|
|
2105
2178
|
writeState(root, after)
|
|
2106
|
-
const
|
|
2179
|
+
const recorded = ctx.deps.logWaveMutation(root, {
|
|
2107
2180
|
source: 'record-batch',
|
|
2108
2181
|
change: manifest.change,
|
|
2109
2182
|
state: after
|
|
2110
2183
|
})
|
|
2111
|
-
|
|
2112
|
-
|
|
2184
|
+
const { step: nextRaw, ok: logOk } = recorded
|
|
2185
|
+
if (!logOk) {
|
|
2186
|
+
// Before the tick and before the commit. The batch result is already on
|
|
2187
|
+
// disk (`writeState` above) and there is no undo API for it, so the halt
|
|
2188
|
+
// names the gap instead of pretending the state never advanced — close's
|
|
2189
|
+
// reconstructability check reads the same trajectory and agrees.
|
|
2190
|
+
ctx.warn('the wave-state trajectory append failed on record-batch')
|
|
2191
|
+
manifest.banners.push(...banners)
|
|
2192
|
+
return finishStep(
|
|
2193
|
+
ctx,
|
|
2194
|
+
manifest,
|
|
2195
|
+
trajectoryHaltStep('wave-state record-batch', waveAppendReason(recorded), { banners })
|
|
2196
|
+
)
|
|
2197
|
+
}
|
|
2198
|
+
// Before the tick and before the commit, exactly like the wave-action above:
|
|
2199
|
+
// a batch whose implementer spawns went unrecorded is a batch nobody can
|
|
2200
|
+
// attribute, and `emitRecordedState` already exits 1 on this same boolean.
|
|
2201
|
+
const laneSpawnsLogged = ctx.deps.logAgentSpawns(root, {
|
|
2113
2202
|
state: after,
|
|
2114
2203
|
change: manifest.change,
|
|
2115
2204
|
step: nextRaw,
|
|
2116
2205
|
source: 'record-batch'
|
|
2117
2206
|
})
|
|
2207
|
+
if (laneSpawnsLogged === false) {
|
|
2208
|
+
ctx.warn('an implementer agent-spawn was not recorded on record-batch')
|
|
2209
|
+
manifest.banners.push(...banners)
|
|
2210
|
+
return finishStep(
|
|
2211
|
+
ctx,
|
|
2212
|
+
manifest,
|
|
2213
|
+
trajectoryHaltStep('agent-spawn', LANE_SPAWN_APPEND_FAILED, { banners })
|
|
2214
|
+
)
|
|
2215
|
+
}
|
|
2118
2216
|
|
|
2119
2217
|
// One authority, read once, for the tick and for the tallies alike.
|
|
2120
2218
|
const verdict = adjudicateBatches([reported], recordedOutcomes(batchOutcomes(state, after)))
|
|
@@ -2227,7 +2325,26 @@ export function runJudge(ctx, { context, results }) {
|
|
|
2227
2325
|
}
|
|
2228
2326
|
verdict = judgeVerification(plan, reportedSteps, { context })
|
|
2229
2327
|
for (const banner of verdict.banners || []) banners.push(banner)
|
|
2230
|
-
logCliExit(ctx, manifest, 'verify judge', verdict.halt ? 1 : 0)
|
|
2328
|
+
const exitFailure = logCliExit(ctx, manifest, 'verify judge', verdict.halt ? 1 : 0)
|
|
2329
|
+
if (exitFailure) {
|
|
2330
|
+
// The judgement happened; the record of it did not. Halting here is what
|
|
2331
|
+
// `verify judge` in bin/interlock already does on the same failure —
|
|
2332
|
+
// before the branch into record-verify, the handoff or the commit.
|
|
2333
|
+
//
|
|
2334
|
+
// A verdict that was ALREADY halting keeps its own reason, the same rule
|
|
2335
|
+
// `finishStep` applies below: this run stopped because verification
|
|
2336
|
+
// failed, and that reason is what reaches the summary, the push and the
|
|
2337
|
+
// resume card. The lost record is a banner beside it, never instead of it.
|
|
2338
|
+
banners.push(`TRAJECTORY APPEND FAILED: ${exitFailure}`)
|
|
2339
|
+
manifest.banners.push(...banners)
|
|
2340
|
+
return finishStep(
|
|
2341
|
+
ctx,
|
|
2342
|
+
manifest,
|
|
2343
|
+
verdict.halt
|
|
2344
|
+
? haltStep(verdict.reason || 'verification halted the run', { banners })
|
|
2345
|
+
: trajectoryHaltStep('cli-exit', exitFailure.replace(/^cli-exit /, ''), { banners })
|
|
2346
|
+
)
|
|
2347
|
+
}
|
|
2231
2348
|
} else {
|
|
2232
2349
|
banners.push(`VERIFICATION SKIPPED: reason=${reason}`)
|
|
2233
2350
|
}
|
|
@@ -2243,13 +2360,21 @@ export function runJudge(ctx, { context, results }) {
|
|
|
2243
2360
|
}
|
|
2244
2361
|
const after = recordVerifyResult(state, record)
|
|
2245
2362
|
writeState(root, after)
|
|
2246
|
-
const
|
|
2363
|
+
const verified = ctx.deps.logWaveMutation(root, {
|
|
2247
2364
|
source: 'record-verify',
|
|
2248
2365
|
change: manifest.change,
|
|
2249
2366
|
state: after
|
|
2250
2367
|
})
|
|
2251
|
-
|
|
2368
|
+
const { step: nextRaw, ok } = verified
|
|
2252
2369
|
manifest.banners.push(...banners)
|
|
2370
|
+
if (!ok) {
|
|
2371
|
+
ctx.warn('the wave-state trajectory append failed on record-verify')
|
|
2372
|
+
return finishStep(
|
|
2373
|
+
ctx,
|
|
2374
|
+
manifest,
|
|
2375
|
+
trajectoryHaltStep('wave-state record-verify', waveAppendReason(verified), { banners })
|
|
2376
|
+
)
|
|
2377
|
+
}
|
|
2253
2378
|
return finishStep(ctx, manifest, decorate(ctx, nextRaw, manifest, { banners }))
|
|
2254
2379
|
}
|
|
2255
2380
|
|
|
@@ -2481,12 +2606,16 @@ export function runReplan(ctx, { results }) {
|
|
|
2481
2606
|
return finishStep(ctx, manifest, haltStep(`replan rejected: ${(err && err.message) || String(err)}`))
|
|
2482
2607
|
}
|
|
2483
2608
|
writeState(root, after)
|
|
2484
|
-
const
|
|
2609
|
+
const replanned = ctx.deps.logWaveMutation(root, {
|
|
2485
2610
|
source: 'replan',
|
|
2486
2611
|
change: manifest.change,
|
|
2487
2612
|
state: after
|
|
2488
2613
|
})
|
|
2489
|
-
|
|
2614
|
+
const { step: nextRaw, ok } = replanned
|
|
2615
|
+
if (!ok) {
|
|
2616
|
+
ctx.warn('the wave-state trajectory append failed on replan')
|
|
2617
|
+
return finishStep(ctx, manifest, trajectoryHaltStep('wave-state replan', waveAppendReason(replanned)))
|
|
2618
|
+
}
|
|
2490
2619
|
return finishStep(ctx, manifest, decorate(ctx, nextRaw, manifest))
|
|
2491
2620
|
}
|
|
2492
2621
|
|
|
@@ -2520,8 +2649,18 @@ function noManifest() {
|
|
|
2520
2649
|
* process — each `run` subcommand is its own.
|
|
2521
2650
|
*/
|
|
2522
2651
|
function finishStep(ctx, manifest, step) {
|
|
2523
|
-
logSpawns(ctx, manifest, step)
|
|
2524
|
-
|
|
2652
|
+
const failure = logSpawns(ctx, manifest, step)
|
|
2653
|
+
// An agent-spawn nobody recorded is a gap in the trajectory, and the step it
|
|
2654
|
+
// belongs to must not run. A step that was ALREADY halting keeps its own
|
|
2655
|
+
// reason: replacing it would trade why the run stopped for the bookkeeping
|
|
2656
|
+
// failure that followed, and it halts either way.
|
|
2657
|
+
const final =
|
|
2658
|
+
failure && step.action !== 'halt'
|
|
2659
|
+
? trajectoryHaltStep('agent-spawn', failure.replace(/^agent-spawn /, ''), {
|
|
2660
|
+
...(Array.isArray(step.banners) ? { banners: step.banners } : {})
|
|
2661
|
+
})
|
|
2662
|
+
: step
|
|
2663
|
+
return recordThen(ctx.root, manifest, final)
|
|
2525
2664
|
}
|
|
2526
2665
|
|
|
2527
2666
|
// --- run close (design D10) -------------------------------------------------
|
|
@@ -2758,13 +2897,23 @@ export async function runClose(
|
|
|
2758
2897
|
: undefined
|
|
2759
2898
|
})
|
|
2760
2899
|
|
|
2900
|
+
// The trajectory writes this close owes. A failure here is the same fatal
|
|
2901
|
+
// class as every other `.claude/ship/runs` append — the close has no tick or
|
|
2902
|
+
// commit left to stop, so the halt it can still perform is a spoken
|
|
2903
|
+
// degradation and a non-zero exit, exactly what an unreconstructable run gets
|
|
2904
|
+
// below. Silence would leave a run that reads complete and cannot be replayed.
|
|
2905
|
+
let trajectoryComplete = true
|
|
2761
2906
|
if (manifest.runId) {
|
|
2762
2907
|
const written = appendRunLogEvent(root, {
|
|
2763
2908
|
...receipt,
|
|
2764
2909
|
runId: manifest.runId,
|
|
2765
2910
|
change: manifest.change
|
|
2766
2911
|
})
|
|
2767
|
-
if (!written.written)
|
|
2912
|
+
if (!written.written) {
|
|
2913
|
+
ctx.warn(`run-receipt not recorded: ${written.reason}`)
|
|
2914
|
+
trajectoryComplete = false
|
|
2915
|
+
degradations.push(`TRAJECTORY APPEND FAILED: run-receipt: ${written.reason}`)
|
|
2916
|
+
}
|
|
2768
2917
|
|
|
2769
2918
|
const terminal = appendRunLogEvent(root, {
|
|
2770
2919
|
runId: manifest.runId,
|
|
@@ -2772,7 +2921,12 @@ export async function runClose(
|
|
|
2772
2921
|
type: haltReason ? 'run-halt' : 'run-complete',
|
|
2773
2922
|
reason: haltReason || undefined
|
|
2774
2923
|
})
|
|
2775
|
-
if (!terminal.written)
|
|
2924
|
+
if (!terminal.written) {
|
|
2925
|
+
const type = haltReason ? 'run-halt' : 'run-complete'
|
|
2926
|
+
ctx.warn(`${type} not recorded: ${terminal.reason}`)
|
|
2927
|
+
trajectoryComplete = false
|
|
2928
|
+
degradations.push(`TRAJECTORY APPEND FAILED: ${type}: ${terminal.reason}`)
|
|
2929
|
+
}
|
|
2776
2930
|
}
|
|
2777
2931
|
|
|
2778
2932
|
// The outcome corpus. Its own write failure is reported and never touches the
|
|
@@ -2821,6 +2975,36 @@ export async function runClose(
|
|
|
2821
2975
|
}
|
|
2822
2976
|
}
|
|
2823
2977
|
|
|
2978
|
+
// The halt resume card. Only on a halt: a clean close has nothing to resume,
|
|
2979
|
+
// and its summary already ends in the archive reminder.
|
|
2980
|
+
//
|
|
2981
|
+
// Written last, so the card carries every banner this close raised — the
|
|
2982
|
+
// reconstructability failure and the autonomy one included. Its own failure is
|
|
2983
|
+
// a banner and never an exit code: it is a pointer to records that were
|
|
2984
|
+
// already written, so losing it costs a reader convenience, not evidence. That
|
|
2985
|
+
// is the outcome-corpus class, not the trajectory's.
|
|
2986
|
+
//
|
|
2987
|
+
// Nothing reads it back. The next ship still decides whether to skip the
|
|
2988
|
+
// classifier from the plan fingerprint, so a card that was never written, or
|
|
2989
|
+
// was hand-edited, cannot change what a later run does.
|
|
2990
|
+
let resumeCard = null
|
|
2991
|
+
if (haltReason) {
|
|
2992
|
+
const written = writeResumeCard(root, {
|
|
2993
|
+
change: manifest.change,
|
|
2994
|
+
runId: manifest.runId,
|
|
2995
|
+
halted: haltReason,
|
|
2996
|
+
leftoverTaskIds,
|
|
2997
|
+
plan: manifest.plan,
|
|
2998
|
+
planStored: Boolean(planFingerprint) && existsSync(join(root, PLAN_FILE_PATH)),
|
|
2999
|
+
waves: manifest.waves || [],
|
|
3000
|
+
degradations,
|
|
3001
|
+
projectSlug: slug,
|
|
3002
|
+
cwd: absRoot
|
|
3003
|
+
})
|
|
3004
|
+
if (written.written) resumeCard = written.path
|
|
3005
|
+
else degradations.push(`RESUME CARD NOT WRITTEN: ${written.reason}`)
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2824
3008
|
manifest.lastThen = null
|
|
2825
3009
|
writeManifest(root, manifest)
|
|
2826
3010
|
|
|
@@ -2829,7 +3013,7 @@ export async function runClose(
|
|
|
2829
3013
|
action: haltReason ? 'halt' : 'complete',
|
|
2830
3014
|
then: null,
|
|
2831
3015
|
spawns: [],
|
|
2832
|
-
exitCode: haltReason || !reconstructable ? 1 : 0,
|
|
3016
|
+
exitCode: haltReason || !reconstructable || !trajectoryComplete ? 1 : 0,
|
|
2833
3017
|
banners: degradations,
|
|
2834
3018
|
summary: formatRunSummary({
|
|
2835
3019
|
change: manifest.change,
|
|
@@ -2841,7 +3025,8 @@ export async function runClose(
|
|
|
2841
3025
|
root: absRoot,
|
|
2842
3026
|
projectSlug: slug,
|
|
2843
3027
|
push,
|
|
2844
|
-
unarchived
|
|
3028
|
+
unarchived,
|
|
3029
|
+
resumeCard
|
|
2845
3030
|
})
|
|
2846
3031
|
}
|
|
2847
3032
|
}
|