@cr1992/agentkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.en.md +107 -0
- package/README.md +103 -0
- package/bin/agentkit.mjs +4 -0
- package/bin/cli.mjs +273 -0
- package/core/atomic-fs.mjs +23 -0
- package/core/cli-help.mjs +54 -0
- package/core/content-digest.mjs +66 -0
- package/core/digest.mjs +67 -0
- package/core/json-schema-lite.mjs +60 -0
- package/core/legacy-entry.mjs +37 -0
- package/core/reflection.mjs +142 -0
- package/core/runtime-bundle.mjs +101 -0
- package/docs/loop/embedded-review-adapter.md +41 -0
- package/docs/loop/loop-state-machine.md +43 -0
- package/docs/loop/recovery-and-fuses.md +34 -0
- package/docs/orchestrate/dispatch-contract.md +92 -0
- package/docs/orchestrate/failure-routing-and-recovery.md +46 -0
- package/docs/orchestrate/host-capability-cache.md +170 -0
- package/docs/orchestrate/isolation-fallback.md +18 -0
- package/docs/orchestrate/model-routing-config.md +186 -0
- package/docs/orchestrate/orchestration-runtime.md +261 -0
- package/docs/orchestrate/review-budget.md +90 -0
- package/docs/orchestrate/task-playbooks.md +85 -0
- package/docs/orchestrate/user-facing-reporting.md +14 -0
- package/docs/verify/evidence-schema.md +167 -0
- package/docs/verify/input-preparation.md +44 -0
- package/docs/verify/verification-protocol.md +76 -0
- package/docs/worktree/batch-integration.md +176 -0
- package/docs/worktree/delivery-identity.md +41 -0
- package/docs/worktree/profile.md +107 -0
- package/docs/worktree/reclaim-and-watch.md +96 -0
- package/docs/worktree/review-lifecycle.md +92 -0
- package/docs/worktree/spawn-and-stack.md +74 -0
- package/domains/loop/loop-runtime.mjs +1056 -0
- package/domains/orchestrate/contract-tool.mjs +169 -0
- package/domains/orchestrate/host_capability_cache.mjs +437 -0
- package/domains/orchestrate/orchestration-ledger.mjs +332 -0
- package/domains/orchestrate/orchestration-metadata.mjs +4 -0
- package/domains/orchestrate/orchestration-reflection.mjs +119 -0
- package/domains/orchestrate/resolve_model_policy.mjs +311 -0
- package/domains/orchestrate/review-budget.mjs +162 -0
- package/domains/orchestrate/worker-capability-preflight.mjs +227 -0
- package/domains/verify/verification-runtime.mjs +1638 -0
- package/domains/worktree/worktree-archive.mjs +135 -0
- package/domains/worktree/worktree-artifact.mjs +123 -0
- package/domains/worktree/worktree-batch-integrate.mjs +713 -0
- package/domains/worktree/worktree-batch-plan.mjs +198 -0
- package/domains/worktree/worktree-batch-result.mjs +241 -0
- package/domains/worktree/worktree-core.mjs +908 -0
- package/domains/worktree/worktree-doctor.mjs +493 -0
- package/domains/worktree/worktree-history.mjs +377 -0
- package/domains/worktree/worktree-learning.mjs +110 -0
- package/domains/worktree/worktree-lifecycle.mjs +786 -0
- package/domains/worktree/worktree-merge-preview.mjs +409 -0
- package/domains/worktree/worktree-mgr.mjs +261 -0
- package/domains/worktree/worktree-process.mjs +55 -0
- package/domains/worktree/worktree-profile.mjs +800 -0
- package/domains/worktree/worktree-provider-gitlab.mjs +59 -0
- package/domains/worktree/worktree-reclaim.mjs +683 -0
- package/domains/worktree/worktree-review-refresh.mjs +574 -0
- package/domains/worktree/worktree-review-watch.mjs +661 -0
- package/domains/worktree/worktree-scan.mjs +510 -0
- package/domains/worktree/worktree-trace-test-worker.mjs +23 -0
- package/domains/worktree/worktree-trace.mjs +478 -0
- package/manage-worktrees/SKILL.md +87 -0
- package/manage-worktrees/agents/openai.yaml +4 -0
- package/manage-worktrees/scripts/worktree-mgr.mjs +10 -0
- package/manage-worktrees/scripts/worktree-scan.mjs +10 -0
- package/orchestrate-subagents/SKILL.md +173 -0
- package/orchestrate-subagents/agents/openai.yaml +4 -0
- package/orchestrate-subagents/scripts/contract-tool.mjs +10 -0
- package/orchestrate-subagents/scripts/host_capability_cache.mjs +10 -0
- package/orchestrate-subagents/scripts/orchestration-ledger.mjs +10 -0
- package/orchestrate-subagents/scripts/orchestration-reflection.mjs +10 -0
- package/orchestrate-subagents/scripts/resolve_model_policy.mjs +10 -0
- package/orchestrate-subagents/scripts/review-budget.mjs +10 -0
- package/orchestrate-subagents/scripts/worker-capability-preflight.mjs +10 -0
- package/package.json +48 -0
- package/run-agent-verify-loop/SKILL.md +127 -0
- package/run-agent-verify-loop/agents/openai.yaml +4 -0
- package/run-agent-verify-loop/scripts/loop-runtime.mjs +10 -0
- package/schemas/artifact-ref-v1.schema.json +23 -0
- package/schemas/batch-result-v1.schema.json +138 -0
- package/schemas/controller-recheck-record-v1.schema.json +22 -0
- package/schemas/convergence-report-v1.schema.json +9 -0
- package/schemas/effective-worker-capability-v1.schema.json +36 -0
- package/schemas/embedded-verification-record-v1.schema.json +32 -0
- package/schemas/evidence-package-v1.schema.json +41 -0
- package/schemas/improvement-proposal-v1.schema.json +18 -0
- package/schemas/loop-state-v1.schema.json +34 -0
- package/schemas/model-policy-resolution-v1.schema.json +41 -0
- package/schemas/orchestration-ledger-v1.schema.json +110 -0
- package/schemas/reflection-record-v1.schema.json +24 -0
- package/schemas/review-result-v1.schema.json +37 -0
- package/schemas/task-contract-v1.schema.json +83 -0
- package/schemas/verification-profile-v1.schema.json +60 -0
- package/schemas/worker-capability-requirements-v1.schema.json +21 -0
- package/schemas/worktree-binding-v1.schema.json +14 -0
- package/shell-manifest.json +79 -0
- package/verify-agent-output/SKILL.md +119 -0
- package/verify-agent-output/agents/openai.yaml +4 -0
- package/verify-agent-output/scripts/verification-runtime.mjs +10 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Record<string, any>} deps
|
|
5
|
+
*/
|
|
6
|
+
export function createCommands(deps) {
|
|
7
|
+
const {
|
|
8
|
+
randomUUID,
|
|
9
|
+
predictReviewRefresh,
|
|
10
|
+
loadRepositoryProfile,
|
|
11
|
+
WorktreeTraceError,
|
|
12
|
+
managerScript,
|
|
13
|
+
runFileCapture,
|
|
14
|
+
FETCH_TIMEOUT_MS,
|
|
15
|
+
SUBMIT_PUSH_TIMEOUT_MS,
|
|
16
|
+
git,
|
|
17
|
+
gitTry,
|
|
18
|
+
commandFailureReason,
|
|
19
|
+
readWatcherHeartbeat,
|
|
20
|
+
watcherHealth,
|
|
21
|
+
refreshTargetRef,
|
|
22
|
+
log,
|
|
23
|
+
die,
|
|
24
|
+
flag,
|
|
25
|
+
rejectUnknownFlags,
|
|
26
|
+
oneLine,
|
|
27
|
+
loadRecords,
|
|
28
|
+
selectRecord,
|
|
29
|
+
liveGitSnapshot,
|
|
30
|
+
updateRecord,
|
|
31
|
+
isAncestor,
|
|
32
|
+
historyChangeSnapshot,
|
|
33
|
+
startWatcher,
|
|
34
|
+
} = deps;
|
|
35
|
+
|
|
36
|
+
function reviewRefreshUpstream(record) {
|
|
37
|
+
if (!record.branch) die('refresh-review 不支持 detached HEAD。', 2);
|
|
38
|
+
const remote = gitTry(['config', '--get', `branch.${record.branch}.remote`], record.path);
|
|
39
|
+
const merge = gitTry(['config', '--get', `branch.${record.branch}.merge`], record.path);
|
|
40
|
+
if (!remote.ok || !remote.out || remote.out === '.' || !merge.ok || !merge.out.startsWith('refs/heads/')) {
|
|
41
|
+
die('refresh-review 要求 source branch 已配置真实 remote upstream。', 2);
|
|
42
|
+
}
|
|
43
|
+
const branch = merge.out.slice('refs/heads/'.length);
|
|
44
|
+
if (!gitTry(['check-ref-format', '--branch', branch], record.path).ok) die(`upstream branch 非法: ${branch}`, 2);
|
|
45
|
+
return { remote: remote.out, branch, ref: merge.out };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {Record<string,any>} record @param {{remote:string,branch:string}} upstream */
|
|
49
|
+
function readRemoteBranchHead(record, upstream) {
|
|
50
|
+
const remote = gitTry(
|
|
51
|
+
['ls-remote', '--heads', upstream.remote, `refs/heads/${upstream.branch}`],
|
|
52
|
+
record.path,
|
|
53
|
+
{ timeoutMs: FETCH_TIMEOUT_MS },
|
|
54
|
+
);
|
|
55
|
+
if (!remote.ok) die(`无法读取 upstream ${upstream.remote}/${upstream.branch}。`);
|
|
56
|
+
return remote.out ? remote.out.split(/\s+/u)[0]?.toLowerCase() ?? null : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 用新的 manager 进程执行已有 managed rebase 状态机。这样 refresh-review 不复制事务实现,
|
|
61
|
+
* rebase 冲突时子进程可按既有退出语义停下,父命令保留 review_refresh marker 供续跑。
|
|
62
|
+
* @param {Record<string,any>} record @param {string[]} rebaseArgs @param {string|null} configPath
|
|
63
|
+
*/
|
|
64
|
+
function runManagedRebaseChild(record, rebaseArgs, configPath) {
|
|
65
|
+
const script = managerScript;
|
|
66
|
+
const childArgs = [script, 'rebase', record.task, '--id', record.worktree_id, ...rebaseArgs];
|
|
67
|
+
if (configPath) childArgs.push('--config', configPath);
|
|
68
|
+
return runFileCapture(process.execPath, childArgs, { cwd: record.path, timeoutMs: SUBMIT_PUSH_TIMEOUT_MS });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @param {Record<string,any>} record @param {Record<string,any>} refresh @param {string} newHead */
|
|
72
|
+
function reviewRefreshRewrite(record, refresh, newHead) {
|
|
73
|
+
const rewrite = [...(record.history_rewrites ?? [])].reverse().find((candidate) =>
|
|
74
|
+
candidate.kind === 'rebase'
|
|
75
|
+
&& candidate.old_head === refresh.old_head
|
|
76
|
+
&& candidate.new_head === newHead
|
|
77
|
+
&& candidate.new_base_sha === refresh.target_sha);
|
|
78
|
+
if (!rewrite) die('refresh-review 找不到对应的 managed rebase lineage,拒绝推进或回滚。', 2);
|
|
79
|
+
if (refresh.managed_rebase_token && refresh.managed_rebase_token !== rewrite.token) {
|
|
80
|
+
die('refresh-review managed rebase token 已变化,拒绝推进或回滚。', 2);
|
|
81
|
+
}
|
|
82
|
+
return rewrite;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {Record<string,any>} snapshot */
|
|
86
|
+
function recordReviewRefreshRebased(loaded, record, refresh, snapshot) {
|
|
87
|
+
if (refresh.state === 'prepared') {
|
|
88
|
+
const rewrite = reviewRefreshRewrite(record, refresh, snapshot.head);
|
|
89
|
+
return updateRecord(record, 'review_refresh_rebased', (next) => {
|
|
90
|
+
if (next.review_refresh?.token !== refresh.token) throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', 'refresh marker 已变化。');
|
|
91
|
+
next.review_refresh.state = 'rebased';
|
|
92
|
+
next.review_refresh.new_head = snapshot.head;
|
|
93
|
+
next.review_refresh.managed_rebase_token = rewrite.token;
|
|
94
|
+
next.review_refresh.rebased_at = new Date().toISOString();
|
|
95
|
+
}, {
|
|
96
|
+
token: refresh.token,
|
|
97
|
+
old_head: refresh.old_head,
|
|
98
|
+
new_head: snapshot.head,
|
|
99
|
+
target_ref: refresh.target_ref,
|
|
100
|
+
target_sha: refresh.target_sha,
|
|
101
|
+
}, loaded.context.common_dir);
|
|
102
|
+
}
|
|
103
|
+
if (refresh.new_head && refresh.new_head !== snapshot.head) {
|
|
104
|
+
die(`refresh-review HEAD 漂移:record=${refresh.new_head}, live=${snapshot.head}`, 2);
|
|
105
|
+
}
|
|
106
|
+
return record;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @param {Record<string,any>} record @param {Record<string,any>} refresh @param {string} newHead */
|
|
110
|
+
function pushReviewRefreshBranch(record, refresh, newHead) {
|
|
111
|
+
const upstream = { remote: refresh.upstream_remote, branch: refresh.upstream_branch };
|
|
112
|
+
const remoteHead = readRemoteBranchHead(record, upstream);
|
|
113
|
+
if (remoteHead === null) {
|
|
114
|
+
die(`refresh-review upstream 分支 ${upstream.remote}/${upstream.branch} 已不存在;拒绝隐式重建或 force push。`, 2);
|
|
115
|
+
}
|
|
116
|
+
if (remoteHead === newHead) return;
|
|
117
|
+
if (remoteHead !== refresh.upstream_sha) {
|
|
118
|
+
die(`refresh-review upstream lease 已变化:expected=${refresh.upstream_sha}, actual=${remoteHead};拒绝 force push。`, 2);
|
|
119
|
+
}
|
|
120
|
+
const pushed = runFileCapture('git', [
|
|
121
|
+
'push',
|
|
122
|
+
`--force-with-lease=refs/heads/${upstream.branch}:${refresh.upstream_sha}`,
|
|
123
|
+
upstream.remote,
|
|
124
|
+
`HEAD:refs/heads/${upstream.branch}`,
|
|
125
|
+
], { cwd: record.path, timeoutMs: SUBMIT_PUSH_TIMEOUT_MS });
|
|
126
|
+
if (!pushed.ok) {
|
|
127
|
+
die(`refresh-review force-with-lease push 失败;可修复凭证/网络后重跑 --continue。\n${(pushed.out || 'unknown error').slice(0, 1000)}`);
|
|
128
|
+
}
|
|
129
|
+
const confirmed = readRemoteBranchHead(record, upstream);
|
|
130
|
+
if (confirmed !== newHead) die(`refresh-review push 后远端回读不一致:expected=${newHead}, actual=${confirmed ?? 'missing'}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {string} newHead */
|
|
134
|
+
function recordReviewRefreshPushed(loaded, record, refresh, newHead) {
|
|
135
|
+
if (record.review_refresh.state === 'pushed') return record;
|
|
136
|
+
return updateRecord(record, 'review_refresh_pushed', (next) => {
|
|
137
|
+
if (next.review_refresh?.token !== refresh.token) throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', 'push 登记时 refresh marker 已变化。');
|
|
138
|
+
next.review_refresh.state = 'pushed';
|
|
139
|
+
next.review_refresh.pushed_at = new Date().toISOString();
|
|
140
|
+
next.task_status = 'ready_for_review';
|
|
141
|
+
next.last_head = newHead;
|
|
142
|
+
next.last_seen_at = new Date().toISOString();
|
|
143
|
+
if (next.change_request) {
|
|
144
|
+
next.change_request.head_sha = newHead;
|
|
145
|
+
next.change_request.source_head_stale = false;
|
|
146
|
+
next.change_request.refreshed_at = new Date().toISOString();
|
|
147
|
+
}
|
|
148
|
+
}, {
|
|
149
|
+
token: refresh.token,
|
|
150
|
+
upstream_remote: refresh.upstream_remote,
|
|
151
|
+
upstream_branch: refresh.upstream_branch,
|
|
152
|
+
old_upstream_sha: refresh.upstream_sha,
|
|
153
|
+
new_head: newHead,
|
|
154
|
+
}, loaded.context.common_dir);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {string} newHead */
|
|
158
|
+
function ensureCompletedReviewWatcher(loaded, record, refresh, newHead) {
|
|
159
|
+
const active = record.auto_reclaim && !['disarmed', 'reclaimed'].includes(record.auto_reclaim.state)
|
|
160
|
+
? record.auto_reclaim
|
|
161
|
+
: null;
|
|
162
|
+
if (active && active.head_sha === newHead && active.target_ref === refresh.target_ref) return record;
|
|
163
|
+
return startWatcher(loaded, record, {
|
|
164
|
+
targetRef: refresh.target_ref,
|
|
165
|
+
targetSha: refresh.target_sha,
|
|
166
|
+
headSha: newHead,
|
|
167
|
+
intervalMs: refresh.interval_ms,
|
|
168
|
+
changeRef: refresh.change_ref,
|
|
169
|
+
notifyMode: refresh.notify,
|
|
170
|
+
explicitConfig: refresh.explicit_config,
|
|
171
|
+
previousHealth: 'review refresh completed',
|
|
172
|
+
armedBy: 'review_refresh',
|
|
173
|
+
}).record;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {string} newHead */
|
|
177
|
+
function recordReviewRefreshCompleted(loaded, record, refresh, newHead) {
|
|
178
|
+
const completedAt = new Date().toISOString();
|
|
179
|
+
return updateRecord(record, 'review_refresh_completed', (next) => {
|
|
180
|
+
if (next.review_refresh?.token !== refresh.token) throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', '完成登记时 refresh marker 已变化。');
|
|
181
|
+
next.review_refreshes ??= [];
|
|
182
|
+
next.review_refreshes.push({ ...next.review_refresh, state: 'completed', completed_at: completedAt });
|
|
183
|
+
next.review_refresh = null;
|
|
184
|
+
}, {
|
|
185
|
+
token: refresh.token,
|
|
186
|
+
old_head: refresh.old_head,
|
|
187
|
+
new_head: newHead,
|
|
188
|
+
target_ref: refresh.target_ref,
|
|
189
|
+
target_sha: refresh.target_sha,
|
|
190
|
+
}, loaded.context.common_dir);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record */
|
|
194
|
+
function finishReviewRefresh(loaded, record) {
|
|
195
|
+
const refresh = record.review_refresh;
|
|
196
|
+
if (!refresh) die('review refresh marker 缺失,无法完成。', 2);
|
|
197
|
+
if (refresh.state === 'aborting') die('review refresh 已进入回滚收口;请运行 refresh-review --abort。', 2);
|
|
198
|
+
if (record.history_operation) die('managed rebase 尚未完成;先解决冲突并运行 refresh-review --continue。', 2);
|
|
199
|
+
const snapshot = liveGitSnapshot(record);
|
|
200
|
+
if (!snapshot.present || snapshot.dirty !== false || !snapshot.head) die('refresh-review 完成前要求 worktree clean 且 HEAD 可读。', 2);
|
|
201
|
+
if (!isAncestor(record.path, refresh.target_sha, snapshot.head)) {
|
|
202
|
+
die(`refresh-review live HEAD 未基于冻结 target ${refresh.target_sha},拒绝 push。`, 2);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
record = recordReviewRefreshRebased(loaded, record, refresh, snapshot);
|
|
206
|
+
const current = record.review_refresh;
|
|
207
|
+
const newHead = current.new_head ?? snapshot.head;
|
|
208
|
+
pushReviewRefreshBranch(record, current, newHead);
|
|
209
|
+
record = recordReviewRefreshPushed(loaded, record, current, newHead);
|
|
210
|
+
record = ensureCompletedReviewWatcher(loaded, record, current, newHead);
|
|
211
|
+
record = recordReviewRefreshCompleted(loaded, record, current, newHead);
|
|
212
|
+
log(`review refresh 已完成 ${record.task}: ${current.old_head.slice(0, 12)} -> ${newHead.slice(0, 12)};已 force-with-lease push 并重新武装 watcher。`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** @param {Record<string,any>} record @param {Record<string,any>} refresh */
|
|
216
|
+
function reviewRefreshAbortMode(record, refresh) {
|
|
217
|
+
const pendingRebase = Boolean(record.history_operation);
|
|
218
|
+
const pausedRebase = refresh.state === 'rebased' && refresh.pause_before_push !== false;
|
|
219
|
+
if (!pendingRebase && !pausedRebase && refresh.state !== 'aborting') {
|
|
220
|
+
die('refresh-review --abort 只接受 managed rebase 冲突态或 pause-before-push 后的 rebased 状态。', 2);
|
|
221
|
+
}
|
|
222
|
+
return { pendingRebase };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** @param {Record<string,any>} record @param {Record<string,any>} refresh */
|
|
226
|
+
function assertReviewRefreshAbortLease(record, refresh) {
|
|
227
|
+
const upstream = { remote: refresh.upstream_remote, branch: refresh.upstream_branch };
|
|
228
|
+
const remoteHead = readRemoteBranchHead(record, upstream);
|
|
229
|
+
if (remoteHead === null) {
|
|
230
|
+
die(`refresh-review upstream 分支 ${upstream.remote}/${upstream.branch} 已不存在;无法自动恢复原 watcher。`, 2);
|
|
231
|
+
}
|
|
232
|
+
if (remoteHead === refresh.upstream_sha) return;
|
|
233
|
+
if (refresh.new_head && remoteHead === refresh.new_head) {
|
|
234
|
+
die('refresh-review rebased HEAD 已存在于 upstream;不能再 abort,请用 --continue 完成登记。', 2);
|
|
235
|
+
}
|
|
236
|
+
die(`refresh-review abort 前 upstream lease 已变化:expected=${refresh.upstream_sha}, actual=${remoteHead}。`, 2);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {boolean} pendingRebase @param {string|null} explicitConfig */
|
|
240
|
+
function restoreReviewRefreshHead(loaded, record, refresh, pendingRebase, explicitConfig) {
|
|
241
|
+
if (pendingRebase) {
|
|
242
|
+
const aborted = runManagedRebaseChild(record, ['--abort'], explicitConfig ? loaded.profile_path : null);
|
|
243
|
+
if (!aborted.ok) die(`refresh-review abort 失败。\n${aborted.out}`);
|
|
244
|
+
return selectRecord(loadRecords(loaded.context.common_dir), record.task, record.worktree_id);
|
|
245
|
+
}
|
|
246
|
+
const snapshot = liveGitSnapshot(record);
|
|
247
|
+
const currentBranch = gitTry(['branch', '--show-current'], record.path);
|
|
248
|
+
if (!snapshot.present || snapshot.dirty !== false || !snapshot.head || !currentBranch.ok || currentBranch.out !== record.branch) {
|
|
249
|
+
die('refresh-review abort 要求 clean、存在且仍位于登记 branch 的 worktree。', 2);
|
|
250
|
+
}
|
|
251
|
+
if (snapshot.head !== refresh.old_head && snapshot.head !== refresh.new_head) {
|
|
252
|
+
die(`refresh-review abort HEAD 漂移:old=${refresh.old_head}, rebased=${refresh.new_head}, live=${snapshot.head}`, 2);
|
|
253
|
+
}
|
|
254
|
+
if (snapshot.head !== refresh.old_head) {
|
|
255
|
+
const reset = gitTry(['reset', '--hard', refresh.old_head], record.path);
|
|
256
|
+
if (!reset.ok) die(`refresh-review 无法恢复原始 HEAD: ${commandFailureReason(reset, 'git reset failed')}`);
|
|
257
|
+
}
|
|
258
|
+
return record;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** @param {Record<string,any>} next @param {Record<string,any>} refresh */
|
|
262
|
+
function restoreReviewRefreshChangeRequest(next, refresh) {
|
|
263
|
+
if ('old_change_request' in refresh) {
|
|
264
|
+
if (refresh.old_change_request === null) delete next.change_request;
|
|
265
|
+
else next.change_request = structuredClone(refresh.old_change_request);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!next.change_request) return;
|
|
269
|
+
next.change_request.source_head_stale = false;
|
|
270
|
+
delete next.change_request.rewritten_head_sha;
|
|
271
|
+
delete next.change_request.rewritten_at;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** @param {Record<string,any>} next @param {Record<string,any>} refresh @param {Record<string,any>} rewrite @param {string} abortedAt */
|
|
275
|
+
function applyReviewRefreshRollback(next, refresh, rewrite, abortedAt) {
|
|
276
|
+
if (next.base_sha !== refresh.target_sha) throw new WorktreeTraceError('REVIEW_REFRESH_BASE_CHANGED', 'abort 补偿前 base 已变化。');
|
|
277
|
+
const epoch = next.ownership_epochs?.at(-1);
|
|
278
|
+
if (epoch && !epoch.ended_at) {
|
|
279
|
+
epoch.ended_at = abortedAt;
|
|
280
|
+
epoch.end_sha = refresh.new_head;
|
|
281
|
+
}
|
|
282
|
+
next.ownership_epochs ??= [];
|
|
283
|
+
next.ownership_epochs.push({
|
|
284
|
+
agent: next.agent,
|
|
285
|
+
started_at: abortedAt,
|
|
286
|
+
start_sha: refresh.old_base_sha,
|
|
287
|
+
end_sha: null,
|
|
288
|
+
ended_at: null,
|
|
289
|
+
source: 'review_refresh_abort',
|
|
290
|
+
});
|
|
291
|
+
next.history_rollbacks ??= [];
|
|
292
|
+
next.history_rollbacks.push({
|
|
293
|
+
kind: 'review_refresh_abort',
|
|
294
|
+
refresh_token: refresh.token,
|
|
295
|
+
managed_rebase_token: rewrite.token,
|
|
296
|
+
from_head: refresh.new_head,
|
|
297
|
+
restored_head: refresh.old_head,
|
|
298
|
+
restored_base_ref: refresh.old_base_ref ?? rewrite.old_base_ref,
|
|
299
|
+
restored_base_sha: refresh.old_base_sha,
|
|
300
|
+
completed_at: abortedAt,
|
|
301
|
+
});
|
|
302
|
+
next.base_ref = refresh.old_base_ref ?? rewrite.old_base_ref;
|
|
303
|
+
next.base_sha = refresh.old_base_sha;
|
|
304
|
+
next.base_reason = refresh.old_base_reason ?? null;
|
|
305
|
+
next.stack_parent = structuredClone(refresh.old_stack_parent ?? null);
|
|
306
|
+
restoreReviewRefreshChangeRequest(next, refresh);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh @param {boolean} pendingRebase */
|
|
310
|
+
function recordReviewRefreshAbortRestored(loaded, record, refresh, pendingRebase) {
|
|
311
|
+
if (record.review_refresh?.state === 'aborting') return record;
|
|
312
|
+
const abortedAt = new Date().toISOString();
|
|
313
|
+
const rewrite = pendingRebase ? null : reviewRefreshRewrite(record, refresh, refresh.new_head);
|
|
314
|
+
return updateRecord(record, 'review_refresh_abort_restored', (next) => {
|
|
315
|
+
if (next.review_refresh?.token !== refresh.token) throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', 'abort 恢复时 refresh marker 已变化。');
|
|
316
|
+
if (rewrite) applyReviewRefreshRollback(next, refresh, rewrite, abortedAt);
|
|
317
|
+
next.review_refresh.state = 'aborting';
|
|
318
|
+
next.review_refresh.abort_kind = rewrite ? 'rebased_before_push' : 'managed_rebase';
|
|
319
|
+
next.review_refresh.restored_at = abortedAt;
|
|
320
|
+
next.task_status = refresh.old_task_status ?? 'ready_for_review';
|
|
321
|
+
next.last_head = refresh.old_head;
|
|
322
|
+
next.last_seen_at = abortedAt;
|
|
323
|
+
}, {
|
|
324
|
+
token: refresh.token,
|
|
325
|
+
abort_kind: rewrite ? 'rebased_before_push' : 'managed_rebase',
|
|
326
|
+
restored_head: refresh.old_head,
|
|
327
|
+
restored_base_ref: refresh.old_base_ref ?? rewrite?.old_base_ref ?? record.base_ref,
|
|
328
|
+
restored_base_sha: refresh.old_base_sha,
|
|
329
|
+
managed_rebase_token: rewrite?.token ?? null,
|
|
330
|
+
}, loaded.context.common_dir);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh */
|
|
334
|
+
function ensureAbortedReviewWatcher(loaded, record, refresh) {
|
|
335
|
+
const active = record.auto_reclaim && !['disarmed', 'reclaimed'].includes(record.auto_reclaim.state)
|
|
336
|
+
? record.auto_reclaim
|
|
337
|
+
: null;
|
|
338
|
+
const heartbeat = readWatcherHeartbeat(loaded.context.common_dir, record.worktree_id);
|
|
339
|
+
const health = watcherHealth(record, heartbeat);
|
|
340
|
+
if (active && active.head_sha === refresh.old_head && active.target_ref === refresh.target_ref && health.healthy) return record;
|
|
341
|
+
return startWatcher(loaded, record, {
|
|
342
|
+
targetRef: refresh.target_ref,
|
|
343
|
+
targetSha: refresh.target_base_sha ?? refresh.old_base_sha,
|
|
344
|
+
headSha: refresh.old_head,
|
|
345
|
+
intervalMs: refresh.interval_ms,
|
|
346
|
+
changeRef: refresh.change_ref,
|
|
347
|
+
notifyMode: refresh.notify,
|
|
348
|
+
explicitConfig: refresh.explicit_config,
|
|
349
|
+
previousHealth: health.reason,
|
|
350
|
+
armedBy: refresh.watch_armed_by ?? 'review_refresh',
|
|
351
|
+
}).record;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} refresh */
|
|
355
|
+
function recordReviewRefreshAborted(loaded, record, refresh) {
|
|
356
|
+
const completedAt = new Date().toISOString();
|
|
357
|
+
return updateRecord(record, 'review_refresh_aborted', (next) => {
|
|
358
|
+
if (next.review_refresh?.token !== refresh.token || next.review_refresh?.state !== 'aborting') {
|
|
359
|
+
throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', 'abort 收口时 refresh marker 已变化。');
|
|
360
|
+
}
|
|
361
|
+
next.review_refreshes ??= [];
|
|
362
|
+
next.review_refreshes.push({ ...next.review_refresh, state: 'aborted', aborted_at: completedAt });
|
|
363
|
+
next.review_refresh = null;
|
|
364
|
+
}, { token: refresh.token, restored_head: refresh.old_head }, loaded.context.common_dir);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* 放弃评审刷新。冲突态复用 managed rebase abort;pause-before-push 已完成本地 rebase 时,
|
|
369
|
+
* 以补偿 event 恢复原 base/ownership/change-request 边界,再重建原 watcher。
|
|
370
|
+
* @param {ReturnType<typeof loadRepositoryProfile>} loaded
|
|
371
|
+
* @param {Record<string,any>} initialRecord
|
|
372
|
+
* @param {string|null} explicitConfig
|
|
373
|
+
*/
|
|
374
|
+
function abortReviewRefresh(loaded, initialRecord, explicitConfig) {
|
|
375
|
+
let record = initialRecord;
|
|
376
|
+
if (!record.review_refresh) die('当前没有可 abort 的 review refresh。', 2);
|
|
377
|
+
const refresh = structuredClone(record.review_refresh);
|
|
378
|
+
const { pendingRebase } = reviewRefreshAbortMode(record, refresh);
|
|
379
|
+
assertReviewRefreshAbortLease(record, refresh);
|
|
380
|
+
record = restoreReviewRefreshHead(loaded, record, refresh, pendingRebase, explicitConfig);
|
|
381
|
+
const snapshot = liveGitSnapshot(record);
|
|
382
|
+
if (snapshot.head !== refresh.old_head || snapshot.dirty !== false) {
|
|
383
|
+
die('refresh-review abort 后未恢复原始 clean HEAD,保留 marker 供 doctor。', 2);
|
|
384
|
+
}
|
|
385
|
+
record = recordReviewRefreshAbortRestored(loaded, record, refresh, pendingRebase);
|
|
386
|
+
record = ensureAbortedReviewWatcher(loaded, record, refresh);
|
|
387
|
+
recordReviewRefreshAborted(loaded, record, refresh);
|
|
388
|
+
log(`review refresh 已 abort,HEAD 恢复 ${refresh.old_head.slice(0, 12)},原评审 watcher 已重新武装。`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {string|null} explicitConfig */
|
|
392
|
+
function continueReviewRefresh(loaded, record, explicitConfig) {
|
|
393
|
+
if (record.history_operation) {
|
|
394
|
+
const continued = runManagedRebaseChild(record, ['--continue'], explicitConfig ? loaded.profile_path : null);
|
|
395
|
+
if (!continued.ok) die(`review refresh rebase --continue 尚未完成;继续解决冲突后重跑。\n${continued.out}`);
|
|
396
|
+
record = selectRecord(loadRecords(loaded.context.common_dir), record.task, record.worktree_id);
|
|
397
|
+
}
|
|
398
|
+
finishReviewRefresh(loaded, record);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @param {Record<string,any>} record @param {{flags:Map<string,unknown>}} args @param {Record<string,any>} snapshot @param {Record<string,any>} watch */
|
|
402
|
+
function resolveReviewRefreshTarget(record, args, snapshot, watch) {
|
|
403
|
+
const targetRef = oneLine(flag(args.flags, 'target') ?? watch.target_ref ?? record.base_ref ?? '', 'target', 240);
|
|
404
|
+
const refreshed = refreshTargetRef(targetRef, record.path);
|
|
405
|
+
if (!refreshed.ok || !refreshed.target_sha) die(`refresh-review 无法刷新 target: ${targetRef}`);
|
|
406
|
+
if (refreshed.target_sha === record.base_sha) {
|
|
407
|
+
log(`target 尚未前进,无需 refresh: ${targetRef}@${refreshed.target_sha.slice(0, 12)}`);
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
if (!record.base_sha || !isAncestor(record.path, record.base_sha, refreshed.target_sha)) {
|
|
411
|
+
die('target 不再是登记 base 的后继(可能发生 force push);拒绝自动选择新基线。', 2);
|
|
412
|
+
}
|
|
413
|
+
if (isAncestor(record.path, snapshot.head, refreshed.target_sha)) {
|
|
414
|
+
log('冻结 HEAD 已进入 target;无需改写历史,现有 watcher 将继续回收。');
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
return { ref: targetRef, sha: refreshed.target_sha };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** @param {Record<string,any>} record @param {Record<string,any>} snapshot */
|
|
421
|
+
function freezeReviewRefreshUpstream(record, snapshot) {
|
|
422
|
+
const upstream = reviewRefreshUpstream(record);
|
|
423
|
+
const sha = readRemoteBranchHead(record, upstream);
|
|
424
|
+
if (sha === null) {
|
|
425
|
+
die(`refresh-review upstream 分支 ${upstream.remote}/${upstream.branch} 已不存在;无法冻结 source branch 边界。`, 2);
|
|
426
|
+
}
|
|
427
|
+
if (sha !== snapshot.head) {
|
|
428
|
+
die(`refresh-review upstream 与冻结 HEAD 不一致:expected=${snapshot.head}, actual=${sha}。`, 2);
|
|
429
|
+
}
|
|
430
|
+
return { ...upstream, sha };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} preparation */
|
|
434
|
+
function recordReviewRefreshPrepared(loaded, record, preparation) {
|
|
435
|
+
const { token, snapshot, watch, target, upstream, reason, pauseBeforePush, explicitConfig } = preparation;
|
|
436
|
+
return updateRecord(record, 'review_refresh_prepared', (next) => {
|
|
437
|
+
if (next.review_refresh || next.history_operation) throw new WorktreeTraceError('REVIEW_REFRESH_ALREADY_PENDING', '刷新准备时已有操作。');
|
|
438
|
+
const live = liveGitSnapshot(next);
|
|
439
|
+
if (live.head !== snapshot.head || live.dirty !== false) throw new WorktreeTraceError('REVIEW_REFRESH_HEAD_CHANGED', '刷新准备时 worktree 已漂移。');
|
|
440
|
+
if (next.auto_reclaim?.token !== watch.token || next.auto_reclaim?.head_sha !== snapshot.head) {
|
|
441
|
+
throw new WorktreeTraceError('REVIEW_REFRESH_WATCH_CHANGED', '刷新准备时 watcher 已变化。');
|
|
442
|
+
}
|
|
443
|
+
next.review_refresh = {
|
|
444
|
+
token,
|
|
445
|
+
state: 'prepared',
|
|
446
|
+
old_head: snapshot.head,
|
|
447
|
+
old_task_status: next.task_status,
|
|
448
|
+
old_base_ref: next.base_ref,
|
|
449
|
+
old_base_sha: next.base_sha,
|
|
450
|
+
old_base_reason: next.base_reason ?? null,
|
|
451
|
+
old_stack_parent: structuredClone(next.stack_parent ?? null),
|
|
452
|
+
old_change_request: structuredClone(next.change_request ?? null),
|
|
453
|
+
target_ref: target.ref,
|
|
454
|
+
target_sha: target.sha,
|
|
455
|
+
target_base_sha: watch.target_base_sha ?? next.base_sha,
|
|
456
|
+
upstream_remote: upstream.remote,
|
|
457
|
+
upstream_branch: upstream.branch,
|
|
458
|
+
upstream_sha: upstream.sha,
|
|
459
|
+
interval_ms: watch.interval_ms,
|
|
460
|
+
change_ref: watch.change_ref ?? null,
|
|
461
|
+
notify: watch.notify ?? 'auto',
|
|
462
|
+
watch_armed_by: watch.armed_by ?? 'explicit',
|
|
463
|
+
pause_before_push: pauseBeforePush,
|
|
464
|
+
explicit_config: explicitConfig ? loaded.profile_path : null,
|
|
465
|
+
reason,
|
|
466
|
+
prepared_at: new Date().toISOString(),
|
|
467
|
+
};
|
|
468
|
+
}, {
|
|
469
|
+
token,
|
|
470
|
+
old_head: snapshot.head,
|
|
471
|
+
old_base_sha: record.base_sha,
|
|
472
|
+
target_ref: target.ref,
|
|
473
|
+
target_sha: target.sha,
|
|
474
|
+
upstream_remote: upstream.remote,
|
|
475
|
+
upstream_branch: upstream.branch,
|
|
476
|
+
upstream_sha: upstream.sha,
|
|
477
|
+
prediction: predictReviewRefresh(record.path, record.base_sha, target.sha, snapshot.head),
|
|
478
|
+
}, loaded.context.common_dir);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} preparation */
|
|
482
|
+
function pauseReviewRefreshBeforePush(loaded, record, preparation) {
|
|
483
|
+
const live = liveGitSnapshot(record);
|
|
484
|
+
if (!live.head || record.history_operation) die('refresh-review 无法在 push 前形成稳定 rebase 结果。', 2);
|
|
485
|
+
const rewrite = reviewRefreshRewrite(record, record.review_refresh, live.head);
|
|
486
|
+
updateRecord(record, 'review_refresh_rebased', (next) => {
|
|
487
|
+
if (next.review_refresh?.token !== preparation.token) throw new WorktreeTraceError('REVIEW_REFRESH_CHANGED', '暂停前 refresh marker 已变化。');
|
|
488
|
+
next.review_refresh.state = 'rebased';
|
|
489
|
+
next.review_refresh.new_head = live.head;
|
|
490
|
+
next.review_refresh.managed_rebase_token = rewrite.token;
|
|
491
|
+
next.review_refresh.rebased_at = new Date().toISOString();
|
|
492
|
+
}, {
|
|
493
|
+
token: preparation.token,
|
|
494
|
+
old_head: preparation.snapshot.head,
|
|
495
|
+
new_head: live.head,
|
|
496
|
+
target_ref: preparation.target.ref,
|
|
497
|
+
target_sha: preparation.target.sha,
|
|
498
|
+
}, loaded.context.common_dir);
|
|
499
|
+
log(`review refresh 已完成本地 rebase,暂停在 push 前;运行项目门禁后执行 refresh-review ${record.task} --continue。`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** @param {ReturnType<typeof loadRepositoryProfile>} loaded @param {Record<string,any>} record @param {Record<string,any>} preparation */
|
|
503
|
+
function executeReviewRefresh(loaded, record, preparation) {
|
|
504
|
+
const rebased = runManagedRebaseChild(record, [
|
|
505
|
+
'--onto', preparation.target.ref,
|
|
506
|
+
'--expected-head', preparation.snapshot.head,
|
|
507
|
+
'--reason', preparation.reason,
|
|
508
|
+
], preparation.explicitConfig ? loaded.profile_path : null);
|
|
509
|
+
if (!rebased.ok) {
|
|
510
|
+
die(`review refresh 停在 managed rebase;解决冲突并 git add 后运行 refresh-review ${record.task} --continue,或用 --abort 放弃。\n${rebased.out}`);
|
|
511
|
+
}
|
|
512
|
+
record = selectRecord(loadRecords(loaded.context.common_dir), record.task, record.worktree_id);
|
|
513
|
+
if (preparation.pauseBeforePush) {
|
|
514
|
+
pauseReviewRefreshBeforePush(loaded, record, preparation);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
finishReviewRefresh(loaded, record);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 显式评审刷新:命令本身构成远端历史改写授权。默认完成 rebase、精确 lease push 与 watcher 重冻结;
|
|
522
|
+
* 项目 wrapper 可用 --pause-before-push 在本地 rebase 后插入项目门禁,再用 --continue 收口。
|
|
523
|
+
* @param {{positionals:string[],flags:Map<string,unknown>}} args
|
|
524
|
+
*/
|
|
525
|
+
function cmdRefreshReview(args) {
|
|
526
|
+
rejectUnknownFlags(args.flags, ['target', 'reason', 'continue', 'abort', 'pause-before-push', 'id', 'config']);
|
|
527
|
+
if (args.flags.get('abort') && args.flags.get('continue')) die('refresh-review --abort 与 --continue 互斥。', 2);
|
|
528
|
+
const explicitConfig = flag(args.flags, 'config');
|
|
529
|
+
const loaded = loadRepositoryProfile({ explicitConfigPath: explicitConfig });
|
|
530
|
+
let record = selectRecord(loadRecords(loaded.context.common_dir), args.positionals[0] ?? null, flag(args.flags, 'id'));
|
|
531
|
+
if (args.flags.get('abort')) return abortReviewRefresh(loaded, record, explicitConfig);
|
|
532
|
+
if (record.review_refresh) {
|
|
533
|
+
if (!args.flags.get('continue')) {
|
|
534
|
+
const recovery = record.review_refresh.state === 'aborting' ? '--abort' : '--continue(或在允许状态用 --abort)';
|
|
535
|
+
die(`已有 review refresh;运行 refresh-review ${record.task} ${recovery} 恢复。`, 2);
|
|
536
|
+
}
|
|
537
|
+
return continueReviewRefresh(loaded, record, explicitConfig);
|
|
538
|
+
}
|
|
539
|
+
if (args.flags.get('continue')) die('当前没有可恢复的 review refresh。', 2);
|
|
540
|
+
if (record.history_operation) die('已有非 refresh-review 的 managed rebase;请先按原流程恢复或 abort。', 2);
|
|
541
|
+
if (record.task_status !== 'ready_for_review' || record.worktree_state !== 'present') {
|
|
542
|
+
die(`refresh-review 要求 ready_for_review/present,当前 ${record.task_status}/${record.worktree_state}。`, 2);
|
|
543
|
+
}
|
|
544
|
+
const snapshot = historyChangeSnapshot(record, 'refresh-review');
|
|
545
|
+
const watch = record.auto_reclaim && !['disarmed', 'reclaimed'].includes(record.auto_reclaim.state)
|
|
546
|
+
? structuredClone(record.auto_reclaim)
|
|
547
|
+
: null;
|
|
548
|
+
if (!watch || watch.head_sha !== snapshot.head) die('refresh-review 要求 watcher 已冻结当前 live HEAD。', 2);
|
|
549
|
+
const target = resolveReviewRefreshTarget(record, args, snapshot, watch);
|
|
550
|
+
if (!target) return;
|
|
551
|
+
const upstream = freezeReviewRefreshUpstream(record, snapshot);
|
|
552
|
+
const reason = flag(args.flags, 'reason')
|
|
553
|
+
? oneLine(flag(args.flags, 'reason'), 'reason', 240)
|
|
554
|
+
: `评审目标 ${target.ref} 前进,刷新冻结分支`;
|
|
555
|
+
const preparation = {
|
|
556
|
+
token: randomUUID(),
|
|
557
|
+
snapshot,
|
|
558
|
+
watch,
|
|
559
|
+
target,
|
|
560
|
+
upstream,
|
|
561
|
+
reason,
|
|
562
|
+
pauseBeforePush: Boolean(args.flags.get('pause-before-push')),
|
|
563
|
+
explicitConfig,
|
|
564
|
+
};
|
|
565
|
+
record = recordReviewRefreshPrepared(loaded, record, preparation);
|
|
566
|
+
executeReviewRefresh(loaded, record, preparation);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
cmdRefreshReview,
|
|
573
|
+
};
|
|
574
|
+
}
|