@ripla/godd-mcp 1.0.6 → 1.0.7-canary.2

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 (33) hide show
  1. package/dist/godd.cjs +194 -185
  2. package/dist/godd.js +191 -49
  3. package/dist/index.js +1 -1
  4. package/notes/api/.env.example +1 -4
  5. package/notes/api/README.md +4 -2
  6. package/notes/api/app/config.py +8 -5
  7. package/notes/api/app/routers/tree.py +3 -3
  8. package/notes/api/app/services/github.py +14 -13
  9. package/notes/api/app/services/github_app_token.py +10 -15
  10. package/notes/api/app/services/github_pr.py +14 -8
  11. package/notes/api/tests/test_files.py +2 -2
  12. package/notes/api/tests/test_github_app_token.py +6 -5
  13. package/notes/api/tests/test_github_service.py +30 -4
  14. package/notes/api/tests/test_pr_chain.py +25 -2
  15. package/notes/api/tests/test_tree.py +1 -1
  16. package/notes/app/e2e/pages/login-page.ts +6 -0
  17. package/notes/app/e2e/password-toggle.spec.ts +198 -0
  18. package/notes/app/src/components/PasswordInput.test.tsx +122 -0
  19. package/notes/app/src/components/PasswordInput.tsx +67 -0
  20. package/notes/app/src/pages/LoginPage.test.tsx +31 -1
  21. package/notes/app/src/pages/LoginPage.tsx +14 -8
  22. package/notes/app/src/pages/UserManagementPage.test.tsx +51 -0
  23. package/notes/app/src/pages/UserManagementPage.tsx +2 -2
  24. package/package.json +1 -1
  25. package/templates/github-actions/aws/deploy-notes.yml.hbs +31 -0
  26. package/templates/notes-compose.yml +0 -1
  27. package/templates/notes-docs/007_guides/setup_godd.md +46 -10
  28. package/templates/notes-docs/007_guides/setup_godd_notes.md +218 -16
  29. package/templates/notes-docs/007_guides/setup_godd_notes.md.styles.json +409 -0
  30. package/templates/notes-docs/007_guides/setup_infra.template.md +75 -22
  31. package/templates/terraform/aws/ecs.tf.hbs +9 -1
  32. package/templates/terraform/aws/provider.tf.hbs +1 -1
  33. package/templates/terraform/aws/secrets.tf.hbs +11 -3
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import PasswordInput from "./PasswordInput";
4
+
5
+ describe("PasswordInput", () => {
6
+ it("REQ-UI-AC-13: 初期状態では伏字(type=password)で描画される", () => {
7
+ render(<PasswordInput id="pw" aria-label="パスワード" defaultValue="secret" />);
8
+ expect(screen.getByLabelText("パスワード").getAttribute("type")).toBe("password");
9
+ });
10
+
11
+ it("REQ-UI-AC-14: 目玉アイコンのクリックで平文表示に切り替わり、再クリックで伏字に戻る", () => {
12
+ render(<PasswordInput id="pw" aria-label="パスワード" defaultValue="secret" />);
13
+ const input = screen.getByLabelText("パスワード");
14
+
15
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
16
+ expect(input.getAttribute("type")).toBe("text");
17
+
18
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを非表示" }));
19
+ expect(input.getAttribute("type")).toBe("password");
20
+ });
21
+
22
+ it("REQ-UI-AC-17: トグルボタンは submit を誘発しない type=button である", () => {
23
+ render(<PasswordInput id="pw" aria-label="パスワード" />);
24
+ expect(screen.getByRole("button", { name: "パスワードを表示" }).getAttribute("type")).toBe(
25
+ "button",
26
+ );
27
+ });
28
+
29
+ it("REQ-UI-AC-17: トグルボタンはキーボードフォーカスリングを持ち、hover と弁別できる", () => {
30
+ render(<PasswordInput id="pw" aria-label="パスワード" />);
31
+ const toggle = screen.getByRole("button", { name: "パスワードを表示" });
32
+
33
+ // outline-none で既定リングを消すため、代替の focus-visible リングが必須(WCAG 2.4.11 / REQ-UI-AC-11)
34
+ expect(toggle.className).toContain("focus-visible:ring-2");
35
+ expect(toggle.className).toContain("focus-visible:ring-blue-500/70");
36
+ });
37
+
38
+ it("REQ-UI-AC-17: disabled はトグルボタンにも伝播し、無効時は表示切替できない", () => {
39
+ render(<PasswordInput id="pw" aria-label="パスワード" defaultValue="secret" disabled />);
40
+ const input = screen.getByLabelText("パスワード");
41
+ const toggle = screen.getByRole("button", { name: "パスワードを表示" });
42
+
43
+ expect((toggle as HTMLButtonElement).disabled).toBe(true);
44
+ fireEvent.click(toggle);
45
+ expect(input.getAttribute("type")).toBe("password");
46
+ });
47
+
48
+ it("平文表示中の値がスペルチェック・自動修正に取り込まれないよう抑止する", () => {
49
+ render(<PasswordInput id="pw" aria-label="パスワード" />);
50
+ const input = screen.getByLabelText("パスワード");
51
+ expect(input.getAttribute("spellcheck")).toBe("false");
52
+ expect(input.getAttribute("autocapitalize")).toBe("off");
53
+ expect(input.getAttribute("autocorrect")).toBe("off");
54
+ });
55
+
56
+ it("スペルチェック抑止は呼び出し側から上書きできない", () => {
57
+ render(
58
+ // @ts-expect-error セキュリティ上の抑止は Omit しているため渡せないことを型でも保証する
59
+ <PasswordInput id="pw" aria-label="パスワード" spellCheck autoCorrect="on" autoCapitalize="characters" />,
60
+ );
61
+ const input = screen.getByLabelText("パスワード");
62
+ expect(input.getAttribute("spellcheck")).toBe("false");
63
+ expect(input.getAttribute("autocorrect")).toBe("off");
64
+ expect(input.getAttribute("autocapitalize")).toBe("off");
65
+ });
66
+
67
+ it("REQ-UI-AC-17: 状態は aria-label とライブリージョンで伝え、aria-pressed は併用しない", () => {
68
+ render(<PasswordInput id="pw" aria-label="パスワード" />);
69
+ const toggle = () => screen.getByRole("button", { name: /パスワードを(非)?表示/ });
70
+
71
+ // aria-label と併用すると「パスワードを非表示, 押されている」と二重に読み上げられ意味が反転する
72
+ expect(toggle().getAttribute("aria-pressed")).toBeNull();
73
+
74
+ const live = screen.getByRole("status");
75
+ expect(live.getAttribute("aria-live")).toBe("polite");
76
+ // sr-only が外れると文言が入力欄内に可視表示されてしまう
77
+ expect(live.className).toContain("sr-only");
78
+ // 初回描画では何も起きていないため通知しない
79
+ expect(live.textContent).toBe("");
80
+
81
+ fireEvent.click(toggle());
82
+ expect(live.textContent).toBe("パスワードを表示しました");
83
+
84
+ fireEvent.click(toggle());
85
+ expect(live.textContent).toBe("パスワードを非表示にしました");
86
+ });
87
+
88
+ it("渡された className を引き継ぎ、アイコン分の inline-end パディングを付与する", () => {
89
+ render(
90
+ <PasswordInput
91
+ id="pw"
92
+ aria-label="パスワード"
93
+ className="custom-class"
94
+ autoComplete="new-password"
95
+ placeholder="入力してください"
96
+ />,
97
+ );
98
+ const input = screen.getByLabelText("パスワード");
99
+ expect(input.className).toContain("custom-class");
100
+ // アイコンと入力文字の重なりを防ぐ契約(仕様書に明記)
101
+ expect(input.className).toContain("pe-10");
102
+ expect(input.getAttribute("autocomplete")).toBe("new-password");
103
+ expect(input.getAttribute("placeholder")).toBe("入力してください");
104
+ });
105
+
106
+ it("呼び出し側は type を上書きできない", () => {
107
+ render(
108
+ // @ts-expect-error type は Omit しているため渡せないことを型でも保証する
109
+ <PasswordInput id="pw" aria-label="パスワード" type="text" />,
110
+ );
111
+ expect(screen.getByLabelText("パスワード").getAttribute("type")).toBe("password");
112
+ });
113
+
114
+ it("onChange が呼び出される", () => {
115
+ let received = "";
116
+ render(
117
+ <PasswordInput id="pw" aria-label="パスワード" onChange={(e) => (received = e.target.value)} />,
118
+ );
119
+ fireEvent.change(screen.getByLabelText("パスワード"), { target: { value: "abc" } });
120
+ expect(received).toBe("abc");
121
+ });
122
+ });
@@ -0,0 +1,67 @@
1
+ import { useState } from "react";
2
+ import type { InputHTMLAttributes } from "react";
3
+ import { Eye, EyeOff } from "lucide-react";
4
+
5
+ /**
6
+ * 呼び出し側に指定させないプロパティ:
7
+ * - `type` … 表示状態から内部で決める
8
+ * - `spellCheck` / `autoCapitalize` / `autoCorrect` … 平文表示中の値が外部送信され得るため無効化を強制する
9
+ *
10
+ * `id` はラベルとの `htmlFor` 紐付けに必須なので必須プロパティに引き上げる。
11
+ */
12
+ type PasswordInputProps = Omit<
13
+ InputHTMLAttributes<HTMLInputElement>,
14
+ "type" | "spellCheck" | "autoCapitalize" | "autoCorrect"
15
+ > & {
16
+ id: string;
17
+ };
18
+
19
+ /**
20
+ * 表示/非表示を目玉アイコンで切り替えられるパスワード入力欄。
21
+ * 見た目はページ側の className をそのまま引き継ぐ(アイコン分の inline-end パディングのみ付与)。
22
+ */
23
+ export default function PasswordInput({ className = "", ...inputProps }: PasswordInputProps) {
24
+ const [visible, setVisible] = useState(false);
25
+ const [toggled, setToggled] = useState(false);
26
+ const { disabled } = inputProps;
27
+
28
+ const toggle = () => {
29
+ setVisible((v) => !v);
30
+ setToggled(true);
31
+ };
32
+
33
+ return (
34
+ <div className="relative">
35
+ {/* 平文表示中は通常の text input になる。ブラウザの拡張スペルチェックは入力内容を外部送信し得るため、
36
+ モバイルの自動修正辞書と併せてパスワードが取り込まれないよう抑止する。
37
+ `type` と同様、スプレッド後に置き型からも Omit して上書き不能にする。 */}
38
+ <input
39
+ {...inputProps}
40
+ type={visible ? "text" : "password"}
41
+ spellCheck={false}
42
+ autoCapitalize="off"
43
+ autoCorrect="off"
44
+ className={`${className} pe-10`}
45
+ />
46
+ <button
47
+ type="button"
48
+ onClick={toggle}
49
+ disabled={disabled}
50
+ // 状態は「次に起きること」を示すラベルで伝える。aria-pressed を併用すると
51
+ // 「パスワードを非表示, 押されている」と二重に読み上げられ意味が反転するため使わない。
52
+ aria-label={visible ? "パスワードを非表示" : "パスワードを表示"}
53
+ // ring-inset 必須: ボタンは入力欄の角に接しており、呼び出し側の角丸(LoginPage は rounded-lg=8px、
54
+ // モーダルは rounded=4px)と異なる半径を持つ。外向きリングだと入力欄の丸角から角張って食み出す。
55
+ className="absolute inset-y-0 end-0 flex items-center px-3 rounded text-gray-400 hover:text-gray-200 disabled:opacity-40 disabled:cursor-not-allowed outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500/70"
56
+ >
57
+ {visible ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
58
+ </button>
59
+ {/* フォーカス中の要素のアクセシブル名を差し替えても再読み上げは SR/ブラウザ依存で不安定なため、
60
+ 状態変化はライブリージョンでも通知する。初回描画では何も起きていないので空にする。
61
+ sr-only を外すと文言が入力欄内に可視表示されてしまうため必須。 */}
62
+ <span className="sr-only" role="status" aria-live="polite">
63
+ {toggled ? (visible ? "パスワードを表示しました" : "パスワードを非表示にしました") : ""}
64
+ </span>
65
+ </div>
66
+ );
67
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi } from "vitest";
2
- import { screen } from "@testing-library/react";
2
+ import { screen, fireEvent } from "@testing-library/react";
3
3
  import { mockAuthContext } from "@/test/mocks";
4
4
  import { renderWithProviders } from "@/test/render";
5
5
  import LoginPage from "./LoginPage";
@@ -14,4 +14,34 @@ describe("LoginPage", () => {
14
14
  renderWithProviders(<LoginPage />);
15
15
  expect(screen.getByText("GoDD Notes")).toBeTruthy();
16
16
  });
17
+
18
+ it("REQ-UI-AC-13/14: パスワード欄は初期状態で伏字であり、目玉アイコンで表示/非表示を切り替えられる", () => {
19
+ renderWithProviders(<LoginPage />);
20
+ const password = screen.getByLabelText("パスワード");
21
+ expect(password.getAttribute("type")).toBe("password");
22
+
23
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
24
+ expect(password.getAttribute("type")).toBe("text");
25
+
26
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを非表示" }));
27
+ expect(password.getAttribute("type")).toBe("password");
28
+ });
29
+
30
+ it("REQ-UI-AC-16: 表示切替後も入力値が保持され、既存の #login-password が維持される", () => {
31
+ renderWithProviders(<LoginPage />);
32
+ const password = screen.getByLabelText("パスワード");
33
+ expect(password.getAttribute("id")).toBe("login-password");
34
+
35
+ fireEvent.change(password, { target: { value: "secret123" } });
36
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
37
+ expect((password as HTMLInputElement).value).toBe("secret123");
38
+ });
39
+
40
+ it("パスワードマネージャ連携のため autoComplete が設定されている", () => {
41
+ renderWithProviders(<LoginPage />);
42
+ expect(screen.getByLabelText("ユーザー名").getAttribute("autocomplete")).toBe("username");
43
+ expect(screen.getByLabelText("パスワード").getAttribute("autocomplete")).toBe(
44
+ "current-password",
45
+ );
46
+ });
17
47
  });
