@galda/cli 0.10.109 → 0.10.111

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.
Files changed (3) hide show
  1. package/CLAUDE.md +41 -0
  2. package/app/index.html +20 -28
  3. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -26,12 +26,53 @@
26
26
  - 新しいロジックは可能な限り純関数として `engine/lib.mjs` 等に切り出し、`engine/test/` にテストを追加する。
27
27
  - テストが書けない変更(純UI等)は、その理由と手動確認手順を報告に書く。
28
28
 
29
+ ### テストの書き方(Masa体感「変に判断せずつなぐだけになった」を守る・2026-07-30 追加)
30
+
31
+ 1日で main を2回赤にした型・3日間気付かれなかった型が実在する。以下は**好みでなく再発防止**。
32
+
33
+ - 🔴 **`app/index.html` のソース文字列を正規表現で照合するテストを新しく書かない。**
34
+ `align-items:center` や `outcome?.requirementEvidence?.…` という**式そのもの**を要求していたテストが、
35
+ 実装の書き換えだけで赤くなった(2026-07-29 に PR #574/#584 の2回)。
36
+ **正規表現は「書き方を変えた」と「壊した」を区別できない。**
37
+ → **実際に開いて、描かれたものを読む**。ヘルパは `engine/test/helpers/serve-app.mjs`+`helpers/browser.mjs`、
38
+ 実例は `engine/test/review-delivery-label.test.mjs`/`review-requirement-coverage-ui.test.mjs`。
39
+ - 🔴 **テストを足したら、製品を壊して赤くなることを必ず確認する**(1〜3通り)。落ちないテストは何も証明しない。
40
+ 確認したことと壊し方を PR 本文に書く。
41
+ - 🔴 **テストだけコミットして実装が無い状態を作らない。** 実例2件:
42
+ `5802056`(Reconnect のテストだけ main に入り実装が存在しなかった)/
43
+ **PR #396 が統合時に engine と app の実装を落とし、テストだけ入れた**(main が3日間、
44
+ 存在しない機能のテストで赤かった。復元=PR #586)。
45
+ → 複数レーンの成果を1本に統合する PR では、**テストだけでなく実装が入っているかを機械で照合する**
46
+ (`node tools/conflict-check.mjs` の `test-without-implementation`)。
47
+ - **transient な状態をポーリングで待たない。** 偽ワーカーは数msで返るので `running` は掴めない。
48
+ **残る証跡**(verdict・キューの中身・保存された状態)を待つ。時計を待つテストは飽和で揺れる。
49
+ - **赤を見つけたら、まず単体で再走**して「飽和フレーク」と「本物」を分ける。飽和で31秒かけて落ちるものが実在する
50
+ (`agent-picker-availability`)。素の `origin/main` と比べて自分の変更のせいかを判定する。
51
+ - **古い open PR をマージする前に、今日の `main` に載せて実機で確認する。**
52
+ main が既に別実装で解決済みなのに残っていた PR が実在(#425/#435/#509・全てクローズ)。
53
+ → `node tools/conflict-check.mjs --pr <n>` の `already-in-main` 検査。
54
+
29
55
  ## デザイン(Masa指示 2026-07-06・必読)
30
56
 
31
57
  - **UI(app/index.html・app/theme.css 等)に触る前に、必ず `docs/design/DESIGN-RULES.md` を読むこと**。特に §1.5 レイアウトの憲法(10条)と §5.5 美学。条文に反する実装はせず、変えたい場合は理由を添えて仲田さんの決定を仰ぐ。
32
58
  - app/ のUIファイル編集時はhook(tools/design_rules_hook.sh)が自動でリマインドする。リマインドが出たら読んだ上で作業すること。
33
59
  - 実装時の詳細契約は `docs/design/CTO-HANDOFF.md`(CDO→CTO引き継ぎ書)に従う。**既存パーツで作れない新しい画面・コンポーネントが必要になったら、自作せず実装前にCDOへ依頼すること。**
34
60
 
61
+ ## プロダクトの不変条件(実装前に読む・2026-07-30 追加)
62
+
63
+ **正典 = `docs/PRD.md` §0(薄いインターフェース+4決定)と §0.1(信頼性の不変条件)。** 実装で迷ったらここに戻る。
64
+ Galda の役割は**依頼を Claude Code / Codex に繋ぐこと**で、途中に自前の AI 判断を挟まない。要約すると:
65
+
66
+ - **測っていない割合を主張しない**(キャッシュヒット率は節約ではない)。基準線が無い間は黙る。
67
+ - **判定は必ず最新の試行のもの**(文章が最新・絵や判定が最初、を作らない。同じ形のバグが3か所にあった)。
68
+ - **出荷していないものを成功として祝わない**(未マージでクローズされた PR を done にしない)。
69
+ - **人が頼んでいないものを要求として数えない**(Manager 発の返信を要求カバレッジに載せない)。
70
+ - **止まっているものは「なぜ」を言う**(`stacked` は動いても失敗してもいない=外からは壊れて見える)。
71
+ - **このマシンにできないことは先に言う**(欠けている実行ファイル名でなく、何ができなくなるかを言う)。
72
+ - **助言と停止判断を混ぜない**(`npm test` を開始できないものだけが blocking。混ぜると「走らせていないスイートが0 failed」になる)。
73
+
74
+ 新しく「Galda が判断する」挙動を足したくなったら、**足す前に Masa の決定を仰ぐ**(§0 決定3の自動選択禁止と同じ理由)。
75
+
35
76
  ## 実装規律
36
77
 
37
78
  - 変更は最小限。周辺コードの流儀に合わせる。
package/app/index.html CHANGED
@@ -2279,19 +2279,6 @@
2279
2279
  /* Changed/Tests aligns with the request TEXT, not the number/chevron — same 18px
2280
2280
  the fold body below uses, so both sit under the same column (Masa 2026-07-29). */
2281
2281
  .redesign .rqit .rqev{font-size:12px;color:var(--ink3);margin:5px 0 0;padding-left:18px}
2282
- /* Canonical requirement coverage: one compact ledger, not three competing
2283
- interpretations. Status is text + a small rule/mark (no badge surface),
2284
- and Manager checks stay visually distinct from worker claims. */
2285
- .redesign .rqcov{display:flex;flex-direction:column;margin-top:2px}
2286
- .redesign .rqcov-row{padding:11px 0;border-top:1px solid var(--hair)}
2287
- .redesign .rqcov-row:first-child{padding-top:0;border-top:0}
2288
- .redesign .rqcov-main{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px;align-items:baseline}
2289
- .redesign .rqcov-text{font-size:13.5px;line-height:1.55;color:var(--ink)}
2290
- .redesign .rqcov-state{font:600 10px/1.4 var(--mono);letter-spacing:.02em;color:var(--ink3);white-space:nowrap}
2291
- .redesign .rqcov-state.verified{color:var(--green)}
2292
- .redesign .rqcov-state.failed{color:var(--danger)}
2293
- .redesign .rqcov-detail{margin-top:5px;font-size:11.5px;line-height:1.55;color:var(--ink3)}
2294
- .redesign .rqcov-detail b{font-weight:600;color:var(--ink2)}
2295
2282
  /* A long ask folds with the SAME .lacc pulldown Activity/Conversation use — chevron on
2296
2283
  the peek line, body is the genuine rest (no duplicate, no trailing "…": Masa
2297
2284
  2026-07-29 flagged both the vertical anchor line and the "…" as unnecessary — the
@@ -3498,7 +3485,7 @@ const savedTheme = (() => {
3498
3485
  if (v !== s) localStorage.setItem('theme', v);
3499
3486
  return v;
3500
3487
  })();
3501
- const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: localStorage.getItem('sel:project') || 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], auth: null, expanded: new Set(), openThreads: new Set(), queueOrder: {}, externalActivity: [], selectedGoal: null, composerDetachedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, runningCounts: {}, parallelLimits: null, hostReadiness: [], connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
3488
+ const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: localStorage.getItem('sel:project') || 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], auth: null, expanded: new Set(), openThreads: new Set(), queueOrder: {}, queueWaits: {}, externalActivity: [], selectedGoal: null, composerDetachedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, runningCounts: {}, parallelLimits: null, hostReadiness: [], connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
3502
3489
  // per-message send overrides set via slash-commands (reset after each send,
3503
3490
  // except model which is sticky in localStorage). palette = open command list.
3504
3491
  // Scoped per project (msgByProject) so a skill/mode picked in one project can't leak
@@ -4630,7 +4617,25 @@ function renderQueue(){
4630
4617
  // layouts keep the explanatory sentence.
4631
4618
  const qn = ob.length + stacked.length + planning.length;
4632
4619
  const headHtml = qn > 0 ? `<div class="qhead">${state.layout === 'flagship' ? `Queued · ${qn}` : `${qn} queued · goals become To dos in ~30s`}</div>` : '';
4620
+ // Why this queued goal is not moving, in the SERVER's words (state.queueWaits,
4621
+ // keyed by goal id — engine/lib.mjs buildQueueWaits). The server owns the slot
4622
+ // accounting, so it is the only thing that can answer this without disagreeing
4623
+ // with itself.
4624
+ //
4625
+ // The fallback below is what this used to do always: re-derive the answer in the
4626
+ // browser from running TASKS plus planning goals. That is the accounting #541
4627
+ // replaced — a goal whose task sat in the queue counted for nothing — so the
4628
+ // browser could print "Next up · starts automatically" about a goal the server
4629
+ // had blocked. Kept only for a server older than queueWaits; when the field is
4630
+ // there, the row repeats the server rather than guessing alongside it.
4633
4631
  const stackedMeta = (g) => {
4632
+ const wait = state.queueWaits?.[g.id];
4633
+ if (wait) {
4634
+ const targets = (wait.targetGoalIds ?? []).filter((id) => id !== g.id);
4635
+ const active = targets.length ? ` · active ${targets.map((id) => `#${id}`).join(', ')}` : '';
4636
+ const ops = targets.length ? ' · Run next / switch Agent' : '';
4637
+ return `Waiting · ${wait.reason}${active} · starts when ${wait.condition}${ops}`;
4638
+ }
4634
4639
  const project = state.projects.find((p) => p.id === g.projectId);
4635
4640
  if (project?.paused) return 'Paused · press play to start';
4636
4641
  const activeIds = [...new Set([
@@ -8797,7 +8802,7 @@ async function refresh(){
8797
8802
  setAgentEffort(state.connectedApp);
8798
8803
  renderModelSelect();
8799
8804
  renderEffortSelect();
8800
- state.queueOrder = s.queueOrder ?? {}; state.externalActivity = s.externalActivity ?? [];
8805
+ state.queueOrder = s.queueOrder ?? {}; state.queueWaits = s.queueWaits ?? {}; state.externalActivity = s.externalActivity ?? [];
8801
8806
  state.runningCounts = s.runningCounts ?? {}; state.parallelLimits = s.parallelLimits ?? null;
8802
8807
  state.workflowColumns = s.workflowColumns ?? {};
8803
8808
  state.reviewDefinitions = s.reviewDefinitions ?? {};
@@ -10282,18 +10287,6 @@ function saItemHtml(it, i){
10282
10287
  const summarySec = summaryText
10283
10288
  ? `<section class="rsec rsum"><div class="rsec-h">Summary</div><p class="rbody v">${esc(summaryText.slice(0, 200))}</p></section>`
10284
10289
  : '';
10285
- const coverage = (it.requirementCoverage || []).filter((r) => r.text);
10286
- const coverageRows = coverage.map((r) => {
10287
- const labels = { verified: 'Verified', failed: 'Failed', 'not-applicable': 'Not applicable', unverified: 'Not verified' };
10288
- const details = [
10289
- r.legacy ? 'Legacy item — no canonical evidence was recorded' : '',
10290
- r.checkFacts?.length ? `Manager: ${r.checkFacts.join(' · ')}` : '',
10291
- r.claims?.length ? `Worker claim: ${r.claims.join(', ')}` : '',
10292
- r.workerEvidence?.length ? r.workerEvidence.join(' · ') : '',
10293
- ].filter(Boolean);
10294
- return `<div class="rqcov-row" data-requirement-id="${esc(r.id)}"><div class="rqcov-main"><div class="rqcov-text">${esc(r.text)}</div><span class="rqcov-state ${esc(r.coverage)}">${esc(labels[r.coverage] || labels.unverified)}</span></div>${details.length ? `<div class="rqcov-detail">${details.map((detail, index) => index === 0 ? esc(detail) : `<br>${esc(detail)}`).join('')}</div>` : ''}</div>`;
10295
- }).join('');
10296
- const coverageSec = coverageRows ? `<section class="rsec"><div class="rsec-h">Requirement coverage</div><div class="rqcov">${coverageRows}</div></section>` : '';
10297
10290
  // ── Open it (Masa 2026-07-22): a plain section in the card's own typography — the same
10298
10291
  // heading + body shape as Request and Result, not a chip in another vocabulary. The
10299
10292
  // address the worker declared IS the control (press it → the process starts on demand →
@@ -10541,7 +10534,6 @@ function saItemHtml(it, i){
10541
10534
  ${reqSec}
10542
10535
  ${S('Open it', openBody + infraBody)}
10543
10536
  ${S('What to check', checkBody)}
10544
- ${coverageSec}
10545
10537
  ${S('Run state', interruptedBody)}
10546
10538
  ${S('PR', prRepairBody)}
10547
10539
  ${S('Also carries', carriesBody)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.109",
3
+ "version": "0.10.111",
4
4
  "type": "module",
5
5
  "description": "Galda - hand off work to Claude Code or Codex, get proof back. Runs on your existing subscription, no extra API cost.",
6
6
  "scripts": {