@galda/cli 0.10.66 → 0.10.68
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/app/index.html +40 -12
- package/engine/lib.mjs +10 -7
- package/engine/server.mjs +6 -3
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -5945,6 +5945,15 @@ function openGoalOrCard(goalId){
|
|
|
5945
5945
|
if (g && ['blocked', 'partial', 'failed', 'interrupted'].includes(g.status) && typeof saOpen === 'function') { saOpen(goalId, true); return; }
|
|
5946
5946
|
openGoalDetail(goalId);
|
|
5947
5947
|
}
|
|
5948
|
+
// List / board 共通の詳細導線。Review は review ledger、Needs you は attention card、
|
|
5949
|
+
// それ以外は通常の goal detail を開く。
|
|
5950
|
+
function fsOpenGoalDetail(goalId){
|
|
5951
|
+
const g = state.goals.find((x) => x.id === goalId);
|
|
5952
|
+
if (!g) return;
|
|
5953
|
+
goToGoalInChat(goalId);
|
|
5954
|
+
if (g.status === 'review') openReviewChecklist(goalId);
|
|
5955
|
+
else openGoalOrCard(goalId);
|
|
5956
|
+
}
|
|
5948
5957
|
function closeGoalDetail(){
|
|
5949
5958
|
$('goalDetailOverlay').classList.remove('show');
|
|
5950
5959
|
const goalId = state.goalDetailOpen;
|
|
@@ -6692,12 +6701,22 @@ function renderFsBoard(){
|
|
|
6692
6701
|
h += `<div class="lbc-meter"><span class="bar"><i style="width:${Math.round(done / total * 100)}%"></i></span><span class="frac">${done}/${total}</span></div>`;
|
|
6693
6702
|
}
|
|
6694
6703
|
}
|
|
6695
|
-
return `<div class="lbc${quiet ? ' is-quiet' : ''}">${h}</div>`;
|
|
6704
|
+
return `<div class="lbc${quiet ? ' is-quiet' : ''}" data-fsgoal="${g.id}">${h}</div>`;
|
|
6696
6705
|
};
|
|
6697
6706
|
const no = (g) => goalNo.get(g.id) ?? Infinity;
|
|
6698
6707
|
const bucket = { todo: [], doing: [], needs: [], review: [], done: [], later: [], rejected: [] };
|
|
6699
6708
|
for (const g of pgoals) bucket[fsBoardBucket(g, list)].push(g); // 'rejected' is collected but never rendered as a column below — closed goals disappear from the board
|
|
6700
6709
|
for (const k of Object.keys(bucket)) bucket[k].sort((a, b) => k === 'done' ? no(b) - no(a) : no(a) - no(b));
|
|
6710
|
+
// List と同じ digest を使い、同一 PR にまとまる Review は看板でも1件として扱う。
|
|
6711
|
+
const reviewDigest = buildReviewDigest({ goals: pgoals, tasks: list });
|
|
6712
|
+
const reviewCard = (r) => {
|
|
6713
|
+
const g0 = pgoals.find((g) => g.id === r.goalIds[0]);
|
|
6714
|
+
const n = r.reviewNumber;
|
|
6715
|
+
return `<div class="lbc" data-fsgoal="${r.goalIds[0]}"><div class="lbc-hd"><span class="lbc-dot rev"></span>`
|
|
6716
|
+
+ (n != null ? `<span class="lbc-n">#${n}</span>` : '')
|
|
6717
|
+
+ `</div><div class="lbc-t">${tt(r.plan ? r.checkLine : (g0?.text || g0?.plan?.[0] || r.checkLine), 90)}</div></div>`;
|
|
6718
|
+
};
|
|
6719
|
+
bucket.review = reviewDigest;
|
|
6701
6720
|
// To Do / Doing / Needs you / Review / Done / Later — the original design's columns
|
|
6702
6721
|
// (Masa 2026-07-16). "Needs you" = a clarifying question is waiting on the user —
|
|
6703
6722
|
// distinct from "blocked" (an error needing retry/archive, which stays in Doing, the
|
|
@@ -6706,11 +6725,15 @@ function renderFsBoard(){
|
|
|
6706
6725
|
col('To Do', 'var(--ink3)', bucket.todo.length, bucket.todo.map(goalCard).join(''), 'todo')
|
|
6707
6726
|
+ col('Doing', 'var(--green)', bucket.doing.length, bucket.doing.map(goalCard).join(''), 'doing')
|
|
6708
6727
|
+ (bucket.needs.length ? col('Needs you', '#ffca16', bucket.needs.length, bucket.needs.map(goalCard).join(''), 'needs') : '')
|
|
6709
|
-
+ col('Review', '#8390f2',
|
|
6728
|
+
+ col('Review', '#8390f2', reviewDigest.length, reviewDigest.map(reviewCard).join(''), 'review')
|
|
6710
6729
|
+ col('Done', 'var(--ink3)', bucket.done.length, bucket.done.slice(0, 40).map(goalCard).join(''), 'done')
|
|
6711
6730
|
+ col('Later', 'var(--ink3)', bucket.later.length, bucket.later.map(goalCard).join(''), 'later');
|
|
6712
6731
|
for (const b of el.querySelectorAll('[data-fsnew]')) b.onclick = () => $('input')?.focus(); // every + New / header + focuses the composer
|
|
6713
6732
|
for (const b of el.querySelectorAll('[data-fspause]')) b.onclick = togglePauseActive; // Doing列の一時停止/再生
|
|
6733
|
+
for (const c of el.querySelectorAll('[data-fsgoal]')) c.onclick = (e) => {
|
|
6734
|
+
if (e.target.closest('button, a')) return;
|
|
6735
|
+
fsOpenGoalDetail(Number(c.dataset.fsgoal));
|
|
6736
|
+
};
|
|
6714
6737
|
renderFsWait(bucket);
|
|
6715
6738
|
}
|
|
6716
6739
|
// Doing列の一時停止/再生 (Masa 2026-07-18・Spotify式トグル). 楽観的にローカルの
|
|
@@ -6934,7 +6957,7 @@ function renderFsTodo(){
|
|
|
6934
6957
|
// renders only when they exist. Same endpoints as the old layout's data-pstart/pdel:
|
|
6935
6958
|
// Start = POST /api/goals/:id/activate, Delete = DELETE /api/goals/:id (confirm).
|
|
6936
6959
|
// No mono # — pending goals are never planned, so they carry no task number yet.
|
|
6937
|
-
const laterRows = later.map((g) => `<div class="trow up dim"><span class="st"></span><span class="tt">${tt(g.text, 70)}</span>
|
|
6960
|
+
const laterRows = later.map((g) => `<div class="trow up dim" data-fsgoalrow="${g.id}"><span class="st"></span><span class="tt">${tt(g.text, 70)}</span>
|
|
6938
6961
|
<span class="acts"><button class="ab txt" data-fspstart="${g.id}" title="Queue it now">Start</button><button class="ab txt" data-fspdel="${g.id}" title="Delete">Delete</button></span></div>`).join('');
|
|
6939
6962
|
// reference numbers (HANDOFF-v45 §11): a #NN prefix on every To Do / Review item's name.
|
|
6940
6963
|
// Same goal = same number everywhere (goalReviewNumber = goal.id, unique & stable per
|
|
@@ -6949,11 +6972,11 @@ function renderFsTodo(){
|
|
|
6949
6972
|
running.filter((t) => !pausedGoalIds.has(t.goalId)).map((t) => {
|
|
6950
6973
|
const goal = pgoals.find((g) => g.id === t.goalId);
|
|
6951
6974
|
const title = t.reply ? (goal?.reviewSummary?.check || goal?.plan?.[0] || goal?.text || t.title) : t.title;
|
|
6952
|
-
return `<div class="drow"><div class="trow now"><span class="st run"></span>${noSpan(t.goalId)}<span class="tt">${tt(title)}</span><span class="pct num">${progressFor(t)}%</span>${fsActs(t.goalId)}</div>
|
|
6975
|
+
return `<div class="drow"><div class="trow now" data-fsgoalrow="${t.goalId}"><span class="st run"></span>${noSpan(t.goalId)}<span class="tt">${tt(title)}</span><span class="pct num">${progressFor(t)}%</span>${fsActs(t.goalId)}</div>
|
|
6953
6976
|
<div class="tbar"><i style="width:${progressFor(t)}%"></i></div></div>`;
|
|
6954
6977
|
}).join('')
|
|
6955
6978
|
// 一時停止したゴールは Doing に「Paused」1行(アンバー)で留める+Resume。走行/中断行は上と needsRows で抑止済み。
|
|
6956
|
-
+ pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume — 再開">Resume</button></div></div>`).join('');
|
|
6979
|
+
+ pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume — 再開">Resume</button></div></div>`).join('');
|
|
6957
6980
|
// NEEDS YOU: stopped/errored work that used to fold into Doing, now under its own caption so
|
|
6958
6981
|
// it no longer reads as "still in flight" (Masa 2026-07-19). Three kinds share it, each
|
|
6959
6982
|
// keeping the affordances it already had — nothing about the failed-task and blocked-goal
|
|
@@ -6978,13 +7001,13 @@ function renderFsTodo(){
|
|
|
6978
7001
|
const reason = String(t.result || '').trim();
|
|
6979
7002
|
const reasonLine = reason && reason.toLowerCase() !== word.toLowerCase()
|
|
6980
7003
|
? `<div class="msg"><span class="x ${isInt ? 'int' : ''}">${icon}</span>${tt(reason, 180)}</div>` : '';
|
|
6981
|
-
return `<div class="drow err${isInt ? ' int' : ''}"><div class="trow now"><span class="st run ${isInt ? 'int' : 'bad'}"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span><span class="stword ${isInt ? 'int' : ''}">${word}</span>${fsActs(t.goalId)}</div>
|
|
7004
|
+
return `<div class="drow err${isInt ? ' int' : ''}"><div class="trow now" data-fsgoalrow="${t.goalId}"><span class="st run ${isInt ? 'int' : 'bad'}"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span><span class="stword ${isInt ? 'int' : ''}">${word}</span>${fsActs(t.goalId)}</div>
|
|
6982
7005
|
<div class="errline">${reasonLine}
|
|
6983
7006
|
<div class="acts2"><button class="errbtn" data-fsretry="${t.id}">${label}</button><button class="errbtn" data-fslog="${t.id}">View log</button></div></div></div>`; }).join('')
|
|
6984
|
-
+ blocked.map((g) => g.blocked?.kind === 'paused' ? '' : `<div class="drow err"><div class="trow now"><span class="st run bad"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword">Failed</span></div>
|
|
7007
|
+
+ blocked.map((g) => g.blocked?.kind === 'paused' ? '' : `<div class="drow err"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run bad"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword">Failed</span></div>
|
|
6985
7008
|
<div class="errline"><div class="msg"><span class="x">✗</span>${tt(g.blocked?.reason ?? 'blocked', 180)}</div>
|
|
6986
7009
|
<div class="acts2"><button class="errbtn" data-fsreverify="${g.id}">Retry</button><button class="errbtn" data-fsglog="${g.id}">View log</button><button class="errbtn" data-fsarchive="${g.id}">Archive</button></div></div></div>`).join('')
|
|
6987
|
-
+ needsInput.map((g) => `<div class="drow err int"><div class="trow now"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.text || g.plan?.[0], 60)}</span><span class="stword int">Needs input</span></div>
|
|
7010
|
+
+ needsInput.map((g) => `<div class="drow err int"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.text || g.plan?.[0], 60)}</span><span class="stword int">Needs input</span></div>
|
|
6988
7011
|
<div class="errline"><div class="msg"><span class="x int">?</span>${tt(g.question?.text || 'A question is waiting on you', 180)}</div>
|
|
6989
7012
|
<div class="acts2"><button class="errbtn" data-fsglog="${g.id}">Answer</button></div></div></div>`).join('');
|
|
6990
7013
|
// Parked over-cap replies ride at the TOP of To Do (above the ordinary Next-up
|
|
@@ -6993,7 +7016,7 @@ function renderFsTodo(){
|
|
|
6993
7016
|
const canUseCodex = state.auth?.reason === 'rate-limited' && (state.agents || []).includes('codex');
|
|
6994
7017
|
const codexAction = (goalId, agent) => canUseCodex && agent === 'claude-code'
|
|
6995
7018
|
? `<button class="ab txt" data-fscodex="${goalId}" title="Start now with Codex">Run with Codex</button>` : '';
|
|
6996
|
-
const parkedRows = parkedReplyGoals.map((g) => `<div class="trow up" data-goalid="${g.id}"><span class="st"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword" style="color:var(--ink3)">Queued</span>${codexAction(g.id, g.agent)}${fsActs(g.id)}</div>`).join('');
|
|
7019
|
+
const parkedRows = parkedReplyGoals.map((g) => `<div class="trow up" data-goalid="${g.id}" data-fsgoalrow="${g.id}"><span class="st"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword" style="color:var(--ink3)">Queued</span>${codexAction(g.id, g.agent)}${fsActs(g.id)}</div>`).join('');
|
|
6997
7020
|
const NCAP = 6;
|
|
6998
7021
|
const upShown = fsUI.todoMore ? queued : queued.slice(0, NCAP);
|
|
6999
7022
|
const seenGoals = new Set();
|
|
@@ -7008,7 +7031,7 @@ function renderFsTodo(){
|
|
|
7008
7031
|
<div class="reps">${reps.map((r) => `<div class="rep">${tt(r.detail ?? r.title, 120)}</div>`).join('')}</div></div>`;
|
|
7009
7032
|
}
|
|
7010
7033
|
}
|
|
7011
|
-
return `<div class="trow up${i >= 2 ? ' dim' : ''}" data-goalid="${t.goalId ?? ''}" data-taskid="${t.id}"><span class="st"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span>${codexAction(t.goalId, t.agent)}${fsActs(t.goalId)}</div>${grp}`;
|
|
7034
|
+
return `<div class="trow up${i >= 2 ? ' dim' : ''}" data-goalid="${t.goalId ?? ''}" data-taskid="${t.id}"${t.goalId != null ? ` data-fsgoalrow="${t.goalId}"` : ''}><span class="st"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span>${codexAction(t.goalId, t.agent)}${fsActs(t.goalId)}</div>${grp}`;
|
|
7012
7035
|
}).join('');
|
|
7013
7036
|
// CDO review 2026-07-08 §7: this header count used to be queued.length alone (the
|
|
7014
7037
|
// "Next up" pool only) — so a project sitting in Doing/Later with an empty Next-up
|
|
@@ -7035,20 +7058,25 @@ function renderFsTodo(){
|
|
|
7035
7058
|
<div class="tl">
|
|
7036
7059
|
${(doingRows || paused) ? `<span class="tlcap tlcap-doing" style="padding-top:0">Doing<button class="tlcap-pause" data-fspause="1" aria-pressed="${paused ? 'true' : 'false'}" title="${paused ? 'Resume — 再開' : 'Pause — 一時停止'}">${paused ? '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>' : '<svg viewBox="0 0 24 24"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>'}</button></span>${doingRows}` : ''}
|
|
7037
7060
|
${needsRows ? `<span class="tlcap needs"${(doingRows || paused) ? '' : ' style="padding-top:0"'}>Needs you</span>${needsRows}` : ''}
|
|
7038
|
-
${rejected.length ? `<span class="tlcap rejcap"${(doingRows || paused) || needsRows ? '' : ' style="padding-top:0"'}>Dismissed<span class="num">${rejected.length}</span></span>${rejected.map((g) => `<div class="trow up rej"><span class="st rej"></span>${noSpan(g.id)}<span class="tt">${tt(g.text, 70)}</span><span class="acts"><button class="ab" data-fsreopen="${g.id}" title="Reopen — back to Review">${FS_ICONS.reopen}</button><button class="ab" data-fsrejdel="${g.id}" title="Delete — discard (closes the PR)">${FS_ICONS.del}</button></span></div>`).join('')}` : ''}
|
|
7061
|
+
${rejected.length ? `<span class="tlcap rejcap"${(doingRows || paused) || needsRows ? '' : ' style="padding-top:0"'}>Dismissed<span class="num">${rejected.length}</span></span>${rejected.map((g) => `<div class="trow up rej" data-fsgoalrow="${g.id}"><span class="st rej"></span>${noSpan(g.id)}<span class="tt">${tt(g.text, 70)}</span><span class="acts"><button class="ab" data-fsreopen="${g.id}" title="Reopen — back to Review">${FS_ICONS.reopen}</button><button class="ab" data-fsrejdel="${g.id}" title="Delete — discard (closes the PR)">${FS_ICONS.del}</button></span></div>`).join('')}` : ''}
|
|
7039
7062
|
${(parkedRows || upRows) ? `<span class="tlcap"${(doingRows || paused) || needsRows || rejected.length ? '' : ' style="padding-top:0"'}>Next up</span>${parkedRows}${upRows}` : ''}
|
|
7040
7063
|
${queued.length > NCAP ? `<div class="morefold" id="fsTodoMore">${fsUI.todoMore ? 'less' : `+${queued.length - NCAP} more`}<span class="g">›</span></div>` : ''}
|
|
7041
7064
|
${laterRows ? `<span class="tlcap">Later</span>${laterRows}` : ''}
|
|
7042
7065
|
<button class="qadd" data-fsnew="1" title="New goal"><span class="paplus"></span>New</button>
|
|
7043
7066
|
</div>
|
|
7044
7067
|
${doneAll.length ? `<div class="doneline${fsUI.doneOpen ? ' open' : ''}" id="fsDoneLine">✓ Done<span class="num">${doneAll.length}</span><span class="fold">▾</span></div>
|
|
7045
|
-
<div id="fsDonelist"${fsUI.doneOpen ? '' : ' hidden'}>${doneAll.map((t) => `<div class="trow up dim done"><span class="st ok"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span></div>`).join('')}</div>` : ''}`;
|
|
7068
|
+
<div id="fsDonelist"${fsUI.doneOpen ? '' : ' hidden'}>${doneAll.map((t) => `<div class="trow up dim done"${t.goalId != null ? ` data-fsgoalrow="${t.goalId}"` : ''}><span class="st ok"></span>${noSpan(t.goalId)}<span class="tt">${tt(t.title)}</span></div>`).join('')}</div>` : ''}`;
|
|
7046
7069
|
el.classList.toggle('folded', !!fsUI.todoFold);
|
|
7047
7070
|
$('fsTodoHd').onclick = () => { fsUI.todoFold = !fsUI.todoFold; el.classList.toggle('folded', fsUI.todoFold); };
|
|
7048
7071
|
const qadd = el.querySelector('.qadd[data-fsnew]'); // + New → focus the composer (the real create path)
|
|
7049
7072
|
if (qadd) qadd.onclick = () => $('input')?.focus();
|
|
7050
7073
|
const dl = el.querySelector('#fsDoneLine');
|
|
7051
7074
|
if (dl) dl.onclick = () => { fsUI.doneOpen = !fsUI.doneOpen; dl.classList.toggle('open', fsUI.doneOpen); el.querySelector('#fsDonelist').hidden = !fsUI.doneOpen; };
|
|
7075
|
+
for (const r of el.querySelectorAll('[data-fsgoalrow]')) r.onclick = (e) => {
|
|
7076
|
+
if (e.target.closest('button, a, input, .acts, .acts2, .errbtn, .ab, .repfold')) return;
|
|
7077
|
+
const id = Number(r.dataset.fsgoalrow);
|
|
7078
|
+
if (Number.isFinite(id)) fsOpenGoalDetail(id);
|
|
7079
|
+
};
|
|
7052
7080
|
for (const b of el.querySelectorAll('[data-fsreopen]')) b.onclick = async (e) => { e.stopPropagation();
|
|
7053
7081
|
const r = await fetch(withKey(`/api/goals/${b.dataset.fsreopen}/reopen`), { method: 'POST' });
|
|
7054
7082
|
if (!r.ok) showErr('Reopen failed.'); else await refresh(); };
|
package/engine/lib.mjs
CHANGED
|
@@ -2008,20 +2008,23 @@ export function isPrMerged({ state, mergedAt } = {}) {
|
|
|
2008
2008
|
return state === 'MERGED' || !!mergedAt;
|
|
2009
2009
|
}
|
|
2010
2010
|
|
|
2011
|
+
export function isPrClosed({ state, mergedAt } = {}) {
|
|
2012
|
+
return state === 'CLOSED' && !mergedAt;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2011
2015
|
// Interpret the same `gh pr view` result's reviewDecision field
|
|
2012
2016
|
// (APPROVED / CHANGES_REQUESTED / REVIEW_REQUIRED / null).
|
|
2013
2017
|
export function isPrApproved({ reviewDecision } = {}) {
|
|
2014
2018
|
return reviewDecision === 'APPROVED';
|
|
2015
2019
|
}
|
|
2016
2020
|
|
|
2017
|
-
// A 'review' goal whose PR merged OR
|
|
2018
|
-
//
|
|
2021
|
+
// A 'review' goal whose PR merged OR closed is done; every other goal status
|
|
2022
|
+
// (including an approved but still-open PR) passes through unchanged — this
|
|
2019
2023
|
// never regresses a goal out of 'review' on its own.
|
|
2020
|
-
export function reviewToDoneStatus({ goalStatus, merged }) {
|
|
2021
|
-
//
|
|
2022
|
-
//
|
|
2023
|
-
|
|
2024
|
-
return goalStatus === 'review' && merged ? 'done' : goalStatus;
|
|
2024
|
+
export function reviewToDoneStatus({ goalStatus, merged, closed }) {
|
|
2025
|
+
// Merge and close both end the GitHub review lifecycle. Approval alone does
|
|
2026
|
+
// not: an approved but still-open PR remains actionable in Review.
|
|
2027
|
+
return goalStatus === 'review' && (merged || closed) ? 'done' : goalStatus;
|
|
2025
2028
|
}
|
|
2026
2029
|
|
|
2027
2030
|
// Build the proof PR body (EN + JA, no emoji) and the PROOF.md committed to
|
package/engine/server.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
23
23
|
import { needsProjectFolder, folderLabel, chooseFolderScript, parseChosenFolder, buildFolderQuestion, resolveFolderAnswer, classifyFolderTarget, buildFolderInitConfirm, isFolderInitConfirmed, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer, routeQuestionAnswer, notUnderstoodNote, parseRelocalizedQuestion, classifyAuthProbe, authBannerText, makeSigninChallenge, parseClaimResponse, isCommandOnPath } from './lib.mjs';
|
|
24
24
|
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, buildReviewSummaryPrompt, parseReviewSummary, buildReviewRuleSection, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, rejectGoal, reopenGoal, permissionModeFor, isPlanReview, nextAfterPlanApprove, GOAL_MODES, parseSkillFrontmatter, collectSkills, retestGoal, cancelRetestGoal, computeRetestOutcome, revertGoal, planRevertActions, archiveGoal, unarchiveGoal, undismissGoal, parseNumstat, truncateDiffText, sumUsage, resolveEntryUrl, parseGitLog, diffNewCommits, replayQueueLog, reconcileOrphanGoals, reorderQueue, sortQueueByPriority, TASK_PRIORITIES, validateWorkflowColumns, DEFAULT_WORKFLOW_COLUMNS, validateReviewDefinition, DEFAULT_REVIEW_DEFINITION, resolveTestGate, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, workerPrompt, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, pickFoldTarget, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, QUEUED_GOAL_STATUSES, verifyLicenseToken, licenseTokenPayload, shouldRefreshLicense, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, testFailureReason, manualTestRetryPrompt, isNothingVerifiable, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, REPLY_INTENTS, buildReplyIntentPrompt, parseReplyIntent, buildGoalMessage, serializeGoalMessage, parseGoalMessages } from './lib.mjs';
|
|
25
25
|
import { createSerialQueue } from './lib.mjs';
|
|
26
|
+
import { isPrClosed } from './lib.mjs';
|
|
26
27
|
import { collectProjectRules } from './lib.mjs';
|
|
27
28
|
import { FALLBACK_CODEX_MODELS, loadCodexModels } from './codex-models.mjs';
|
|
28
29
|
import { CLAUDE_MODELS } from './claude-models.mjs';
|
|
@@ -1400,7 +1401,7 @@ async function syncAllExternal() {
|
|
|
1400
1401
|
}
|
|
1401
1402
|
|
|
1402
1403
|
// Goals awaiting review carry their PR url in goal.pr; poll `gh pr view` for
|
|
1403
|
-
// each and flip 'review' -> 'done' once GitHub reports it merged or
|
|
1404
|
+
// each and flip 'review' -> 'done' once GitHub reports it merged or closed.
|
|
1404
1405
|
function ghPrView(url) {
|
|
1405
1406
|
return new Promise((res) => {
|
|
1406
1407
|
execFile('gh', ['pr', 'view', url, '--json', 'state,mergedAt,reviewDecision'], (e, out, stderr) => {
|
|
@@ -1418,13 +1419,15 @@ async function syncGoalMerges() {
|
|
|
1418
1419
|
if (!raw) return;
|
|
1419
1420
|
let data;
|
|
1420
1421
|
try { data = JSON.parse(raw); } catch { return; }
|
|
1421
|
-
const
|
|
1422
|
+
const merged = isPrMerged(data);
|
|
1423
|
+
const closed = isPrClosed(data);
|
|
1424
|
+
const newStatus = reviewToDoneStatus({ goalStatus: g.status, merged, closed, approved: isPrApproved(data) });
|
|
1422
1425
|
if (newStatus !== g.status) {
|
|
1423
1426
|
g.status = newStatus; saveGoal(g);
|
|
1424
1427
|
// §3/§7: a merged PR is truly terminal (no rework possible) — reclaim the
|
|
1425
1428
|
// per-goal worktree so .manager-wt/goal-<id> doesn't leak forever. (Manual
|
|
1426
1429
|
// Approve stays reworkable via revert, so we don't clean those up here.)
|
|
1427
|
-
if (newStatus === 'done' &&
|
|
1430
|
+
if (newStatus === 'done' && (merged || closed)) {
|
|
1428
1431
|
const project = projects.find((p) => p.id === g.projectId);
|
|
1429
1432
|
if (project) removeGoalWorkDir(g, project);
|
|
1430
1433
|
}
|
package/package.json
CHANGED