@galda/cli 0.10.12 → 0.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/index.html +23 -44
- package/engine/server.mjs +57 -42
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -971,7 +971,7 @@
|
|
|
971
971
|
.gearbtn{display:grid;place-items:center;width:30px;height:28px;border:1px solid var(--hair2);border-radius:8px;background:transparent;color:var(--ink2);cursor:pointer}
|
|
972
972
|
.gearbtn:hover{background:var(--surface2);color:var(--ink)}
|
|
973
973
|
.gearbtn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
|
974
|
-
.gearpop{display:none;position:absolute;top:36px;right:0;z-index:80;background:var(--panel);border-radius:12px;box-shadow:0 10px 30px rgba(20,23,31,.18),0 0 0 1px var(--hair);padding:12px 13px;width:
|
|
974
|
+
.gearpop{display:none;position:absolute;top:36px;right:0;z-index:80;background:var(--panel);border-radius:12px;box-shadow:0 10px 30px rgba(20,23,31,.18),0 0 0 1px var(--hair);padding:12px 13px;width:250px;
|
|
975
975
|
max-height:78vh;overflow-y:auto;scrollbar-width:thin; /* TB edge + colour sections made it taller than the viewport */
|
|
976
976
|
/* opaque chip fill (HANDOFF-v45 §9): a translucent --canvas would let the wallpaper
|
|
977
977
|
bleed through and colour each row differently — this token mixes off the OPAQUE
|
|
@@ -1008,7 +1008,7 @@
|
|
|
1008
1008
|
the rail (left is set on open from the measured rail edge), max-height = viewport −
|
|
1009
1009
|
margins, internal scroll. White-translucent per §3: color-mix 74% + blur28 (theme-tinted).
|
|
1010
1010
|
The node is adopted to <body> in flagship (positionLayout) — .top is display:none there. */
|
|
1011
|
-
body[data-layout="flagship"] .gearpop{position:fixed;top:120px;right:auto;width:
|
|
1011
|
+
body[data-layout="flagship"] .gearpop{position:fixed;top:120px;right:auto;width:300px;max-height:calc(100vh - 160px);border-radius:14px;
|
|
1012
1012
|
background:color-mix(in srgb,var(--panel) 74%,transparent);
|
|
1013
1013
|
-webkit-backdrop-filter:blur(28px) saturate(1.35);backdrop-filter:blur(28px) saturate(1.35)}
|
|
1014
1014
|
/* settings = EDIT SESSION (HANDOFF-v45 §3, flagship .cfoot/.mcpbox port): sticky footer,
|
|
@@ -2183,8 +2183,9 @@
|
|
|
2183
2183
|
<div class="lbrow"><span class="lb">Projects</span>
|
|
2184
2184
|
<button class="ghostbtn morebtn" id="fsMoreBtn" title="Settings"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 00.34 1.87l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.7 1.7 0 00-1.87-.34 1.7 1.7 0 00-1 1.56V21a2 2 0 11-4 0v-.09a1.7 1.7 0 00-1-1.56 1.7 1.7 0 00-1.87.34l-.06.06a2 2 0 11-2.83-2.83l.06-.06a1.7 1.7 0 00.34-1.87 1.7 1.7 0 00-1.56-1H3a2 2 0 110-4h.09a1.7 1.7 0 001.56-1 1.7 1.7 0 00-.34-1.87l-.06-.06a2 2 0 112.83-2.83l.06.06a1.7 1.7 0 001.87.34h.09a1.7 1.7 0 001-1.56V3a2 2 0 114 0v.09a1.7 1.7 0 001 1.56h.09a1.7 1.7 0 001.87-.34l.06-.06a2 2 0 112.83 2.83l-.06.06a1.7 1.7 0 00-.34 1.87v.09a1.7 1.7 0 001.56 1H21a2 2 0 110 4h-.09a1.7 1.7 0 00-1.56 1z"/></svg></button>
|
|
2185
2185
|
<div class="moremenu" id="fsMoreMenu">
|
|
2186
|
-
<button class="ghostbtn" id="fsCust">
|
|
2186
|
+
<button class="ghostbtn" id="fsCust">Customize…</button>
|
|
2187
2187
|
<button class="ghostbtn" id="fsConn">Connect MCP</button>
|
|
2188
|
+
<button class="ghostbtn" id="fsTodoBd">To Do board</button>
|
|
2188
2189
|
<button class="ghostbtn" id="fsHist">History</button>
|
|
2189
2190
|
<button class="ghostbtn" id="fsSet">Settings</button>
|
|
2190
2191
|
</div>
|
|
@@ -5400,19 +5401,6 @@ function renderClawdHero(){
|
|
|
5400
5401
|
tgUpdate();
|
|
5401
5402
|
$('fsMoreBtn').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.toggle('open'); };
|
|
5402
5403
|
document.addEventListener('click', (e) => { if (!e.target.closest('#fsRoot .lbrow')) $('fsMoreMenu').classList.remove('open'); });
|
|
5403
|
-
// Hover-first (Masa指示 2026-07-16): on a real pointer, aligning the cursor over
|
|
5404
|
-
// the gear reveals the menu with no click, and resting on a menu row previews
|
|
5405
|
-
// its panel straight away — click still works underneath for touch. A short
|
|
5406
|
-
// hover-intent delay (120ms) stops a mouse just passing through from flashing
|
|
5407
|
-
// every panel open.
|
|
5408
|
-
if (matchMedia('(hover:hover)').matches) {
|
|
5409
|
-
const lbrow = $('fsMoreBtn').closest('.lbrow');
|
|
5410
|
-
let closeT = null;
|
|
5411
|
-
if (lbrow) {
|
|
5412
|
-
lbrow.addEventListener('mouseenter', () => { clearTimeout(closeT); $('fsMoreMenu').classList.add('open'); });
|
|
5413
|
-
lbrow.addEventListener('mouseleave', () => { closeT = setTimeout(() => $('fsMoreMenu').classList.remove('open'), 150); });
|
|
5414
|
-
}
|
|
5415
|
-
}
|
|
5416
5404
|
const closeMobilePanels = () => {
|
|
5417
5405
|
document.body.classList.remove('laneopen', 'projectsopen');
|
|
5418
5406
|
};
|
|
@@ -5434,21 +5422,11 @@ function renderClawdHero(){
|
|
|
5434
5422
|
// v46 (HANDOFF-v45 §3): the menu entries deep-link into the 4-tab settings panel —
|
|
5435
5423
|
// Customize…→THEME / Connect MCP→MCP / History→HISTORY / Settings→SETTINGS.
|
|
5436
5424
|
// stopPropagation: the document click-away handler otherwise closes it in the same event.
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
if (matchMedia('(hover:hover)').matches) {
|
|
5443
|
-
let t = null;
|
|
5444
|
-
btn.addEventListener('mouseenter', () => { t = setTimeout(open, 120); });
|
|
5445
|
-
btn.addEventListener('mouseleave', () => clearTimeout(t));
|
|
5446
|
-
}
|
|
5447
|
-
};
|
|
5448
|
-
menuItem($('fsCust'), 'theme');
|
|
5449
|
-
menuItem($('fsConn'), 'mcp');
|
|
5450
|
-
menuItem($('fsHist'), 'history');
|
|
5451
|
-
menuItem($('fsSet'), 'settings');
|
|
5425
|
+
$('fsCust').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('theme'); };
|
|
5426
|
+
$('fsConn').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('mcp'); };
|
|
5427
|
+
$('fsTodoBd').onclick = () => { $('fsMoreMenu').classList.remove('open'); $('toggle').click(); }; // To Do board stays reachable from the menu; ⌘K itself now opens the project switcher (HANDOFF-v45 §11)
|
|
5428
|
+
$('fsHist').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('history'); };
|
|
5429
|
+
$('fsSet').onclick = (e) => { e.stopPropagation(); $('fsMoreMenu').classList.remove('open'); custOpen('settings'); };
|
|
5452
5430
|
// parallel-runs switcher (§1.5-1/-7): same toggle+click-away shape as fsMoreMenu above.
|
|
5453
5431
|
// Row content is rebuilt by renderFsFeed() (running list changes live); the open/closed
|
|
5454
5432
|
// class here is left alone by that rebuild so the popover doesn't snap shut mid-render.
|
|
@@ -5836,12 +5814,19 @@ function bootDisconnected(info){
|
|
|
5836
5814
|
const cw = document.getElementById('composerWrap');
|
|
5837
5815
|
if (cw) cw.addEventListener('mousedown', (e) => { if (e.target.closest('#appsel, #modelsel, #effortsel')) return; gate(e); });
|
|
5838
5816
|
|
|
5839
|
-
//
|
|
5840
|
-
//
|
|
5817
|
+
// Come alive automatically: while this device's engine is offline the relay
|
|
5818
|
+
// 503s every engine-backed path (only /, /theme.css, /fonts.css are served);
|
|
5819
|
+
// the instant the agent connects, the same path is proxied to the live engine
|
|
5820
|
+
// (200). So we probe an engine-only endpoint by STATUS and reload when it stops
|
|
5821
|
+
// 503-ing. We must NOT decide this by fetching `/` and grepping its HTML for a
|
|
5822
|
+
// sentinel: this very poll's source contains the string '__GALDA_DISCONNECTED',
|
|
5823
|
+
// so it is baked into every proxied copy of the page — the match is ALWAYS
|
|
5824
|
+
// "found", the reload never fires, and the tab stays stuck on the connect
|
|
5825
|
+
// screen forever ("npx said done but nothing happens"). (fix: status, not body)
|
|
5841
5826
|
setInterval(() => {
|
|
5842
|
-
fetch('/', { cache: 'no-store', credentials: 'same-origin' })
|
|
5843
|
-
if (
|
|
5844
|
-
|
|
5827
|
+
fetch('/api/state', { cache: 'no-store', credentials: 'same-origin' })
|
|
5828
|
+
.then((r) => { if (r.status !== 503) location.reload(); })
|
|
5829
|
+
.catch(() => {});
|
|
5845
5830
|
}, 4000);
|
|
5846
5831
|
}
|
|
5847
5832
|
|
|
@@ -7473,11 +7458,11 @@ function gearSetHTML(){
|
|
|
7473
7458
|
<button class="optchip" data-gsignin="1">Sign in with Google</button></div>
|
|
7474
7459
|
<div class="srow dis" id="licKeyMsg" style="display:none"></div>`;
|
|
7475
7460
|
} else if (bill.plan === 'paid') {
|
|
7476
|
-
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google
|
|
7461
|
+
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><span class="bx-sval">${esc(bill.email)}</span></div>
|
|
7477
7462
|
<div class="srow"><div class="sl">Plan</div><span class="bx-pdot"><i></i>Paid</span></div>
|
|
7478
7463
|
<div class="srow"><div class="sl">Billing<span class="sd">manage or cancel in Stripe</span></div><button class="bx-cchip" data-gportal="1">Manage</button></div>`;
|
|
7479
7464
|
} else {
|
|
7480
|
-
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google
|
|
7465
|
+
billingRow = `<div class="srow"><div class="sl">Account<span class="sd">Signed in with Google</span></div><span class="bx-sval">${esc(bill.email)}</span></div>
|
|
7481
7466
|
<div class="srow"><div class="sl">Plan</div><span class="bx-sval">Free</span></div>
|
|
7482
7467
|
<div class="srow"><div class="sl">To Dos<span class="sd">pending, this install</span></div><span class="bx-sval bx-num">${u.pending ?? 0} / ${lim.pending ?? 5}</span></div>
|
|
7483
7468
|
<div class="srow"><div class="sl">Lifetime</div><span class="bx-sval bx-num">${u.cumulative ?? 0} / ${lim.cumulative ?? 19}</span></div>
|
|
@@ -7539,12 +7524,6 @@ function buildGearPop(){
|
|
|
7539
7524
|
if (upBtn) upBtn.onclick = () => startCheckout();
|
|
7540
7525
|
const portalBtn = pop.querySelector('[data-gportal]');
|
|
7541
7526
|
if (portalBtn) portalBtn.onclick = () => openBillingPortal();
|
|
7542
|
-
const signOutBtn = pop.querySelector('[data-gsignout]');
|
|
7543
|
-
if (signOutBtn) signOutBtn.onclick = async () => {
|
|
7544
|
-
signOutBtn.disabled = true;
|
|
7545
|
-
try { await fetch(withKey('/api/signout'), { method: 'POST' }); }
|
|
7546
|
-
finally { await refresh(); if ($('gearPop')) buildGearPop(); }
|
|
7547
|
-
};
|
|
7548
7527
|
return;
|
|
7549
7528
|
}
|
|
7550
7529
|
if (custTab === 'mcp') {
|
package/engine/server.mjs
CHANGED
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
import { createServer } from 'node:http';
|
|
15
15
|
import { spawn, execFile, spawnSync } from 'node:child_process';
|
|
16
|
-
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync, readdirSync
|
|
16
|
+
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync, readdirSync } from 'node:fs';
|
|
17
17
|
import { emitEvent } from './analytics-client.mjs';
|
|
18
18
|
import { randomBytes, createHash } from 'node:crypto';
|
|
19
19
|
import { resolve, dirname, join, basename } from 'node:path';
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { pathToFileURL } from 'node:url';
|
|
23
|
-
import { needsProjectFolder, folderLabel, buildFolderQuestion, resolveFolderAnswer } from './lib.mjs';
|
|
24
|
-
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd,
|
|
23
|
+
import { needsProjectFolder, folderLabel, buildFolderQuestion, resolveFolderAnswer, needsReviewPreference, buildReviewPreferenceQuestion, resolveReviewPreferenceAnswer } from './lib.mjs';
|
|
24
|
+
import { parseStreamEvents, parsePlan, refinePlanTasks, findOverlappingGoal, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, isForcedStop, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildGoalProofMd, buildGoalPrBody, nextGoalStatus, isPrMerged, isPrApproved, reviewToDoneStatus, advanceReviewGoal, revertReviewGoal, approveGoal, dismissGoal, 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, buildEphemeralSeedLog, buildEphemeralSeedProjects, trimTaskActivityForState, buildAskContext, WORKER_TOOLS, verifyCfAccessJwt, resolveIdentity, goalVisibleTo, clampParallelLimit, canStartMore, nextRunnableTasks, goalsConflict, detectConflicts, isUiChange, shouldCaptureProof, shouldCaptureBaseline, hasTestRelevantChanges, buildExecutionPlan, buildContextHandoffSummary, buildFailurePostmortem, appendFailureMemory, latestFailurePolicy, classifyChangeRisk, checkRunBudget, usageBudgetTokens, isRateLimited, nextResumeDelay, checkFreeTierLimit, resolveEntitlement, resolveCachedEntitlement, FREE_TIER_LIMITS, verifyLicenseToken, shouldEmitSetupCompleted, pickAnalyticsUid, shouldEmitFreeExhausted, detectRequestLanguage, isNothingVerifiable } from './lib.mjs';
|
|
25
25
|
import { openPR } from './pr.mjs';
|
|
26
26
|
import { runVerification, exerciseUi } from './verify.mjs';
|
|
27
27
|
|
|
@@ -2160,6 +2160,11 @@ async function finishGoalIfComplete(goal, project, activityTask = null) {
|
|
|
2160
2160
|
// point (all its tasks are done), so its conflictsWith is accurate even if
|
|
2161
2161
|
// PR creation itself fails (e.g. no `gh` auth) or is still no-op'd (no code).
|
|
2162
2162
|
updateGoalConflicts(goal);
|
|
2163
|
+
// Computed BEFORE createGoalPR (not inside verifyGate, which used to run
|
|
2164
|
+
// after) so the PR body's "確認ポイント" headline is available the first
|
|
2165
|
+
// time the PR is opened, not only on the review card (Masa 2026-07-16).
|
|
2166
|
+
goalAct('Writing the review summary from the worker report and diff.');
|
|
2167
|
+
goal.reviewSummary = await summarizeForReview(goal, siblings, wdir, reviewDefinition);
|
|
2163
2168
|
goalAct('Checking changed files and preparing the PR branch if code changed.');
|
|
2164
2169
|
await createGoalPR(goal, prProject, siblings, reviewDefinition, activityTask);
|
|
2165
2170
|
// goal 427 §4: classify the change's risk from the SAME changed-files/diff
|
|
@@ -2228,8 +2233,13 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
2228
2233
|
goal.testResult = { ran: false, skipped: true, skippedReason: changedFiles.length ? 'documentation-only change' : 'no changed files' };
|
|
2229
2234
|
goalAct(`Skipped project tests: ${goal.testResult.skippedReason}.`);
|
|
2230
2235
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2236
|
+
// Normally already set by finishGoalIfComplete before the PR was opened
|
|
2237
|
+
// (so the PR body can include it too); only fall back here for the
|
|
2238
|
+
// standalone /reverify path or any older goal that never got one.
|
|
2239
|
+
if (!goal.reviewSummary) {
|
|
2240
|
+
goalAct('Writing the review summary from the worker report and diff.');
|
|
2241
|
+
goal.reviewSummary = await summarizeForReview(goal, siblings, wdir, reviewDefinition);
|
|
2242
|
+
}
|
|
2233
2243
|
const uiTasks = siblings.filter((t) => isUiChange(t.changedFiles));
|
|
2234
2244
|
// Only require/capture proof for projects we can actually boot & screenshot
|
|
2235
2245
|
// (Manager-shaped). A generic web repo that edits a stylesheet can't be
|
|
@@ -2371,26 +2381,12 @@ async function createGoalPR(goal, project, goalTasks, reviewDefinition = DEFAULT
|
|
|
2371
2381
|
// (Masa: 「ユーザーの言語で」).
|
|
2372
2382
|
const lang = reviewDefinition.language === 'auto' ? detectRequestLanguage(goal.text) : (reviewDefinition.language || 'ja');
|
|
2373
2383
|
const L = (en, ja) => (lang === 'en' ? en : ja);
|
|
2374
|
-
const bodyFor = (branch) =>
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
] : []),
|
|
2381
|
-
L('## Goal', '## ゴール'),
|
|
2382
|
-
'',
|
|
2383
|
-
goal.text.slice(0, 1500),
|
|
2384
|
-
'',
|
|
2385
|
-
...buildReviewRuleSection(reviewDefinition.description),
|
|
2386
|
-
L(`Implemented by Manager for AI: the goal was decomposed into ${goalTasks.length} task(s), each executed by a Claude Code worker (flat-rate, no extra API), tests written and run per task.${proofItems.length ? ' Tasks with a pass condition were verified independently in a headless browser.' : ''}`,
|
|
2387
|
-
`Manager for AI が実装: ゴールを${goalTasks.length}個のタスクに分解し、Claude Code worker が順に実行(既存サブスク・追加API無し・タスク毎にテストを作成し実行)。${proofItems.length ? '成功条件つきタスクは headless ブラウザで独立検証済み。' : ''}`),
|
|
2388
|
-
'',
|
|
2389
|
-
L('## Tasks and worker reports', '## タスクとworker報告'),
|
|
2390
|
-
...goalTasks.map((t) => `\n### ${t.num} ${t.title} — ${t.status} (${t.secs}s)\n${(t.result ?? '').slice(0, 800)}`),
|
|
2391
|
-
'',
|
|
2392
|
-
L(`Changed files: ${files.map((f) => `\`${f}\``).join(' ')}`, `変更ファイル: ${files.map((f) => `\`${f}\``).join(' ')}`),
|
|
2393
|
-
].join('\n');
|
|
2384
|
+
const bodyFor = (branch) => buildGoalPrBody({
|
|
2385
|
+
lang, goal, goalTasks, files, owner, branch, proofRel,
|
|
2386
|
+
proofItemsCount: proofItems.length,
|
|
2387
|
+
reviewDescription: reviewDefinition.description,
|
|
2388
|
+
reviewSummary: goal.reviewSummary,
|
|
2389
|
+
});
|
|
2394
2390
|
const title = `Manager for AI: ${isRework ? L('rework — ', '再作業 — ') : ''}${goal.plan?.[0] ?? goal.text.slice(0, 60)}${goal.plan?.length > 1 ? L(` +${goal.plan.length - 1} more`, ` 他${goal.plan.length - 1}件`) : ''}`;
|
|
2395
2391
|
const { url, branch } = openPR({
|
|
2396
2392
|
repoDir: project.dir, slug: `goal-${goal.id}`,
|
|
@@ -2401,7 +2397,7 @@ async function createGoalPR(goal, project, goalTasks, reviewDefinition = DEFAULT
|
|
|
2401
2397
|
extraCopies,
|
|
2402
2398
|
extraWrites: proofItems.length ? [{
|
|
2403
2399
|
destRel: `${proofRel}/PROOF.md`,
|
|
2404
|
-
content: buildGoalProofMd({ goalText: goal.text, items: proofItems }),
|
|
2400
|
+
content: buildGoalProofMd({ goalText: goal.text, items: proofItems, lang }),
|
|
2405
2401
|
}] : [],
|
|
2406
2402
|
...(isRework ? { existingBranch: goal.prBranch, existingPrUrl: goal.pr } : {}),
|
|
2407
2403
|
});
|
|
@@ -2556,16 +2552,6 @@ const server = createServer(async (req, res) => {
|
|
|
2556
2552
|
return;
|
|
2557
2553
|
}
|
|
2558
2554
|
|
|
2559
|
-
// POST /api/signout — forget the local license so the settings panel can
|
|
2560
|
-
// offer a plain "Log out" instead of requiring the `--signin` CLI flag to
|
|
2561
|
-
// switch Google accounts. Clears the on-disk token and the in-memory cache;
|
|
2562
|
-
// idempotent when already signed out.
|
|
2563
|
-
if (url.pathname === '/api/signout' && req.method === 'POST') {
|
|
2564
|
-
if (existsSync(licenseFile)) unlinkSync(licenseFile);
|
|
2565
|
-
licenseState = { email: null, isPaying: false, verifiedAt: 0 };
|
|
2566
|
-
return json(res, 200, { ok: true });
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
2555
|
// GET /api/signin-url — mints a one-time nonce and returns the billing
|
|
2570
2556
|
// Worker's /connect URL for "Sign in with Google". The app opens this in the
|
|
2571
2557
|
// browser; Google bounces back to /oauth/callback (below) with a token.
|
|
@@ -2760,15 +2746,22 @@ const server = createServer(async (req, res) => {
|
|
|
2760
2746
|
// for pending/review items (they never spawn a worker).
|
|
2761
2747
|
const runnable = !pending && !review;
|
|
2762
2748
|
const needsFolder = runnable && needsProjectFolder({ dir: project.dir, homeDir: HOME, isRepo: isGitRepo(project.dir) });
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
|
|
2749
|
+
const lang = /[-ヿ㐀-鿿]/.test(text) ? 'ja' : 'en';
|
|
2750
|
+
const folderQuestion = needsFolder ? buildFolderQuestion(detectRepos(HOME), { lang }) : undefined;
|
|
2751
|
+
// Task 5 (PR運用ヒアリング): once the folder is settled, ask — once per
|
|
2752
|
+
// project, before its first runnable goal — whether this project wants
|
|
2753
|
+
// PRs. Skipped this round if a folder question already claimed the
|
|
2754
|
+
// needsInput slot; that goal gets rebound to a real repo via /answer
|
|
2755
|
+
// and the NEXT goal for that project is what actually asks this.
|
|
2756
|
+
const needsReviewPref = runnable && !needsFolder && needsReviewPreference({ askedBefore: project.reviewPrefAsked });
|
|
2757
|
+
const reviewPrefQuestion = needsReviewPref ? buildReviewPreferenceQuestion({ lang }) : undefined;
|
|
2758
|
+
if (needsReviewPref) { project.reviewPrefAsked = true; saveProjects(); }
|
|
2766
2759
|
const goalAgent = workerAgent(agent);
|
|
2767
2760
|
const priorFailureMemory = recentProjectFailureMemory(projectId, text.trim());
|
|
2768
2761
|
const goal = {
|
|
2769
2762
|
id: nextId++, projectId, text: text.trim().slice(0, 8000),
|
|
2770
|
-
status: needsFolder ? 'needsInput' : review ? 'review' : pending ? 'pending' : 'planning',
|
|
2771
|
-
question: folderQuestion,
|
|
2763
|
+
status: (needsFolder || needsReviewPref) ? 'needsInput' : review ? 'review' : pending ? 'pending' : 'planning',
|
|
2764
|
+
question: folderQuestion || reviewPrefQuestion,
|
|
2772
2765
|
reviewOnly: review ? true : undefined,
|
|
2773
2766
|
note: review && typeof note === 'string' && note.trim() ? note.trim().slice(0, 8000) : undefined,
|
|
2774
2767
|
wantsPR: review ? false : resolveWantsPR(pr, text, getReviewDefinition(projectId).defaultWantsPR),
|
|
@@ -2798,7 +2791,7 @@ const server = createServer(async (req, res) => {
|
|
|
2798
2791
|
// pending = deliberately shelved (Phase 2 material): never planned,
|
|
2799
2792
|
// never queued, until POST /api/goals/:id/activate. review-only items
|
|
2800
2793
|
// are terminal-until-human too, so they also skip planGoal.
|
|
2801
|
-
if (runnable && !needsFolder) {
|
|
2794
|
+
if (runnable && !needsFolder && !needsReviewPref) {
|
|
2802
2795
|
planGoal(goal).catch((e) => {
|
|
2803
2796
|
goal.status = 'failed'; goal.prError = String(e).slice(0, 300); saveGoal(goal);
|
|
2804
2797
|
});
|
|
@@ -3125,6 +3118,28 @@ const server = createServer(async (req, res) => {
|
|
|
3125
3118
|
saveGoal(goal);
|
|
3126
3119
|
return json(res, 200, goal);
|
|
3127
3120
|
}
|
|
3121
|
+
// Review-preference answer (task 5 onboarding funnel): fold into the
|
|
3122
|
+
// project's review definition (defaultWantsPR) rather than goal.text —
|
|
3123
|
+
// like the folder question, this is project setup, not a task detail.
|
|
3124
|
+
if (goal.question?.kind === 'reviewPref') {
|
|
3125
|
+
const project = projects.find((p) => p.id === goal.projectId);
|
|
3126
|
+
const patch = resolveReviewPreferenceAnswer(answer, goal.question);
|
|
3127
|
+
if (project) {
|
|
3128
|
+
const merged = validateReviewDefinition({ ...getReviewDefinition(project.id), ...patch });
|
|
3129
|
+
if (merged.ok) {
|
|
3130
|
+
reviewDefinitions[project.id] = merged.definition;
|
|
3131
|
+
saveReviewDefinitions();
|
|
3132
|
+
send({ ev: 'review-definition', projectId: project.id, reviewDefinition: merged.definition });
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
goal.wantsPR = resolveWantsPR(undefined, goal.text, patch.defaultWantsPR);
|
|
3136
|
+
goal.question = null;
|
|
3137
|
+
goal.clarified = true;
|
|
3138
|
+
goal.status = 'planning';
|
|
3139
|
+
saveGoal(goal);
|
|
3140
|
+
planGoal(goal).catch((e) => { goal.status = 'failed'; goal.prError = String(e).slice(0, 300); saveGoal(goal); });
|
|
3141
|
+
return json(res, 200, goal);
|
|
3142
|
+
}
|
|
3128
3143
|
goal.text = `${goal.text}\n[確認: ${goal.question?.text ?? ''} → ${answer}]`;
|
|
3129
3144
|
goal.clarified = true; // answered once → planGoal must never re-ask (persisted via saveGoal)
|
|
3130
3145
|
goal.question = null;
|
package/package.json
CHANGED