Compare commits

..

65 Commits

Author SHA1 Message Date
a954f62f55 Decouple stats daemon and preserve final mine OSD status
- Run `subminer stats -b` as a dedicated daemon process, independent from the overlay app
- Stop Anki progress spinner before showing final `✓`/`x` mine result so it is not overwritten
- Keep grammar/noise subtitle tokens hoverable while stripping annotation metadata
2026-03-18 23:49:27 -07:00
4d96ebf5c0 fix: reduce prefetched subtitle annotation delay 2026-03-18 23:47:33 -07:00
7a0d7a488b docs: redesign README for cleaner layout and scannability
- Condense features into bold-label paragraphs instead of H3 subsections
- Collapse install instructions into <details> sections
- Remove redundant screenshots (annotations-key, stats-vocabulary)
- Add AUR version badge
- Merge first-launch and setup steps into a single paragraph
- Add horizontal rule dividers between major sections
2026-03-18 23:35:17 -07:00
f916b65d7f fix: sync texthooker-ui annotation overrides 2026-03-18 19:32:51 -07:00
36627bf87d fix(anki): avoid unnecessary known-word cache restarts 2026-03-18 19:29:47 -07:00
ad1f66a842 feat: sync animated anki images to sentence audio 2026-03-18 19:21:12 -07:00
f4cce31d4a fix: align texthooker and stats formatting with CI expectations 2026-03-18 19:01:29 -07:00
ec56970646 fix(ci): install stats deps in release builds 2026-03-18 02:37:58 -07:00
48f10dbb03 chore(backlog): maintain task backlog and add changelog fragments
- Move completed tasks (85, 117, 118, 155) to backlog/completed/
- Delete superseded task files (166 verification, 172 drilldown)
- Add stats dashboard milestone m-1
- Add new tasks (190, 194)
- Update task metadata across remaining backlog items
- Add changelog fragments for stats, mpv args, and subtitle filtering
2026-03-18 02:25:07 -07:00
1cb129b0b7 chore: update README, gitignore, and add CLAUDE.md
- Refresh README with feature overview and screenshot embeds
- Add .superpowers/ and clean up duplicate gitignore entries
- Add CLAUDE.md project instructions
- Remove stale release/release-notes.md
2026-03-18 02:24:57 -07:00
af1505fbe6 docs: update config examples, docs site, and add screenshots
- Update config examples with word field, retention, and stats options
- Add immersion tracking documentation for retention presets
- Add Anki integration and configuration docs updates
- Add stats dashboard screenshots
2026-03-18 02:24:46 -07:00
97126caf4e feat(stats): add note ID resolution and session event handling improvements
- Add note ID resolution through merge redirects in stats API
- Build Anki note previews using configured field names
- Add session event helpers for merged note dedup and stable request keys
- Refactor SessionDetail to prevent redundant note info requests
- Add session event popover and API client tests
2026-03-18 02:24:38 -07:00
a0015dc75c feat: add configurable Anki word field with note ID merge tracking
- Extract word field config into reusable anki-field-config module
- Add ankiConnect.fields.word config option (default: "Expression")
- Replace hardcoded "Expression" field references across Anki integration
- Add note ID redirect tracking for merged/moved cards
- Support legacy ankiConnect.wordField migration path
2026-03-18 02:24:26 -07:00
61e1621137 perf: split stats app bundles by route 2026-03-18 00:05:51 -07:00
a5b1c0509d fix(stats): align session event popovers with chart plot area 2026-03-17 23:56:58 -07:00
e694963426 fix(stats): address PR 19 review follow-ups 2026-03-17 23:56:42 -07:00
a69254f976 feat(stats): show seek length in session event tooltip 2026-03-17 23:38:45 -07:00
a1348cf8e4 chore(backlog): add m-1 milestone for remaining stats fixes 2026-03-17 23:38:33 -07:00
f9b582582b fix(stats): load full session timelines by default 2026-03-17 22:37:34 -07:00
8f39416ff5 fix(stats): use yomitan tokens for subtitle counts 2026-03-17 22:33:08 -07:00
ecb41a490b feat(launcher): add mpv args passthrough 2026-03-17 21:51:52 -07:00
b061b265c2 chore(vendor): bump subminer-yomitan 2026-03-17 20:12:42 -07:00
f2b3af17d7 docs: update docs, add backlog tasks and change notes
Update configuration, immersion tracking, and mining workflow docs.
Add backlog tasks for upcoming work items and change notes for recent
features and fixes.
2026-03-17 20:12:42 -07:00
5698121996 chore: minor fixes and cleanup across services and renderer 2026-03-17 20:12:41 -07:00
f8e2ae4887 feat: overhaul stats dashboard with navigation, trends, and anime views
Add navigation state machine for tab/detail routing, anime overview
stats with Yomitan lookup rates, session word count accuracy fixes,
vocabulary tab hook order fix, simplified trends data fetching from
backend-aggregated endpoints, and improved session detail charts.
2026-03-17 20:12:41 -07:00
08a5401a7d feat: add background stats server daemon lifecycle
Implement `subminer stats -b` to start a background stats daemon and
`subminer stats -s` to stop it, with PID-based process lifecycle
management, single-instance lock bypass for daemon mode, and automatic
reuse of running daemon instances.
2026-03-17 20:12:41 -07:00
55ee12e87f feat: refactor immersion tracker queries and session word tracking
Add comprehensive query helpers for session deletion with word aggregate
refresh, known-words-per-session timeline, anime-level word summaries,
and trends dashboard aggregation. Track yomitanLookupCount in session
metrics and support bulk session operations.
2026-03-17 20:12:41 -07:00
a5a6426fe1 feat: add mark-as-watched keybinding and Yomitan lookup tracking
Add configurable keybinding to mark the current video as watched with
IPC plumbing between renderer and main process. Add event listener
infrastructure for tracking Yomitan dictionary lookups per session.
2026-03-17 20:12:41 -07:00
75f2c212c7 refactor: centralize watch threshold constant
Extract DEFAULT_MIN_WATCH_RATIO (0.85) into src/shared/watch-threshold.ts
to replace the hardcoded ANILIST_UPDATE_MIN_WATCH_RATIO in main.ts.
2026-03-17 20:05:08 -07:00
3dd337f518 update backlog tasks 2026-03-17 20:05:08 -07:00
94ec28b48c refactor: isolate character dictionary completion handling 2026-03-17 20:05:08 -07:00
078792e0b2 docs: refresh immersion and stats documentation 2026-03-17 20:05:08 -07:00
390ae1b2f2 feat: optimize stats dashboard data and components 2026-03-17 20:05:08 -07:00
11710f20db feat: stabilize startup sync and overlay/runtime paths 2026-03-17 20:05:08 -07:00
de574c04bd Add isolated typecheck test for get_frequency script
- add `scripts/get_frequency.test.ts` to verify `scripts/get_frequency.ts` typechecks alone
- remove duplicate `yomitanSession` property from runtime state/interface
2026-03-17 20:05:08 -07:00
a9e33618e7 chore: apply remaining workspace formatting and updates 2026-03-17 20:05:08 -07:00
77c35c770d chore: add stats lint/check wiring for CI 2026-03-17 20:05:08 -07:00
64e9821e7a chore(backlog): sync task metadata and archives 2026-03-17 20:05:08 -07:00
5c529802c6 fix(stats): restore cross-anime words table 2026-03-17 20:05:08 -07:00
8123a145c0 fix(plugin): add lowercase linux binary fallbacks 2026-03-17 20:05:07 -07:00
659118c20c docs: document stats page mining, word exclusions, and vocabulary UX improvements 2026-03-17 20:05:07 -07:00
929159bba5 test(renderer): verify excluded interjections remain visible as non-interactive text 2026-03-17 20:05:07 -07:00
a317019bb9 feat(tokenizer): exclude interjections and sound effects from subtitle annotations
- Filter out 感動詞 (interjection) POS1 tokens from annotation payloads
- Exclude common interjection terms (ああ, ええ, はあ, etc.)
- Exclude reduplicated kana SFX with optional trailing と
- shouldExcludeTokenFromSubtitleAnnotations checks both POS1 and term patterns
- filterSubtitleAnnotationTokens applied after annotation stage
2026-03-17 20:05:07 -07:00
5767667d51 feat(stats): add mine card from stats page with Yomitan bridge
- POST /api/stats/mine-card endpoint with mode=word|sentence|audio
- mode=word: creates full Yomitan card (definition/reading/pitch) via hidden search page bridge
- mode=sentence/audio: creates card directly with Lapis/Kiku flags
- Audio + image generated in parallel from source video via ffmpeg
- Respects all AnkiConnect config (AVIF, static, field mappings, metadata pattern)
- addYomitanNoteViaSearch calls window.__subminerAddNote exposed by Yomitan fork
- Syncs AnkiConnect URL to Yomitan before each word mine
2026-03-17 20:05:07 -07:00
a1f30fd482 feat(tracking): store secondary subtitle text and source path in occurrence data
- Add secondary_text column to imm_subtitle_lines with migration
- Pass currentSecondarySubText through recordSubtitleLine flow
- Include secondaryText and sourcePath in word/kanji occurrence queries
- Update all type interfaces (backend + frontend)
2026-03-17 20:05:07 -07:00
5a30446809 feat(stats): add click handler to bar charts for word detail navigation 2026-03-17 20:05:07 -07:00
6634255f43 feat(stats): fix truncated readings and improve word detail UX
- fullReading() reconstructs full word reading from headword + partial stored reading
- FrequencyRankTable always shows reading for every row
- Word highlighted in example sentences with underline style
- Bar chart clicks open word detail panel
2026-03-17 20:05:07 -07:00
a3ed8dcf3d feat(stats): add word exclusion list for vocabulary tab
- useExcludedWords hook manages excluded words in localStorage
- ExclusionManager modal for viewing/removing excluded words
- Exclusion button on WordDetailPanel header
- Filtered words affect stat cards, charts, frequency table, and word list
2026-03-17 20:05:07 -07:00
92c1557e46 refactor: split known words config from n-plus-one 2026-03-17 20:05:07 -07:00
04682a02cc feat: improve stats dashboard and annotation settings 2026-03-17 20:05:07 -07:00
650e95cdc3 Feature/renderer performance (#24) 2026-03-17 20:05:07 -07:00
46fbea902a Harden stats APIs and fix Electron Yomitan debug runtime
- Validate stats session IDs/limits and add AnkiConnect request timeouts
- Stabilize stats window/runtime lifecycle and tighten window security defaults
- Fix Electron CLI debug startup by unsetting `ELECTRON_RUN_AS_NODE` and wiring Yomitan session state
- Expand regression coverage for tracker queries/events ordering and session aggregates
- Update docs for stats dashboard usage and Yomitan lookup troubleshooting
2026-03-17 20:05:07 -07:00
93811ebfde fix(launcher): default stats cleanup to vocab mode 2026-03-17 20:05:07 -07:00
74544d79a7 docs: update stats dashboard docs, config, and releasing checklist
- Update dashboard tab descriptions to include Anime tab and richer session timelines
- Add autoOpenBrowser config option to stats section
- Add subminer stats cleanup command to changelog fragment
- Expand releasing checklist with doc verification, changelog lint, and build gates
2026-03-17 20:05:07 -07:00
536f0a1315 feat(stats): redesign session timeline and clean up vocabulary tab
- Replace cumulative line chart with activity-focused area chart showing per-interval new words
- Add total words as a blue line on a secondary right Y-axis
- Add pause shaded regions, seek markers, and card mined markers with numeric x-axis for reliable rendering
- Add clickable header logo with proper aspect ratio
- Remove unused "Hide particles & single kana" checkbox from vocabulary tab
2026-03-17 20:05:07 -07:00
ff2d9141bc feat(stats): add episodes completed and anime completed to tracking snapshot
- Query watched videos count and anime with all episodes watched
- Display in overview tracking snapshot
- Remove efficiency section from trends
2026-03-17 20:05:07 -07:00
249a7cada8 chore: remove implementation plan documents 2026-03-17 20:05:07 -07:00
9530445a95 feat: add AniList rate limiter and remaining backlog tasks 2026-03-17 20:05:07 -07:00
2d87dae6cc docs: update documentation site for stats dashboard and immersion tracking 2026-03-17 20:05:07 -07:00
0f44107beb feat(stats): build anime-centric stats dashboard frontend
5-tab React dashboard with Catppuccin Mocha theme:
- Overview: hero stats, streak calendar, watch time chart, recent sessions
- Anime: grid with cover art, episode list with completion %, detail view
- Trends: 15 charts across Activity, Efficiency, Anime, and Patterns
- Vocabulary: POS-filtered word/kanji lists with detail panels
- Sessions: expandable session history with event timeline

Features:
- Cross-tab navigation (anime <-> vocabulary)
- Global word detail panel overlay
- Expandable episode detail with Anki card links (Expression field)
- Per-anime multi-line trend charts
- Watch time by day-of-week and hour-of-day
- Collapsible sections with accessibility (aria-expanded)
- Card size selector for anime grid
- Cover art caching via AniList
- HTTP API client with file:// protocol fallback for Electron overlay
2026-03-17 20:05:07 -07:00
950263bd66 feat(stats): add launcher stats command and build integration
- Launcher stats subcommand with cleanup mode
- Stats frontend build integrated into Makefile
- CI workflow updated for stats package
- Config example updated with stats section
- mpv plugin menu entry for stats toggle
2026-03-17 20:05:07 -07:00
26fb5b4162 feat(stats): wire stats server, overlay, and CLI into main process
- Stats server auto-start on immersion tracker init
- Stats overlay toggle via keybinding and IPC
- Stats CLI command (subminer stats) with cleanup mode
- mpv plugin menu integration for stats toggle
- CLI args for --stats, --stats-cleanup, --stats-response-path
2026-03-17 20:04:40 -07:00
ffe5c6e1c6 feat(stats): add stats server, API endpoints, config, and Anki integration
- Hono HTTP server with 20+ REST endpoints for stats data
- Stats overlay BrowserWindow with toggle keybinding
- IPC channel definitions and preload bridge
- Stats config section (toggleKey, serverPort, autoStartServer, autoOpenBrowser)
- Config resolver for stats section
- AnkiConnect proxy endpoints (guiBrowse, notesInfo)
- Note ID passthrough in card mining callback chain
- Stats CLI command with autoOpenBrowser respect
2026-03-17 20:04:40 -07:00
fe8bb167c4 feat(immersion): add anime metadata, occurrence tracking, and schema upgrades
- Add imm_anime table with AniList integration
- Add imm_subtitle_lines, imm_word_line_occurrences, imm_kanji_line_occurrences
- Add POS fields (part_of_speech, pos1, pos2, pos3) to imm_words
- Add anime metadata parsing with guessit fallback
- Add video duration tracking and watched status
- Add episode, streak, trend, and word/kanji detail queries
- Deduplicate subtitle line recording within sessions
- Pass Anki note IDs through card mining callback chain
2026-03-17 20:01:23 -07:00
cc5d270b8e docs: add stats dashboard design docs, plans, and knowledge base
- Stats dashboard redesign design and implementation plans
- Episode detail and Anki card link design
- Internal knowledge base restructure
- Backlog tasks for testing, verification, and occurrence tracking
2026-03-17 20:01:23 -07:00
690 changed files with 13267 additions and 44277 deletions

View File

@@ -1,20 +0,0 @@
{
"name": "subminer-local",
"interface": {
"displayName": "SubMiner Local"
},
"plugins": [
{
"name": "subminer-workflow",
"source": {
"source": "local",
"path": "./plugins/subminer-workflow"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}

View File

@@ -1,22 +1,127 @@
---
name: 'subminer-change-verification'
description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.'
name: "subminer-change-verification"
description: "Use when working in the SubMiner repo and you need to verify code changes actually work. Covers targeted regression checks during debugging and pre-handoff verification, with cheap-first lane selection for config, docs, launcher/plugin, runtime-compat, and optional real-runtime escalation."
---
# Compatibility Shim
# SubMiner Change Verification
Canonical source:
Use this skill for SubMiner code changes. Default to cheap, repo-native verification first. Escalate only when the changed behavior actually depends on Electron, mpv, overlay/window tracking, or other GUI-sensitive runtime behavior.
- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md`
## Scripts
Canonical helper scripts:
- `scripts/classify_subminer_diff.sh`
- Emits suggested lanes and flags from explicit paths or current git changes.
- `scripts/verify_subminer_change.sh`
- Runs selected lanes, captures artifacts, and writes a compact summary.
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
If you need an explicit installed path, use the directory that contains this `SKILL.md`. The helper scripts live under:
When this shim is invoked:
```bash
export SUBMINER_VERIFY_SKILL="<path-to-skill>"
```
1. Read the canonical plugin-owned skill.
2. Follow the plugin-owned skill as the source of truth.
3. Use the wrapper scripts in this shim directory only for compatibility with existing commands, docs, and backlog history.
4. Do not duplicate workflow changes here; update the plugin-owned skill and scripts instead.
## Default workflow
1. Inspect the changed files or user-requested area.
2. Run the classifier unless you already know the right lane.
3. Run the verifier with the cheapest sufficient lane set.
4. If the classifier emits `flag:real-runtime-candidate`, do not jump straight to runtime verification. First run the non-runtime lanes.
5. Escalate to explicit `--lane real-runtime --allow-real-runtime` only when cheaper lanes cannot validate the behavior claim.
6. Return:
- verification summary
- exact commands run
- artifact paths
- skipped lanes and blockers
## Quick start
Repo-source quick start:
```bash
bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
```
Installed-skill quick start:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/classify_subminer_diff.sh"
```
Classify explicit files:
```bash
bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh \
launcher/main.ts \
plugin/subminer/lifecycle.lua \
src/main/runtime/mpv-client-runtime-service.ts
```
Run automatic lane selection:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
```
Installed-skill form:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/verify_subminer_change.sh"
```
Run targeted lanes:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--lane launcher-plugin \
--lane runtime-compat
```
Dry-run to inspect planned commands and artifact layout:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--dry-run \
launcher/main.ts \
src/main.ts
```
## Lane guidance
- `docs`
- For `docs-site/`, `docs/`, and doc-only edits.
- `config`
- For `src/config/` and config-template-sensitive edits.
- `core`
- For general source changes where `typecheck` + `test:fast` is the best cheap signal.
- `launcher-plugin`
- For `launcher/`, `plugin/subminer/`, plugin gating scripts, and wrapper/mpv routing work.
- `runtime-compat`
- For `src/main*`, runtime/composer wiring, mpv/overlay services, window trackers, and dist-sensitive behavior.
- `real-runtime`
- Only after deliberate escalation.
## Real Runtime Escalation
Escalate only when the change claim depends on actual runtime behavior, for example:
- overlay appears, hides, or tracks a real mpv window
- mpv launch flags or pause-until-ready behavior
- plugin/socket/auto-start handshake under a real player
- macOS/window-tracker/focus-sensitive behavior
If the environment cannot support authoritative runtime verification, report the blocker explicitly. Do not silently downgrade a runtime-required claim to a pass.
## Artifact contract
The verifier writes under `.tmp/skill-verification/<timestamp>/`:
- `summary.json`
- `summary.txt`
- `classification.txt`
- `env.txt`
- `lanes.txt`
- `steps.tsv`
- `steps/*.stdout.log`
- `steps/*.stderr.log`
On failure, quote the exact failing command and point at the artifact directory.

View File

@@ -1,13 +1,163 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh"
usage() {
cat <<'EOF'
Usage: classify_subminer_diff.sh [path ...]
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
Emit suggested verification lanes for explicit paths or current local git changes.
Output format:
lane:<name>
flag:<name>
reason:<text>
EOF
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
add_lane() {
local lane=$1
if ! has_item "$lane" "${LANES[@]:-}"; then
LANES+=("$lane")
fi
}
add_flag() {
local flag=$1
if ! has_item "$flag" "${FLAGS[@]:-}"; then
FLAGS+=("$flag")
fi
}
add_reason() {
REASONS+=("$1")
}
collect_git_paths() {
local top_level
if ! top_level=$(git rev-parse --show-toplevel 2>/dev/null); then
return 0
fi
(
cd "$top_level"
if git rev-parse --verify HEAD >/dev/null 2>&1; then
git diff --name-only --relative HEAD --
git diff --name-only --relative --cached --
else
git diff --name-only --relative --
git diff --name-only --relative --cached --
fi
git ls-files --others --exclude-standard
) | awk 'NF' | sort -u
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
exec "$TARGET" "$@"
declare -a PATHS=()
declare -a LANES=()
declare -a FLAGS=()
declare -a REASONS=()
if [[ $# -gt 0 ]]; then
while [[ $# -gt 0 ]]; do
PATHS+=("$1")
shift
done
else
while IFS= read -r line; do
[[ -n "$line" ]] && PATHS+=("$line")
done < <(collect_git_paths)
fi
if [[ ${#PATHS[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no changed paths detected -> default to core"
fi
for path in "${PATHS[@]}"; do
specialized=0
case "$path" in
docs-site/*|docs/*|changes/*|README.md)
add_lane "docs"
add_reason "$path -> docs"
specialized=1
;;
esac
case "$path" in
src/config/*|src/generate-config-example.ts|src/verify-config-example.ts|docs-site/public/config.example.jsonc|config.example.jsonc)
add_lane "config"
add_reason "$path -> config"
specialized=1
;;
esac
case "$path" in
launcher/*|plugin/subminer/*|plugin/subminer.conf|scripts/test-plugin-*|scripts/get-mpv-window-*|scripts/configure-plugin-binary-path.mjs)
add_lane "launcher-plugin"
add_reason "$path -> launcher-plugin"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
case "$path" in
src/main.ts|src/main-entry.ts|src/preload.ts|src/main/*|src/core/services/mpv*|src/core/services/overlay*|src/renderer/*|src/window-trackers/*|scripts/prepare-build-assets.mjs)
add_lane "runtime-compat"
add_reason "$path -> runtime-compat"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
if [[ "$specialized" == "0" ]]; then
case "$path" in
src/*|package.json|tsconfig*.json|scripts/*|Makefile)
add_lane "core"
add_reason "$path -> core"
;;
esac
fi
case "$path" in
package.json|src/main.ts|src/main-entry.ts|src/preload.ts)
add_flag "broad-impact"
add_reason "$path -> broad-impact"
;;
esac
done
if [[ ${#LANES[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no lane-specific matches -> default to core"
fi
for lane in "${LANES[@]}"; do
printf 'lane:%s\n' "$lane"
done
for flag in "${FLAGS[@]}"; do
printf 'flag:%s\n' "$flag"
done
for reason in "${REASONS[@]}"; do
printf 'reason:%s\n' "$reason"
done

View File

@@ -1,13 +1,566 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh"
usage() {
cat <<'EOF'
Usage: verify_subminer_change.sh [options] [path ...]
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
Options:
--lane <name> Force a verification lane. Repeatable.
--artifact-dir <dir> Use an explicit artifact directory.
--allow-real-runtime Allow explicit real-runtime execution.
--allow-real-gui Deprecated alias for --allow-real-runtime.
--dry-run Record planned steps without executing commands.
--help Show this help text.
If no lanes are supplied, the script classifies the provided paths. If no paths are
provided, it classifies the current local git changes.
Authoritative real-runtime verification should be requested with explicit path
arguments instead of relying on inferred local git changes.
EOF
}
timestamp() {
date +%Y%m%d-%H%M%S
}
timestamp_iso() {
date -u +%Y-%m-%dT%H:%M:%SZ
}
generate_session_id() {
local tmp_dir
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/subminer-verify-$(timestamp)-XXXXXX")
basename "$tmp_dir"
rmdir "$tmp_dir"
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
normalize_lane_name() {
case "$1" in
real-gui)
printf '%s' "real-runtime"
;;
*)
printf '%s' "$1"
;;
esac
}
add_lane() {
local lane
lane=$(normalize_lane_name "$1")
if ! has_item "$lane" "${SELECTED_LANES[@]:-}"; then
SELECTED_LANES+=("$lane")
fi
}
add_blocker() {
BLOCKERS+=("$1")
BLOCKED=1
}
append_step_record() {
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" >>"$STEPS_TSV"
}
record_env() {
{
printf 'repo_root=%s\n' "$REPO_ROOT"
printf 'session_id=%s\n' "$SESSION_ID"
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
printf 'path_selection_mode=%s\n' "$PATH_SELECTION_MODE"
printf 'dry_run=%s\n' "$DRY_RUN"
printf 'allow_real_runtime=%s\n' "$ALLOW_REAL_RUNTIME"
printf 'session_home=%s\n' "$SESSION_HOME"
printf 'session_xdg_config_home=%s\n' "$SESSION_XDG_CONFIG_HOME"
printf 'session_mpv_dir=%s\n' "$SESSION_MPV_DIR"
printf 'session_logs_dir=%s\n' "$SESSION_LOGS_DIR"
printf 'session_mpv_log=%s\n' "$SESSION_MPV_LOG"
printf 'pwd=%s\n' "$(pwd)"
git rev-parse --short HEAD 2>/dev/null | sed 's/^/git_head=/' || true
git status --short 2>/dev/null || true
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
printf 'requested_paths=\n'
printf ' %s\n' "${PATH_ARGS[@]}"
fi
} >"$ARTIFACT_DIR/env.txt"
}
run_step() {
local lane=$1
local name=$2
local command=$3
local note=${4:-}
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
local stdout_path="$ARTIFACT_DIR/$stdout_rel"
local stderr_path="$ARTIFACT_DIR/$stderr_rel"
local status exit_code
COMMANDS_RUN+=("$command")
printf '%s\n' "$command" >"$ARTIFACT_DIR/steps/${slug}.command.txt"
if [[ "$DRY_RUN" == "1" ]]; then
printf '[dry-run] %s\n' "$command" >"$stdout_path"
: >"$stderr_path"
status="dry-run"
exit_code=0
else
if bash -lc "cd \"$REPO_ROOT\" && $command" >"$stdout_path" 2>"$stderr_path"; then
status="passed"
exit_code=0
EXECUTED_REAL_STEPS=1
else
exit_code=$?
status="failed"
FAILED=1
fi
fi
append_step_record "$lane" "$name" "$status" "$exit_code" "$command" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
if [[ "$status" == "failed" ]]; then
FAILURE_STEP="$name"
FAILURE_COMMAND="$command"
FAILURE_STDOUT="$stdout_rel"
FAILURE_STDERR="$stderr_rel"
return "$exit_code"
fi
}
record_nonpassing_step() {
local lane=$1
local name=$2
local status=$3
local note=$4
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
printf '%s\n' "$note" >"$ARTIFACT_DIR/$stdout_rel"
: >"$ARTIFACT_DIR/$stderr_rel"
append_step_record "$lane" "$name" "$status" "0" "" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
}
record_skipped_step() {
record_nonpassing_step "$1" "$2" "skipped" "$3"
}
record_blocked_step() {
add_blocker "$3"
record_nonpassing_step "$1" "$2" "blocked" "$3"
}
record_failed_step() {
FAILED=1
FAILURE_STEP=$2
FAILURE_COMMAND=${FAILURE_COMMAND:-"(validation)"}
FAILURE_STDOUT="steps/${2//[^a-zA-Z0-9_-]/-}.stdout.log"
FAILURE_STDERR="steps/${2//[^a-zA-Z0-9_-]/-}.stderr.log"
add_blocker "$3"
record_nonpassing_step "$1" "$2" "failed" "$3"
}
find_real_runtime_helper() {
local candidate
for candidate in \
"$SCRIPT_DIR/run_real_runtime_smoke.sh" \
"$SCRIPT_DIR/run_real_mpv_smoke.sh"; do
if [[ -x "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi
done
return 1
}
acquire_real_runtime_lease() {
local lease_root="$REPO_ROOT/.tmp/skill-verification/locks"
local lease_dir="$lease_root/exclusive-real-runtime"
mkdir -p "$lease_root"
if mkdir "$lease_dir" 2>/dev/null; then
REAL_RUNTIME_LEASE_DIR="$lease_dir"
printf '%s\n' "$SESSION_ID" >"$lease_dir/session_id"
return 0
fi
local owner=""
if [[ -f "$lease_dir/session_id" ]]; then
owner=$(cat "$lease_dir/session_id")
fi
add_blocker "real-runtime lease already held${owner:+ by $owner}"
return 1
}
release_real_runtime_lease() {
if [[ -n "$REAL_RUNTIME_LEASE_DIR" && -d "$REAL_RUNTIME_LEASE_DIR" ]]; then
if [[ -f "$REAL_RUNTIME_LEASE_DIR/session_id" ]]; then
local owner
owner=$(cat "$REAL_RUNTIME_LEASE_DIR/session_id")
if [[ "$owner" != "$SESSION_ID" ]]; then
return 0
fi
fi
rm -rf "$REAL_RUNTIME_LEASE_DIR"
fi
}
compute_final_status() {
if [[ "$FAILED" == "1" ]]; then
FINAL_STATUS="failed"
elif [[ "$BLOCKED" == "1" ]]; then
FINAL_STATUS="blocked"
elif [[ "$EXECUTED_REAL_STEPS" == "1" ]]; then
FINAL_STATUS="passed"
else
FINAL_STATUS="skipped"
fi
}
write_summary_files() {
local lane_lines
lane_lines=$(printf '%s\n' "${SELECTED_LANES[@]}")
printf '%s\n' "$lane_lines" >"$ARTIFACT_DIR/lanes.txt"
printf '%s\n' "${BLOCKERS[@]}" >"$ARTIFACT_DIR/blockers.txt"
printf '%s\n' "${PATH_ARGS[@]}" >"$ARTIFACT_DIR/requested-paths.txt"
ARTIFACT_DIR_ENV="$ARTIFACT_DIR" \
SESSION_ID_ENV="$SESSION_ID" \
FINAL_STATUS_ENV="$FINAL_STATUS" \
PATH_SELECTION_MODE_ENV="$PATH_SELECTION_MODE" \
ALLOW_REAL_RUNTIME_ENV="$ALLOW_REAL_RUNTIME" \
SESSION_HOME_ENV="$SESSION_HOME" \
SESSION_XDG_CONFIG_HOME_ENV="$SESSION_XDG_CONFIG_HOME" \
SESSION_MPV_DIR_ENV="$SESSION_MPV_DIR" \
SESSION_LOGS_DIR_ENV="$SESSION_LOGS_DIR" \
SESSION_MPV_LOG_ENV="$SESSION_MPV_LOG" \
STARTED_AT_ENV="$STARTED_AT" \
FINISHED_AT_ENV="$FINISHED_AT" \
FAILED_ENV="$FAILED" \
FAILURE_COMMAND_ENV="${FAILURE_COMMAND:-}" \
FAILURE_STDOUT_ENV="${FAILURE_STDOUT:-}" \
FAILURE_STDERR_ENV="${FAILURE_STDERR:-}" \
bun -e '
const fs = require("fs");
const path = require("path");
function readLines(filePath) {
if (!fs.existsSync(filePath)) return [];
return fs.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
}
const artifactDir = process.env.ARTIFACT_DIR_ENV;
const reportsDir = path.join(artifactDir, "reports");
const lanes = readLines(path.join(artifactDir, "lanes.txt"));
const blockers = readLines(path.join(artifactDir, "blockers.txt"));
const requestedPaths = readLines(path.join(artifactDir, "requested-paths.txt"));
const steps = readLines(path.join(artifactDir, "steps.tsv")).map((line) => {
const [lane, name, status, exitCode, command, stdout, stderr, note] = line.split("\t");
return {
lane,
name,
status,
exitCode: Number(exitCode || 0),
command,
stdout,
stderr,
note,
};
});
const summary = {
sessionId: process.env.SESSION_ID_ENV || "",
artifactDir,
reportsDir,
status: process.env.FINAL_STATUS_ENV || "failed",
selectedLanes: lanes,
failed: process.env.FAILED_ENV === "1",
failure:
process.env.FAILED_ENV === "1"
? {
command: process.env.FAILURE_COMMAND_ENV || "",
stdout: process.env.FAILURE_STDOUT_ENV || "",
stderr: process.env.FAILURE_STDERR_ENV || "",
}
: null,
blockers,
pathSelectionMode: process.env.PATH_SELECTION_MODE_ENV || "git-inferred",
requestedPaths,
allowRealRuntime: process.env.ALLOW_REAL_RUNTIME_ENV === "1",
startedAt: process.env.STARTED_AT_ENV || "",
finishedAt: process.env.FINISHED_AT_ENV || "",
env: {
home: process.env.SESSION_HOME_ENV || "",
xdgConfigHome: process.env.SESSION_XDG_CONFIG_HOME_ENV || "",
mpvDir: process.env.SESSION_MPV_DIR_ENV || "",
logsDir: process.env.SESSION_LOGS_DIR_ENV || "",
mpvLog: process.env.SESSION_MPV_LOG_ENV || "",
},
steps,
};
const summaryJson = JSON.stringify(summary, null, 2) + "\n";
fs.writeFileSync(path.join(artifactDir, "summary.json"), summaryJson);
fs.writeFileSync(path.join(reportsDir, "summary.json"), summaryJson);
const lines = [];
lines.push(`session_id: ${summary.sessionId}`);
lines.push(`artifact_dir: ${artifactDir}`);
lines.push(`selected_lanes: ${lanes.join(", ") || "(none)"}`);
lines.push(`status: ${summary.status}`);
lines.push(`path_selection_mode: ${summary.pathSelectionMode}`);
if (requestedPaths.length > 0) {
lines.push(`requested_paths: ${requestedPaths.join(", ")}`);
}
if (blockers.length > 0) {
lines.push(`blockers: ${blockers.join(" | ")}`);
}
for (const step of steps) {
lines.push(`${step.lane}/${step.name}: ${step.status}`);
if (step.command) lines.push(` command: ${step.command}`);
lines.push(` stdout: ${step.stdout}`);
lines.push(` stderr: ${step.stderr}`);
if (step.note) lines.push(` note: ${step.note}`);
}
if (summary.failed) {
lines.push(`failure_command: ${process.env.FAILURE_COMMAND_ENV || ""}`);
}
const summaryText = lines.join("\n") + "\n";
fs.writeFileSync(path.join(artifactDir, "summary.txt"), summaryText);
fs.writeFileSync(path.join(reportsDir, "summary.txt"), summaryText);
'
}
cleanup() {
release_real_runtime_lease
}
CLASSIFIER_OUTPUT=""
ARTIFACT_DIR=""
ALLOW_REAL_RUNTIME=0
DRY_RUN=0
FAILED=0
BLOCKED=0
EXECUTED_REAL_STEPS=0
FINAL_STATUS=""
FAILURE_STEP=""
FAILURE_COMMAND=""
FAILURE_STDOUT=""
FAILURE_STDERR=""
REAL_RUNTIME_LEASE_DIR=""
STARTED_AT=""
FINISHED_AT=""
declare -a EXPLICIT_LANES=()
declare -a SELECTED_LANES=()
declare -a PATH_ARGS=()
declare -a COMMANDS_RUN=()
declare -a BLOCKERS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--lane)
EXPLICIT_LANES+=("$(normalize_lane_name "$2")")
shift 2
;;
--artifact-dir)
ARTIFACT_DIR=$2
shift 2
;;
--allow-real-runtime|--allow-real-gui)
ALLOW_REAL_RUNTIME=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--help|-h)
usage
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
PATH_ARGS+=("$1")
shift
done
;;
*)
PATH_ARGS+=("$1")
shift
;;
esac
done
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
SESSION_ID=$(generate_session_id)
PATH_SELECTION_MODE="git-inferred"
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
PATH_SELECTION_MODE="explicit"
fi
exec "$TARGET" "$@"
if [[ -z "$ARTIFACT_DIR" ]]; then
mkdir -p "$REPO_ROOT/.tmp/skill-verification"
ARTIFACT_DIR="$REPO_ROOT/.tmp/skill-verification/$SESSION_ID"
fi
SESSION_HOME="$ARTIFACT_DIR/home"
SESSION_XDG_CONFIG_HOME="$ARTIFACT_DIR/xdg"
SESSION_MPV_DIR="$ARTIFACT_DIR/mpv"
SESSION_LOGS_DIR="$ARTIFACT_DIR/logs"
SESSION_MPV_LOG="$SESSION_LOGS_DIR/mpv.log"
mkdir -p "$ARTIFACT_DIR/steps" "$ARTIFACT_DIR/reports" "$SESSION_HOME" "$SESSION_XDG_CONFIG_HOME" "$SESSION_MPV_DIR" "$SESSION_LOGS_DIR"
STEPS_TSV="$ARTIFACT_DIR/steps.tsv"
: >"$STEPS_TSV"
trap cleanup EXIT
STARTED_AT=$(timestamp_iso)
if [[ ${#EXPLICIT_LANES[@]} -gt 0 ]]; then
local_lane=""
for local_lane in "${EXPLICIT_LANES[@]}"; do
add_lane "$local_lane"
done
printf 'reason:explicit lanes supplied\n' >"$ARTIFACT_DIR/classification.txt"
else
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh" "${PATH_ARGS[@]}")
else
CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh")
fi
printf '%s\n' "$CLASSIFIER_OUTPUT" >"$ARTIFACT_DIR/classification.txt"
while IFS= read -r line; do
case "$line" in
lane:*)
add_lane "${line#lane:}"
;;
esac
done <<<"$CLASSIFIER_OUTPUT"
fi
record_env
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
printf 'selected_lanes=%s\n' "$(IFS=,; echo "${SELECTED_LANES[*]}")"
for lane in "${SELECTED_LANES[@]}"; do
case "$lane" in
docs)
run_step "$lane" "docs-test" "bun run docs:test" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "docs-build" "bun run docs:build" || break
;;
config)
run_step "$lane" "test-config" "bun run test:config" || break
;;
core)
run_step "$lane" "typecheck" "bun run typecheck" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-fast" "bun run test:fast" || break
;;
launcher-plugin)
run_step "$lane" "launcher-smoke-src" "bun run test:launcher:smoke:src" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "plugin-src" "bun run test:plugin:src" || break
;;
runtime-compat)
run_step "$lane" "build" "bun run build" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-runtime-compat" "bun run test:runtime:compat" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-smoke-dist" "bun run test:smoke:dist" || break
;;
real-runtime)
if [[ "$PATH_SELECTION_MODE" != "explicit" ]]; then
record_blocked_step \
"$lane" \
"real-runtime-guard" \
"real-runtime lane requires explicit paths; inferred local git changes are non-authoritative"
break
fi
if [[ "$ALLOW_REAL_RUNTIME" != "1" ]]; then
record_blocked_step \
"$lane" \
"real-runtime-guard" \
"real-runtime lane requested but --allow-real-runtime was not supplied"
break
fi
if ! acquire_real_runtime_lease; then
record_blocked_step \
"$lane" \
"real-runtime-lease" \
"real-runtime lease already held; rerun after the active runtime verification finishes"
break
fi
if ! REAL_RUNTIME_HELPER=$(find_real_runtime_helper); then
record_blocked_step \
"$lane" \
"real-runtime-helper" \
"real-runtime helper not implemented yet"
break
fi
printf -v REAL_RUNTIME_COMMAND \
'SESSION_ID=%q HOME=%q XDG_CONFIG_HOME=%q SUBMINER_MPV_LOG=%q bash %q' \
"$SESSION_ID" \
"$SESSION_HOME" \
"$SESSION_XDG_CONFIG_HOME" \
"$SESSION_MPV_LOG" \
"$REAL_RUNTIME_HELPER"
run_step "$lane" "real-runtime-smoke" "$REAL_RUNTIME_COMMAND" || break
;;
*)
record_failed_step "$lane" "lane-validation" "unknown lane: $lane"
break
;;
esac
if [[ "$FAILED" == "1" || "$BLOCKED" == "1" ]]; then
break
fi
done
FINISHED_AT=$(timestamp_iso)
compute_final_status
write_summary_files
printf 'status=%s\n' "$FINAL_STATUS"
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
case "$FINAL_STATUS" in
failed)
printf 'result=failed\n'
printf 'failure_command=%s\n' "$FAILURE_COMMAND"
exit 1
;;
blocked)
printf 'result=blocked\n'
exit 2
;;
*)
printf 'result=ok\n'
exit 0
;;
esac

View File

@@ -1,18 +1,146 @@
---
name: 'subminer-scrum-master'
description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.'
name: "subminer-scrum-master"
description: "Use in the SubMiner repo when a request should be turned into planned work and driven through execution. Assesses whether backlog tracking is warranted, creates or updates tasks when needed, records a plan, dispatches one or more subagents, and requires verification before handoff."
---
# Compatibility Shim
# SubMiner Scrum Master
Canonical source:
Own workflow, not code by default.
- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md`
Use this skill when the user gives a feature request, bug report, issue, refactor, or implementation ask and the agent should manage intake, planning, backlog hygiene, worker dispatch, and verification through completion.
When this shim is invoked:
## Core Rules
1. Read the canonical plugin-owned skill.
2. Follow the plugin-owned skill as the source of truth.
3. Do not duplicate workflow changes here; update the plugin-owned skill instead.
1. Decide first whether backlog tracking is warranted.
2. If backlog is needed, search first. Update existing work when it clearly matches.
3. If backlog is not needed, keep the process light. Do not invent ticket ceremony.
4. Record a plan before dispatching coding work.
5. Use parent + subtasks for multi-part work when backlog is used.
6. Dispatch conservatively. Parallelize only disjoint write scopes.
7. Require verification before handoff, typically via `subminer-change-verification`.
8. Report backlog actions, dispatched workers, verification, blockers, and remaining risks.
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.
## Backlog Decision
Skip backlog when the request is:
- question only
- obvious mechanical edit
- tiny isolated change with no real planning
Use backlog when the work:
- needs planning or scope decisions
- spans multiple phases or subsystems
- is likely to need subagent dispatch
- should remain traceable for handoff/resume
If backlog is used:
- search existing tasks first
- create/update a standalone task for one focused deliverable
- create/update a parent task plus subtasks for multi-part work
- record the implementation plan in the task before implementation begins
## Intake Workflow
1. Parse the request.
Classify it as question, mechanical edit, bugfix, feature, refactor, investigation, or follow-up.
2. Decide whether backlog is needed.
3. If backlog is needed:
- search first
- update existing task if clearly relevant
- otherwise create the right structure
- write the implementation plan before dispatch
4. If backlog is skipped:
- write a short working plan in-thread
- proceed without fake ticketing
5. Choose execution mode:
- no subagents for trivial work
- one worker for focused work
- parallel workers only for disjoint scopes
6. Run verification before handoff.
## Dispatch Rules
The scrum master orchestrates. Workers implement.
- Do not become the default implementer unless delegation is unnecessary.
- Do not parallelize overlapping files or tightly coupled runtime work.
- Give every worker explicit ownership of files/modules.
- Tell every worker other agents may be active and they must not revert unrelated edits.
- Require each worker to report:
- changed files
- tests run
- blockers
Use worker agents for implementation and explorer agents only for bounded codebase questions.
## Verification
Every nontrivial code task gets verification.
Preferred flow:
1. use `subminer-change-verification`
2. start with the cheapest sufficient lane
3. escalate only when needed
4. if worker verification is sufficient, accept it or run one final consolidating pass
Never hand off nontrivial work without stating what was verified and what was skipped.
## Pre-Handoff Policy Checks (Required)
Before handoff, always ask and answer both of these questions explicitly:
1. **Docs update required?**
2. **Changelog fragment required?**
Rules:
- Do not assume silence implies "no." Record an explicit yes/no decision for each item.
- If the answer is yes, either complete the update or report the blocker before handoff.
- Include the final answers in the handoff summary even when both answers are "no."
## Failure / Scope Handling
- If a worker hits ambiguity, pause and ask the user.
- If verification fails, either:
- send the worker back with exact failure context, or
- fix it directly if it is tiny and clearly in scope
- If new scope appears, revisit backlog structure before silently expanding work.
## Representative Flows
### Trivial no-ticket work
- decide backlog is unnecessary
- keep a short plan
- implement directly or with one worker if helpful
- run targeted verification
- report outcome concisely
### Single-task implementation
- search/create/update one task
- record plan
- dispatch one worker
- integrate
- verify
- update task and report outcome
### Parent + subtasks execution
- search/create/update parent task
- create subtasks for distinct deliverables/phases
- record sequencing in the plan
- dispatch workers only where scopes are disjoint
- integrate
- run consolidated verification
- update task state and report outcome
## Output Expectations
At the end, report:
- whether backlog was used and what changed
- which workers were dispatched and what they owned
- what verification ran
- explicit answers to:
- docs update required?
- changelog fragment required?
- blockers, skips, and risks

View File

@@ -61,16 +61,6 @@ jobs:
- name: Test suite (source)
run: bun run test:fast
- name: Coverage suite (maintained source lane)
run: bun run test:coverage:src
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-test-src
path: coverage/test-src/lcov.info
if-no-files-found: error
- name: Launcher smoke suite (source)
run: bun run test:launcher:smoke:src

View File

@@ -49,16 +49,6 @@ jobs:
- name: Test suite (source)
run: bun run test:fast
- name: Coverage suite (maintained source lane)
run: bun run test:coverage:src
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-test-src
path: coverage/test-src/lcov.info
if-no-files-found: error
- name: Launcher smoke suite (source)
run: bun run test:launcher:smoke:src
@@ -344,14 +334,6 @@ jobs:
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Build changelog artifacts for release
run: |
if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then
bun run changelog:build --version "${{ steps.version.outputs.VERSION }}"
else
echo "No pending changelog fragments found."
fi
- name: Verify changelog is ready for tagged release
run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}"

1
.gitignore vendored
View File

@@ -9,7 +9,6 @@ out/
dist/
release/
build/yomitan/
coverage/
# Launcher build artifact (produced by make build-launcher)
/subminer

View File

@@ -83,6 +83,7 @@ This project uses Backlog.md MCP for all task and project management activities.
- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work
These guides cover:
- Decision framework for when to create tasks
- Search-first workflow to avoid duplicates
- Links to detailed guides for task creation, execution, and finalization

View File

@@ -1,266 +0,0 @@
# Backlog
Purpose: lightweight repo-local task board. Seeded with current testing / coverage work.
Status keys:
- `todo`: not started
- `doing`: in progress
- `blocked`: waiting
- `done`: shipped
Priority keys:
- `P0`: urgent / release-risk
- `P1`: high value
- `P2`: useful cleanup
- `P3`: nice-to-have
## Active
| ID | Pri | Status | Area | Title |
| ------ | --- | ------ | -------------- | --------------------------------------------------- |
| SM-013 | P1 | done | review-followup | Address PR #36 CodeRabbit action items |
## Ready
| ID | Pri | Status | Area | Title |
| ------ | --- | ------ | ----------------- | ---------------------------------------------------------------- |
| SM-001 | P1 | todo | launcher | Add tests for CLI parser and args normalizer |
| SM-002 | P1 | todo | immersion-tracker | Backfill tests for uncovered query exports |
| SM-003 | P1 | todo | anki | Add focused field-grouping service + merge edge-case tests |
| SM-004 | P2 | todo | tests | Extract shared test utils for deps factories and polling helpers |
| SM-005 | P2 | todo | tests | Strengthen weak assertions in app-ready and IPC tests |
| SM-006 | P2 | todo | tests | Break up monolithic youtube-flow and subtitle-sidebar tests |
| SM-007 | P2 | todo | anilist | Add tests for AniList rate limiter |
| SM-008 | P3 | todo | subtitles | Add core subtitle-position persistence/path tests |
| SM-009 | P3 | todo | tokenizer | Add tests for JLPT token filter |
| SM-010 | P1 | todo | immersion-tracker | Refactor storage + immersion-tracker service into focused modules |
| SM-011 | P1 | done | tests | Add coverage reporting for maintained test lanes |
| SM-012 | P2 | done | config/runtime | Replace JSON serialize-clone helpers with structured cloning |
## Icebox
None.
## Ticket Details
### SM-001
Title: Add tests for CLI parser and args normalizer
Priority: P1
Status: done
Scope:
- `launcher/config/cli-parser-builder.ts`
- `launcher/config/args-normalizer.ts`
Acceptance:
- root options parsing covered
- subcommand routing covered
- invalid action / invalid log level / invalid backend cases covered
- target classification covered: file, directory, URL, invalid
### SM-002
Title: Backfill tests for uncovered query exports
Priority: P1
Status: todo
Scope:
- `src/core/services/immersion-tracker/query-*.ts`
Targets:
- headword helpers
- anime/media detail helpers not covered by existing wrapper tests
- lexical detail / appearance helpers
- maintenance helpers beyond `deleteSession` and `upsertCoverArt`
Acceptance:
- every exported query helper either directly tested or explicitly justified as covered elsewhere
- at least one focused regression per complex SQL branch / aggregation branch
### SM-003
Title: Add focused field-grouping service + merge edge-case tests
Priority: P1
Status: todo
Scope:
- `src/anki-integration/field-grouping.ts`
- `src/anki-integration/field-grouping-merge.ts`
Acceptance:
- auto/manual/disabled flow branches covered
- duplicate-card preview failure path covered
- merge edge cases covered: empty fields, generated media fallback, strict grouped spans, audio synchronization
### SM-004
Title: Extract shared test utils for deps factories and polling helpers
Priority: P2
Status: todo
Scope:
- common `makeDeps` / `createDeps` helpers
- common `waitForCondition`
Acceptance:
- shared helper module added
- at least 3 duplicated polling helpers removed
- at least 5 duplicated deps factories consolidated or clearly prepared for follow-up migration
### SM-005
Title: Strengthen weak assertions in app-ready and IPC tests
Priority: P2
Status: todo
Scope:
- `src/core/services/app-ready.test.ts`
- `src/core/services/ipc.test.ts`
Acceptance:
- replace broad `assert.ok(...)` presence checks with exact value / order assertions where expected value known
- handler registration tests assert channel-specific behavior, not only existence
### SM-006
Title: Break up monolithic youtube-flow and subtitle-sidebar tests
Priority: P2
Status: todo
Scope:
- `src/main/runtime/youtube-flow.test.ts`
- `src/renderer/modals/subtitle-sidebar.test.ts`
Acceptance:
- reduce single-test breadth
- split largest tests into focused cases by behavior
- keep semantics unchanged
### SM-007
Title: Add tests for AniList rate limiter
Priority: P2
Status: todo
Scope:
- `src/core/services/anilist/rate-limiter.ts`
Acceptance:
- capacity-window wait behavior covered
- `x-ratelimit-remaining` + reset handling covered
- `retry-after` handling covered
### SM-008
Title: Add core subtitle-position persistence/path tests
Priority: P3
Status: todo
Scope:
- `src/core/services/subtitle-position.ts`
Acceptance:
- save/load persistence covered
- fallback behavior covered
- path normalization behavior covered for URL vs local target
### SM-009
Title: Add tests for JLPT token filter
Priority: P3
Status: todo
Scope:
- `src/core/services/jlpt-token-filter.ts`
Acceptance:
- excluded term membership covered
- ignored POS1 membership covered
- exported list / entry consistency covered
### SM-010
Title: Refactor storage + immersion-tracker service into focused layers without API changes
Priority: P1
Status: todo
Scope:
- `src/core/database/storage/storage.ts`
- `src/core/database/storage/schema.ts`
- `src/core/database/storage/cover-blob.ts`
- `src/core/database/storage/records.ts`
- `src/core/database/storage/write-path.ts`
- `src/core/services/immersion-tracker/youtube.ts`
- `src/core/services/immersion-tracker/youtube-manager.ts`
- `src/core/services/immersion-tracker/write-queue.ts`
- `src/core/services/immersion-tracker/immersion-tracker-service.ts`
Acceptance:
- behavior and public API remain unchanged for all callers
- `storage.ts` responsibilities split into DDL/migrations, cover blob helpers, record CRUD, and write-path execution
- `immersion-tracker-service.ts` reduces to session state, media change orchestration, query proxies, and lifecycle
- YouTube code split into pure utilities, a stateful manager (`YouTubeManager`), and a dedicated write queue (`WriteQueue`)
- removed `storage.ts` is replaced with focused modules and updated imports
- no API or migration regressions; existing tests for trackers/storage coverage remain green or receive focused updates
### SM-011
Title: Add coverage reporting for maintained test lanes
Priority: P1
Status: done
Scope:
- `package.json`
- CI workflow files under `.github/`
- `docs/workflow/verification.md`
Acceptance:
- at least one maintained test lane emits machine-readable coverage output
- CI surfaces coverage as an artifact, summary, or check output
- local contributor path for coverage is documented
- chosen coverage path works with Bun/TypeScript lanes already maintained by the repo
Implementation note:
- Added `bun run test:coverage:src` for the maintained source lane via a sharded coverage runner, with merged LCOV output at `coverage/test-src/lcov.info` and CI/release artifact upload as `coverage-test-src`.
### SM-012
Title: Replace JSON serialize-clone helpers with structured cloning
Priority: P2
Status: todo
Scope:
- `src/runtime-options.ts`
- `src/config/definitions.ts`
- `src/config/service.ts`
- `src/main/controller-config-update.ts`
Acceptance:
- runtime/config clone helpers stop using `JSON.parse(JSON.stringify(...))`
- replacement preserves current behavior for plain config/runtime objects
- focused tests cover clone/merge behavior that could regress during the swap
- no new clone helper is introduced in these paths without a documented reason
Done:
- replaced JSON serialize-clone call sites in runtime/config/controller update paths with `structuredClone`
- updated focused tests and fixtures to cover detached clone behavior and guard against regressions
### SM-013
Title: Address PR #36 CodeRabbit action items
Priority: P1
Status: done
Scope:
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
- `scripts/subminer-change-verification.test.ts`
- `src/core/services/immersion-tracker/query-sessions.ts`
- `src/core/services/immersion-tracker/query-trends.ts`
- `src/core/services/immersion-tracker/maintenance.ts`
- `src/main/boot/services.ts`
- `src/main/character-dictionary-runtime/zip.test.ts`
Acceptance:
- fix valid open CodeRabbit findings on PR #36
- add focused regression coverage for behavior changes where practical
- verify touched tests plus typecheck stay green
Done:
- hardened `--artifact-dir` validation in the verification script
- fixed trend aggregation rounding and monthly ratio bucketing
- preserved unwatched anime episodes in episode queries
- restored seconds-based aggregate timestamps in shared maintenance
- fixed the startup refactor compile break by making the predicates local at the call site
- verified with `bun test src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/__tests__/query-split-modules.test.ts` and `bun run typecheck`

View File

@@ -1,163 +1,5 @@
# Changelog
## v0.10.0 (2026-03-29)
### Changed
- Integrations: Replaced the deprecated Discord Rich Presence wrapper with the maintained `@xhayper/discord-rpc` package.
### Fixed
- Stats: Fixed stats startup so the immersion tracker can run when `Bun.serve` is unavailable.
- Stats: Stats server now falls back to a Node `http` listener in Electron/runtime paths that do not expose Bun.
- Overlay: Fixed the macOS visible-overlay toggle path so manual hides stay hidden and the plugin uses the explicit visible-overlay toggle command.
- Subtitle Sidebar: Restored macOS mpv passthrough while the overlay subtitle sidebar is open so clicks outside the sidebar can refocus mpv and keep native keybindings working.
### Internal
- Release: Added a maintained source coverage lane that shards Bun coverage one test file at a time and merges LCOV output into `coverage/test-src/lcov.info`.
- Release: CI and release quality-gate now upload the merged source-lane LCOV artifact for inspection.
- Runtime: Extracted remaining inline runtime logic from `src/main.ts` into dedicated runtime modules and composer helpers.
- Runtime: Added focused regression tests for the extracted runtime/composer boundaries.
- Runtime: Updated task tracking notes to mark TASK-238.6 complete and confirm follow-on boot-phase split can be deferred.
- Runtime: Split `src/main.ts` boot wiring into dedicated `src/main/boot/services.ts`, `src/main/boot/runtimes.ts`, and `src/main/boot/handlers.ts` modules.
- Runtime: Added focused tests for the new boot-phase seams and kept the startup/typecheck/build verification lanes green.
- Runtime: Updated internal architecture/task docs to record the boot-phase split and new ownership boundary.
## v0.9.3 (2026-03-25)
### Changed
- Launcher: Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
- Launcher: Removed the placeholder YouTube subtitle retime step and now uses downloaded primary subtitle tracks directly, so there is no fake path rewrite before playback/sidebar loading.
- YouTube: Removed the `src/core/services/youtube/retime` helper and its tests after retiring the internal retime strategy.
- Docs: Clarified optional `alass` / `ffsubsync` subtitle-sync requirements and setup steps, including fallback behavior when sync tools are absent.
- Launcher: Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
## v0.9.2 (2026-03-25)
### Fixed
- Overlay: Fixed overlay pointer tracking so Windows click-through toggles immediately when the cursor enters or leaves subtitle regions, without waiting for a later hover resync.
- Overlay: Fixed Windows overlay window tracking on scaled displays by converting native tracked window bounds to Electron DIP coordinates before applying overlay bounds.
- Launcher: Fixed Windows direct `--youtube-play` startup so MPV boots reliably, stays paused until the app-owned subtitle flow is ready, and reuses an already-running SubMiner instance when available.
- Launcher: Fixed standalone Windows `--youtube-play` sessions so closing MPV fully exits SubMiner instead of leaving hidden overlay windows or a background process behind.
- Overlay: Fixed `subminer <youtube-url>` on Linux so the YouTube playback flow waits for Yomitan to load before creating the overlay window, avoiding the broken lookup popup state that previously required a manual overlay refresh.
## v0.9.1 (2026-03-24)
### Changed
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
### Fixed
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
## v0.9.0 (2026-03-23)
### Added
- Docs: Added a new WebSocket / Texthooker API and integration guide covering WebSocket payloads, custom client patterns, mpv plugin automation, and webhook-style relay examples. Linked from configuration and mining workflow docs for easier discovery.
### Changed
- Launcher: Added an app-owned YouTube subtitle flow that pauses mpv, uses absPlayer-style YouTube timedtext parsing/conversion to download subtitle tracks, and injects them as external files before playback resumes.
- Launcher: Changed YouTube subtitle startup to auto-load the best-available primary and secondary subtitle tracks at launch instead of forcing the picker modal first. Secondary subtitle failures no longer block playback resume.
- Launcher: Added `Ctrl+Alt+C` as the default keybinding to manually open the YouTube subtitle picker during active YouTube playback.
- Launcher: Added yt-dlp metadata probing so YouTube playback and immersion tracking record canonical video title and channel metadata.
- Launcher: Stopped forcing `--ytdl-raw-options=` before user-provided mpv options so existing YouTube cookie integrations in user `--args` are no longer clobbered.
- Launcher: Disabled mpv native YouTube subtitle auto-loading for the app-owned flow so injected external subtitle files remain authoritative.
- Launcher: Added OSD status messages for YouTube playback startup, subtitle acquisition, and subtitle loading so the flow stays visible before and during the picker.
- Subtitle Sidebar: Added startup-auto-open controls and resume positioning improvements so the sidebar jumps directly to the first resolved active cue.
- Subtitle Sidebar: Improved subtitle prefetch and embedded overlay passthrough sync so sidebar and overlay subtitle states stay consistent across media transitions.
- Subtitle Sidebar: Updated scroll handling, embedded layout styling, and active-cue visual behavior.
- Stats: Stats Library tab now displays YouTube video title, channel name, and channel thumbnail for YouTube media entries, with retry logic to fill in metadata that arrives after initial load.
### Fixed
- Launcher: Fixed Anki media mining for mpv YouTube streams by unwrapping the stream URL so audio and screenshot capture work correctly for YouTube playback sessions.
- Immersion: Fixed YouTube media path handling in the immersion runtime and tracking so YouTube sessions record correct media references, AniList guessing skips YouTube URLs, and post-watch state transitions do not fire for YouTube media.
- Launcher: Fixed startup-launched YouTube playback so primary subtitle overlay updates continue after auto-load completes.
- Launcher: Fixed auto-loaded YouTube primary subtitles so parsed cues appear in the subtitle sidebar without needing a manual picker retry.
- Launcher: Fixed the YouTube picker to guard against duplicate subtitle submissions and tightened YouTube URL detection so follow-up runtime flows only treat real YouTube hosts as YouTube playback.
- Launcher: Fixed primary subtitle failure notifications being shown while app-owned YouTube subtitle probing and downloads are still in flight.
- Launcher: Preserved existing authoritative YouTube subtitle tracks when available; downloaded tracks are used only to fill missing sides, and native mpv secondary subtitle rendering is hidden so the overlay remains the sole secondary display.
## v0.8.0 (2026-03-22)
### Added
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface and rendered sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
### Changed
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
- Runtime: Improved subtitle prefetch/rendering flow so sidebar and overlay subtitle states stay in sync across media transitions.
### Fixed
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.
- Overlay: Improved sidebar resume/start behavior to jump directly to the first resolved active cue.
- Overlay: Stopped stale subtitle refreshes from regressing active-cue and text state.
## v0.7.0 (2026-03-19)
### Added
- Immersion: Added Mine Word, Mine Sentence, and Mine Audio buttons to word detail example lines in the stats dashboard.
- Immersion: Mine Word creates a full Yomitan card (definition, reading, pitch accent) via the hidden search page bridge, then enriches with sentence audio, screenshot, and metadata extracted from the source video.
- Immersion: Mine Sentence and Mine Audio create cards directly with appropriate Lapis/Kiku flags, sentence highlighting, and media from the source file.
- Immersion: Media generation (audio + image/AVIF) runs in parallel and respects all AnkiConnect config options.
- Immersion: Added word exclusion list to the Vocabulary tab with localStorage persistence and a management modal.
- Immersion: Fixed truncated readings in the frequency rank table (e.g. お前 now shows おまえ instead of まえ).
- Immersion: Clicking a bar in the Top Repeated Words chart now opens the word detail panel.
- Immersion: Secondary subtitle text is now stored alongside primary subtitle lines for use as translation when mining cards from the stats page.
- Stats: Added `subminer stats -b` to start or reuse a dedicated background stats server without blocking normal SubMiner instances.
- Stats: Added `subminer stats -s` to stop the dedicated background stats server without closing browser tabs.
- Stats: Stats server startup now reuses a running background stats daemon instead of trying to bind a second local server in another SubMiner instance.
- Launcher: Added launcher passthrough for `-a/--args` so mpv receives raw extra launch flags (`--fs`, `--ytdl-format`, custom audio/video settings, etc.) from the `subminer` command.
- Launcher: Added `subminer stats` to launch the local stats dashboard, force-start the stats server on demand, and open the dashboard in your browser.
- Launcher: Added `subminer stats cleanup` to backfill vocabulary metadata and prune stale or excluded immersion rows on demand.
- Launcher: Added `stats.autoOpenBrowser` so browser launch after `subminer stats` can be enabled or disabled explicitly.
- Immersion: Added a local stats dashboard for immersion tracking with Overview, Anime, Trends, Vocabulary, and Sessions views.
- Immersion: Added anime progress, episode completion, Anki card links, and occurrence drill-down across the stats dashboard.
- Immersion: Added richer session timelines with new-word activity, cumulative totals, and pause/seek/card event markers.
- Immersion: Added completed-episodes and completed-anime totals to the Overview tracking snapshot.
### Changed
- Anki: Changed known-word cache settings to live under `ankiConnect.knownWords` instead of mixing them into `ankiConnect.nPlusOne`.
- Anki: Kept legacy `ankiConnect.nPlusOne` known-word keys and older `ankiConnect.behavior.nPlusOne*` keys as deprecated compatibility fallbacks.
- Stats: Added session deletion to the Sessions tab with the same confirmation prompt used by anime episode/session deletes, and removed all associated session rows from the stats database.
- Immersion: Kept immersion tracking history by default while preserving daily/monthly rollup maintenance.
- Immersion: Added exact lifetime summary reads for overview/anime/media stats so dashboard totals no longer depend on rescanning raw telemetry.
- Immersion: Reduced tracker storage overhead by removing duplicated subtitle text from subtitle-line event payloads.
- Immersion: Deduplicated episode cover-art blobs through a shared blob store and updated cover-art reads/writes to resolve shared images correctly.
- Immersion: Added indexes for large-history session, telemetry, vocabulary, kanji, and cover-art queries to keep dashboard reads fast as the SQLite database grows.
- Immersion: Renamed the stats dashboard's Anime tab to Library so the media browser label matches non-anime sources like YouTube and other yt-dlp-backed content.
- Anilist: Standardized episode completion threshold by introducing `DEFAULT_MIN_WATCH_RATIO` and using it for both local watched state transitions and AniList post-watch progress updates.
- Anilist: Episode auto-marking now uses the same threshold as AniList (`85%`), removing divergent completion behavior.
- Overlay: Excluded interjections and sound-effect tokens from subtitle annotation styling so they no longer inherit misleading lexical highlight treatment while still remaining visible and hoverable as plain subtitle tokens.
- Overlay: Expanded subtitle annotation noise filtering to also strip annotation metadata from standalone grammar-only helper tokens such as particles, auxiliaries, adnominals, common explanatory endings like `んです` / `のだ`, and merged trailing quote-particle forms like `...って` while keeping them tokenized for hover lookup.
### Fixed
- Launcher: Fixed mpv Lua plugin binary auto-detection on Linux to also search `/usr/bin/subminer` and `/usr/local/bin/subminer` (lowercase), matching the conventional Unix wrapper name used by packaged installs such as the AUR package.
- Stats: Fixed the in-app stats overlay so it connects to the configured `stats.serverPort` instead of falling back to the default port.
- Overlay: Fixed subtitle frequency tagging for merged lookup-backed tokens like `陰に` by falling back to exact surface-form Yomitan frequencies when the normalized headword lookup misses.
- Overlay: Fixed MeCab merged-token position mapping across line breaks so merged content-plus-particle tokens like `陰に` keep their matched Yomitan frequency instead of inheriting shifted POS tags.
- Overlay: Fixed grouped frequency parsing in both Yomitan and fallback frequency-dictionary lookups so display values like `118,121` use the leading rank instead of collapsing the rank and occurrence count into `118121`.
- Overlay: Fixed frequency-rank ingestion to ignore Yomitan dictionaries explicitly marked `occurrence-based`, so raw occurrence counts are no longer treated as subtitle rank values.
- Overlay: Fixed inflected headword frequency tagging to prefer ranks from the selected Yomitan `termsFind` popup entry itself, ordered by configured dictionary priority, so forms like `潜み` use primary-dictionary ranks like `4073` before falling back to lower-priority raw lemma metadata such as `CC100`.
- Overlay: Fixed annotation-stage frequency filtering so exact kanji noun tokens like `者` keep their matched rank even when MeCab labels them `名詞/非自立`, instead of dropping the highlight after scan-time frequency lookup succeeds.
- Anki: Fixed repeated character-dictionary startup work by scheduling auto-sync only from mpv media-path changes instead of also re-triggering it from connection and media-title events for the same title.
- Overlay: Fixed macOS fullscreen overlay stability by keeping the passive visible overlay from stealing focus, re-raising the overlay window when reasserting its macOS topmost level, and tolerating one transient macOS tracker/helper miss before hiding the overlay.
- Overlay: Kept subtitle tokenization warmup one-shot for the lifetime of the app so later fullscreen/media churn on macOS does not replay the startup warmup gate after the first file is ready.
- Overlay: Added a bounded macOS tracker loss-grace window so fullscreen enter/leave transitions do not immediately hide and reload the overlay when the helper briefly loses the mpv window.
- Overlay: Skipped subtitle/tokenization refresh invalidation on character-dictionary auto-sync completion when the dictionary was already current, preventing startup flash/reload loops on unchanged media.
- Stats: Fixed session stats so known-word counts track real known-word occurrences without collapsing subtitle-line gaps.
- Stats: Fixed session word totals in session-facing stats views to prefer token counts when available, preventing known words from exceeding total words in the session chart.
- Stats: Fixed the stats Vocabulary tab blank-screen regression caused by a hook-order crash after vocabulary data finished loading.
- Anki: Fixed card-mine OSD feedback so the final mine result stops the Anki spinner first, then shows a single-line `✓`/`x` status without being overwritten by a later spinner tick.
- Stats: Removed the misleading `New words` series from expanded session charts; session detail now shows only the real total-word and known-word lines.
- Stats: Restored the cross-anime word table behavior in stats vocabulary surfaces so shared vocabulary entries no longer disappear or merge incorrectly across related media.
- Stats: `subminer stats -b` now runs as a standalone background stats daemon instead of reusing the main SubMiner app process, so the overlay app can still be launched separately for normal video watching.
- Stats: Dashboard word mining still works against the background daemon by using a short-lived hidden helper for the Yomitan add-note flow.
- Stats: Load full session timelines by default in stats session detail views so long sessions preserve complete telemetry history instead of being truncated by a fixed sample limit.
- Stats: Replaced heuristic stats word counts with Yomitan token counts, so session, media, anime, and trend subtitle totals now come directly from parsed subtitle tokens.
- Stats: Updated stats UI labels and lookup-rate copy to refer to tokens instead of words where those counts are shown.
- Overlay: Reduced repeated `Overlay loading...` popups on macOS when fullscreen tracker flaps briefly hide and recover the visible overlay.
- Stats: Scaled expanded session-detail known-word charts to the session's actual percentage range so small changes no longer render as a nearly flat line.
- Jlpt: Reduced JLPT dictionary startup log noise by summarizing duplicate surface-form collisions instead of logging one line per duplicate entry.
## v0.6.5 (2026-03-15)
### Internal

236
README.md
View File

@@ -1,168 +1,60 @@
<div align="center">
<img src="assets/SubMiner.png" width="140" alt="SubMiner logo">
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
# SubMiner
# SubMiner
**Sentence-mine from mpv — look up words, one-key Anki export, immersion tracking.**
## Turn mpv into a sentence-mining workstation.
Look up words with Yomitan, export to Anki in one key, track your immersion — all without leaving mpv.
[![License: GPL v3](https://img.shields.io/badge/license-GPLv3-1a1a2e?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
[![Platform](https://img.shields.io/badge/platform-Linux%20·%20macOS%20·%20Windows-1a1a2e?style=flat-square)](https://github.com/ksyasuda/SubMiner)
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-e6a817?style=flat-square)](https://docs.subminer.moe)
[![AUR](https://img.shields.io/aur/version/subminer-bin?style=flat-square&color=1a1a2e)](https://aur.archlinux.org/packages/subminer-bin)
[![SubMiner demo](./assets/minecard.webp)](./assets/minecard.mp4)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Linux](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-informational)]()
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-blueviolet)](https://docs.subminer.moe)
[![AUR](https://img.shields.io/aur/version/subminer-bin)](https://aur.archlinux.org/packages/subminer-bin)
</div>
## How It Works
SubMiner runs as an invisible Electron overlay on top of mpv. Subtitles render as an interactive layer. Move your cursor over any word and trigger a [Yomitan](https://github.com/yomidevs/yomitan) lookup. Press one key to snapshot the sentence, audio, and screenshot into Anki via AnkiConnect.
## Features
### Dictionary Lookups
Yomitan runs inside the overlay. Trigger a lookup on any word for full dictionary popups — definitions, pitch accent, frequency data — without ever leaving mpv.
<div align="center">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
</div>
<br>
### Instant Anki Mining
Create an Anki card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment with one key press, click, or controller input.
<div align="center">
<img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Anki card created from SubMiner with sentence, audio, and screenshot">
</div>
<br>
### Reading Annotations
Real-time subtitle annotations with frequency highlighting, JLPT tags, N+1 targeting, and a character name dictionary. Known words fade back; new words stand out. Grammar-only tokens render as plain text so you focus on what matters.
<div align="center">
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency coloring, JLPT underlines, and N+1 targets">
</div>
<br>
### Immersion Dashboard
Local stats dashboard — watch time, anime library, vocabulary growth, mining throughput, session history, and trends. All stored locally, no third-party tracking.
<div align="center">
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
</div>
<br>
### Integrations
<table>
<tr>
<td><b>YouTube</b></td>
<td>Auto-loaded yt-dlp subtitle tracks at startup with a manual overlay picker on demand (<code>Ctrl+Alt+C</code>)</td>
</tr>
<tr>
<td><b>AniList</b></td>
<td>Automatic episode tracking and progress sync</td>
</tr>
<tr>
<td><b>Jellyfin</b></td>
<td>Browse and launch media from your Jellyfin server</td>
</tr>
<tr>
<td><b>Jimaku</b></td>
<td>Search and download Japanese subtitles</td>
</tr>
<tr>
<td><b>alass / ffsubsync</b></td>
<td>Automatic subtitle retiming — requires <code>alass</code> or <code>ffsubsync</code> on your <code>PATH</code> (optional; subtitle syncing is disabled without them)</td>
</tr>
<tr>
<td><b>WebSocket</b></td>
<td>Annotated subtitle feed for external clients (texthooker pages, custom tools)</td>
</tr>
</table>
<div align="center">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page receiving annotated subtitle lines via WebSocket">
</div>
<br>
---
## Requirements
SubMiner is an Electron overlay for [mpv](https://mpv.io) that turns video into a sentence-mining workstation. Look up any word with [Yomitan](https://github.com/yomidevs/yomitan), mine it to Anki with one key, and track your immersion over time.
| | Required | Optional |
| -------------- | --------------------------------------- | -------------------------------------- |
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — |
| **Processing** | `ffmpeg`, `mecab` + `mecab-ipadic` | `guessit` (AniSkip), `alass` / `ffsubsync` (subtitle sync) |
| **Media** | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
| **Selection** | — | `fzf` / `rofi` |
<div align="center">
> [!NOTE]
> [`bun`](https://bun.sh) is required if building from source or using the CLI wrapper: `subminer`. Pre-built releases (AppImage, DMG, installer) do not require it.
[![SubMiner demo (Animated preview)](./assets/minecard.webp)](./assets/minecard.mp4)
**Platform-specific:**
</div>
| Linux | macOS | Windows |
| ----------------------------------- | ------------------------ | ------------- |
| `hyprctl` or `xdotool` + `xwininfo` | Accessibility permission | No extra deps |
## Features
<details>
<summary><b>Arch Linux</b></summary>
**Dictionary lookups** — Yomitan runs inside the overlay. Hover or navigate to any word for full dictionary popups without leaving mpv.
```bash
paru -S --needed mpv ffmpeg mecab-git mecab-ipadic
# Optional
paru -S --needed yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
# Optional: subtitle sync (install at least one for subtitle syncing to work)
paru -S --needed alass python-ffsubsync
# X11 / XWAYLAND
paru -S --needed xdotool xorg-xwininfo
```
**One-key Anki mining** — Press one key to create a card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment.
</details>
<div align="center">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan popup with dictionary entry and mine button over annotated subtitles in mpv">
</div>
<details>
<summary><b>macOS</b></summary>
**Reading annotations** — Real-time subtitle annotations with N+1 targeting, frequency highlighting, JLPT tags, and a character name dictionary. Grammar-only tokens render as plain text.
```bash
brew install mpv ffmpeg mecab mecab-ipadic
# Optional
brew install yt-dlp fzf rofi chafa ffmpegthumbnailer
# Optional: subtitle sync (install at least one for subtitle syncing to work)
brew install alass
pip install ffsubsync
```
<div align="center">
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency highlighting, JLPT underlines, known words, and N+1 targets">
</div>
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
**Immersion dashboard** — Local stats dashboard with watch time, anime progress, vocabulary growth, mining throughput, and session history.
</details>
<div align="center">
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard with watch time, cards mined, streaks, and tracking snapshot">
</div>
<details>
<summary><b>Windows</b></summary>
**Integrations** — AniList episode tracking, Jellyfin remote playback, Jimaku subtitle downloads, alass/ffsubsync, and an annotated websocket feed for external clients.
Install [`mpv`](https://mpv.io/installation/) and [`ffmpeg`](https://ffmpeg.org/download.html) and ensure both are on your `PATH`.
For MeCab, install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary.
</details>
<div align="center">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page with annotated subtitle lines and frequency highlighting">
</div>
---
## Quick Start
### 1. Install
### Install
<details>
<summary><b>Arch Linux (AUR)</b></summary>
@@ -183,7 +75,6 @@ git clone https://aur.archlinux.org/subminer-bin.git && cd subminer-bin && makep
<summary><b>Linux (AppImage)</b></summary>
```bash
mkdir -p ~/.local/bin
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage \
&& chmod +x ~/.local/bin/SubMiner.AppImage
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer \
@@ -196,63 +87,50 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~
</details>
<details>
<summary><b>macOS</b></summary>
<summary><b>macOS / Windows / From source</b></summary>
Download the latest DMG or ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
**macOS**Download the latest DMG/ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
**Windows** — Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Keep `mpv` on `PATH`.
**From source** — See [docs.subminer.moe/installation#from-source](https://docs.subminer.moe/installation#from-source).
</details>
<details>
<summary><b>Windows</b></summary>
### First Launch
Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Make sure `mpv` is on your `PATH`.
Run `SubMiner.AppImage` (Linux), `SubMiner.app` (macOS), or `SubMiner.exe` (Windows). On first launch, SubMiner starts in the tray, creates a default config, and opens a setup popup where you can install the mpv plugin and configure Yomitan dictionaries.
</details>
<details>
<summary><b>From source</b></summary>
See the [build-from-source guide](https://docs.subminer.moe/installation#from-source).
</details>
### 2. First Launch
Run the app. On first launch SubMiner starts in the system tray, creates a default config, and opens a setup popup to install the mpv plugin and configure Yomitan dictionaries.
### 3. Mine
### Mine
```bash
subminer video.mkv # play video with overlay
subminer --start video.mkv # explicit overlay start
subminer stats # open immersion dashboard
subminer stats -b # stats daemon in background
subminer stats -s # stop background stats daemon
subminer video.mkv # auto-starts overlay + resumes playback
subminer --start video.mkv # explicit overlay start (if plugin auto_start=no)
subminer stats # open the immersion dashboard
```
---
## Requirements
| Required | Optional |
|---|---|
| [`mpv`](https://mpv.io) with IPC socket | `yt-dlp` |
| `ffmpeg` | `guessit` (AniSkip detection) |
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
| [`bun`](https://bun.sh) (source builds, Linux wrapper) | `chafa`, `ffmpegthumbnailer` |
| Linux: `hyprctl` or `xdotool` + `xwininfo` | |
| macOS: Accessibility permission | |
Windows uses native window tracking and does not need the Linux compositor tools.
## Documentation
Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
---
Full guides on configuration, Anki, Jellyfin, immersion tracking, and more at **[docs.subminer.moe](https://docs.subminer.moe)**.
## Acknowledgments
SubMiner builds on the work of these open-source projects:
| Project | Role |
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow |
| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing |
| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles |
| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration |
| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration |
| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads |
| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration |
| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser |
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
Built on [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), and [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary). Subtitles from [Jimaku.cc](https://jimaku.cc). Lookups via [Yomitan](https://github.com/yomidevs/yomitan). JLPT tags from [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab).
## License

View File

@@ -1,35 +0,0 @@
---
id: TASK-243
title: 'Assess and address PR #36 latest CodeRabbit review round'
status: Done
assignee: []
created_date: '2026-03-29 07:39'
updated_date: '2026-03-29 07:41'
labels:
- code-review
- pr-36
dependencies: []
references:
- 'https://github.com/ksyasuda/SubMiner/pull/36'
priority: high
ordinal: 3600
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the latest CodeRabbit review round on PR #36, verify each actionable comment against the current branch, implement the confirmed fixes, and verify the touched paths.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Confirmed review comments are implemented or explicitly deferred with rationale.
- [ ] #2 Touched paths are verified with the smallest sufficient test/build lane.
- [ ] #3 Current PR feedback is reduced to resolved or intentionally deferred suggestions.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed the confirmed latest CodeRabbit review items on PR #36. `scripts/run-coverage-lane.ts` now uses the Bun-style `import.meta.main` entrypoint check with a local ts-ignore to preserve the repo's CommonJS typecheck settings. `src/core/services/immersion-tracker/maintenance.ts` no longer shadows the imported `nowMs` helper in retention functions. `src/main.ts` now centralizes the startup-mode predicates behind a shared helper and releases `resolvedSource.cleanup` on the cached-subtitle fast path so materialized sources do not leak.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,35 +0,0 @@
---
id: TASK-244
title: 'Assess and address PR #36 latest CodeRabbit review round 2'
status: Done
assignee: []
created_date: '2026-03-29 08:09'
updated_date: '2026-03-29 08:10'
labels:
- code-review
- pr-36
dependencies: []
references:
- 'https://github.com/ksyasuda/SubMiner/pull/36'
priority: high
ordinal: 3610
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the newest CodeRabbit review round on PR #36, verify the actionable comment against the current branch, implement the confirmed fix, and verify the touched path.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 The actionable review comment is implemented or explicitly deferred with rationale.
- [ ] #2 Touched path is verified with the smallest sufficient test lane.
- [ ] #3 Current PR feedback is reduced to resolved or intentionally deferred suggestions.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed the actionable latest CodeRabbit comment on PR #36. `src/core/services/immersion-tracker/maintenance.ts` now skips retention deletions when a window is disabled with `Infinity`, so `toDbMs(...)` is only called for finite retention values. Added a regression test in `maintenance.test.ts` that verifies disabled retention windows preserve session events, telemetry, and sessions while returning zero deletions.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,11 +1,11 @@
project_name: "SubMiner"
default_status: "To Do"
statuses: ["To Do", "In Progress", "Done"]
project_name: 'SubMiner'
default_status: 'To Do'
statuses: ['To Do', 'In Progress', 'Done']
labels: []
definition_of_done: []
date_format: yyyy-mm-dd
max_column_width: 20
default_editor: "nvim"
default_editor: 'nvim'
auto_open_browser: false
default_port: 6420
remote_operations: true
@@ -13,4 +13,4 @@ auto_commit: false
bypass_git_hooks: false
check_active_branches: true
active_branch_days: 30
task_prefix: "task"
task_prefix: 'task'

View File

@@ -1,8 +0,0 @@
---
id: m-2
title: 'Mining Workflow Upgrades'
---
## Description
Future user-facing workflow improvements that directly improve discoverability, previewability, and mining control without depending on speculative platform integrations like OCR, marketplace infrastructure, or cloud sync.

View File

@@ -2,10 +2,9 @@
id: TASK-143
title: Keep character dictionary auto-sync non-blocking during startup
status: Done
assignee:
- codex
assignee: []
created_date: '2026-03-09 01:45'
updated_date: '2026-03-23 03:22'
updated_date: '2026-03-18 05:28'
labels:
- dictionary
- startup
@@ -18,7 +17,7 @@ references:
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/current-media-tokenization-gate.ts
priority: high
ordinal: 144500
ordinal: 38500
---
## Description
@@ -34,20 +33,8 @@ Keep character dictionary auto-sync running in parallel during startup without d
- [x] #3 Regression coverage verifies auto-sync builds before the gate and only mutates Yomitan after the gate resolves.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test for startup autoplay release surviving delayed mpv readiness or late subtitle refresh after dictionary sync.
2. Harden the autoplay-ready release path so paused startup keeps retrying until mpv is actually released or media changes, without resuming user-paused playback later.
3. Keep the existing character-dictionary revisit fixes and paused-startup OSD fixes aligned with the autoplay change, then run targeted runtime tests and typecheck.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a small current-media tokenization gate in main runtime. Media changes reset the gate, the first tokenization-ready event marks it ready, and auto-sync now waits on that gate only before Yomitan dictionary inspection/import/settings updates. Snapshot generation and merged ZIP build still run immediately in parallel.
2026-03-20: User reports startup remains paused after annotations/tokenization are visible and only resumes after character-dictionary generation/import finishes. Investigating autoplay-ready release regression vs dictionary sync completion refresh.
2026-03-20: Added startup autoplay retry-budget helper so paused startup retries cover the full plugin gate window instead of only ~2.8s. Verification: bun test src/main/runtime/startup-autoplay-release-policy.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts src/main/runtime/startup-osd-sequencer.test.ts src/main/runtime/character-dictionary-auto-sync-completion.test.ts; bun run typecheck; bun run test:fast; bun run test:env; bun run build; bun run test:smoke:dist; runtime-compat verifier passed at .tmp/skill-verification/subminer-verify-20260320-022106-nM28Nk. Pending real installed-app/mpv validation.
<!-- SECTION:NOTES:END -->

View File

@@ -1,80 +0,0 @@
---
id: TASK-169
title: Cut minor release v0.7.0 for stats and runtime polish
status: Done
assignee:
- codex
created_date: '2026-03-19 17:20'
updated_date: '2026-03-19 17:31'
labels:
- release
- docs
- minor
dependencies:
- TASK-168
references:
- package.json
- README.md
- docs/RELEASING.md
- docs-site/changelog.md
- CHANGELOG.md
- release/release-notes.md
priority: high
ordinal: 108000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Prepare the next release cut as `v0.7.0`, keeping 0-ver semantics by rolling the accumulated stats/dashboard, launcher, overlay, and stability work into the next minor line instead of a `1.0.0` release.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Repository version metadata is updated to `0.7.0`.
- [x] #2 Root release-facing docs are refreshed for the `0.7.0` release cut.
- [x] #3 `CHANGELOG.md` and `release/release-notes.md` contain the committed `v0.7.0` section and consumed fragments are removed.
- [x] #4 Public changelog/docs surfaces reflect the new release.
- [x] #5 Release-prep verification is recorded.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Bump `package.json` to `0.7.0`.
2. Refresh release-facing docs: root `README.md`, release guide versioning note, and public docs changelog summary.
3. Run `bun run changelog:build --version 0.7.0` to commit release artifacts and consume pending fragments.
4. Run release-prep verification (`changelog`, typecheck, tests, docs build if docs-site changed).
5. Update this task with notes, verification, and final summary.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Bumped `package.json` from `0.6.5` to `0.7.0` and refreshed the root release-facing copy in `README.md` so the release prep explicitly calls out the new stats/dashboard line plus the background stats daemon commands. Updated `docs/RELEASING.md` with the repo's 0-ver versioning policy and an explicit `--date` reminder after the changelog generator initially stamped `2026-03-20` from UTC instead of the intended local release date `2026-03-19`.
Ran `bun run changelog:build --version 0.7.0`, which generated `CHANGELOG.md` and `release/release-notes.md` and removed the queued `changes/*.md` fragments for the accumulated stats, launcher, overlay, JLPT, and stability work. Added a curated `v0.7.0` summary to `docs-site/changelog.md` so the public docs changelog stays aligned with the committed root changelog while remaining user-facing.
Verification:
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
- `bun run changelog:lint`
- `bun run changelog:check --version 0.7.0`
- `bun run verify:config-example`
- `bun run typecheck`
- `bun run test:fast`
- `bun run test:env`
- `bun run build`
- `bun run docs:test`
- `bun run docs:build`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Prepared minor release `v0.7.0` as the next 0-ver major line. Version metadata, root changelog, generated release notes, README release copy, release-guide policy, and the public docs changelog are now aligned for the release cut.
Docs update required: yes. Completed in `README.md`, `docs/RELEASING.md`, and `docs-site/changelog.md`.
Changelog fragment required: no new fragment for this task. Existing pending release fragments were consumed into the committed `v0.7.0` changelog section and `release/release-notes.md`.
Release-prep verification passed across changelog validation, config-example verification, typecheck, fast/env tests, full build, and docs-site test/build.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-177.1
title: Fix overview lookup rate metric
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 17:46'
updated_date: '2026-03-23 03:22'
labels:
- stats
- immersion-tracking
- yomitan
dependencies: []
references:
- stats/src/components/overview/OverviewTab.tsx
- stats/src/lib/dashboard-data.ts
- stats/src/lib/yomitan-lookup.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/stats-server.ts
parent_task_id: TASK-177
priority: medium
ordinal: 132500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Update the stats homepage Tracking Snapshot so Lookup Rate reflects lifetime intentional Yomitan lookups normalized by total tokens seen, matching the newer stats semantics already used in session, media, and anime views.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overview data exposes the lifetime totals needed to compute global Yomitan lookups per 100 tokens on the homepage
- [x] #2 The homepage Tracking Snapshot Lookup Rate card shows Yomitan lookup rate as `X / 100 tokens` with tooltip/copy aligned to that meaning
- [x] #3 Automated tests cover the lifetime totals plumbing and homepage summary/rendering change
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Extend overview lifetime hints/query plumbing to include total tokens seen and total intentional Yomitan lookups from finished sessions.
2. Add/adjust focused tests first for query hints, stats overview API typing/mocks, and overview summary formatting so the homepage metric fails under old semantics.
3. Update the overview summary/card to derive Lookup Rate from lifetime Yomitan lookups per 100 tokens and align tooltip/copy with that meaning.
4. Run focused verification on the touched query, stats-server, and stats UI tests; record results and blockers in the task notes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Extended overview lifetime hints to include total tokens seen and total intentional Yomitan lookups from finished sessions so the homepage can compute a true global lookup rate.
Extracted the homepage Tracking Snapshot into a dedicated presentational component to keep OverviewTab smaller and make the Lookup Rate card copy directly testable.
Focused verification passed for query hints, IPC/stats overview plumbing, stats server overview response, dashboard summary logic, and homepage snapshot rendering.
SubMiner verifier core lane artifact: .tmp/skill-verification/subminer-verify-20260319-105320-7FDlwh. `bun run typecheck` passed there; `bun run test:fast` failed for a pre-existing/unrelated environment issue in scripts/update-aur-package.test.ts because scripts/update-aur-package.sh reported `mapfile: command not found`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Homepage Lookup Rate now uses lifetime intentional Yomitan lookups normalized by lifetime tokens seen, matching the existing session/media/anime semantics instead of the old known-word hit-rate metric. I extended overview query hints and API typings with total token and Yomitan lookup totals, updated the overview summary builder to reuse the shared per-100-token formatter, and replaced the inline Tracking Snapshot block with a dedicated component that renders `X / 100 tokens` plus Yomitan-specific tooltip copy.
Tests added/updated: query hints coverage for the new lifetime totals, stats server and IPC overview fixtures, overview summary assertions, and a dedicated Tracking Snapshot render test for the homepage card text. Focused `bun test` runs passed for those touched areas. Repo-native verifier `--lane core` also passed `bun run typecheck`; its `bun run test:fast` step still fails for the unrelated existing `scripts/update-aur-package.sh: line 71: mapfile: command not found` environment issue.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,63 +0,0 @@
---
id: TASK-177.2
title: Count homepage new words by headword
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 19:38'
updated_date: '2026-03-23 03:22'
labels:
- stats
- immersion-tracking
- vocabulary
dependencies: []
references:
- src/core/services/immersion-tracker/query.ts
- stats/src/components/overview/TrackingSnapshot.tsx
- stats/src/lib/dashboard-data.ts
parent_task_id: TASK-177
priority: medium
ordinal: 130500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Align the homepage New Words metric with the Known Words semantics by counting distinct headwords first seen in the selected window, so inflected or alternate forms of the same word do not inflate the summary.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Homepage new-word counts use distinct headwords by earliest first-seen timestamp instead of counting separate word-form rows
- [x] #2 Homepage tooltip/copy reflects the headword-based semantics
- [x] #3 Automated tests cover the headword de-duplication behavior and affected overview copy
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Change the new-word aggregate query to group `imm_words` by headword, compute each headword's earliest `first_seen`, and count headwords whose first sighting falls within today/week windows.
2. Add failing tests first for the aggregate path so multiple rows sharing a headword only contribute once.
3. Update homepage tooltip/copy to say unique headwords first seen today/week.
4. Run focused query and stats overview tests, then record verification and any blockers.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Updated the new-word aggregate to count distinct headwords by each headword's earliest `first_seen` timestamp, so multiple inflected/form rows for the same headword contribute only once.
Adjusted homepage tooltip copy to say unique headwords first seen today/week, keeping the visible card labels unchanged.
Focused verification passed for the query aggregate and homepage snapshot tests.
SubMiner verifier core lane artifact: .tmp/skill-verification/subminer-verify-20260319-123942-4intgW. `bun run typecheck` passed there; `bun run test:fast` still fails for the unrelated environment issue in scripts/update-aur-package.test.ts (`mapfile: command not found`).
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Homepage New Words now uses headword-level semantics instead of counting separate `(headword, word, reading)` rows. The aggregate query groups `imm_words` by headword, uses each headword's earliest `first_seen`, and counts headwords first seen today or this week so alternate forms do not inflate the summary. The homepage tooltip copy now explicitly says the metric is based on unique headwords.
Added focused regression coverage for the de-duplication rule in `getQueryHints` and for the updated homepage tooltip text. Targeted `bun test` runs passed for the touched query and stats UI files. Repo verifier `--lane core` again passed `bun run typecheck`; `bun run test:fast` remains blocked by the unrelated existing `scripts/update-aur-package.sh: line 71: mapfile: command not found` failure.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-177.3
title: Fix attached stats command flow and browser config
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 20:15'
updated_date: '2026-03-23 03:22'
labels:
- launcher
- stats
- cli
dependencies: []
references:
- launcher/commands/stats-command.ts
- launcher/commands/command-modules.test.ts
- launcher/main.test.ts
- src/main/runtime/stats-cli-command.ts
- src/main/runtime/stats-cli-command.test.ts
parent_task_id: TASK-177
priority: medium
ordinal: 129500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Make `subminer stats` stay attached to the foreground app process instead of routing through daemon startup, while keeping background/stop behavior on the daemon path. Ensure browser opening for stats respects only `stats.autoOpenBrowser` in the normal stats flow.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Default `subminer stats` forwards through the attached foreground stats command path instead of the daemon-start path
- [x] #2 `subminer stats --background` and `subminer stats --stop` continue using the daemon control path
- [x] #3 Normal stats launches do not open a browser when `stats.autoOpenBrowser` is false, and automated tests cover the launcher/runtime regressions
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing launcher tests first so default `stats` expects `--stats` forwarding while `--background` and `--stop` continue to expect daemon control flags.
2. Add/adjust runtime stats command tests to prove `stats.autoOpenBrowser=false` suppresses browser opening on the normal attached stats path.
3. Patch launcher forwarding logic in `launcher/commands/stats-command.ts` to choose foreground vs daemon flags correctly without changing cleanup handling.
4. Run targeted launcher and stats runtime tests, then record verification results and blockers.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Confirmed root cause: launcher default `stats` flow always forwarded `--stats-daemon-start` plus `--stats-daemon-open-browser`, which detached the terminal process and bypassed `stats.autoOpenBrowser` because browser opening happened in daemon control instead of the normal stats CLI handler.
Updated launcher forwarding so plain `subminer stats` now uses the attached `--stats` path, while explicit `--background` and `--stop` continue using daemon control flags.
Added launcher regression coverage for the attached/default path and preserved background/stop expectations; added runtime coverage proving `stats.autoOpenBrowser=false` suppresses browser opening on the normal stats path.
Verifier passed for `launcher-plugin` and `runtime-compat` lanes. Artifact: .tmp/skill-verification/subminer-verify-20260319-131703-ZaAaUV.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed `subminer stats` so the default command now forwards to the normal attached `--stats` app path instead of the daemon-start path. That keeps the foreground process attached to the terminal as expected, while `subminer stats --background` and `subminer stats --stop` still use daemon control. Because the normal stats CLI path already respects `config.stats.autoOpenBrowser`, this also fixes the unwanted browser-open behavior that previously bypassed config via `--stats-daemon-open-browser`.
Added launcher command and launcher integration regressions for the new forwarding behavior, plus a runtime stats CLI regression that asserts `stats.autoOpenBrowser=false` suppresses browser opening. Verification passed with targeted launcher tests, targeted runtime stats tests, and the SubMiner verifier `launcher-plugin` + `runtime-compat` lanes.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,67 +0,0 @@
---
id: TASK-182.2
title: Improve session detail known-word chart scaling
status: Done
assignee:
- codex
created_date: '2026-03-19 20:31'
updated_date: '2026-03-23 03:22'
labels:
- bug
- stats
- ui
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionDetail.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/lib/session-detail.test.tsx
parent_task_id: TASK-182
ordinal: 128500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Adjust the expanded session-detail known-word percentage chart so the vertical range reflects the session's actual percent range instead of always spanning 0-100. Keep the chart easier to read while preserving the percent-based tooltip/legend behavior already used in the stats UI.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Expanded session detail scales the known/unknown percent chart to the session's observed percent range instead of hard-coding a 0-100 top bound
- [x] #2 The chart keeps a small headroom above the highest observed known-word percent so the line remains visually readable near the top edge
- [x] #3 Automated frontend coverage locks the new percent-domain behavior and preserves existing session-detail rendering
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused frontend regression test for the session-detail ratio chart domain calculation, covering a session whose known-word percentage stays in a narrow band below 100% and expecting a dynamic top bound with headroom.
2. Update `stats/src/components/sessions/SessionDetail.tsx` to compute a dynamic percent-axis domain and matching ticks for the ratio chart, keeping the lower bound at 0%, adding modest padding above the highest known percentage, rounding to clean tick steps, and capping at 100%.
3. Apply the computed percent-axis bounds consistently to the right-side Y axis and the session chart pause overlays so the visual framing stays aligned.
4. Run targeted frontend tests and the SubMiner verification helper on the touched files, then record results and any blockers in the task.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented dynamic known-percentage axis scaling in `stats/src/components/sessions/SessionDetail.tsx`: the ratio chart now keeps a 0% floor, uses the highest observed known percentage plus 5 points of headroom for the top bound, rounds that bound up to clean 10-point ticks, caps at 100%, and enables `allowDataOverflow` so the stacked area chart actually honors the tighter domain.
Added frontend regression coverage in `stats/src/lib/session-detail.test.tsx` for the axis-max helper, covering both a narrow-band session and near-100% cap behavior.
Added user-visible changelog fragment `changes/2026-03-19-session-detail-chart-scaling.md`.
Verification: `bun test stats/src/lib/session-detail.test.tsx` passed; `bun run typecheck` passed; `bun run changelog:lint` passed; `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core stats/src/components/sessions/SessionDetail.tsx stats/src/lib/session-detail.test.tsx` ran and passed `typecheck` but failed `bun run test:fast` on a pre-existing unrelated issue in `scripts/update-aur-package.test.ts` / `scripts/update-aur-package.sh` (`mapfile: command not found`). Artifacts: `.tmp/skill-verification/subminer-verify-20260319-134440-JRHAUJ`.
Docs decision: no internal docs update required; the behavior change is localized UI presentation with no API/workflow change. Changelog decision: yes, required and completed because the fix is user-visible.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Improved expanded session-detail chart readability by replacing the fixed 0-100 known-word percentage axis with a dynamic top bound based on the sessions highest observed known percentage plus modest headroom, rounded to clean ticks and capped at 100%. The ratio chart now also enables `allowDataOverflow` so Recharts preserves the tighter percent domain even though the stacked known/unknown areas sum to 100%.
Added frontend regression coverage for the new axis-max behavior and a changelog fragment for the user-visible stats fix.
Verification: `bun test stats/src/lib/session-detail.test.tsx`, `bun run typecheck`, and `bun run changelog:lint` passed. The SubMiner verification helpers `core` lane also passed `typecheck`, but `bun run test:fast` remains red on a pre-existing unrelated bash-compat failure in `scripts/update-aur-package.test.ts` / `scripts/update-aur-package.sh` (`mapfile: command not found`).
<!-- SECTION:FINAL_SUMMARY:END -->

Some files were not shown because too many files have changed in this diff Show More