@simplysm/sd-claude 13.0.71 → 13.0.74

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 (40) hide show
  1. package/README.md +286 -13
  2. package/claude/refs/sd-code-conventions.md +11 -0
  3. package/claude/refs/sd-library-issue.md +7 -0
  4. package/claude/rules/sd-claude-rules.md +15 -4
  5. package/claude/rules/sd-refs-linker.md +1 -0
  6. package/claude/sd-statusline.js +1 -1
  7. package/claude/skills/sd-brainstorm/SKILL.md +1 -1
  8. package/claude/skills/sd-check/SKILL.md +15 -6
  9. package/claude/skills/sd-commit/SKILL.md +2 -0
  10. package/claude/skills/sd-debug/find-polluter.sh +8 -2
  11. package/claude/skills/sd-debug/root-cause-tracing.md +2 -2
  12. package/claude/skills/sd-document/extract_docx.py +5 -5
  13. package/claude/skills/sd-document/extract_pdf.py +11 -11
  14. package/claude/skills/sd-document/extract_pptx.py +5 -5
  15. package/claude/skills/sd-document/extract_xlsx.py +7 -7
  16. package/claude/skills/sd-email-analyze/email-analyzer.py +28 -28
  17. package/claude/skills/sd-plan/SKILL.md +11 -2
  18. package/claude/skills/sd-plan-dev/SKILL.md +5 -3
  19. package/claude/skills/sd-plan-dev/final-review-prompt.md +3 -3
  20. package/claude/skills/sd-readme/SKILL.md +86 -106
  21. package/claude/skills/sd-review/SKILL.md +58 -62
  22. package/claude/skills/sd-review/api-reviewer-prompt.md +90 -0
  23. package/claude/skills/sd-review/code-reviewer-prompt.md +85 -0
  24. package/claude/skills/sd-review/code-simplifier-prompt.md +88 -0
  25. package/claude/skills/sd-worktree/SKILL.md +10 -8
  26. package/claude/skills/sd-worktree/sd-worktree.mjs +5 -5
  27. package/dist/commands/auth-list.d.ts +1 -1
  28. package/dist/commands/auth-list.d.ts.map +1 -1
  29. package/dist/commands/auth-list.js +79 -21
  30. package/dist/commands/auth-list.js.map +1 -1
  31. package/dist/sd-claude.js +2 -2
  32. package/dist/sd-claude.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/commands/auth-list.ts +110 -24
  35. package/src/sd-claude.ts +2 -2
  36. package/tests/auth-list.spec.ts +42 -19
  37. package/claude/agents/sd-api-reviewer.md +0 -81
  38. package/claude/agents/sd-code-reviewer.md +0 -48
  39. package/claude/agents/sd-code-simplifier.md +0 -47
  40. package/claude/agents/sd-security-reviewer.md +0 -92
package/README.md CHANGED
@@ -1,24 +1,297 @@
1
1
  # @simplysm/sd-claude
2
2
 
3
- Simplysm Claude Code CLI — asset installer
3
+ Simplysm Claude Code CLI — asset installer and Claude account profile manager.
4
4
 
5
5
  ## Installation
6
6
 
7
+ ```bash
7
8
  pnpm add @simplysm/sd-claude
9
+ ```
8
10
 
9
- ## Source Index
11
+ On install, the `postinstall` script automatically copies Claude Code assets (`sd-*` entries) from the package's `claude/` directory into the project's `.claude/` directory and configures the status line in `.claude/settings.json`.
10
12
 
11
- ### Commands
13
+ ## CLI
12
14
 
