mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 06:54:27 +00:00
Records feat-014 (GitHub GraphQL batch push/delete, commit114a575) and feat-015 (avoid stale remote-tree read after push, commit7676325) as done. Also archived feat-011/012/013 and other completed narratives out of progress.md (had grown past its own 80-line cleanup threshold) into archive/2026-07.md, and trimmed feature_list.json evidence strings under 300 chars — harness self-audit was 96/100, now 100/100.
127 lines
10 KiB
JSON
127 lines
10 KiB
JSON
{
|
|
"_note": "GitHub Issues (firstsun-dev/git-files-sync, Project #6) is the source of truth for the full backlog and priority/estimate fields. This file mirrors only the active feature and the next few candidates so an agent session has a local, offline checkpoint — sync it against `gh issue list --repo firstsun-dev/git-files-sync --state open` at the start of a session rather than treating it as authoritative.",
|
|
"_prPolicy": "User explicitly requested fewer PRs (2026-07-13): all issue work is consolidated onto branch claude/fix-directory-symlink-pull-260713 -> PR #51, not one PR per issue. Commit new work directly onto that branch unless told otherwise.",
|
|
"features": [
|
|
{
|
|
"id": "feat-001",
|
|
"name": "Project Setup",
|
|
"description": "Confirm the project can install dependencies, run verification, and start from a clean checkout",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 28f4f8e - npm install/lint/build/test all pass from a clean checkout"
|
|
},
|
|
{
|
|
"id": "feat-002",
|
|
"name": "Settings UX bundle (issues #40, #41, #42)",
|
|
"description": "Local ignore-pattern sync setting (#40), persistent connection status badge in settings (#41), and resized/tabbed conflict resolution modal (#42)",
|
|
"dependencies": ["feat-001"],
|
|
"status": "done",
|
|
"evidence": "Consolidated onto PR #51 (branch claude/fix-directory-symlink-pull-260713); 302 tests pass"
|
|
},
|
|
{
|
|
"id": "feat-003",
|
|
"name": "fix: symbolic link pull fails (issue #33)",
|
|
"description": "Directory symlinks were walked as real trees by local scanning code, causing bogus remote .gitignore 404s and a permanently-stuck 'remote only' status; also fixed a missing Windows symlinkSync type hint",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 4c8896b, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-004",
|
|
"name": "fix: resolve sonarqube issues (issue #45)",
|
|
"description": "Review current SonarQube findings and fix code smells/bugs/security hotspots where applicable",
|
|
"dependencies": [],
|
|
"status": "not-started",
|
|
"evidence": ""
|
|
},
|
|
{
|
|
"id": "feat-005",
|
|
"name": "feat(settings): folder picker for root path / vault folder (issue #48)",
|
|
"description": "Searchable folder suggester for the Root path and Vault folder settings fields, replacing free-text entry",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit c107979, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-006",
|
|
"name": "perf(refresh): use tree blob SHAs to avoid per-file content fetches (issue #36)",
|
|
"description": "Status refresh compares a locally-computed git blob SHA against each remote tree entry's SHA instead of one getFile per file; diff content fetched on demand via new getBlob(sha,path); symlink-aware hashing per real/follow/skip modes",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 2ed5a43, consolidated onto PR #51; 302 tests pass"
|
|
},
|
|
{
|
|
"id": "feat-007",
|
|
"name": "fix: clear error when requestUrl() rejects with HTML (issue #31)",
|
|
"description": "Some Obsidian versions eagerly JSON-parse inside requestUrl() itself, so a login/proxy HTML page rejected the whole call with a raw SyntaxError bypassing the existing parseJson() HTML-detection wrapper; added the same detection to safeRequest's outer catch",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit a867217, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-008",
|
|
"name": "feat: show new feature tips after update (issue #39)",
|
|
"description": "Hand-curated src/changelog.ts (separate from auto-generated CHANGELOG.md) with a notable flag per entry, numeric compareVersions(), WhatsNewModal shown once per version bump via a new lastSeenVersion setting",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 4eebebc, consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-009",
|
|
"name": "feat: add i18n (multi-language) support (issue #38)",
|
|
"description": "Extract hardcoded UI strings (settings tab, ribbon/command labels, Notice messages, sync status view/modals) into an i18n key system with locale detection and English fallback",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "Commit 144eb28, merged into claude/fix-directory-symlink-pull-260713 - 159 i18n keys (en+zh-tw), ~130 strings replaced; lint/build clean, 308 tests pass; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-010",
|
|
"name": "feat: add bitbucket support (issue #37)",
|
|
"description": "Add Bitbucket as a fourth GitServiceInterface provider; Bitbucket's API has no native blob SHA concept and is PR-based rather than direct-commit, so GitFile.sha semantics may need adjusting",
|
|
"dependencies": ["feat-009"],
|
|
"status": "not-started",
|
|
"evidence": ""
|
|
},
|
|
{
|
|
"id": "feat-011",
|
|
"name": "perf(push): batch-commit push-all + SHA-based diffing",
|
|
"description": "Push-all was up to 3N sequential HTTP calls and N separate commits for N files, plus two redundant full-tree fetches per run. Added optional GitServiceInterface.pushBatch (GitHub/Gitea via blob->tree->commit->ref, generalizing pushSymlink's pattern; GitLab via the native multi-action Commits API + a follow-up tree read for blob shas); sync-manager now classifies each file via a locally-computed git blob sha against one pre-fetched remote tree (no getFile per file) and commits all queued files in one grouped call (chunked at MAX_BATCH_PUSH_SIZE=200), falling back to the old per-file path when a provider has no pushBatch; main.ts/GitignoreManager now share one tree fetch instead of two",
|
|
"dependencies": [],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 330/330 passed; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-012",
|
|
"name": "perf(delete): batch-commit remote-only file deletion",
|
|
"description": "Follow-up to feat-011: batch-deleting remote-only files via the sync status panel's checkbox multi-select was still N separate commits (SyncStatusView.performRemoteDeletion looped gitService.deleteFile per file). Added optional GitServiceInterface.deleteBatch, mirroring pushBatch: GitHub/Gitea via the git blob->tree->commit->ref Data API (a tree entry with sha:null removes that path); GitLab via the same Commits API actions array used for pushBatch, with action:'delete'. SyncStatusView.performRemoteDeletion now calls deleteBatch once (chunked at MAX_BATCH_PUSH_SIZE) when the provider supports it, falling back to the original sequential per-file performRemoteDeletionSequential otherwise",
|
|
"dependencies": ["feat-011"],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 339/339 passed; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-013",
|
|
"name": "perf(push): parallelize blob creation within a batch commit",
|
|
"description": "User reported push-all was still slow on GitHub after feat-011's one-commit-per-run batching landed. Root cause: GitHubService/GiteaService.pushBatch still created each file's blob with a sequential `for` loop (N sequential POST .../git/blobs round trips before the single tree/commit/ref sequence), so wall-clock time was still O(N) latency-bound. Added a shared BaseGitService.mapWithConcurrency helper (order-preserving, bounded concurrency) and a BLOB_CREATE_CONCURRENCY=8 cap in git-service-base.ts; both services' pushBatch now creates blobs concurrently instead of one at a time. GitLab unaffected (its Commits API already sends the whole batch in one request, no per-file blob step)",
|
|
"dependencies": ["feat-011"],
|
|
"status": "done",
|
|
"evidence": "npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 340/340 passed; consolidated onto PR #51"
|
|
},
|
|
{
|
|
"id": "feat-014",
|
|
"name": "perf(push): GitHub batch push/delete via GraphQL createCommitOnBranch",
|
|
"description": "User reported batch push was still slow on GitHub after feat-013's blob-concurrency fix. Root cause: GitHubService.pushBatch/deleteBatch still needed N/8 rounds of sequential REST blob-creation calls before the tree/commit/ref sequence. Replaced GitHub's pushBatch/deleteBatch with a single GraphQL createCommitOnBranch mutation carrying file content directly (additions/deletions), cutting an N-file batch to 2-3 HTTP calls total (ref lookup + mutation, plus one follow-up tree fetch for pushBatch's per-file shas, which the mutation doesn't return). Extracted BaseGitService.getLatestCommitSha out of resolveGitHubStyleBaseTree (still used by pushSymlink and Gitea's REST-only batch path, which has no GraphQL equivalent). Added explicit handling for GraphQL's 200-status-with-errors-array failure mode, which REST's status-code check can't catch.",
|
|
"dependencies": ["feat-013"],
|
|
"status": "done",
|
|
"evidence": "Commit 114a575. npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 341/341 passed; live smoke test against a real GitHub repo confirmed pushBatch/deleteBatch each produce one commit."
|
|
},
|
|
{
|
|
"id": "feat-015",
|
|
"name": "fix(push): avoid stale remote-tree read right after a batch push",
|
|
"description": "User reported: batch-pushing two empty files, then refreshing immediately, showed them as 'modified' even though the diff was identical; refreshing again a bit later showed them synced. Root cause (confirmed via live GitHub API test, not a content bug - the pushed blob was byte-for-byte correct): SyncStatusView re-fetched the remote tree via refreshAllStatuses() right after push, and GitHub's tree-by-branch-name read can lag a just-completed write by a moment. Fix: SyncManager.pushAllFiles now also returns syncedPaths (path + new sha) from data already known at each write site (batch chunk result, sequential fallback, immediate symlink/rename push); SyncStatusView marks those paths 'synced' directly instead of re-fetching the tree, avoiding the race entirely. Pull unaffected.",
|
|
"dependencies": ["feat-014"],
|
|
"status": "done",
|
|
"evidence": "Commit 7676325. npx eslint . -> 0 errors; npm run build -> clean; npx vitest run -> 344/344 passed."
|
|
}
|
|
],
|
|
"_evidenceStyle": "Keep evidence to one line: commit hash + short pointer (e.g. 'Commit abc1234 - added X, tests pass'). Debugging narrative and design discussion belong in the commit message, not this file."
|
|
}
|