@@ -6,6 +6,7 @@ import { resolvePostLoginPath } from "@/lib/postLoginRedirect";
6
6
  import { useAuth } from "@/contexts/AuthContext";
7
7
  import { useToast } from "@/contexts/ToastContext";
8
8
  import { PageSkeleton } from "@/components/Skeleton";
9
+ import PasswordInput from "@/components/PasswordInput";
9
10
 
10
11
  export default function LoginPage() {
11
12
  const { login, loading, isAuthenticated } = useAuth();
@@ -60,8 +61,10 @@ export default function LoginPage() {
60
61
  </div>
61
62
  )}
62
63
 
63
- <label htmlFor="login-username" className="block mb-4">
64
- <span className="text-sm text-gray-400">ユーザー名</span>
64
+ <div className="mb-4">
65
+ <label htmlFor="login-username" className="block text-sm text-gray-400">
66
+ ユーザー名
67
+ </label>
65
68
  <input
66
69
  id="login-username"
67
70
  type="text"
@@ -69,20 +72,23 @@ export default function LoginPage() {
69
72
  onChange={(e) => setUsername(e.target.value)}
70
73
  className="mt-1 block w-full rounded-lg bg-gray-700 border border-gray-600 px-3 py-2 text-gray-100 placeholder-gray-500 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none"
71
74
  placeholder="admin"
75
+ autoComplete="username"
72
76
  autoFocus
73
77
  />
74
- </label>
78
+ </div>
75
79
 
76
- <label htmlFor="login-password" className="block mb-6">
77
- <span className="text-sm text-gray-400">パスワード</span>
78
- <input
80
+ <div className="mb-6">
81
+ <label htmlFor="login-password" className="block text-sm text-gray-400">
82
+ パスワード
83
+ </label>
84
+ <PasswordInput
79
85
  id="login-password"
80
- type="password"
81
86
  value={password}
82
87
  onChange={(e) => setPassword(e.target.value)}
83
88
  className="mt-1 block w-full rounded-lg bg-gray-700 border border-gray-600 px-3 py-2 text-gray-100 placeholder-gray-500 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none"
89
+ autoComplete="current-password"
84
90
  />
85
- </label>
91
+ </div>
86
92
 
87
93
  <button
88
94
  type="submit"
@@ -148,6 +148,57 @@ describe("UserManagementPage", () => {
148
148
  );
149
149
  });