13
- | Source | Exports | Description | Test |
14
- |--------|---------|-------------|------|
15
- | `src/commands/install.ts` | `runInstall` | Copies sd-* Claude Code assets into the project's `.claude/` directory | `-` |
16
- | `src/commands/auth-utils.ts` | `validateName`, `getProfileDir`, `profileExists`, `listProfiles`, `readCurrentAuth`, `readCurrentCredentials`, `getCurrentUserID` | Shared helpers for reading and locating Claude auth profile data | `auth-utils.spec.ts` |
17
- | `src/commands/auth-add.ts` | `runAuthAdd` | Saves the current Claude login session as a named auth profile | `auth-add.spec.ts` |
18
- | `src/commands/auth-use.ts` | `runAuthUse` | Switches the active Claude login to a saved named profile | `auth-use.spec.ts` |
19
- | `src/commands/auth-list.ts` | `runAuthList` | Lists all saved auth profiles with email and token expiry info | `auth-list.spec.ts` |
20
- | `src/commands/auth-remove.ts` | `runAuthRemove` | Deletes a saved auth profile from the local profile store | `auth-remove.spec.ts` |
15
+ The package provides the `sd-claude` binary.
21
16
 
22
- ## License
17
+ ```
18
+ sd-claude <command> [options]
19
+ sd-claude --help
20
+ ```
23
21
 
