@galda/cli 0.10.92 → 0.10.94
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/engine/completion-contract.mjs +42 -4
- package/engine/lib.mjs +20 -56
- package/engine/server.mjs +72 -43
- package/package.json +1 -1
|
@@ -1,5 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Which user requests are "change requests" whose ONLY acceptable outcome is the
|
|
2
|
+
// change itself. Deliberately wide on Japanese request forms: the first cut only
|
|
3
|
+
// matched the 「〜して」 imperative stem, so ordinary asks like 「〜してほしい」
|
|
4
|
+
// 「〜できる?」「〜消せますか?」「〜にして」 never qualified and their refusals
|
|
5
|
+
// sailed straight into Review as completed work (2026-07-29 audit).
|
|
6
|
+
const CHANGE_REQUEST_RE = new RegExp([
|
|
7
|
+
'修正', '直して', '直せ', 'なおして', '解決', '改善', '一致させ', '統一',
|
|
8
|
+
'実装', '追加', '削除', '変更', '作って', '対応して', '消して', '消せ',
|
|
9
|
+
'出して', '表示して', '止めて', '揃え', '直したい', '外して', '戻して',
|
|
10
|
+
'できるように', 'にして', 'して(?:ほしい|下さい|ください)', 'られるように',
|
|
11
|
+
'\\bfix\\b', '\\bsolve\\b', '\\bresolve\\b', '\\bimplement\\b', '\\bchange\\b',
|
|
12
|
+
'\\bmake\\b', '\\bremove\\b', '\\badd\\b', '\\bhide\\b', '\\bshow\\b',
|
|
13
|
+
].join('|'), 'i');
|
|
14
|
+
|
|
15
|
+
// What counts as "the agent closed the request by declaring it impossible".
|
|
16
|
+
// The earlier list missed the exact phrasings Masa reported —「仕様なので
|
|
17
|
+
// 変えられません」「対応できません」— plus the English "by design / working as
|
|
18
|
+
// intended" family, which is the same move in a different register.
|
|
19
|
+
const REFUSAL_RESULT_RE = new RegExp([
|
|
20
|
+
'直せません', '直せない', '修正できません', '修正できない',
|
|
21
|
+
'解決できません', '解決できない', '実現できません', '実現できない',
|
|
22
|
+
'変えられません', '変えられない', '変更できません', '変更できない',
|
|
23
|
+
'対応できません', '対応できない', '削除できません', '消せません',
|
|
24
|
+
'実装できません', '実装できない', '不可能です', '仕様(?:のため|なので|上)[^。\\n]{0,20}(?:できません|変えられません|変更できません)',
|
|
25
|
+
'cannot\\s+(?:be\\s+)?(?:fix(?:ed)?|solv(?:e|ed)|implement(?:ed)?|chang(?:e|ed)|remov(?:e|ed)|done)',
|
|
26
|
+
"can't\\s+(?:fix|solve|implement|change|remove|do)",
|
|
27
|
+
'unable\\s+to\\s+(?:fix|solve|implement|change|remove)',
|
|
28
|
+
'not\\s+possible\\s+to\\s+(?:fix|change|implement|remove)',
|
|
29
|
+
'by\\s+design[^.\\n]{0,30}(?:cannot|can\'t|won\'t)',
|
|
30
|
+
'working\\s+as\\s+intended',
|
|
31
|
+
].join('|'), 'i');
|
|
32
|
+
|
|
33
|
+
// An explicit, resolved answer beats the refusal phrase: a worker that says
|
|
34
|
+
// "直せませんでしたが、代わりに X を実装しました" DID deliver. Only the shapes
|
|
35
|
+
// where the refusal is the whole outcome should be blocked.
|
|
36
|
+
const RESOLVED_ANYWAY_RE = /(?:代わりに|その代わり|instead[, ]|workaround|回避策)/i;
|
|
3
37
|
|
|
4
38
|
export const COMPLETION_CONTRACT_PROMPT = [
|
|
5
39
|
'MANAGER COMPLETION CONTRACT:',
|
|
@@ -8,10 +42,14 @@ export const COMPLETION_CONTRACT_PROMPT = [
|
|
|
8
42
|
'- Do not report a change request as complete with “cannot fix”, “直せません”, or equivalent. If a real external blocker remains, state the concrete blocker and leave the work explicitly unfinished instead of presenting it for Review as completed.',
|
|
9
43
|
].join('\n');
|
|
10
44
|
|
|
11
|
-
export function completionContractIssue({ request = '', followUp = '', result = '', status = '' } = {}) {
|
|
45
|
+
export function completionContractIssue({ request = '', followUp = '', result = '', status = '', changedFiles = [] } = {}) {
|
|
12
46
|
if (status !== 'done') return null;
|
|
13
47
|
const requested = `${request}\n${followUp}`;
|
|
14
|
-
|
|
48
|
+
const text = String(result);
|
|
49
|
+
if (!CHANGE_REQUEST_RE.test(requested) || !REFUSAL_RESULT_RE.test(text)) return null;
|
|
50
|
+
// Evidence beats phrasing, in both directions: if the run actually changed
|
|
51
|
+
// files, or explicitly delivered an alternative, it is not a closed door.
|
|
52
|
+
if ((changedFiles ?? []).length > 0 || RESOLVED_ANYWAY_RE.test(text)) return null;
|
|
15
53
|
return {
|
|
16
54
|
kind: 'unresolved-refusal',
|
|
17
55
|
reason: '変更・解決依頼に対して「対応不能」で終了しており、要求した結果が実現されていません。',
|
package/engine/lib.mjs
CHANGED
|
@@ -1346,9 +1346,18 @@ export function looksIncomplete(text) {
|
|
|
1346
1346
|
const t = String(text ?? '').trim();
|
|
1347
1347
|
if (!t) return true;
|
|
1348
1348
|
if (t.length >= 200) return false;
|
|
1349
|
-
// A short blurb ending mid-
|
|
1350
|
-
//
|
|
1351
|
-
|
|
1349
|
+
// A short blurb ending mid-clause reads as truncated. This used to be "no
|
|
1350
|
+
// sentence-final punctuation → truncated", which was far too wide: a terse
|
|
1351
|
+
// but FINISHED one-liner ("worker done", "対応済み") has no period either, and
|
|
1352
|
+
// that false positive marked the task `interrupted` → the whole goal `partial`,
|
|
1353
|
+
// so a legitimate 0-file answer/investigation goal never reached Review
|
|
1354
|
+
// (2026-07-29 audit; it is also what deterministically broke
|
|
1355
|
+
// auto-test-rework.test.mjs). Require an actual DANGLING tail instead: a
|
|
1356
|
+
// trailing connector/particle, a comma/colon/opening bracket, or a hyphen
|
|
1357
|
+
// left mid-word. The fragment this rule was written for (goal-783/task-448)
|
|
1358
|
+
// is still caught — by the future-intent opener below, which is the real
|
|
1359
|
+
// signal there.
|
|
1360
|
+
if (!/[.!?。!?」))]$/.test(t) && /(?:[,、::\-—((「【]|\b(?:and|but|or|so|then|to|the|a|an|of|for|with|because|that|which|while|after|before|from|into)|[でをにがはとしてやりつつ])$/i.test(t)) return true;
|
|
1352
1361
|
// goal-783/task-448's actual fragment ("I'll wait for this background
|
|
1353
1362
|
// full-suite run to complete rather than poll.") IS a grammatically
|
|
1354
1363
|
// complete sentence, so the check above misses it — but it's a one-shot
|
|
@@ -2477,59 +2486,14 @@ export function dismissGoal({ goalStatus, outcome = 'continue' } = {}) {
|
|
|
2477
2486
|
// remains unsure. Returning unsure is safe because the UI already presents
|
|
2478
2487
|
// these exact choices without moving the goal.
|
|
2479
2488
|
export const REPLY_INTENTS = [...REPLY_OUTCOMES, 'unsure'];
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
'- "split": a separate piece of work that should not touch this one.',
|
|
2489
|
-
'- "answer": a question about the work. Nothing should be built or changed.',
|
|
2490
|
-
'- "unsure": you genuinely cannot tell. Prefer this over guessing.',
|
|
2491
|
-
'Judge the full meaning. A question followed by a requested change is "continue".',
|
|
2492
|
-
'Also decide which project must receive the requested work.',
|
|
2493
|
-
'- Keep targetProjectId equal to the current project for ordinary fixes to the reviewed work.',
|
|
2494
|
-
'- If the current project is design/docs/prototype-only and the reply asks to implement or launch the chosen design in the real product, select the matching production/code project.',
|
|
2495
|
-
'- Never infer a project that is not listed. If the target is ambiguous, use outcome "unsure" and targetProjectId null.',
|
|
2496
|
-
'Output ONLY one line of raw JSON: {"outcome":"continue|rescope|split|answer|unsure","targetProjectId":"listed-id-or-null"}.',
|
|
2497
|
-
'',
|
|
2498
|
-
`Current project: ${currentProject?.id || '(unknown)'}`,
|
|
2499
|
-
'Available projects:',
|
|
2500
|
-
...(projectLines.length ? projectLines : ['- (none)']),
|
|
2501
|
-
'',
|
|
2502
|
-
'The request that was worked on:',
|
|
2503
|
-
String(goalText ?? '').slice(0, 2000),
|
|
2504
|
-
'',
|
|
2505
|
-
'Their reply:',
|
|
2506
|
-
String(reply ?? '').slice(0, 4000),
|
|
2507
|
-
].join('\n');
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
export function parseReplyIntent(raw) {
|
|
2511
|
-
const unread = { outcome: 'unsure', read: false };
|
|
2512
|
-
if (!raw || typeof raw !== 'string') return unread;
|
|
2513
|
-
const m = raw.match(/\{[\s\S]*\}/);
|
|
2514
|
-
if (!m) return unread;
|
|
2515
|
-
let obj;
|
|
2516
|
-
try { obj = JSON.parse(m[0]); } catch { return unread; }
|
|
2517
|
-
if (!REPLY_INTENTS.includes(obj?.outcome)) return unread;
|
|
2518
|
-
const targetProjectId = typeof obj.targetProjectId === 'string' && obj.targetProjectId.trim()
|
|
2519
|
-
? obj.targetProjectId.trim() : null;
|
|
2520
|
-
return { outcome: obj.outcome, targetProjectId, read: true };
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
// 宛先(docs/design/ONE-CONVERSATION.md §1.2)
|
|
2524
|
-
//
|
|
2525
|
-
// チャットで「#12 の件だけど」と書けたら、そのチケット宛て。**決定論で、我々の推測は
|
|
2526
|
-
// ゼロ**。これが要るのは、今まで「同じ話かどうか」を我々の層が文字列の似かた(0.72)で
|
|
2527
|
-
// 当てずっぽうに決めていたから——当たれば黙って合流し、外れれば別のカードが生えた。
|
|
2528
|
-
// 人が番号を書けるなら、推測する理由が無い。
|
|
2529
|
-
//
|
|
2530
|
-
// 最初の `#N` だけを見る。番号が実在しなければ宛先なしとして扱う(=ふつうに新しい
|
|
2531
|
-
// チケットになる)ので、`#` を含むだけの文章がどこかへ黙って吸い込まれることはない。
|
|
2532
|
-
// 本文は削らない: 言われた言葉をそのまま作業AIへ渡す(P0 の「原文100%」と同じ約束)。
|
|
2489
|
+
// buildReplyIntentPrompt / parseReplyIntent lived here: the prompt and parser for a
|
|
2490
|
+
// utility LLM that decided what a Review reply MEANT before the goal's own agent saw
|
|
2491
|
+
// it. They are gone with their caller. Its "answer" bucket — "a question about the
|
|
2492
|
+
// work. Nothing should be built or changed" — swallowed every politely-phrased
|
|
2493
|
+
// Japanese instruction, because there is no other way to ask politely in Japanese.
|
|
2494
|
+
// Claude Code and Codex already tell a question from an instruction; a second opinion
|
|
2495
|
+
// in front of them could only ever be worse-informed. See the `auto` branch of
|
|
2496
|
+
// /dismiss in engine/server.mjs and engine/test/reply-goes-to-the-agent.test.mjs.
|
|
2533
2497
|
export function parseGoalAddress(text) {
|
|
2534
2498
|
// `(?![\w-])` =「番号の直後に英数字やハイフンが続くなら、それは番号ではない」:
|
|
2535
2499
|
// `#1f6f4a`(色) や `#12-3` を宛先と読み違えない。日本語は \w に入らないので
|
package/engine/server.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { homedir } from 'node:os';
|
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
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, parseRequestedWorkspaceDir } from './lib.mjs';
|
|
24
|
-
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildReviewAttemptLedger, latestGoalOutcomeTask, buildDirtyWorkspacePrBlock, 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, rebasePreviewUrl, shouldCreateGoalPR, normalizePullRequestUrl, 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, autoFoldReviewsEnabled, parseGitUnifiedDiffLocations, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, classifyPrSafety, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildVerificationInfraError, verificationInfraErrorFrom, 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, isNothingVerifiableForPR, isSuspiciouslyIncompleteDone, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, shouldUsePlannerForGoal, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES,
|
|
24
|
+
import { parseStreamEvents, parseCodexEvents, buildCodexArgs, parsePlan, refinePlanTasks, canEditGoal, canDeleteGoal, shouldAskClarification, workerExitReason, classifyWorkerFailure, isForcedStop, taskStatusAfterVerify, workerResultText, taskCountsAsComplete, resolveWantsPR, resolveGoalSource, buildGoalSummary, buildReviewAttemptLedger, latestGoalOutcomeTask, buildDirtyWorkspacePrBlock, 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, rebasePreviewUrl, shouldCreateGoalPR, normalizePullRequestUrl, 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, autoFoldReviewsEnabled, parseGitUnifiedDiffLocations, hasTestRelevantChanges, parseFailingTestNames, classifyTestGate, classifyPrSafety, isInconclusiveTestRun, countInfraFlakes, classifyRunFailures, classifyVerifyGate, buildVerificationInfraError, verificationInfraErrorFrom, 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, isNothingVerifiableForPR, isSuspiciouslyIncompleteDone, classifyComposerIntentHeuristic, detectPauseIntent, buildIntentPrompt, parseIntentResponse, buildBoardSnapshot, validateBoardSnapshot, boardIsEmpty, decideBoardPull, resolveConnectedAgents, pickAvailableAgent, pickUtilityAgent, utilityModel, liveTakeoverDecision, shouldOpenBrowserOnBoot, goalTaskTitle, buildGoalTask, shouldUsePlannerForGoal, RUN_DECL_FILE, parseRunDeclaration, SHOT_DIR, collectShotNames, parseGoalAddress, REPLY_OUTCOMES, buildGoalMessage, serializeGoalMessage, parseGoalMessages, computeInternalQualityMetrics, classifyInternalQualityTaskError, resolveWorkspaceMode, goalSessionFor, setGoalAgentSession, switchGoalAgentSession, parseApprovalRequest } from './lib.mjs';
|
|
25
25
|
import { createSerialQueue } from './lib.mjs';
|
|
26
26
|
import { parseRequirementEvidence, unmappedChangedFiles } from './lib.mjs';
|
|
27
27
|
import { isPrClosed } from './lib.mjs';
|
|
@@ -2813,6 +2813,7 @@ async function runTask(task) {
|
|
|
2813
2813
|
followUp: task.detail,
|
|
2814
2814
|
result: task.result,
|
|
2815
2815
|
status: task.status,
|
|
2816
|
+
changedFiles: task.changedFiles,
|
|
2816
2817
|
});
|
|
2817
2818
|
if (completionIssue) {
|
|
2818
2819
|
task.status = 'interrupted';
|
|
@@ -3047,6 +3048,7 @@ async function runTask(task) {
|
|
|
3047
3048
|
followUp: task.reply ? task.detail : '',
|
|
3048
3049
|
result: task.result,
|
|
3049
3050
|
status: task.status,
|
|
3051
|
+
changedFiles: task.changedFiles,
|
|
3050
3052
|
});
|
|
3051
3053
|
if (completionIssue) {
|
|
3052
3054
|
task.status = 'interrupted';
|
|
@@ -3105,13 +3107,21 @@ function testEnvironmentPreflight(dir, dependencySourceDir = null) {
|
|
|
3105
3107
|
const sources = nodePath ? nodePath.split(process.platform === 'win32' ? ';' : ':') : [];
|
|
3106
3108
|
const hasPuppeteer = sources.some((p) => existsSync(join(p, 'puppeteer-core', 'package.json')));
|
|
3107
3109
|
const npm = spawnSync('npm', ['--version'], { encoding: 'utf8' });
|
|
3110
|
+
// BLOCKING vs ADVISORY (2026-07-29, docs/INCIDENT-2026-07-25-29-INTERFACE-AUDIT.md).
|
|
3111
|
+
// Only `node`/`npm` make `npm test` impossible to START. `dependencies` and
|
|
3112
|
+
// `puppeteer` are properties of THIS repo's own browser suite — most user
|
|
3113
|
+
// projects legitimately have neither, and treating them as blocking meant
|
|
3114
|
+
// runTestsOnce returned "0 failed" for a suite it never ran, which
|
|
3115
|
+
// runProjectTests then published as ok:true. A missing puppeteer-core is
|
|
3116
|
+
// already discounted downstream by countInfraFlakes, so the honest thing is
|
|
3117
|
+
// to run the suite and report the advisory alongside the real result.
|
|
3108
3118
|
const checks = [
|
|
3109
|
-
{ id: 'node', ok: Boolean(process.execPath), detail: process.version },
|
|
3110
|
-
{ id: 'npm', ok: npm.status === 0, detail: npm.status === 0 ? npm.stdout.trim() : 'npm command unavailable' },
|
|
3111
|
-
{ id: 'dependencies', ok: sources.length > 0, detail: sources.length ? `${sources.length} node_modules source(s)` : 'node_modules not found' },
|
|
3112
|
-
{ id: 'puppeteer', ok: hasPuppeteer, detail: hasPuppeteer ? 'puppeteer-core available' : 'puppeteer-core missing; run npm install in the project directory' },
|
|
3119
|
+
{ id: 'node', ok: Boolean(process.execPath), detail: process.version, blocking: true },
|
|
3120
|
+
{ id: 'npm', ok: npm.status === 0, detail: npm.status === 0 ? npm.stdout.trim() : 'npm command unavailable', blocking: true },
|
|
3121
|
+
{ id: 'dependencies', ok: sources.length > 0, detail: sources.length ? `${sources.length} node_modules source(s)` : 'node_modules not found', blocking: false },
|
|
3122
|
+
{ id: 'puppeteer', ok: hasPuppeteer, detail: hasPuppeteer ? 'puppeteer-core available' : 'puppeteer-core missing; run npm install in the project directory', blocking: false },
|
|
3113
3123
|
];
|
|
3114
|
-
return { ok: checks.every((c) => c.ok), checks };
|
|
3124
|
+
return { ok: checks.every((c) => c.ok), canRun: checks.every((c) => c.ok || !c.blocking), checks };
|
|
3115
3125
|
}
|
|
3116
3126
|
function runTestsOnce(dir, dependencySourceDir = null) {
|
|
3117
3127
|
// The timeout is env-overridable so tests can force the killed-run path fast;
|
|
@@ -3119,7 +3129,12 @@ function runTestsOnce(dir, dependencySourceDir = null) {
|
|
|
3119
3129
|
const runTimeout = Number(process.env.MANAGER_TEST_TIMEOUT_MS) || 240000;
|
|
3120
3130
|
return testRunQueue.run(() => new Promise((res) => {
|
|
3121
3131
|
const preflight = testEnvironmentPreflight(dir, dependencySourceDir);
|
|
3122
|
-
|
|
3132
|
+
// Never report an unrun suite as a run with zero failures — runProjectTests
|
|
3133
|
+
// turns `failed === 0` into ok:true, and that is the shape a caller reads as
|
|
3134
|
+
// "verification passed". A suite that could not START is `notRun`.
|
|
3135
|
+
if (!preflight.canRun) {
|
|
3136
|
+
return res({ notRun: true, passed: 0, failed: 0, preflight, timedOut: false, realAssertion: false, infraOnly: true, infraFlakes: 0, detail: preflight.checks.filter((c) => !c.ok && c.blocking).map((c) => c.detail).join('\n'), failingNames: [] });
|
|
3137
|
+
}
|
|
3123
3138
|
const nodePath = testNodePath(dir, dependencySourceDir);
|
|
3124
3139
|
execFile('npm', ['test'], { cwd: dir, timeout: runTimeout, env: { ...process.env, ...(nodePath ? { NODE_PATH: nodePath } : {}) }, maxBuffer: 8 * 1024 * 1024 }, (e, out = '', err = '') => {
|
|
3125
3140
|
const text = `${out}\n${err}`;
|
|
@@ -3158,6 +3173,12 @@ async function runProjectTests(dir, dependencySourceDir = null) {
|
|
|
3158
3173
|
try { hasTest = !!JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).scripts?.test; } catch { /* no package.json */ }
|
|
3159
3174
|
if (!hasTest) return { ran: false };
|
|
3160
3175
|
let r = await runTestsOnce(dir, dependencySourceDir);
|
|
3176
|
+
// The suite could not be started at all (node/npm missing). Report it as
|
|
3177
|
+
// NOT RUN — the same honest shape as the documentation-only / gate-off skips
|
|
3178
|
+
// — instead of ran:true + ok:true, which reads as "verification passed".
|
|
3179
|
+
if (r.notRun) {
|
|
3180
|
+
return { ran: false, skipped: true, skippedReason: `verification preflight blocked: ${r.detail || 'test environment unavailable'}`, preflight: r.preflight ?? null, detail: r.detail ?? '' };
|
|
3181
|
+
}
|
|
3161
3182
|
// Flake resistance: the integration suite spawns real servers; under load a
|
|
3162
3183
|
// startup can time out. Re-run once and take the fewer failures — a real
|
|
3163
3184
|
// failure fails both runs, a flake clears on the retry — so a good goal is
|
|
@@ -3701,10 +3722,17 @@ async function verifyGate(goal, prProject, siblings, baseProject, activityTask =
|
|
|
3701
3722
|
if (goal.testResult.ran && goal.testResult.infraOnly && !goal.testResult.inconclusive) {
|
|
3702
3723
|
goalAct(`Project tests: all ${goal.testResult.infraFlakes || goal.testResult.failed || ''} failure(s) were infra flakes (a real server could not boot / a wait timed out under machine load), not code regressions — not blocking. Re-run when the machine is idle to confirm.`.replace(/\s{2,}/g, ' '));
|
|
3703
3724
|
}
|
|
3704
|
-
|
|
3725
|
+
// Preflight is an ADVISORY note, not a verdict (2026-07-29). It used to force
|
|
3726
|
+
// inconclusive=true, which silently disabled the gate for every project that
|
|
3727
|
+
// lacks this repo's puppeteer suite — i.e. essentially every user project —
|
|
3728
|
+
// while still publishing testResult.ok === true. Now: say what is missing, and
|
|
3729
|
+
// let the REAL run decide. A suite that could not start at all never reaches
|
|
3730
|
+
// here (runProjectTests returns ran:false with skippedReason instead).
|
|
3731
|
+
if (goal.testResult.preflight && !goal.testResult.preflight.ok) {
|
|
3705
3732
|
const missing = goal.testResult.preflight.checks.filter((c) => !c.ok).map((c) => c.id).join(', ');
|
|
3706
|
-
goal.testResult.
|
|
3707
|
-
|
|
3733
|
+
goalAct(goal.testResult.ran
|
|
3734
|
+
? `Verification environment note: ${missing} missing. The suite still ran; browser-dependent tests in it may fail for environment reasons (those are discounted as infra flakes).`
|
|
3735
|
+
: `Project tests were not started: verification preflight failed (${missing}). This is an environment issue, not an implementation failure. Fix the listed dependency or tool, then re-run verification.`);
|
|
3708
3736
|
}
|
|
3709
3737
|
// Baseline diff (Masa 2026-07-19): block ONLY on failures THIS change
|
|
3710
3738
|
// introduced. A test already red on the base — e.g. persist-failure on clean
|
|
@@ -5494,29 +5522,13 @@ const server = createServer(async (req, res) => {
|
|
|
5494
5522
|
// 「聞いてるだけ」への返事 (ONE-CONVERSATION.md §1.3 answer)。
|
|
5495
5523
|
//
|
|
5496
5524
|
// 答えるのは**作業した AI 本人**=その agent の session を resume する。状況一覧しか
|
|
5497
|
-
//
|
|
5498
|
-
//
|
|
5499
|
-
//
|
|
5500
|
-
//
|
|
5501
|
-
//
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
const currentProject = projects.find((project) => project.id === goal.projectId) ?? null;
|
|
5505
|
-
const r = await runUtility({
|
|
5506
|
-
prompt: buildReplyIntentPrompt({ goalText: goal.text, reply, currentProject, projects }),
|
|
5507
|
-
cwd: ROOT,
|
|
5508
|
-
tools: 'Read',
|
|
5509
|
-
});
|
|
5510
|
-
const parsed = parseReplyIntent(r.result);
|
|
5511
|
-
if (!parsed.read) return { outcome: 'unsure', targetProjectId: null };
|
|
5512
|
-
if (parsed.targetProjectId && !projects.some((project) => project.id === parsed.targetProjectId)) {
|
|
5513
|
-
return { outcome: 'unsure', targetProjectId: null };
|
|
5514
|
-
}
|
|
5515
|
-
return { outcome: parsed.outcome, targetProjectId: parsed.targetProjectId };
|
|
5516
|
-
} catch {
|
|
5517
|
-
return { outcome: 'unsure', targetProjectId: null };
|
|
5518
|
-
}
|
|
5519
|
-
}
|
|
5525
|
+
// judgeReplyIntent lived here: a utility-LLM pass that sorted a Review reply into
|
|
5526
|
+
// continue / rescope / split / answer BEFORE the goal's own agent saw it. It is
|
|
5527
|
+
// gone — see the `auto` branch of /dismiss for the measured reason. Its stated
|
|
5528
|
+
// worry was "accidentally starting a ten-minute implementation worker for a
|
|
5529
|
+
// question", but the trade it actually made was the opposite one: it answered
|
|
5530
|
+
// real instructions, and the person paid several round trips to get the work
|
|
5531
|
+
// started at all. Deciding that is the agent's job.
|
|
5520
5532
|
|
|
5521
5533
|
// 知らない別AIでは「なぜそうしたか」に答えられない(Masa確定 2026-07-22)。
|
|
5522
5534
|
// コードは1文字も変わらない: ツールは Read だけ、permissionMode は 'plan'
|
|
@@ -5580,16 +5592,33 @@ async function answerGoalQuestion(goal, question) {
|
|
|
5580
5592
|
|
|
5581
5593
|
if (auto) {
|
|
5582
5594
|
if (!text) return json(res, 400, { error: 'text required' });
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5595
|
+
// The reply goes to the agent exactly as written. Galda does not decide
|
|
5596
|
+
// first what the person meant.
|
|
5597
|
+
//
|
|
5598
|
+
// This used to call a utility LLM (judgeReplyIntent) to sort the reply
|
|
5599
|
+
// into continue / rescope / split / answer before the agent ever saw it.
|
|
5600
|
+
// Its definition of "answer" was "a question about the work. Nothing
|
|
5601
|
+
// should be built or changed" — and Japanese has no other way to make a
|
|
5602
|
+
// polite request. Measured on the operator's own instance 2026-07-25..29:
|
|
5603
|
+
// 「案3で実装出来ますか?」「Aの方針で実装できますか!?」「あれ、prいるよね?」
|
|
5604
|
+
// 「okなんでprなげれますか?」「案がみえないのでローカルで確認できるように貰える?」
|
|
5605
|
+
// were all real instructions, and every one of them could be read as a
|
|
5606
|
+
// question. When it read one that way it created NO task at all, so the
|
|
5607
|
+
// request was answered instead of done and the person had to ask two or
|
|
5608
|
+
// three more times.
|
|
5609
|
+
//
|
|
5610
|
+
// The fix is not a better classifier. Claude Code and Codex already
|
|
5611
|
+
// decide correctly whether a message wants an answer or a change — that
|
|
5612
|
+
// is what they are. Galda's job is the interface around them, not a
|
|
5613
|
+
// second opinion in front of them. Note the `answer` route spawned an
|
|
5614
|
+
// agent too (Read-only, plan mode), so passing the reply through costs
|
|
5615
|
+
// no extra run — it removes one utility-LLM call per reply.
|
|
5616
|
+
//
|
|
5617
|
+
// Explicit routes are untouched: when the PERSON picks rescope / split /
|
|
5618
|
+
// answer from the chips, `requested.outcome` carries it and this branch
|
|
5619
|
+
// never runs. A human choosing where their words go is the interface
|
|
5620
|
+
// working; Galda guessing is not.
|
|
5621
|
+
outcome = 'continue';
|
|
5593
5622
|
}
|
|
5594
5623
|
// 返事の行き先4種 (ONE-CONVERSATION.md §1.3). 既定は今までどおり continue なので、
|
|
5595
5624
|
// outcome を送ってこない今の UI の挙動は1ミリも変わらない。
|
package/package.json
CHANGED