Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d99b66e
TTSをAzure SpeechからOpenAI gpt-4o-mini-ttsの女性声へ全面移行 (#2)
TinyKitten Aug 13, 2026
b2ea58b
TTSの既定ボイスを日英で分けて早口寄りに調整する (#3)
TinyKitten Aug 13, 2026
7782d5c
AIチャットのモデルにGemini(Vertex AI)を追加する (#4)
TinyKitten Aug 17, 2026
bf4776d
TTSをOpenAIからGoogle Cloud Text-to-Speechへ全面移行する (#5)
TinyKitten Aug 17, 2026
16a6518
本番の AGENT_MODEL を Gemini(Vertex AI)へ切り替える (#6)
TinyKitten Aug 17, 2026
335bbf4
staging の駅検索を sapi-bff-stg から stationapi-stg へ切り替える (#7)
TinyKitten Aug 22, 2026
d7dcbb3
本番の駅検索を sapi-bff から stationapi へ切り替える (#9)
TinyKitten Aug 23, 2026
7370018
フィードバックを原因リポジトリへ振り分け、トリアージの起票品質を改善する (#14)
TinyKitten Aug 27, 2026
f850f3e
アプリ指定の読み上げ速度を受け付け既定速度を等速へ変更 (#16)
TinyKitten Aug 27, 2026
06ce6bc
フィードバックのキューに dead letter queue を設定する (#19)
TinyKitten Aug 27, 2026
631398b
DLQ の紐付けを確認する手順を README に追加する (#22)
TinyKitten Aug 27, 2026
8c47ab7
フィードバック再試行での Issue 重複起票を防ぐ (#23)
TinyKitten Aug 28, 2026
94a409c
AI エージェントのモデルを Gemini 3.8 Flash へ更新する (#24)
TinyKitten Sep 3, 2026
f3fa862
英語TTSで「Keisei」が「かいせい」と読まれる誤読を合成前の表記置換で修正する (#25)
TinyKitten Sep 6, 2026
e73f616
GitHub Actions で dev・master からのデプロイを自動化する (#26)
TinyKitten Sep 6, 2026
a63ffe3
英語TTSで「Seibu」が「さいぶ」と読まれる誤読を合成前の表記置換で修正する (#27)
TinyKitten Sep 6, 2026
439ec90
master を dev へ取り込み AGENTS.md を同期する (#30)
TinyKitten Sep 12, 2026
3dda858
フィードバックトリアージの判定をTypeSafeに切り替える (#32)
TinyKitten Sep 17, 2026
6ee0bef
提案駅のリランクをTypeSafeで判定して対話ターンへ組み込む (#33)
TinyKitten Sep 17, 2026
cce388d
リランクの注記を user ロールで注入し Gemini でのターン失敗を直す (#34)
TinyKitten Sep 17, 2026
a81fe64
フィードバックから自動で修正PRを出すワークフローを追加 (#36)
TinyKitten Sep 21, 2026
fe49160
feedback-autofixを更新し準備が失敗した場合も結果を報告するようにした (#38)
TinyKitten Sep 21, 2026
0c1e8f3
本文が空白のみのフィードバックをpostFeedbackで弾くようにする (#39)
TinyKitten Sep 21, 2026
e507721
masterを取り込みauto_fix_from_feedback.ymlはdev側を採る
claude Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/auto_fix_from_feedback.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
fetch-depth: 0
persist-credentials: false

- uses: TrainLCD/feedback-autofix/prepare@406de875a89dd1e640f8b66b71d4de9516a952ca # feedback-autofix#1
- uses: TrainLCD/feedback-autofix/prepare@c71e2b4abf4149b4b83e9a87cd185b1558ed8237 # feedback-autofix#4
id: prepare
with:
stub_issue_number: ${{ github.event.issue.number }}
Expand Down Expand Up @@ -141,8 +141,12 @@ jobs:
# always() を外さないこと。エージェントが失敗した場合こそ報告が要る。
# 何も言わずに終わると、管理チケットを見た人には「調べたうえで何もしな
# かった」のか「そもそも動かなかった」のかが分からない。
- uses: TrainLCD/feedback-autofix/report@406de875a89dd1e640f8b66b71d4de9516a952ca # feedback-autofix#1
if: ${{ always() && steps.prepare.outputs.eligible == 'true' }}
#
# failed も外さないこと。prepare が対象だと分かったあとで落ちた場合、
# eligible は 'true' になりません。この条件が無いとここが走らず、ジョブが
# 赤くなるだけで管理チケットには何も残りません。
- uses: TrainLCD/feedback-autofix/report@c71e2b4abf4149b4b83e9a87cd185b1558ed8237 # feedback-autofix#4
if: ${{ always() && (steps.prepare.outputs.eligible == 'true' || steps.prepare.outputs.failed == 'true') }}
with:
issue_number: ${{ steps.prepare.outputs.issue_number }}
issues_repo_token: ${{ secrets.ISSUES_REPO_TOKEN }}
Expand Down
77 changes: 77 additions & 0 deletions src/routes/feedback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { CallableError } from '../lib/callable';
import type { Env } from '../types';
import { handleFeedback } from './feedback';

jest.mock('../lib/auth/session', () => ({
verifySessionToken: jest.fn(async () => 'verified-uid'),
}));

const send = jest.fn(async (_message: unknown) => undefined);
const env = { FEEDBACK_QUEUE: { send } } as unknown as Env;

const buildRequest = (report: unknown): Request =>
new Request('https://example.com/postFeedback', {
method: 'POST',
headers: {
'content-type': 'application/json; charset=UTF-8',
Authorization: 'Bearer dummy',
},
body: JSON.stringify({ data: { report } }),
});

const baseReport = {
id: 'feedback-id',
reportType: 'feedback',
description: '遅い',
reporterUid: 'client-claimed-uid',
};

describe('handleFeedback', () => {
afterEach(() => {
jest.clearAllMocks();
});

// 文字数の下限は撤廃したため、短い本文もそのままキューへ流す
it('queues a short report', async () => {
const res = await handleFeedback(buildRequest(baseReport), env);

expect(res.status).toBe(200);
expect(send).toHaveBeenCalledTimes(1);
expect(send.mock.calls[0][0]).toMatchObject({
id: 'feedback-id',
report: { description: '遅い', reporterUid: 'verified-uid' },
});
});

// code は HTTP ステータスへ直結する(invalid-argument なら 400)。CallableError で
// あることしか見ていないと、internal へ変わって 500 を返すようになっても気づけない。
// アプリは 4xx と 5xx で扱いを変えられるため、コードまで固定する。
it.each([
['empty', ''],
['whitespace only', ' \n\t '],
['number', 1],
['null', null],
])(
'rejects a %s description without queueing',
async (_label, description) => {
const rejected = handleFeedback(
buildRequest({ ...baseReport, description }),
env
);

await expect(rejected).rejects.toThrow(CallableError);
await expect(rejected).rejects.toMatchObject({
code: 'invalid-argument',
});
expect(send).not.toHaveBeenCalled();
}
);

it('rejects a missing description without queueing', async () => {
const rejected = handleFeedback(buildRequest({ id: 'feedback-id' }), env);

await expect(rejected).rejects.toThrow(/report.description required/);
await expect(rejected).rejects.toMatchObject({ code: 'invalid-argument' });
expect(send).not.toHaveBeenCalled();
});
});
5 changes: 5 additions & 0 deletions src/routes/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const handleFeedback = async (
if (!report?.id) {
throw new CallableError('invalid-argument', 'report.id required');
}
// 本文が空白のみのフィードバックはトリアージへ流さない。文字数の下限は設けず、
// 短い本文やクラッシュレポートの短いエラーメッセージはそのまま受け付ける。
if (typeof report.description !== 'string' || !report.description.trim()) {
throw new CallableError('invalid-argument', 'report.description required');
}

// reporterUid はクライアント申告を信用せず、検証済みトークンの sub で上書きする。
// (他ユーザーの UID を名乗って Issue/Discord に載せるなりすましを防ぐ)
Expand Down
Loading