150
150
 
151
+ it("REQ-UI-AC-15/16: 作成モーダルのパスワード欄は目玉アイコンで表示/非表示を切り替えられる", async () => {
152
+ renderPage();
153
+ await waitFor(() => expect(screen.getByText("+ ユーザー追加")).toBeTruthy());
154
+
155
+ fireEvent.click(screen.getByText("+ ユーザー追加"));
156
+ await waitFor(() => expect(screen.getByText("ユーザー作成")).toBeTruthy());
157
+
158
+ const password = screen.getByLabelText("パスワード");
159
+ expect(password.getAttribute("type")).toBe("password");
160
+
161
+ fireEvent.change(password, { target: { value: "password123" } });
162
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
163
+ expect(password.getAttribute("type")).toBe("text");
164
+ expect((password as HTMLInputElement).value).toBe("password123");
165
+
166
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを非表示" }));
167
+ expect(password.getAttribute("type")).toBe("password");
168
+ });
169
+
170
+ it("REQ-UI-AC-15: 編集モーダルのパスワード欄でも表示/非表示を切り替えられる", async () => {
171
+ renderPage();
172
+ await waitFor(() => expect(screen.getAllByText("編集").length).toBeGreaterThan(0));
173
+
174
+ fireEvent.click(screen.getAllByText("編集")[0]);
175
+ await waitFor(() => expect(screen.getByText("ユーザー編集")).toBeTruthy());
176
+
177
+ // 編集モードはラベル文言が異なるため、作成モードのクエリは流用できない
178
+ const password = screen.getByLabelText("新しいパスワード(変更する場合のみ)");
179
+ expect(password.getAttribute("type")).toBe("password");
180
+
181
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
182
+ expect(password.getAttribute("type")).toBe("text");
183
+ });
184
+
185
+ it("REQ-UI-AC-13: モーダルを閉じて開き直すと伏字に戻る", async () => {
186
+ renderPage();
187
+ await waitFor(() => expect(screen.getByText("+ ユーザー追加")).toBeTruthy());
188
+
189
+ fireEvent.click(screen.getByText("+ ユーザー追加"));
190
+ await waitFor(() => expect(screen.getByText("ユーザー作成")).toBeTruthy());
191
+ fireEvent.click(screen.getByRole("button", { name: "パスワードを表示" }));
192
+ expect(screen.getByLabelText("パスワード").getAttribute("type")).toBe("text");
193
+
194
+ fireEvent.click(screen.getByLabelText("閉じる"));
195
+ await waitFor(() => expect(screen.queryByText("ユーザー作成")).toBeNull());
196
+
197
+ fireEvent.click(screen.getByText("+ ユーザー追加"));
198
+ await waitFor(() => expect(screen.getByText("ユーザー作成")).toBeTruthy());
199
+ expect(screen.getByLabelText("パスワード").getAttribute("type")).toBe("password");
200
+ });
201
+
151
202
  it("shows validation error for short password on create", async () => {
152
203
  renderPage();
153
204
  await waitFor(() => expect(screen.getByText("+ ユーザー追加")).toBeTruthy());
@@ -12,6 +12,7 @@ import {
12
12
  import type { UserDetail, UserCreateInput, UserUpdateInput } from "@/lib/api";
13
13
  import { ArrowLeft, X } from "lucide-react";
14
14
  import { ListSkeleton } from "@/components/Skeleton";
15
+ import PasswordInput from "@/components/PasswordInput";
15
16
 
16
17
  type UserRole = "admin" | "editor" | "viewer";
17
18
 
@@ -200,9 +201,8 @@ function UserForm({
200
201
  <label htmlFor="form-password" className="block text-xs font-medium text-gray-300 mb-1">
201
202
  {mode === "create" ? "パスワード" : "新しいパスワード(変更する場合のみ)"}
202
203
  </label>
203
- <input
204
+ <PasswordInput
204
205
  id="form-password"
205
- type="password"
206
206
  value={password}
207
207
  onChange={(e) => setPassword(e.target.value)}
208
208
  className={inputClasses}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripla/godd-mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.7-canary.2",
4
4
  "type": "module",
5
5
  "description": "GoDD MCP Server - AI-powered development workflow tools via Model Context Protocol (slash commands support)",
6
6
  "main": "dist/index.js",
@@ -80,11 +80,42 @@ jobs:
80
80
  cache-to: type=gha,mode=max
81
81
 
82
82
  - name: Deploy to ECS
83
+ env:
84
+ IMAGE_URI: $\{{ steps.ecr-login.outputs.registry }}/$\{{ env.ECR_REPOSITORY }}:$\{{ github.sha }}
83
85
  run: |
86
+ TASK_DEF=$(aws ecs describe-services \
87
+ --cluster $ECS_CLUSTER \
88
+ --services $ECS_SERVICE \
89
+ --query 'services[0].taskDefinition' \
90
+ --output text)
91
+
92
+ TASK_DEF_JSON=$(aws ecs describe-task-definition \
93
+ --task-definition "$TASK_DEF" \
94
+ --query 'taskDefinition')
95
+
96
+ # Strip legacy PAT injection (GITHUB_TOKEN) from every container (match ecs-task-def.ts).
97
+ NEW_TASK_DEF=$(echo "$TASK_DEF_JSON" | jq --arg IMAGE "$IMAGE_URI" '
98
+ .containerDefinitions[0].image = $IMAGE
99
+ | .containerDefinitions |= map(
100
+ if .secrets != null then
101
+ .secrets |= map(select(.name != "GITHUB_TOKEN"))
102
+ else .
103
+ end
104
+ )
105
+ | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)
106
+ ')
107
+
108
+ NEW_TASK_DEF_ARN=$(aws ecs register-task-definition \
109
+ --cli-input-json "$NEW_TASK_DEF" \
110
+ --query 'taskDefinition.taskDefinitionArn' \
111
+ --output text)
112
+
84
113
  aws ecs update-service \
85
114
  --cluster $ECS_CLUSTER \
86
115
  --service $ECS_SERVICE \
116
+ --task-definition "$NEW_TASK_DEF_ARN" \
87
117
  --force-new-deployment
118
+
88
119
  aws ecs wait services-stable \
89
120
  --cluster $ECS_CLUSTER \
90
121
  --services $ECS_SERVICE
@@ -37,7 +37,6 @@ services:
37
37
  environment:
38
38
  TZ: Asia/Tokyo
39
39
  PORT: "3100"
40
- GITHUB_TOKEN: ${GITHUB_TOKEN:-}
41
40
  GITHUB_APP_ID: ${GITHUB_APP_ID:-}
42
41
  GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
43
42
  GITHUB_APP_INSTALLATION_ID: ${GITHUB_APP_INSTALLATION_ID:-}
@@ -114,31 +114,65 @@ Step 3 まで完了したら、利用する IDE に応じて MCP を登録しま
114
114
 
115
115
  ### Cursor IDE
116
116
 
117
+ `godd init`(Step 3)で `.cursor/mcp.json` が自動生成されます。**`claude mcp add` は不要**です。
118
+
117
119
  1. Cursor IDE で対象プロジェクトを開く
118
120
  2. `Settings → MCP` で `godd` サーバーが認識されていることを確認
119
121
  3. 必要に応じてリロード(`Reload Window`)
120
122
 
121
- > `.cursor/mcp.json` は Step 3 の `godd init` で自動生成されます。内容を手動で編集する場合は、`env.GODD_CONFIG` がプロジェクトの `config.godd` を指していることを確認してください。
123
+ > 内容を手動で編集する場合は、`env.GODD_CONFIG` がプロジェクトの `config.godd` を指していることを確認してください。
122
124
 
123
125
  ### Claude Code
124
126
 
125
127
  Claude Code では `claude mcp` コマンドで MCP サーバーを登録します。
126
- **プロジェクトルート** で以下を実行してください(`godd install` 済みであること)。
128
+ **実行順序(重要):** ① `godd init`(Step 3)→ ② `.env` をシェルに読み込む → ③ `claude mcp add`
129
+
130
+ `godd install` 済みであること。**プロジェクトルート** で以下を実行してください。
131
+
132
+ #### 1. 先に `godd init` を完了する(Step 3)
133
+
134
+ Step 3 をスキップしている場合は先に実行してください。`godd init` が `.env` と `config.godd` を生成します。
135
+
136
+ ```bash
137
+ cd /path/to/your-project
138
+ godd init
139
+ ```
127
140
 
128
- #### 1. 環境変数を用意する
141
+ #### 2. `.env` をシェル環境変数として読み込む
129
142
 
130
- `godd init` はライセンスキー等をプロジェクトの `.env` に書き込みます。
131
143
  MCP 登録時は **シェル環境変数として参照** し、コマンドラインやドキュメントに平文で書かないでください。
132
144
 
145
+ **macOS / Linux(bash / zsh):**
146
+
133
147
  ```bash
134
148
  cd /path/to/your-project
135
149
 
136
- # .env から読み込む例(.env は Git 管理外にすること)
137
- set -a && source .env && set +a # macOS / Linux
138
- # PowerShell の場合は .env の各キーを $env:KEY = "..." で設定
150
+ # set -a が必須(変数を自動エクスポート)。bare の source .env だけでは不十分な場合がある
151
+ set -a && source .env && set +a
152
+
153
+ # 読み込み確認(値そのものは表示しない)
154
+ test -n "$GODD_LICENSE_KEY" && echo "GODD_LICENSE_KEY: loaded" || echo "GODD_LICENSE_KEY: EMPTY"
155
+ ```
156
+
157
+ **Windows (PowerShell):**
158
+
159
+ ```powershell
160
+ cd C:\path\to\your-project
161
+
162
+ # .env の各 GODD_* キーを $env: に設定(例)
163
+ Get-Content .env | ForEach-Object {
164
+ if ($_ -match '^\s*([^#=]+)=(.*)$') {
165
+ $name = $matches[1].Trim()
166
+ $value = $matches[2].Trim().Trim('"')
167
+ if ($name -like 'GODD_*') { Set-Item -Path "env:$name" -Value $value }
168
+ }
169
+ }
170
+
171
+ # 読み込み確認
172
+ if ($env:GODD_LICENSE_KEY) { "GODD_LICENSE_KEY: loaded" } else { "GODD_LICENSE_KEY: EMPTY" }
139
173
  ```
140
174
 
141
- #### 2. MCP サーバーを登録する
175
+ #### 3. MCP サーバーを登録する
142
176
 
143
177
  ```bash
144
178
  claude mcp add godd -s local \
@@ -160,7 +194,7 @@ claude mcp add godd -s local \
160
194
 
161
195
  > Windows (PowerShell) では `${PWD}` の代わりに `$PWD.Path`、環境変数展開は `"$env:GODD_LICENSE_KEY"` 形式を使用してください。
162
196
 
163
- #### 3. Claude Code を再起動する
197
+ #### 4. Claude Code を再起動する
164
198
 
165
199
  登録後、Claude Code を一度終了して再起動すると MCP 接続が反映されます。
166
200
 
@@ -238,7 +272,9 @@ docs/
238
272
  |---|---|
239
273
  | `godd: command not found` | ターミナルを再起動。それでも解決しない場合は PATH に `~/.godd/bin/`(Windows: `%LOCALAPPDATA%\GoDD\`)を追加 |
240
274
  | Cursor が MCP を認識しない | `Settings → MCP` で `godd` が green になっているか確認。`.cursor/mcp.json` の `env.GODD_CONFIG` が正しい絶対パスか確認 |
241
- | Claude Code が MCP を認識しない | `claude mcp list` で `godd` を確認。`GODD_CONFIG` が `config.godd` の絶対パスか確認。Claude Code を再起動 |
275
+ | Claude Code が MCP を認識しない | `claude mcp list` または `/mcp` で `godd` を確認。`GODD_CONFIG` が `config.godd` の絶対パスか確認。Claude Code を再起動 |
276
+ | `GODD_LICENSE_KEY` が空 | `.env` の読み込み方法を確認。macOS/Linux は `set -a && source .env && set +a`(bare の `source .env` だけでは不十分な場合あり)。PowerShell は各 `GODD_*` を `$env:` に設定 |
277
+ | MCP 登録後も接続できない | ターミナルで `godd server` が起動するか確認。失敗する場合は PATH またはライセンスキーを確認 |
242
278
  | ライセンス検証エラー | Registry URL とライセンスキーが正しいか確認。`.env` の `GODD_LICENSE_KEY` または MCP 登録時の環境変数を確認。管理者にライセンス状態を問い合わせ |
243
279
  | 「開発元不明」エラー (macOS) | `xattr -d com.apple.quarantine $(which godd)` を実行 |
244
280