24
- Apache-2.0
22
+ ### `install`
23
+
24
+ Installs Claude Code assets to the project's `.claude/` directory. This is also run automatically via the `postinstall` script.
25
+
26
+ ```bash
27
+ sd-claude install
28
+ ```
29
+
30
+ **Behavior:**
31
+
32
+ - Locates the project root via `INIT_CWD` environment variable or by finding `node_modules` in the path.
33
+ - Copies all `sd-*` entries from the package's `claude/` directory to the project's `.claude/` directory.
34
+ - Removes any previously installed `sd-*` entries before copying (clean install).
35
+ - Adds a `statusLine` entry to `.claude/settings.json` if one is not already configured:
36
+ ```json
37
+ { "type": "command", "command": "node .claude/sd-statusline.js" }
38
+ ```
39
+ - Skips installation when running inside the simplysm monorepo at the same major version.
40
+ - Errors during installation are suppressed (logged as warnings) to avoid blocking `pnpm install`.
41
+
42
+ ### `auth`
43
+
44
+ Manages Claude account profiles. Profiles are stored in `~/.sd-claude/auth/<name>/`.
45
+
46
+ Each profile stores:
47
+ - `auth.json` — `oauthAccount` and `userID` from `~/.claude.json`
48
+ - `credentials.json` — full contents of `~/.claude/.credentials.json`
49
+
50
+ #### `auth add <name>`
51
+
52
+ Saves the currently logged-in Claude account as a named profile.
53
+
54
+ ```bash
55
+ sd-claude auth add <name>
56
+ ```
57
+
58
+ - `<name>`: Profile name. Must match `[a-z0-9_-]+`.
59
+ - Reads the current auth state from `~/.claude.json` and `~/.claude/.credentials.json`.
60
+ - Fails if the profile name already exists. Remove it first with `auth remove`.
61
+ - Requires an active Claude login. Run `/login` in Claude Code before saving.
62
+
63
+ **Example:**
64
+
65
+ ```bash
66
+ sd-claude auth add personal
67
+ sd-claude auth add work
68
+ ```
69
+
70
+ #### `auth use <name>`
71
+
72
+ Switches to a saved Claude account profile.
73
+
74
+ ```bash
75
+ sd-claude auth use <name>
76
+ ```
77
+
78
+ - Reads the saved `auth.json` and `credentials.json` from `~/.sd-claude/auth/<name>/`.
79
+ - Updates `~/.claude.json` (only `oauthAccount` and `userID` fields; other fields are preserved).
80
+ - Replaces `~/.claude/.credentials.json` entirely with the saved credentials.
81
+ - Warns if the saved token has expired (run `/login` after switching to refresh).
82
+
83
+ **Example:**
84
+
85
+ ```bash
86
+ sd-claude auth use work
87
+ ```
88
+
89
+ #### `auth list`
90
+
91
+ Displays all saved profiles with their active status, email, token expiry, and usage.
92
+
93
+ ```bash
94
+ sd-claude auth list
95
+ ```
96
+
97
+ Output format per profile:
98
+
99
+ ```
100
+ * <name> (<email>) expires: YYYY-MM-DD │ 5h: <pct>(<remaining>) │ 7d: <pct>(<remaining>)
101
+ ```
102
+
103
+ - `*` marks the currently active profile (matched by `userID`); inactive profiles show a space.
104
+ - Usage is fetched live from `https://api.anthropic.com/api/oauth/usage` using the profile's OAuth token (5-second timeout). Shows `?` when unavailable or when the token is expired.
105
+ - Usage shows the `daily` window (falling back to `five_hour`) as the `5h` column, and `seven_day` as the `7d` column.
106
+ - Profiles are sorted alphabetically.
107
+
108
+ **Example output:**
109
+
110
+ ```
111
+ * work (work@company.com) expires: 2025-12-31 │ 5h: 42%(3h15m) │ 7d: 18%(2d4h)
112
+ personal (personal@gmail.com) expires: 2025-11-01 │ 5h: ? │ 7d: ?
113
+ ```
114
+
115
+ #### `auth remove <name>`
116
+
117
+ Removes a saved Claude account profile.
118
+
119
+ ```bash
120
+ sd-claude auth remove <name>
121
+ ```
122
+
123
+ - Deletes `~/.sd-claude/auth/<name>/` and all its contents.
124
+ - Warns if the profile being removed is the currently active account (the active session in Claude Code is not affected).
125
+
126
+ **Example:**
127
+
128
+ ```bash
129
+ sd-claude auth remove personal
130
+ ```
131
+
132
+ ## Programmatic API
133
+
134
+ All commands are also exported as functions for use in Node.js scripts.
135
+
136
+ ```ts
137
+ import {
138
+ runInstall,
139
+ runAuthAdd,
140
+ runAuthUse,
141
+ runAuthList,
142
+ runAuthRemove,
143
+ validateName,
144
+ getProfileDir,
145
+ profileExists,
146
+ listProfiles,
147
+ readCurrentAuth,
148
+ readCurrentCredentials,
149
+ getCurrentUserID,
150
+ } from "@simplysm/sd-claude";
151
+ ```
152
+
153
+ ### `runInstall(): void`
154
+
155
+ Runs the install command programmatically. See [`install`](#install) for behavior details.
156
+
157
+ ```ts
158
+ import { runInstall } from "@simplysm/sd-claude";
159
+
160
+ runInstall();
161
+ ```
162
+
163
+ ### `runAuthAdd(name: string, homeDir?: string): void`
164
+
165
+ Saves the currently logged-in Claude account as a named profile.
166
+
167
+ - `name`: Profile name (validated against `[a-z0-9_-]+`).
168
+ - `homeDir`: Override for the home directory (defaults to `os.homedir()`). Primarily used in tests.
169
+
170
+ ```ts
171
+ import { runAuthAdd } from "@simplysm/sd-claude";
172
+
173
+ runAuthAdd("work");
174
+ ```
175
+
176
+ ### `runAuthUse(name: string, homeDir?: string): void`
177
+
178
+ Switches to a saved Claude account profile.
179
+
180
+ - `name`: Profile name to switch to.
181
+ - `homeDir`: Override for the home directory.
182
+
183
+ ```ts
184
+ import { runAuthUse } from "@simplysm/sd-claude";
185
+
186
+ runAuthUse("work");
187
+ ```
188
+
189
+ ### `runAuthList(homeDir?: string): Promise<void>`
190
+
191
+ Prints all saved profiles to stdout with status, expiry, and live usage data.
192
+
193
+ - `homeDir`: Override for the home directory.
194
+
195
+ ```ts
196
+ import { runAuthList } from "@simplysm/sd-claude";
197
+
198
+ await runAuthList();
199
+ ```
200
+
201
+ ### `runAuthRemove(name: string, homeDir?: string): void`
202
+
203
+ Removes a saved Claude account profile.
204
+
205
+ - `name`: Profile name to remove.
206
+ - `homeDir`: Override for the home directory.
207
+
208
+ ```ts
209
+ import { runAuthRemove } from "@simplysm/sd-claude";
210
+
211
+ runAuthRemove("personal");
212
+ ```
213
+
214
+ ### `validateName(name: string): void`
215
+
216
+ Validates that a profile name matches `[a-z0-9_-]+`. Throws an `Error` if invalid.
217
+
218
+ ```ts
219
+ import { validateName } from "@simplysm/sd-claude";
220
+
221
+ validateName("my-profile"); // OK
222
+ validateName("My Profile"); // throws Error
223
+ ```
224
+
225
+ ### `getProfileDir(name: string, homeDir?: string): string`
226
+
227
+ Returns the absolute path to the profile directory: `<homeDir>/.sd-claude/auth/<name>`.
228
+
229
+ ```ts
230
+ import { getProfileDir } from "@simplysm/sd-claude";
231
+
232
+ const dir = getProfileDir("work");
233
+ // e.g. "/home/user/.sd-claude/auth/work"
234
+ ```
235
+
236
+ ### `profileExists(name: string, homeDir?: string): boolean`
237
+
238
+ Returns `true` if the profile directory exists.
239
+
240
+ ```ts
241
+ import { profileExists } from "@simplysm/sd-claude";
242
+
243
+ if (profileExists("work")) {
244
+ console.log("Profile work exists");
245
+ }
246
+ ```
247
+
248
+ ### `listProfiles(homeDir?: string): string[]`
249
+
250
+ Returns an array of all saved profile names (directory names under `~/.sd-claude/auth/`). Returns an empty array if no profiles exist.
251
+
252
+ ```ts
253
+ import { listProfiles } from "@simplysm/sd-claude";
254
+
255
+ const profiles = listProfiles();
256
+ // e.g. ["personal", "work"]
257
+ ```
258
+
259
+ ### `readCurrentAuth(homeDir?: string): { oauthAccount: Record<string, unknown>; userID: string }`
260
+
261
+ Reads `oauthAccount` and `userID` from `~/.claude.json`. Throws if not logged in.
262
+
263
+ ```ts
264
+ import { readCurrentAuth } from "@simplysm/sd-claude";
265
+
266
+ const { oauthAccount, userID } = readCurrentAuth();
267
+ ```
268
+
269
+ ### `readCurrentCredentials(homeDir?: string): Record<string, unknown>`
270
+
271
+ Reads and returns the full contents of `~/.claude/.credentials.json`.
272
+
273
+ ```ts
274
+ import { readCurrentCredentials } from "@simplysm/sd-claude";
275
+
276
+ const credentials = readCurrentCredentials();
277
+ ```
278
+
279
+ ### `getCurrentUserID(homeDir?: string): string | undefined`
280
+
281
+ Returns the `userID` from `~/.claude.json`, or `undefined` if the file does not exist or cannot be read.
282
+
283
+ ```ts
284
+ import { getCurrentUserID } from "@simplysm/sd-claude";
285
+
286
+ const userID = getCurrentUserID();
287
+ ```
288
+
289
+ ## Profile Storage Layout
290
+
291
+ ```
292
+ ~/.sd-claude/
293
+ auth/
294
+ <name>/
295
+ auth.json # { oauthAccount, userID }
296
+ credentials.json # full ~/.claude/.credentials.json snapshot
297
+ ```
@@ -39,8 +39,19 @@ async function readFileAsync() { ... } // Async suffix prohibited
39
39
  - Not enforced — omit when code is self-explanatory
40
40
  - When written, use Korean
41
41
 
42
+ ## Re-export Restriction
43
+
44
+ - Re-export (`export * from`, `export { } from`) is **only allowed in `src/index.ts`**
45
+ - All other files must not re-export — duplicated re-exports make code harder to find and maintain
46
+
42
47
  ## index.ts Export Pattern
43
48
 
44
49
  - Large packages: `#region`/`#endregion` for sections + `//` for sub-groups
45
50
  - Small packages (≤10 exports): `//` comments only
46
51
  - Always `export *` (wildcard), never explicit `export type { ... } from "..."`
52
+
53
+ ## Type Safety for Public APIs
54
+
55
+ - API changes must be detectable via **typecheck alone** — all affected usage sites must show compile errors
56
+ - Public component props must support **IDE intellisense** (autocomplete, type hints)
57
+ - Avoid `any` in public-facing types; use generics or specific union types instead
@@ -0,0 +1,7 @@
1
+ # Simplysm Library Issue Reporting
2
+
3
+ Source code for `@simplysm/*` packages can be found in `node_modules/@simplysm/`. If debugging reveals the root cause is in the simplysm library itself, generate a GitHub issue-formatted text (title, reproduction steps, expected behavior, actual behavior) and display it to the user.
4
+
5
+ **Report facts only — do not suggest fixes or include code location hints. Do not auto-submit the issue — only display the text.**
6
+
7
+ The issue body must NEVER include internal analysis of library code (class names, variable names, style properties, inheritance chains, etc.). Only describe user-observable symptoms.
@@ -21,9 +21,9 @@ If a referenced file or document cannot be found, **stop immediately and ask the
21
21
 
22
22
  ### Questions vs. Code Requests — CRITICAL
23
23
 
24
- - **If the user asks a question** (e.g., "이건 이래?", "이거 뭐야?", "어떻게 동작해?") → **answer with text only**. Do NOT edit, write, or create any files.
24
+ - **If the user asks a question** (e.g., "Why is this like this?", "What is this?", "How does this work?") → **answer with text only**. Do NOT edit, write, or create any files.
25
25
  - **If the user discusses, explains, or shares opinions** → **respond with text only**. Do NOT touch any files.
26
- - **Only edit/write/create files when the user explicitly requests code changes** (e.g., "수정해줘", "만들어줘", "변경해줘", "추가해줘", "고쳐줘").
26
+ - **Only edit/write/create files when the user explicitly requests code changes** (e.g., "Fix this", "Create this", "Change this", "Add this").
27
27
  - Reading files to answer a question is fine. **Modifying files to answer a question is prohibited.**
28
28
 
29
29
  ### General Rules
@@ -32,7 +32,18 @@ If a referenced file or document cannot be found, **stop immediately and ask the
32
32
  - Do NOT add features, refactoring, improvements, or documentation beyond the requested scope.
33
33
  - When in doubt, **ask first** before proceeding.
34
34
  - Responses like "I'll create it myself" or "I'll add that as well" are strictly prohibited.
35
+ - **Do NOT comment on code outside the requested change.** This includes:
36
+ - Listing issues you noticed but did not fix
37
+ - Describing what you "left alone" or "did not change"
38
+ - "참고", "suggestions", "by the way", "note", "what I left alone"
39
+ - Any unsolicited observations about surrounding code quality
40
+ - Only describe **what you changed** — nothing else
35
41
 
36
- ## ⚠️ CRITICAL — NEVER SKIP
42
+ ## Asking Clarifying Questions
37
43
 
38
- **Before EVERY `AskUserQuestion` call, output `---` as the last line.** The widget clips text above it. No exceptions.
44
+ When you need to ask the user a question, you MUST use the `AskUserQuestion` tool. Do NOT ask questions in plain text.
45
+
46
+ - **Wrong:** Writing questions as text in your response
47
+ - **Right:** Calling the `AskUserQuestion` tool
48
+
49
+ **Before EVERY `AskUserQuestion` call, output `---` as the last line.** The widget clips text above it. No exceptions.
@@ -19,6 +19,7 @@ Determine the major version by the `version` field in `package.json`.
19
19
  | Debugging, problem-solving, or planning approach | `.claude/refs/sd-workflow.md` |
20
20
  | Using `@simplysm/service-*` packages | `.claude/refs/sd-service.md` |
21
21
  | Migrating/porting code from another codebase | `.claude/refs/sd-migration.md` |
22
+ | Debugging in a project that uses `@simplysm/*` as an external dependency (not the simplysm monorepo itself) | `.claude/refs/sd-library-issue.md` |
22
23
 
23
24
  ## v12 only (< 13)
24
25
 
@@ -241,7 +241,7 @@ async function main() {
241
241
  const cwd = input.cwd ?? process.cwd();
242
242
  const folderName = path.basename(cwd);
243
243
 
244
- // 출력
244
+ // Output
245
245
  const dailyStr = dailyResetTime ? `${dailyPercent}%(${dailyResetTime})` : `${dailyPercent}%`;
246
246
  const weekStr = weekResetDay ? `${weekPercent}%(${weekResetDay})` : `${weekPercent}%`;
247
247
  const parts = [folderName, modelName, `${contextPercent}%`, dailyStr, weekStr];
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sd-brainstorm
3
- description: "Design exploration before implementation (explicit invocation only)"
3
+ description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation."
4
4
  ---
5
5
 
6
6
  # Brainstorming Ideas Into Designs
@@ -1,17 +1,26 @@
1
1
  ---
2
2
  name: sd-check
3
3
  description: "Typecheck, lint, test verification (explicit invocation only)"
4
- allowed-tools: Bash(npm run check), Bash(npm run typecheck), Bash(npm run lint --fix), Bash(npm run vitest)
4
+ allowed-tools: Bash(npm run check:*), Bash(pnpm run check:*), Bash(yarn run check:*), Bash(npm run typecheck:*), Bash(pnpm run typecheck:*), Bash(yarn run typecheck:*), Bash(npm run lint:*), Bash(pnpm run lint:*), Bash(yarn run lint:*), Bash(npm run vitest:*), Bash(pnpm run vitest:*), Bash(yarn run vitest:*)
5
5
  ---
6
6
 
7
7
  # sd-check
8
8
 
9
- Run `npm run check`, fix errors, repeat until clean.
9
+ Run `$PM run check`, fix errors, repeat until clean.
10
+
11
+ ## Package Manager Detection
12
+
13
+ Before running any commands, detect the package manager:
14
+ - If `pnpm-lock.yaml` exists in project root → use `pnpm`
15
+ - If `yarn.lock` exists in project root → use `yarn`
16
+ - Otherwise → use `npm`
17
+
18
+ `$PM` in all commands below refers to the detected package manager.
10
19
 
11
20
  ## Usage
12
21
 
13
22
  ```
14
- npm run check [path] [--type typecheck|lint|test]
23
+ $PM run check [path] [--type typecheck|lint|test]
15
24
  ```
16
25
 
17
26
  | Example | Effect |
@@ -25,11 +34,11 @@ Multiple types: `--type typecheck,lint`. No path = full project. No type = all c
25
34
 
26
35
  ## Workflow
27
36
 
28
- 1. **Run** `npm run check [path] [--type type]` (timeout: 600000)
37
+ 1. **Run** `$PM run check [path] [--type type]` (timeout: 600000)
29
38
  2. **All passed?** Report with actual output numbers → done
30
39
  3. **Errors?** Fix in priority order: typecheck → lint → test (fixes cascade)
31
- - Test failures: run `git diff` to decide — update test or fix source
32
- - **E2E test failures** (browser/solid/service project): use Playwright MCP to investigate before fixing
40
+ - Test failures: **MUST** run `git log` to decide — update test or fix source
41
+ - **E2E test failures**: use Playwright MCP to investigate before fixing
33
42
  1. `browser_navigate` to the target URL
34
43
  2. `browser_snapshot` / `browser_take_screenshot` (save to `.tmp/playwright/`) to see page state
35
44
  3. `browser_console_messages` for JS errors
@@ -58,6 +58,8 @@ Examples:
58
58
  - `fix(orm-node): handle null values in bulk insert`
59
59
  - `docs: update README with new API examples`
60
60
 
61
+ > **Note:** The examples above are in English for reference only. The actual description MUST be written in the system's configured language.
62
+
61
63
  Use a HEREDOC for multi-line messages when needed.
62
64
 
63
65
  ## Execution
@@ -14,6 +14,12 @@ fi
14
14
  CHECK_PATH="$1"
15
15
  TEST_PATTERN="$2"
16
16
 
17
+ # Detect package manager: pnpm -> yarn -> npm (default)
18
+ if [ -f "pnpm-lock.yaml" ]; then PM="pnpm"
19
+ elif [ -f "yarn.lock" ]; then PM="yarn"
20
+ else PM="npm"
21
+ fi
22
+
17
23
  # Find all test files matching pattern
18
24
  readarray -t TEST_FILES < <(find . -path "$TEST_PATTERN" -type f)
19
25
 
@@ -37,7 +43,7 @@ for test_file in "${TEST_FILES[@]}"; do
37
43
  echo "Testing: $test_file"
38
44
 
39
45
  # Run the test
40
- npm test "$test_file" > /dev/null 2>&1 || true
46
+ $PM test "$test_file" > /dev/null 2>&1 || true
41
47
 
42
48
  # Check if pollution appeared
43
49
  if [ -e "$CHECK_PATH" ]; then
@@ -48,7 +54,7 @@ for test_file in "${TEST_FILES[@]}"; do
48
54
  ls -la "$CHECK_PATH" 2>/dev/null || echo "(path exists but can't stat)"
49
55
  echo ""
50
56
  echo "Investigate with:"
51
- echo " npm test '$test_file' -- --reporter=verbose"
57
+ echo " $PM test '$test_file' -- --reporter=verbose"
52
58
  echo " git diff"
53
59
  exit 0
54
60
  fi
@@ -93,10 +93,10 @@ async function gitInit(directory: string) {
93
93
 
94
94
  **Critical:** Use `console.error()` in tests (not logger - may not show)
95
95
 
96
- **Run and capture:**
96
+ **Run and capture** (detect PM: `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, otherwise → npm):
97
97
 
98
98
  ```bash
99
- npm test 2>&1 | grep 'DEBUG git init'
99
+ $PM test 2>&1 | grep 'DEBUG git init'
100
100
  ```
101
101
 
102
102
  **Analyze stack traces:**
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """DOCX 파일에서 텍스트와 이미지를 문단 흐름 순서로 추출한다."""
2
+ """Extract text and images from DOCX files in paragraph flow order."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -17,7 +17,7 @@ def ensure_packages():
17
17
  try:
18
18
  __import__(import_name)
19
19
  except ImportError:
20
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
20
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
21
21
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
22
22
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
23
23
 
@@ -71,7 +71,7 @@ def extract(file_path):
71
71
  elif not has_image and not text:
72
72
  continue
73
73
 
74
- # 추출
74
+ # Table extraction
75
75
  for t_idx, table in enumerate(doc.tables):
76
76
  print(f"\n### Table {t_idx + 1}\n")
77
77
  for row in table.rows:
@@ -79,9 +79,9 @@ def extract(file_path):
79
79
  print("| " + " | ".join(cells) + " |")
80
80
 
81
81
  if img_idx > 0:
82
- print(f"\n---\n이미지 {img_idx} 저장: {out_dir}")
82
+ print(f"\n---\n{img_idx} image(s) saved: {out_dir}")
83
83
  else:
84
- print("\n---\n이미지 없음")
84
+ print("\n---\nNo images")
85
85
 
86
86
 
87
87
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PDF 파일에서 텍스트, 표, 이미지를 페이지별로 추출한다."""
2
+ """Extract text, tables, and images from PDF files page by page."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -16,7 +16,7 @@ def ensure_packages():
16
16
  try:
17
17
  __import__(import_name)
18
18
  except ImportError:
19
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
19
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
20
20
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
21
21
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
22
22
 
@@ -32,7 +32,7 @@ def extract(file_path):
32
32
 
33
33
  print(f"# {Path(file_path).name}\n")
34
34
 
35
- # 텍스트 + 추출 (pdfplumber)
35
+ # Text + table extraction (pdfplumber)
36
36
  with pdfplumber.open(file_path) as pdf:
37
37
  for page_num, page in enumerate(pdf.pages, 1):
38
38
  print(f"## Page {page_num}\n")
@@ -52,7 +52,7 @@ def extract(file_path):
52
52
  print("| " + " | ".join(cells) + " |")
53
53
  print()
54
54
 
55
- # 이미지 추출 (pypdf)
55
+ # Image extraction (pypdf)
56
56
  reader = PdfReader(file_path)
57
57
  for page_num, page in enumerate(reader.pages, 1):
58
58
  if "/XObject" not in (page.get("/Resources") or {}):
@@ -79,19 +79,19 @@ def extract(file_path):
79
79
  img_path.write_bytes(obj._data if hasattr(obj, "_data") else b"")
80
80
  print(f"[IMG] (page={page_num}) {img_path}")
81
81
 
82
- # OCR 안내
82
+ # OCR notice
83
83
  if total_text_len == 0:
84
- print("\n⚠ 텍스트가 추출되지 않았습니다 (스캔 PDF일 있음).")
85
- print("OCR 필요합니다:")
86
- print(" 1. Tesseract OCR 설치: https://github.com/tesseract-ocr/tesseract")
84
+ print("\n⚠ No text was extracted (may be a scanned PDF).")
85
+ print("OCR is required:")
86
+ print(" 1. Install Tesseract OCR: https://github.com/tesseract-ocr/tesseract")
87
87
  print(" 2. pip install pytesseract pdf2image")
88
- print(" 3. pytesseract.image_to_string() 으로 추출")
88
+ print(" 3. Extract with pytesseract.image_to_string()")
89
89
 
90
90
  print()
91
91
  if img_idx > 0:
92
- print(f"---\n이미지 {img_idx} 저장: {out_dir}")
92
+ print(f"---\n{img_idx} image(s) saved: {out_dir}")
93
93
  else:
94
- print("---\n이미지 없음")
94
+ print("---\nNo images")
95
95
 
96
96
 
97
97
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PPTX 파일에서 텍스트와 이미지를 슬라이드별 좌표와 함께 추출한다."""
2
+ """Extract text and images from PPTX files with per-slide coordinates."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -16,13 +16,13 @@ def ensure_packages():
16
16
  try:
17
17
  __import__(import_name)
18
18
  except ImportError:
19
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
19
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
20
20
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
21
21
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
22
22
 
23
23
 
24
24
  def emu_to_inches(emu):
25
- """EMU 인치로 변환 (소수점 1자리)."""
25
+ """Convert EMU to inches (1 decimal place)."""
26
26
  if emu is None:
27
27
  return "?"
28
28
  return f"{emu / 914400:.1f}"
@@ -64,9 +64,9 @@ def extract(file_path):
64
64
  print()
65
65
 
66
66
  if img_idx > 0:
67
- print(f"---\n이미지 {img_idx} 저장: {out_dir}")
67
+ print(f"---\n{img_idx} image(s) saved: {out_dir}")
68
68
  else:
69
- print("---\n이미지 없음")
69
+ print("---\nNo images")
70
70
 
71
71
 
72
72
  if __name__ == "__main__":