fix(git): kill post-merge friction — sync FF-only realign (not rebase), merge-not-squash docs, deterministic spawn registry
This commit is contained in:
@@ -46,7 +46,7 @@ Only branch with git WRITE access. WRITE git (commit, push, checkout, merge, res
|
||||
- Chained read+write blocks the whole command (e.g. `git log && git push` → blocked). Keep read and write in separate invocations.
|
||||
|
||||
Three rules:
|
||||
1. Work on dev, merge to main when satisfied. `drone @git merge dev` squash-merges.
|
||||
1. Work on dev, merge to main when satisfied. `drone @git merge <PR#>` makes a MERGE COMMIT (`gh pr merge --merge`) — dev stays a clean FF-able ancestor of main, never diverges. Post-merge "dev 1 behind main" is just the merge commit (cosmetic); realign with `drone @git sync` from dev (no checkout). Sync local main without checkout: `git fetch origin main:main`.
|
||||
2. You commit, agents don't. Agents build+test, report results. You review, commit.
|
||||
3. Local files = source of truth.
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
{
|
||||
"file": "apps/modules/git_module.py",
|
||||
"standard": "unused_function",
|
||||
"lines": [655],
|
||||
"lines": [665],
|
||||
"reason": "get_introspective() called dynamically via getattr() by module_registry_handler.py:219 for internal module introspection. Also tested in test_git_module, test_system_pr, test_devpulse_plugins, test_git_access."
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
Branch synchronization — works on both main and dev.
|
||||
|
||||
On main: pulls latest from origin/main.
|
||||
On dev: pulls origin/main into dev (realigns after PR merge).
|
||||
On dev: fast-forwards dev to origin/main (realigns after PR merge).
|
||||
Refuses if dev has diverged — no silent rewrite.
|
||||
From other branch: checks out main first, then pulls.
|
||||
"""
|
||||
|
||||
@@ -24,10 +25,10 @@ from aipass.drone.apps.handlers.git.lock_handler import find_repo_root
|
||||
|
||||
|
||||
def sync_main(autostash: bool = False) -> dict:
|
||||
"""Checkout main and pull latest changes.
|
||||
"""Sync current branch with origin/main. On dev: fast-forward (no checkout). On main/other: pull.
|
||||
|
||||
Args:
|
||||
autostash: If True, stash local changes before pull and restore after.
|
||||
autostash: If True, stash local changes before sync and restore after.
|
||||
Use when sync fails with 'unstaged changes' error.
|
||||
|
||||
Returns:
|
||||
@@ -174,7 +175,13 @@ def sync_main(autostash: bool = False) -> dict:
|
||||
|
||||
|
||||
def _sync_dev(repo_root, autostash: bool = False) -> dict:
|
||||
"""Pull origin/main into dev branch to realign after PR merge."""
|
||||
"""Fast-forward dev to origin/main after PR merge.
|
||||
|
||||
Uses ``git merge --ff-only origin/main`` instead of rebase so that
|
||||
existing commits are never silently rewritten. Since merges to main
|
||||
are always merge-commits (not squash), dev is always fast-forwardable
|
||||
after a merge. If it is not, the command refuses loudly.
|
||||
"""
|
||||
stashed = False
|
||||
|
||||
if autostash:
|
||||
@@ -198,7 +205,7 @@ def _sync_dev(repo_root, autostash: bool = False) -> dict:
|
||||
return {"success": False, "message": f"Fetch failed: {fetch.stderr.strip()}", "stdout": ""}
|
||||
|
||||
result = subprocess.run(
|
||||
["git", "pull", "origin", "main", "--rebase"],
|
||||
["git", "merge", "--ff-only", "origin/main"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(repo_root),
|
||||
@@ -209,13 +216,45 @@ def _sync_dev(repo_root, autostash: bool = False) -> dict:
|
||||
|
||||
if result.returncode != 0:
|
||||
raw = result.stderr.strip()
|
||||
msg = f"Failed to sync dev from main: {raw}"
|
||||
if not autostash and ("unstaged changes" in raw or "uncommitted changes" in raw):
|
||||
msg += "\n Tip: retry with 'drone @git sync --autostash'"
|
||||
if "fast-forward" in raw.lower():
|
||||
msg = "Dev has diverged from main — cannot fast-forward. Use 'drone @git fix' to resolve."
|
||||
else:
|
||||
msg = f"Failed to sync dev from main: {raw}"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg, "stdout": result.stdout}
|
||||
|
||||
stdout = result.stdout.strip()
|
||||
msg = f"Synced dev from origin/main: {stdout}"
|
||||
msg = f"Synced dev from origin/main (fast-forward): {stdout}"
|
||||
json_handler.log_operation("sync_dev", {"result": stdout, "autostash": autostash})
|
||||
logger.info(msg)
|
||||
return {"success": True, "message": msg, "stdout": stdout}
|
||||
|
||||
|
||||
def sync_main_ref() -> dict:
|
||||
"""Update local main ref to match origin without checkout.
|
||||
|
||||
Uses git fetch origin main:main — works from any branch.
|
||||
Fails if local main has commits not on origin (non-fast-forward).
|
||||
"""
|
||||
repo_root = find_repo_root()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "fetch", "origin", "main:main"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(repo_root),
|
||||
)
|
||||
if result.returncode != 0:
|
||||
msg = f"Cannot update local main ref: {result.stderr.strip()}"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg, "stdout": ""}
|
||||
|
||||
stdout = result.stdout.strip()
|
||||
msg = f"Updated local main ref: {stdout or 'up to date'}"
|
||||
json_handler.log_operation("sync_main_ref", {"result": stdout})
|
||||
logger.info(msg)
|
||||
return {"success": True, "message": msg, "stdout": stdout}
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
msg = f"sync_main_ref failed: {exc}"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg, "stdout": ""}
|
||||
|
||||
@@ -487,6 +487,12 @@ def _handle_checkout(args: list[str]) -> dict:
|
||||
|
||||
def _handle_sync(args: list[str]) -> dict:
|
||||
"""Handle the sync subcommand (owner tier)."""
|
||||
if "--main-ref" in args:
|
||||
result = sync_handler.sync_main_ref()
|
||||
if result["success"]:
|
||||
return {"stdout": result["message"], "stderr": "", "exit_code": 0}
|
||||
return {"stdout": "", "stderr": result["message"], "exit_code": 1}
|
||||
|
||||
autostash = "--autostash" in args
|
||||
result = sync_handler.sync_main(autostash=autostash)
|
||||
|
||||
@@ -598,9 +604,13 @@ def get_help(command: str | None = None) -> str:
|
||||
return "git checkout <main|dev> — Switch branches (main or dev only) [owner]\n"
|
||||
if command == "sync":
|
||||
return (
|
||||
"git sync [--autostash] — Checkout main and pull latest changes [owner]\n"
|
||||
"git sync [--autostash] [--main-ref] — Sync with origin/main [owner]\n"
|
||||
" On dev: fast-forward to origin/main (stays on dev, no checkout).\n"
|
||||
" On main: pull latest. From other branch: checkout main first.\n"
|
||||
" Refuses if dev has diverged — no silent rewrite.\n"
|
||||
" Options:\n"
|
||||
" --autostash Stash local changes before pull and restore after.\n"
|
||||
" --autostash Stash local changes before sync and restore after.\n"
|
||||
" --main-ref Update local main ref without checkout (from any branch).\n"
|
||||
)
|
||||
if command == "unlock":
|
||||
return "git unlock --force — Force-release the PR lock [owner]\n"
|
||||
@@ -645,7 +655,7 @@ def get_help(command: str | None = None) -> str:
|
||||
" delete-branch <name> Delete a remote branch\n"
|
||||
" close-pr <number> Close a PR\n"
|
||||
" merge <PR#> Merge a PR\n"
|
||||
" sync [--autostash] Checkout main and pull\n"
|
||||
" sync [--autostash] Sync with origin/main (FF on dev)\n"
|
||||
" smart-sync Fetch + rebase if behind\n"
|
||||
" unlock --force Force-release the PR lock\n"
|
||||
" fix [--dry-run] Fix broken git states\n"
|
||||
|
||||
@@ -365,6 +365,168 @@ class TestSyncHandler:
|
||||
assert "failed" in result["message"].lower()
|
||||
|
||||
|
||||
class TestSyncDev:
|
||||
"""Sync from dev branch — fast-forward, not rebase."""
|
||||
|
||||
def test_sync_dev_ff_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Sync on dev fast-forwards cleanly when behind main."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_fetch = MagicMock(returncode=0, stdout="", stderr="")
|
||||
mock_merge = MagicMock(returncode=0, stdout="Updating abc..def\nFast-forward", stderr="")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_fetch, mock_merge],
|
||||
):
|
||||
result = sync_main()
|
||||
|
||||
assert result["success"] is True
|
||||
assert "dev" in result["message"].lower()
|
||||
|
||||
def test_sync_dev_stays_on_dev(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Sync on dev does NOT checkout main — no git checkout call."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_fetch = MagicMock(returncode=0, stdout="", stderr="")
|
||||
mock_merge = MagicMock(returncode=0, stdout="Already up to date.", stderr="")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_fetch, mock_merge],
|
||||
) as mock_run:
|
||||
sync_main()
|
||||
|
||||
cmds = [call[0][0] for call in mock_run.call_args_list]
|
||||
assert not any("checkout" in cmd for cmd in cmds), "sync on dev must not checkout"
|
||||
|
||||
def test_sync_dev_uses_ff_only(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Sync on dev uses git merge --ff-only, not git pull --rebase."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_fetch = MagicMock(returncode=0, stdout="", stderr="")
|
||||
mock_merge = MagicMock(returncode=0, stdout="Fast-forward", stderr="")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_fetch, mock_merge],
|
||||
) as mock_run:
|
||||
sync_main()
|
||||
|
||||
merge_call = mock_run.call_args_list[2][0][0]
|
||||
assert "merge" in merge_call, "should use git merge"
|
||||
assert "--ff-only" in merge_call, "should use --ff-only"
|
||||
assert "--rebase" not in str(mock_run.call_args_list), "must NOT use rebase"
|
||||
|
||||
def test_sync_dev_refuses_on_diverge(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Sync on dev fails loud when dev has diverged (not fast-forwardable)."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_fetch = MagicMock(returncode=0, stdout="", stderr="")
|
||||
mock_merge = MagicMock(returncode=1, stdout="", stderr="fatal: Not possible to fast-forward, aborting.")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_fetch, mock_merge],
|
||||
):
|
||||
result = sync_main()
|
||||
|
||||
assert result["success"] is False
|
||||
assert "fast-forward" in result["message"].lower() or "diverged" in result["message"].lower()
|
||||
|
||||
def test_sync_dev_autostash(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Sync on dev with --autostash stashes and pops."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_stash = MagicMock(returncode=0, stdout="Saved working directory", stderr="")
|
||||
mock_fetch = MagicMock(returncode=0, stdout="", stderr="")
|
||||
mock_merge = MagicMock(returncode=0, stdout="Fast-forward", stderr="")
|
||||
mock_pop = MagicMock(returncode=0, stdout="Restored", stderr="")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_stash, mock_fetch, mock_merge, mock_pop],
|
||||
) as mock_run:
|
||||
result = sync_main(autostash=True)
|
||||
|
||||
assert result["success"] is True
|
||||
cmds = [call[0][0] for call in mock_run.call_args_list]
|
||||
assert any(cmd == ["git", "stash"] for cmd in cmds)
|
||||
assert any(cmd == ["git", "stash", "pop"] for cmd in cmds)
|
||||
|
||||
def test_sync_dev_fetch_failure(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Fetch failure in dev sync returns error."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_head = MagicMock(returncode=0, stdout="dev", stderr="")
|
||||
mock_fetch = MagicMock(returncode=1, stdout="", stderr="fatal: unable to access remote")
|
||||
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
side_effect=[mock_head, mock_fetch],
|
||||
):
|
||||
result = sync_main()
|
||||
|
||||
assert result["success"] is False
|
||||
assert "fetch" in result["message"].lower()
|
||||
|
||||
|
||||
class TestSyncMainRef:
|
||||
"""Sync local main ref without checkout."""
|
||||
|
||||
def test_sync_main_ref_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""sync_main_ref updates local main ref."""
|
||||
from aipass.drone.apps.handlers.git.sync_handler import sync_main_ref
|
||||
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
return_value=mock_result,
|
||||
):
|
||||
result = sync_main_ref()
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_sync_main_ref_failure(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""sync_main_ref fails on non-fast-forward."""
|
||||
from aipass.drone.apps.handlers.git.sync_handler import sync_main_ref
|
||||
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_result = MagicMock(returncode=1, stdout="", stderr="! [rejected] main -> main (non-fast-forward)")
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
return_value=mock_result,
|
||||
):
|
||||
result = sync_main_ref()
|
||||
|
||||
assert result["success"] is False
|
||||
assert "main" in result["message"].lower()
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 4. pr_handler — error paths (no actual git)
|
||||
# ===========================================================================
|
||||
@@ -671,6 +833,22 @@ class TestGitModuleRouting:
|
||||
|
||||
assert result["exit_code"] == 0
|
||||
|
||||
@patch("aipass.drone.apps.plugins.devpulse_ops.auth.verify_git_access", return_value="devpulse")
|
||||
def test_sync_main_ref_routes(self, _mock_auth: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""sync --main-ref routes to sync_main_ref."""
|
||||
registry = tmp_path / "AIPASS_REGISTRY.json"
|
||||
registry.write_text("{}", encoding="utf-8")
|
||||
monkeypatch.chdir(tmp_path)
|
||||
|
||||
mock_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch(
|
||||
"aipass.drone.apps.handlers.git.sync_handler.subprocess.run",
|
||||
return_value=mock_result,
|
||||
):
|
||||
result = handle_command("sync", ["--main-ref"])
|
||||
|
||||
assert result["exit_code"] == 0
|
||||
|
||||
@patch("aipass.drone.apps.plugins.devpulse_ops.auth.verify_git_access", return_value="test_branch")
|
||||
def test_status_no_branch_dir(self, _mock_auth: MagicMock, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""status outside a branch directory returns error."""
|
||||
|
||||
@@ -47,18 +47,22 @@ Run by **devpulse** (only branch with git write). Tick each step as you go; fill
|
||||
|
||||
### Why `dev` shows "behind main" after a merge — and why it's fine
|
||||
|
||||
`drone @git merge` does a **squash** merge: GitHub bundles dev's commits into **one brand-new
|
||||
commit** on main. Your `dev` branch keeps its **original** commits. Git compares by commit
|
||||
*identity*, not content — so it sees "main has 1 commit dev doesn't" → the UI shows
|
||||
**"dev is 1 behind main."**
|
||||
`drone @git merge` runs `gh pr merge --merge` — a **merge commit**, not a squash. GitHub
|
||||
adds a merge commit on main whose parent IS dev's tip. After merging, `dev` is a **clean
|
||||
ancestor** of `main` (fast-forwardable), never diverged. The "dev is 1 behind main" is
|
||||
just that one merge commit — **cosmetic and trivially resolved**.
|
||||
|
||||
- **The files are identical. It is 100% cosmetic. You can always move forward** — the next
|
||||
`dev-pr` compares real file changes and works perfectly regardless of this graph quirk.
|
||||
- Because it's a squash (not a fast-forward), **`dev` is NOT an ancestor of `main`**, so
|
||||
`git merge --ff-only main` will **fail**. Do not use it after a squash merge.
|
||||
- **Optional**, only if you want the graph to show dev even/ahead: back-merge with
|
||||
`git merge origin/main` while on dev (via `drone @git`) — creates a merge commit, dev moves
|
||||
*ahead*, push normally (NO force, NO reset). **Do NOT rebase** (rewrites dev, needs force-push).
|
||||
- Because `dev` is a clean ancestor of `main`, **`git merge --ff-only origin/main` on dev
|
||||
WORKS** — a clean fast-forward realign, no merge commit created, no history rewrite.
|
||||
- **Realign dev to even** (recommended): `drone @git sync` from dev (clean FF), or
|
||||
manually `git merge --ff-only origin/main` on dev. No force-push, no rebase needed.
|
||||
- **Sync local `main` ref WITHOUT checkout**: `git fetch origin main:main` — updates the
|
||||
local main ref to match origin with **zero working-tree touch, no checkout**. This is the
|
||||
answer to the IDE "switch to main → your local changes would be overwritten by checkout"
|
||||
dialog: that dialog is git SAFETY working — **Cancel, never Force Checkout**. You never
|
||||
need to stand on main.
|
||||
|
||||
---
|
||||
|
||||
@@ -97,15 +101,16 @@ The PR gate (verified against `.github/workflows/`):
|
||||
## 5. Merge to main
|
||||
|
||||
- [ ] **User's call to merge** — confirm GO
|
||||
- [ ] `drone @git merge <PR#>` (squash-merge)
|
||||
- [ ] `drone @git merge <PR#>` (merge commit via `gh pr merge --merge`)
|
||||
- [ ] ⚠️ The merge command **echoes the PR's ORIGINAL opening description** — often stale if the PR accumulated more work after it was opened. Don't trust it as the merge summary; the real contents are `git log main..dev` from before the merge.
|
||||
- [ ] ⚠️ **Verify `dev` SURVIVES the merge** (the #625 scar — empirical, every time): `drone @git branches` → `dev` still present; `git rev-parse dev` resolves
|
||||
|
||||
## 6. Post-merge realign
|
||||
|
||||
- [ ] **Expect `dev` to show "1 behind main" — that's the squash artifact, it's cosmetic, keep going.** See "Why dev shows behind main" up top. Do NOT reach for `--ff-only` (it fails after a squash) or a rebase/reset.
|
||||
- [ ] **Stay on `dev`. Do not check out `main`.** Local main being behind is fine and expected — it's a push-target, not a thing to maintain.
|
||||
- [ ] (Optional, cosmetic only) If you want the graph to show dev even/ahead: back-merge `git merge origin/main` on dev (via `drone @git`), then normal push. Never rebase, never reset, never checkout main.
|
||||
- [ ] **Expect `dev` to show "1 behind main" — that's the merge commit, it's cosmetic + fast-forwardable.** See "Why dev shows behind main" up top.
|
||||
- [ ] **Realign dev** (recommended): `drone @git sync` from dev, or `git merge --ff-only origin/main` on dev. Clean FF, no merge commit, no rewrite.
|
||||
- [ ] **Stay on `dev`. Do not check out `main`.** Local main being behind is fine — sync it without checkout: `git fetch origin main:main` (zero working-tree touch).
|
||||
- [ ] Never rebase, never reset, never checkout main.
|
||||
- [ ] Dependabot / other PRs targeting main: they go green once main has the fix + bots rebase — check after the push
|
||||
|
||||
## 7. Release tag (only if cutting a release)
|
||||
|
||||
@@ -87,7 +87,7 @@ def regenerate_template_registry(template_dir: Path) -> dict:
|
||||
try:
|
||||
tmp_path = registry_path.with_suffix(".tmp")
|
||||
tmp_path.write_text(
|
||||
json.dumps(registry, indent=2, ensure_ascii=False) + "\n",
|
||||
json.dumps(registry, indent=2, sort_keys=True, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
tmp_path.replace(registry_path)
|
||||
@@ -213,27 +213,27 @@ def _scan_template_directory(
|
||||
)
|
||||
|
||||
# Assign IDs to files with three-pass global matching.
|
||||
# This prevents new files from stealing IDs that existing files should claim.
|
||||
# Path first (stable), then hash (handles renames), then new IDs.
|
||||
files: dict[str, dict] = {}
|
||||
unmatched_files: list[dict] = []
|
||||
|
||||
# Pass 1: hash matching for all files
|
||||
# Pass 1: path matching (deterministic — same path keeps same ID)
|
||||
for entry in raw_files:
|
||||
content_hash = entry.get("content_hash", "")
|
||||
if content_hash and content_hash in hash_to_id:
|
||||
candidate = hash_to_id[content_hash]
|
||||
path = entry.get("path", "")
|
||||
if path and path in path_to_file_id:
|
||||
candidate = path_to_file_id[path]
|
||||
if candidate not in claimed_file_ids:
|
||||
claimed_file_ids.add(candidate)
|
||||
files[candidate] = entry
|
||||
continue
|
||||
unmatched_files.append(entry)
|
||||
|
||||
# Pass 2: path matching for remaining files
|
||||
# Pass 2: hash matching for remaining files (catches renames)
|
||||
still_unmatched: list[dict] = []
|
||||
for entry in unmatched_files:
|
||||
path = entry.get("path", "")
|
||||
if path and path in path_to_file_id:
|
||||
candidate = path_to_file_id[path]
|
||||
content_hash = entry.get("content_hash", "")
|
||||
if content_hash and content_hash in hash_to_id:
|
||||
candidate = hash_to_id[content_hash]
|
||||
if candidate not in claimed_file_ids:
|
||||
claimed_file_ids.add(candidate)
|
||||
files[candidate] = entry
|
||||
|
||||
@@ -1,390 +1,390 @@
|
||||
{
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"last_updated": "2026-06-10",
|
||||
"description": "Template file tracking registry for ID-based updates"
|
||||
"directories": {
|
||||
"d001": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".ai_mail.local",
|
||||
"path": ".ai_mail.local"
|
||||
},
|
||||
"d003": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".aipass",
|
||||
"path": ".aipass"
|
||||
},
|
||||
"d004": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".archive",
|
||||
"path": ".archive"
|
||||
},
|
||||
"d005": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".claude",
|
||||
"path": ".claude"
|
||||
},
|
||||
"d006": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".seedgo",
|
||||
"path": ".seedgo"
|
||||
},
|
||||
"d007": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".trinity",
|
||||
"path": ".trinity"
|
||||
},
|
||||
"d008": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "apps",
|
||||
"path": "apps"
|
||||
},
|
||||
"d009": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "handlers",
|
||||
"path": "apps/handlers"
|
||||
},
|
||||
"d010": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "modules",
|
||||
"path": "apps/modules"
|
||||
},
|
||||
"d011": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "plugins",
|
||||
"path": "apps/plugins"
|
||||
},
|
||||
"d012": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "artifacts",
|
||||
"path": "artifacts"
|
||||
},
|
||||
"d013": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "docs",
|
||||
"path": "docs"
|
||||
},
|
||||
"d014": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "docs.local",
|
||||
"path": "docs.local"
|
||||
},
|
||||
"d015": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "sub_agent_drops",
|
||||
"path": "docs.local/sub_agent_drops"
|
||||
},
|
||||
"d016": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "dropbox",
|
||||
"path": "dropbox"
|
||||
},
|
||||
"d017": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "logs",
|
||||
"path": "logs"
|
||||
},
|
||||
"d018": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "templates",
|
||||
"path": "templates"
|
||||
},
|
||||
"d019": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "tests",
|
||||
"path": "tests"
|
||||
},
|
||||
"d020": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "tools",
|
||||
"path": "tools"
|
||||
},
|
||||
"d021": {
|
||||
"has_branch_placeholder": true,
|
||||
"name": "{{BRANCH}}_json",
|
||||
"path": "{{BRANCH}}_json"
|
||||
},
|
||||
"d022": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "custom_config",
|
||||
"path": "{{BRANCH}}_json/custom_config"
|
||||
},
|
||||
"d023": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".spawn",
|
||||
"path": ".spawn"
|
||||
},
|
||||
"d024": {
|
||||
"has_branch_placeholder": false,
|
||||
"name": "integrations",
|
||||
"path": "apps/integrations"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"f001": {
|
||||
"path": ".ai_mail.local/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "49299c242a01",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".ai_mail.local/README.md"
|
||||
},
|
||||
"f002": {
|
||||
"path": ".ai_mail.local/inbox.json",
|
||||
"name": "inbox.json",
|
||||
"content_hash": "c9702fe2cc21",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "inbox.json",
|
||||
"path": ".ai_mail.local/inbox.json"
|
||||
},
|
||||
"f003": {
|
||||
"path": ".aipass/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "f42d87684fdf",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".aipass/README.md"
|
||||
},
|
||||
"f004": {
|
||||
"path": ".aipass/aipass_local_prompt.md",
|
||||
"name": "aipass_local_prompt.md",
|
||||
"content_hash": "bf82b35fa7d5",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "aipass_local_prompt.md",
|
||||
"path": ".aipass/aipass_local_prompt.md"
|
||||
},
|
||||
"f005": {
|
||||
"path": ".archive/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "93d3fcb74f23",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".archive/README.md"
|
||||
},
|
||||
"f006": {
|
||||
"path": ".claude/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "adb0ce8c53c1",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".claude/README.md"
|
||||
},
|
||||
"f007": {
|
||||
"path": ".claude/settings.local.json",
|
||||
"name": "settings.local.json",
|
||||
"content_hash": "eacf065629cd",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "settings.local.json",
|
||||
"path": ".claude/settings.local.json"
|
||||
},
|
||||
"f008": {
|
||||
"path": ".gitignore",
|
||||
"name": ".gitignore",
|
||||
"content_hash": "2dd6758a96d5",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".gitignore",
|
||||
"path": ".gitignore"
|
||||
},
|
||||
"f009": {
|
||||
"path": ".seedgo/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "ea03468bbf16",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".seedgo/README.md"
|
||||
},
|
||||
"f010": {
|
||||
"path": ".seedgo/bypass.json",
|
||||
"name": "bypass.json",
|
||||
"content_hash": "0ac90a35515b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f024": {
|
||||
"path": ".spawn/.registry_ignore.json",
|
||||
"name": ".registry_ignore.json",
|
||||
"content_hash": "34f5e7ff7e01",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f043": {
|
||||
"path": ".spawn/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e22ad5337efd",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "bypass.json",
|
||||
"path": ".seedgo/bypass.json"
|
||||
},
|
||||
"f011": {
|
||||
"path": ".trinity/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "f461c9b16fc5",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".trinity/README.md"
|
||||
},
|
||||
"f012": {
|
||||
"path": ".trinity/local.json",
|
||||
"name": "local.json",
|
||||
"content_hash": "8f8a98e42d92",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "local.json",
|
||||
"path": ".trinity/local.json"
|
||||
},
|
||||
"f013": {
|
||||
"path": ".trinity/observations.json",
|
||||
"name": "observations.json",
|
||||
"content_hash": "62160dfa243c",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "observations.json",
|
||||
"path": ".trinity/observations.json"
|
||||
},
|
||||
"f014": {
|
||||
"path": ".trinity/passport.json",
|
||||
"name": "passport.json",
|
||||
"content_hash": "a05a4352e7a9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f016": {
|
||||
"path": "DASHBOARD.local.json",
|
||||
"name": "DASHBOARD.local.json",
|
||||
"content_hash": "f4775daf1f75",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f017": {
|
||||
"path": "README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "ad99517a50f9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f019": {
|
||||
"path": "apps/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "92a956009e0e",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f020": {
|
||||
"path": "apps/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "0ef6c27137dc",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f021": {
|
||||
"path": "apps/handlers/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "2e4f4a0c1b47",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f022": {
|
||||
"path": "apps/handlers/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "dbfc0e044461",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f044": {
|
||||
"path": "apps/integrations/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "31c09afe1299",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f023": {
|
||||
"path": "apps/modules/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "a4cf0a8e3b4f",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f018": {
|
||||
"path": "apps/modules/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f025": {
|
||||
"path": "apps/plugins/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "d1e4e2b98c38",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f027": {
|
||||
"path": "apps/{{BRANCH}}.py",
|
||||
"name": "{{BRANCH}}.py",
|
||||
"content_hash": "024209a8c889",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f028": {
|
||||
"path": "artifacts/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "de20d11e5cfd",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f029": {
|
||||
"path": "artifacts/birth_certificate.json",
|
||||
"name": "birth_certificate.json",
|
||||
"content_hash": "0b6e4319781e",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f030": {
|
||||
"path": "docs/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "2434da568727",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f031": {
|
||||
"path": "docs.local/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "c19d8872ea2c",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f032": {
|
||||
"path": "docs.local/sub_agent_drops/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e3e5a6b9c9c5",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f033": {
|
||||
"path": "dropbox/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "9e1e9b71f93b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f034": {
|
||||
"path": "logs/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "4ca207af6bd3",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f035": {
|
||||
"path": "pytest.ini",
|
||||
"name": "pytest.ini",
|
||||
"content_hash": "7b39ba7bca40",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f045": {
|
||||
"path": "requirements.project.txt",
|
||||
"name": "requirements.project.txt",
|
||||
"content_hash": "1facc521802b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f036": {
|
||||
"path": "templates/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "73b020b003f9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f037": {
|
||||
"path": "tests/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "c157895c9b27",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f038": {
|
||||
"path": "tests/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "881f06bb6574",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f039": {
|
||||
"path": "tests/conftest.py",
|
||||
"name": "conftest.py",
|
||||
"content_hash": "97f220799d19",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f040": {
|
||||
"path": "tools/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "3c7eaedb16ac",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f041": {
|
||||
"path": "{{BRANCH}}_json/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e64fa555e7b8",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f042": {
|
||||
"path": "{{BRANCH}}_json/custom_config/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "28e9ae373563",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "passport.json",
|
||||
"path": ".trinity/passport.json"
|
||||
},
|
||||
"f015": {
|
||||
"path": "apps/plugins/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
"has_branch_placeholder": false
|
||||
"has_branch_placeholder": false,
|
||||
"name": "__init__.py",
|
||||
"path": "apps/plugins/__init__.py"
|
||||
},
|
||||
"f016": {
|
||||
"content_hash": "f4775daf1f75",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "DASHBOARD.local.json",
|
||||
"path": "DASHBOARD.local.json"
|
||||
},
|
||||
"f017": {
|
||||
"content_hash": "ad99517a50f9",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "README.md"
|
||||
},
|
||||
"f018": {
|
||||
"content_hash": "e3b0c44298fc",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "__init__.py",
|
||||
"path": "apps/modules/__init__.py"
|
||||
},
|
||||
"f019": {
|
||||
"content_hash": "92a956009e0e",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "apps/README.md"
|
||||
},
|
||||
"f020": {
|
||||
"content_hash": "0ef6c27137dc",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "__init__.py",
|
||||
"path": "apps/__init__.py"
|
||||
},
|
||||
"f021": {
|
||||
"content_hash": "2e4f4a0c1b47",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "apps/handlers/README.md"
|
||||
},
|
||||
"f022": {
|
||||
"content_hash": "dbfc0e044461",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "__init__.py",
|
||||
"path": "apps/handlers/__init__.py"
|
||||
},
|
||||
"f023": {
|
||||
"content_hash": "a4cf0a8e3b4f",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "apps/modules/README.md"
|
||||
},
|
||||
"f024": {
|
||||
"content_hash": "34f5e7ff7e01",
|
||||
"has_branch_placeholder": false,
|
||||
"name": ".registry_ignore.json",
|
||||
"path": ".spawn/.registry_ignore.json"
|
||||
},
|
||||
"f025": {
|
||||
"content_hash": "d1e4e2b98c38",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "apps/plugins/README.md"
|
||||
},
|
||||
"f027": {
|
||||
"content_hash": "024209a8c889",
|
||||
"has_branch_placeholder": true,
|
||||
"name": "{{BRANCH}}.py",
|
||||
"path": "apps/{{BRANCH}}.py"
|
||||
},
|
||||
"f028": {
|
||||
"content_hash": "de20d11e5cfd",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "artifacts/README.md"
|
||||
},
|
||||
"f029": {
|
||||
"content_hash": "0b6e4319781e",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "birth_certificate.json",
|
||||
"path": "artifacts/birth_certificate.json"
|
||||
},
|
||||
"f030": {
|
||||
"content_hash": "2434da568727",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "docs/README.md"
|
||||
},
|
||||
"f031": {
|
||||
"content_hash": "c19d8872ea2c",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "docs.local/README.md"
|
||||
},
|
||||
"f032": {
|
||||
"content_hash": "e3e5a6b9c9c5",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "docs.local/sub_agent_drops/README.md"
|
||||
},
|
||||
"f033": {
|
||||
"content_hash": "9e1e9b71f93b",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "dropbox/README.md"
|
||||
},
|
||||
"f034": {
|
||||
"content_hash": "4ca207af6bd3",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "logs/README.md"
|
||||
},
|
||||
"f035": {
|
||||
"content_hash": "7b39ba7bca40",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "pytest.ini",
|
||||
"path": "pytest.ini"
|
||||
},
|
||||
"f036": {
|
||||
"content_hash": "73b020b003f9",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "templates/README.md"
|
||||
},
|
||||
"f037": {
|
||||
"content_hash": "c157895c9b27",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "tests/README.md"
|
||||
},
|
||||
"f038": {
|
||||
"content_hash": "881f06bb6574",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "__init__.py",
|
||||
"path": "tests/__init__.py"
|
||||
},
|
||||
"f039": {
|
||||
"content_hash": "97f220799d19",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "conftest.py",
|
||||
"path": "tests/conftest.py"
|
||||
},
|
||||
"f040": {
|
||||
"content_hash": "3c7eaedb16ac",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "tools/README.md"
|
||||
},
|
||||
"f041": {
|
||||
"content_hash": "e64fa555e7b8",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "{{BRANCH}}_json/README.md"
|
||||
},
|
||||
"f042": {
|
||||
"content_hash": "28e9ae373563",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "{{BRANCH}}_json/custom_config/README.md"
|
||||
},
|
||||
"f043": {
|
||||
"content_hash": "e22ad5337efd",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": ".spawn/README.md"
|
||||
},
|
||||
"f044": {
|
||||
"content_hash": "31c09afe1299",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "README.md",
|
||||
"path": "apps/integrations/README.md"
|
||||
},
|
||||
"f045": {
|
||||
"content_hash": "1facc521802b",
|
||||
"has_branch_placeholder": false,
|
||||
"name": "requirements.project.txt",
|
||||
"path": "requirements.project.txt"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"d001": {
|
||||
"path": ".ai_mail.local",
|
||||
"name": ".ai_mail.local",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d003": {
|
||||
"path": ".aipass",
|
||||
"name": ".aipass",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d004": {
|
||||
"path": ".archive",
|
||||
"name": ".archive",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d005": {
|
||||
"path": ".claude",
|
||||
"name": ".claude",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d006": {
|
||||
"path": ".seedgo",
|
||||
"name": ".seedgo",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d023": {
|
||||
"path": ".spawn",
|
||||
"name": ".spawn",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d007": {
|
||||
"path": ".trinity",
|
||||
"name": ".trinity",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d008": {
|
||||
"path": "apps",
|
||||
"name": "apps",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d009": {
|
||||
"path": "apps/handlers",
|
||||
"name": "handlers",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d024": {
|
||||
"path": "apps/integrations",
|
||||
"name": "integrations",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d010": {
|
||||
"path": "apps/modules",
|
||||
"name": "modules",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d011": {
|
||||
"path": "apps/plugins",
|
||||
"name": "plugins",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d012": {
|
||||
"path": "artifacts",
|
||||
"name": "artifacts",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d013": {
|
||||
"path": "docs",
|
||||
"name": "docs",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d014": {
|
||||
"path": "docs.local",
|
||||
"name": "docs.local",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d015": {
|
||||
"path": "docs.local/sub_agent_drops",
|
||||
"name": "sub_agent_drops",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d016": {
|
||||
"path": "dropbox",
|
||||
"name": "dropbox",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d017": {
|
||||
"path": "logs",
|
||||
"name": "logs",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d018": {
|
||||
"path": "templates",
|
||||
"name": "templates",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d019": {
|
||||
"path": "tests",
|
||||
"name": "tests",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d020": {
|
||||
"path": "tools",
|
||||
"name": "tools",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d021": {
|
||||
"path": "{{BRANCH}}_json",
|
||||
"name": "{{BRANCH}}_json",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"d022": {
|
||||
"path": "{{BRANCH}}_json/custom_config",
|
||||
"name": "custom_config",
|
||||
"has_branch_placeholder": false
|
||||
}
|
||||
"metadata": {
|
||||
"description": "Template file tracking registry for ID-based updates",
|
||||
"last_updated": "2026-06-11",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user