@simplysm/sd-claude 13.0.70 → 13.0.72
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/claude/refs/sd-library-issue.md +7 -0
- package/claude/rules/sd-claude-rules.md +2 -2
- package/claude/rules/sd-refs-linker.md +1 -0
- package/claude/sd-statusline.js +1 -1
- package/claude/skills/sd-check/SKILL.md +0 -1
- package/claude/skills/sd-commit/SKILL.md +1 -1
- package/claude/skills/sd-document/extract_docx.py +5 -5
- package/claude/skills/sd-document/extract_pdf.py +11 -11
- package/claude/skills/sd-document/extract_pptx.py +5 -5
- package/claude/skills/sd-document/extract_xlsx.py +7 -7
- package/claude/skills/sd-email-analyze/email-analyzer.py +28 -28
- package/claude/skills/sd-worktree/SKILL.md +53 -0
- package/claude/skills/sd-worktree/sd-worktree.mjs +5 -5
- package/package.json +1 -1
|
@@ -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., "
|
|
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
|
|
@@ -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
|
|
package/claude/sd-statusline.js
CHANGED
|
@@ -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];
|
|
@@ -50,7 +50,7 @@ type(scope): short description
|
|
|
50
50
|
| ------------- | ---------------------------------------------------------------------------- |
|
|
51
51
|
| `type` | `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `build`, `style`, `perf` |
|
|
52
52
|
| `scope` | package name or area (e.g., `solid`, `core-common`, `orm-node`) |
|
|
53
|
-
| `description` | imperative, lowercase, no period at end
|
|
53
|
+
| `description` | written in the system's configured language, imperative, lowercase, no period at end |
|
|
54
54
|
|
|
55
55
|
Examples:
|
|
56
56
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
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"
|
|
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
|
|
82
|
+
print(f"\n---\n{img_idx} image(s) saved: {out_dir}")
|
|
83
83
|
else:
|
|
84
|
-
print("\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
|
-
"""
|
|
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"
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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⚠
|
|
85
|
-
print("OCR
|
|
86
|
-
print(" 1. Tesseract OCR
|
|
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
|
|
92
|
+
print(f"---\n{img_idx} image(s) saved: {out_dir}")
|
|
93
93
|
else:
|
|
94
|
-
print("---\
|
|
94
|
+
print("---\nNo images")
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
if __name__ == "__main__":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
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"
|
|
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
|
|
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
|
|
67
|
+
print(f"---\n{img_idx} image(s) saved: {out_dir}")
|
|
68
68
|
else:
|
|
69
|
-
print("---\
|
|
69
|
+
print("---\nNo images")
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
if __name__ == "__main__":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""Extract data and images from XLSX files with cell positions."""
|
|
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"
|
|
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
|
|
|
@@ -37,10 +37,10 @@ def extract(file_path):
|
|
|
37
37
|
ws = wb[sheet_name]
|
|
38
38
|
print(f"## Sheet: {sheet_name}\n")
|
|
39
39
|
|
|
40
|
-
#
|
|
40
|
+
# Data extraction
|
|
41
41
|
rows = list(ws.iter_rows(values_only=False))
|
|
42
42
|
if not rows:
|
|
43
|
-
print("(
|
|
43
|
+
print("(empty sheet)\n")
|
|
44
44
|
continue
|
|
45
45
|
|
|
46
46
|
for row in rows:
|
|
@@ -53,7 +53,7 @@ def extract(file_path):
|
|
|
53
53
|
cells.append(str(val).strip())
|
|
54
54
|
print(f"[{row[0].coordinate.split('1')[0]}{row[0].row}] " + " | ".join(cells))
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# Image extraction
|
|
57
57
|
if ws._images:
|
|
58
58
|
for img in ws._images:
|
|
59
59
|
img_idx += 1
|
|
@@ -70,9 +70,9 @@ def extract(file_path):
|
|
|
70
70
|
print()
|
|
71
71
|
|
|
72
72
|
if img_idx > 0:
|
|
73
|
-
print(f"---\n
|
|
73
|
+
print(f"---\n{img_idx} image(s) saved: {out_dir}")
|
|
74
74
|
else:
|
|
75
|
-
print("---\
|
|
75
|
+
print("---\nNo images")
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
if __name__ == "__main__":
|
|
@@ -12,13 +12,13 @@ import base64
|
|
|
12
12
|
from email.policy import default as default_policy
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
-
# stdout UTF-8
|
|
15
|
+
# Force stdout UTF-8 (Windows compatibility)
|
|
16
16
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
17
17
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def ensure_packages():
|
|
21
|
-
"""
|
|
21
|
+
"""Auto-install required packages."""
|
|
22
22
|
packages = {"extract-msg": "extract_msg"}
|
|
23
23
|
missing = []
|
|
24
24
|
for pip_name, import_name in packages.items():
|
|
@@ -27,7 +27,7 @@ def ensure_packages():
|
|
|
27
27
|
except ImportError:
|
|
28
28
|
missing.append(pip_name)
|
|
29
29
|
if missing:
|
|
30
|
-
print(f"
|
|
30
|
+
print(f"Installing packages: {', '.join(missing)}...", file=sys.stderr)
|
|
31
31
|
subprocess.check_call(
|
|
32
32
|
[sys.executable, "-m", "pip", "install", "-q", *missing],
|
|
33
33
|
stdout=subprocess.DEVNULL,
|
|
@@ -326,45 +326,45 @@ def build_report(filepath):
|
|
|
326
326
|
saved_attachments = save_files(attachments, out_dir)
|
|
327
327
|
|
|
328
328
|
out = []
|
|
329
|
-
out.append("#
|
|
330
|
-
out.append(f"
|
|
331
|
-
|
|
332
|
-
# ──
|
|
333
|
-
out.append("##
|
|
334
|
-
out.append("|
|
|
335
|
-
out.append("
|
|
336
|
-
out.append(f"|
|
|
337
|
-
out.append(f"|
|
|
338
|
-
out.append(f"|
|
|
329
|
+
out.append("# Email Analysis Report\n")
|
|
330
|
+
out.append(f"**Source file**: `{os.path.basename(filepath)}`\n")
|
|
331
|
+
|
|
332
|
+
# ── Email info
|
|
333
|
+
out.append("## Email Info\n")
|
|
334
|
+
out.append("| Field | Value |")
|
|
335
|
+
out.append("|-------|-------|")
|
|
336
|
+
out.append(f"| **Subject** | {headers['subject']} |")
|
|
337
|
+
out.append(f"| **From** | {headers['from']} |")
|
|
338
|
+
out.append(f"| **To** | {headers['to']} |")
|
|
339
339
|
if headers["cc"]:
|
|
340
|
-
out.append(f"|
|
|
341
|
-
out.append(f"|
|
|
342
|
-
out.append(f"|
|
|
340
|
+
out.append(f"| **CC** | {headers['cc']} |")
|
|
341
|
+
out.append(f"| **Date** | {headers['date']} |")
|
|
342
|
+
out.append(f"| **Attachments** | {len(saved_attachments)} |")
|
|
343
343
|
if all_inline:
|
|
344
|
-
out.append(f"|
|
|
344
|
+
out.append(f"| **Inline images** | {len(all_inline)} |")
|
|
345
345
|
out.append("")
|
|
346
346
|
|
|
347
|
-
# ──
|
|
348
|
-
out.append("##
|
|
347
|
+
# ── Body
|
|
348
|
+
out.append("## Body\n")
|
|
349
349
|
body = body_plain
|
|
350
350
|
if not body and body_html:
|
|
351
351
|
body = strip_html(body_html)
|
|
352
|
-
out.append(body.strip() if body else "_(
|
|
352
|
+
out.append(body.strip() if body else "_(No body)_")
|
|
353
353
|
out.append("")
|
|
354
354
|
|
|
355
|
-
# ──
|
|
355
|
+
# ── Inline images
|
|
356
356
|
if all_inline:
|
|
357
|
-
out.append("##
|
|
358
|
-
out.append("| # |
|
|
357
|
+
out.append("## Inline Images\n")
|
|
358
|
+
out.append("| # | Filename | Size | Saved path |")
|
|
359
359
|
out.append("|---|--------|------|-----------|")
|
|
360
360
|
for i, img in enumerate(all_inline, 1):
|
|
361
361
|
out.append(f"| {i} | {img['filename']} | {fmt_size(img['size'])} | `{img['saved_path']}` |")
|
|
362
362
|
out.append("")
|
|
363
363
|
|
|
364
|
-
# ──
|
|
364
|
+
# ── Attachments
|
|
365
365
|
if saved_attachments:
|
|
366
|
-
out.append("##
|
|
367
|
-
out.append("| # |
|
|
366
|
+
out.append("## Attachments\n")
|
|
367
|
+
out.append("| # | Filename | Size | Saved path |")
|
|
368
368
|
out.append("|---|--------|------|-----------|")
|
|
369
369
|
for i, a in enumerate(saved_attachments, 1):
|
|
370
370
|
out.append(f"| {i} | {a['filename']} | {fmt_size(a['size'])} | `{a['saved_path']}` |")
|
|
@@ -382,12 +382,12 @@ if __name__ == "__main__":
|
|
|
382
382
|
|
|
383
383
|
path = sys.argv[1]
|
|
384
384
|
if not os.path.isfile(path):
|
|
385
|
-
print(f"
|
|
385
|
+
print(f"File not found: {path}", file=sys.stderr)
|
|
386
386
|
sys.exit(1)
|
|
387
387
|
|
|
388
388
|
ext = Path(path).suffix.lower()
|
|
389
389
|
if ext not in (".eml", ".msg"):
|
|
390
|
-
print(f"
|
|
390
|
+
print(f"Unsupported format: {ext} (only .eml and .msg are supported)", file=sys.stderr)
|
|
391
391
|
sys.exit(1)
|
|
392
392
|
|
|
393
393
|
print(build_report(path))
|
|
@@ -6,6 +6,51 @@ model: haiku
|
|
|
6
6
|
|
|
7
7
|
# sd-worktree
|
|
8
8
|
|
|
9
|
+
## CRITICAL SAFETY RULES — MERGE & STASH
|
|
10
|
+
|
|
11
|
+
**These rules are ABSOLUTE. No exceptions. Applies to ALL modes (yolo, normal, plan, etc.).**
|
|
12
|
+
|
|
13
|
+
1. **NEVER run `git stash drop`, `git stash pop`, or `git stash clear`.**
|
|
14
|
+
- If you stashed something, ONLY use `git stash apply` (which keeps the stash intact).
|
|
15
|
+
- If stash is no longer needed, ASK the user before dropping it.
|
|
16
|
+
|
|
17
|
+
2. **If `merge` fails or produces conflicts → STOP IMMEDIATELY and show this message:**
|
|
18
|
+
```
|
|
19
|
+
⚠️ A problem occurred during merge.
|
|
20
|
+
Please proceed with the merge manually.
|
|
21
|
+
(Error details: <print error/conflict info as-is>)
|
|
22
|
+
```
|
|
23
|
+
- Show this message in the system's configured language.
|
|
24
|
+
- Do NOT attempt to resolve conflicts yourself.
|
|
25
|
+
- Do NOT run `git merge --abort` without asking.
|
|
26
|
+
- Do NOT proceed to `clean` after a failed merge.
|
|
27
|
+
- Do NOT retry or work around the error.
|
|
28
|
+
- Just show the message above and STOP. Do nothing else.
|
|
29
|
+
|
|
30
|
+
3. **If `rebase` fails or produces conflicts → STOP IMMEDIATELY and show this message:**
|
|
31
|
+
```
|
|
32
|
+
⚠️ A problem occurred during rebase.
|
|
33
|
+
Please proceed with the rebase manually.
|
|
34
|
+
(Error details: <print error/conflict info as-is>)
|
|
35
|
+
```
|
|
36
|
+
- Show this message in the system's configured language.
|
|
37
|
+
- Same rules as merge. Do NOT auto-resolve. Just show the message and STOP.
|
|
38
|
+
|
|
39
|
+
4. **NEVER run destructive git commands during worktree workflows:**
|
|
40
|
+
- `git reset --hard`, `git checkout -- .`, `git restore .`, `git clean -f`
|
|
41
|
+
- `git branch -D` (force delete) — only `-d` (safe delete) is allowed
|
|
42
|
+
- `git stash drop`, `git stash pop`, `git stash clear`
|
|
43
|
+
|
|
44
|
+
5. **Before ANY merge/rebase, verify:**
|
|
45
|
+
- Both main and worktree have NO uncommitted changes (`git status --porcelain`)
|
|
46
|
+
- If uncommitted changes exist → ask the user (do NOT auto-stash)
|
|
47
|
+
|
|
48
|
+
6. **After merge completes, verify success before proceeding:**
|
|
49
|
+
- Check `git status` — if merge conflicts exist, STOP and report
|
|
50
|
+
- Do NOT proceed to `clean` until merge is confirmed successful
|
|
51
|
+
|
|
52
|
+
**Violation of these rules causes IRREVERSIBLE DATA LOSS.**
|
|
53
|
+
|
|
9
54
|
## Overview
|
|
10
55
|
|
|
11
56
|
Create, merge, and clean up git worktrees under `.worktrees/`. Uses the current branch of the main working tree as the source branch.
|
|
@@ -44,6 +89,7 @@ node .claude/skills/sd-worktree/sd-worktree.mjs rebase [name]
|
|
|
44
89
|
- Rebases the worktree branch onto the latest commit of the main branch
|
|
45
90
|
- Errors if uncommitted changes exist → commit or stash first
|
|
46
91
|
- Use when you want a clean history before merging
|
|
92
|
+
- **If rebase fails or conflicts → STOP. Report to user. Do NOT auto-resolve.**
|
|
47
93
|
|
|
48
94
|
### merge — Merge into main branch
|
|
49
95
|
|
|
@@ -56,6 +102,13 @@ node .claude/skills/sd-worktree/sd-worktree.mjs merge [name]
|
|
|
56
102
|
- Errors if uncommitted changes exist → commit or stash first
|
|
57
103
|
- After merge, always `cd <project-root>` (required for subsequent clean)
|
|
58
104
|
|
|
105
|
+
**MERGE SAFETY PROTOCOL:**
|
|
106
|
+
1. Before merge: check BOTH main and worktree for uncommitted changes
|
|
107
|
+
2. If the script exits with non-zero → show "Please proceed with the merge manually." message (in system language) and STOP.
|
|
108
|
+
3. After merge: run `git status` in main to confirm no conflicts
|
|
109
|
+
4. If conflicts or errors → show "Please proceed with the merge manually." message (in system language) and STOP.
|
|
110
|
+
5. Only proceed to `clean` after confirming merge was fully successful
|
|
111
|
+
|
|
59
112
|
### clean — Remove worktree and delete branch
|
|
60
113
|
|
|
61
114
|
```bash
|
|
@@ -13,7 +13,7 @@ function getOutput(command) {
|
|
|
13
13
|
return execSync(command, { encoding: "utf-8" }).trim();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Main working tree path (accurate even when run inside a worktree)
|
|
17
17
|
const mainWorktree = getOutput("git worktree list --porcelain")
|
|
18
18
|
.split("\n")[0]
|
|
19
19
|
.replace("worktree ", "");
|
|
@@ -71,7 +71,7 @@ switch (cmd) {
|
|
|
71
71
|
console.error("Usage: sd-worktree.mjs merge [name] (or run inside .worktrees/<name>)");
|
|
72
72
|
process.exit(1);
|
|
73
73
|
}
|
|
74
|
-
// uncommitted
|
|
74
|
+
// Check for uncommitted changes
|
|
75
75
|
const worktreePath_m = resolve(mainWorktree, ".worktrees", name);
|
|
76
76
|
if (existsSync(worktreePath_m)) {
|
|
77
77
|
const status = getOutput(`git -C "${worktreePath_m}" status --porcelain`);
|
|
@@ -99,7 +99,7 @@ switch (cmd) {
|
|
|
99
99
|
console.error(`Error: worktree '${name}' does not exist.`);
|
|
100
100
|
process.exit(1);
|
|
101
101
|
}
|
|
102
|
-
// uncommitted
|
|
102
|
+
// Check for uncommitted changes
|
|
103
103
|
const statusR = getOutput(`git -C "${worktreePath_r}" status --porcelain`);
|
|
104
104
|
if (statusR) {
|
|
105
105
|
console.error(`Error: worktree '${name}' has uncommitted changes:\n${statusR}`);
|
|
@@ -119,7 +119,7 @@ switch (cmd) {
|
|
|
119
119
|
console.error("Usage: sd-worktree.mjs clean [name] (or run inside .worktrees/<name>)");
|
|
120
120
|
process.exit(1);
|
|
121
121
|
}
|
|
122
|
-
//
|
|
122
|
+
// Block execution from inside the worktree
|
|
123
123
|
const worktreePath = resolve(mainWorktree, ".worktrees", name);
|
|
124
124
|
const cwd = process.cwd();
|
|
125
125
|
if (cwd === worktreePath || cwd.startsWith(worktreePath + "/")) {
|
|
@@ -134,7 +134,7 @@ switch (cmd) {
|
|
|
134
134
|
try {
|
|
135
135
|
run(`git worktree remove --force "${worktreePath}"`, { cwd: mainWorktree });
|
|
136
136
|
} catch {
|
|
137
|
-
//
|
|
137
|
+
// Manual cleanup when git worktree remove fails (e.g., due to node_modules)
|
|
138
138
|
console.log("git worktree remove failed, cleaning up manually...");
|
|
139
139
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
140
140
|
run("git worktree prune", { cwd: mainWorktree });
|