diff --git a/.gitignore b/.gitignore index 7ccc3d0..a569fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,8 @@ .DS_Store -# Hop's local state and isolated workspaces. -# Hop's database and disposable workspaces stay local. Its immutable prompt -# records are intentionally versioned with the repository. -/.hop/* -!/.hop/records/ -!/.hop/records/** +# Hop's local state, prompt records, and isolated workspaces can contain private +# user requests and machine paths. Never publish them through Git. +/.hop/ # Local build and verification artifacts. /hop diff --git a/.hop/records/prompts/P_06FN3MBF9ABQ6NTST1B7GZ0.json b/.hop/records/prompts/P_06FN3MBF9ABQ6NTST1B7GZ0.json deleted file mode 100644 index 3136ab2..0000000 --- a/.hop/records/prompts/P_06FN3MBF9ABQ6NTST1B7GZ0.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN3MBF9ABQ6NTST1B7GZ0", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN3MBF9ABQ6NTST1B7GZ0", - "prompt": "what? wtf? i need it in the right folder how do i do that", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T15:35:07.850559Z", - "metadata": { - "source_tree": "012d4872472b5c8621c6ef3cedd50509e09e7aa6", - "git_commit": "a70773826f67e30e57b7c0a0419e3b601674632f", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN4EVBXYRV3MQJE7FXJ9R.json b/.hop/records/prompts/P_06FN4EVBXYRV3MQJE7FXJ9R.json deleted file mode 100644 index 28a884c..0000000 --- a/.hop/records/prompts/P_06FN4EVBXYRV3MQJE7FXJ9R.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN4EVBXYRV3MQJE7FXJ9R", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN4EVBXYRV3MQJE7FXJ9R", - "prompt": "so.. i can have multiple agents working on the same codebase at the same time and it should be fine?", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T17:30:53.807892Z", - "metadata": { - "source_tree": "22e1b6bfde927afbe0622a552cfe6703fed85f3f", - "git_commit": "dcad0b49f3e5cde9cffd3ea23f50088e6ba4c617", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN4K2V1S84CQ0XFJDQRQG.json b/.hop/records/prompts/P_06FN4K2V1S84CQ0XFJDQRQG.json deleted file mode 100644 index f3cc0b4..0000000 --- a/.hop/records/prompts/P_06FN4K2V1S84CQ0XFJDQRQG.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN4K2V1S84CQ0XFJDQRQG", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN4K2V1S84CQ0XFJDQRQG", - "prompt": "hop is broken. \"I updated the reference to v=3, but Hop blocked landing because another recent task also changed hop-home.css, creating an overlap. That stylesheet conflict needs to be resolved before I can safely apply the cache-busting fix.\" this is fucking horse shit. the agent should intelligently resolve the conflict", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T17:49:23.598284Z", - "metadata": { - "source_tree": "22e1b6bfde927afbe0622a552cfe6703fed85f3f", - "git_commit": "94d2ac053cd45d514d586764836ee46eb26c2af2", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN4ZBB60PW23320GTM4T8.json b/.hop/records/prompts/P_06FN4ZBB60PW23320GTM4T8.json deleted file mode 100644 index a78dc83..0000000 --- a/.hop/records/prompts/P_06FN4ZBB60PW23320GTM4T8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN4ZBB60PW23320GTM4T8", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN4ZBB60PW23320GTM4T8", - "prompt": "now i want to publish this as a real product people can use. how will people install it? by cloning a git repo and running some commands? can you update the readme with clear installation/getting started instructions, and create the wiki pages as well", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T18:42:58.992885Z", - "metadata": { - "source_tree": "8e3074490c46530db90e20242bd16000aee239d0", - "git_commit": "56fe10e4deb1d42626d0a5dfc4f1446987338019", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN4ZY663SBZ8CDXP9YPF8.json b/.hop/records/prompts/P_06FN4ZY663SBZ8CDXP9YPF8.json deleted file mode 100644 index 8a44729..0000000 --- a/.hop/records/prompts/P_06FN4ZY663SBZ8CDXP9YPF8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN4ZY663SBZ8CDXP9YPF8", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN4ZY663SBZ8CDXP9YPF8", - "prompt": "wait? should we create a packaged release? HELLO IM ASKING YOU WHATS THE BEST IMPLEMENTATION FOR A POLISHED PRODUCT NOT TO JUST WORK WITH HOWEVER IT IS NOW JSUT BECAUSE", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T18:45:33.360226Z", - "metadata": { - "source_tree": "8e3074490c46530db90e20242bd16000aee239d0", - "git_commit": "422e59ec24e57ddb0d3df46199c4f16bf428371e", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN5172P0R8W5XZ1CHK8N0.json b/.hop/records/prompts/P_06FN5172P0R8W5XZ1CHK8N0.json deleted file mode 100644 index 9462930..0000000 --- a/.hop/records/prompts/P_06FN5172P0R8W5XZ1CHK8N0.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN5172P0R8W5XZ1CHK8N0", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN5172P0R8W5XZ1CHK8N0", - "prompt": "i wont be hosting this on github, ill be hosting it on githop.xyz which is a custom gitea. the site isnt live yet but its done", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T18:51:08.33613Z", - "metadata": { - "source_tree": "7879aa3466a9b33dcebbe8c1c9873334976577e5", - "git_commit": "f434be463cc2940449970408924e653161dc9ac8", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN572J3TK7PBAZVM7HSVG.json b/.hop/records/prompts/P_06FN572J3TK7PBAZVM7HSVG.json deleted file mode 100644 index f5b45fa..0000000 --- a/.hop/records/prompts/P_06FN572J3TK7PBAZVM7HSVG.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN572J3TK7PBAZVM7HSVG", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN572J3TK7PBAZVM7HSVG", - "prompt": "can you publish it under GnosysLabsLLC/Hop .. i have added my SSH key to githop so you should be able to", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:16:44.190798Z", - "metadata": { - "source_tree": "a91a4bd37315fa30673005970ce750755e9582be", - "git_commit": "e13e291812530b2dee72d0caec1e5974a87fbf47", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN57YDR2V5WBGFMNBYN48.json b/.hop/records/prompts/P_06FN57YDR2V5WBGFMNBYN48.json deleted file mode 100644 index 9d652df..0000000 --- a/.hop/records/prompts/P_06FN57YDR2V5WBGFMNBYN48.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN57YDR2V5WBGFMNBYN48", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN57YDR2V5WBGFMNBYN48", - "prompt": "sorry actually its https://githop.xyz/org/GnosysLabs/Hop", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:20:32.448864Z", - "metadata": { - "source_tree": "a91a4bd37315fa30673005970ce750755e9582be", - "git_commit": "020f15acdb2ae92f5fb80e431485a6ff8ea8025a", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN58K0XX5MSYB34619NX0.json b/.hop/records/prompts/P_06FN58K0XX5MSYB34619NX0.json deleted file mode 100644 index 61ef6ec..0000000 --- a/.hop/records/prompts/P_06FN58K0XX5MSYB34619NX0.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN58K0XX5MSYB34619NX0", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN58K0XX5MSYB34619NX0", - "prompt": "i turned off the proxy? does that make it work", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:23:21.19964Z", - "metadata": { - "source_tree": "a91a4bd37315fa30673005970ce750755e9582be", - "git_commit": "ad1055c4f4e79f0a0efa9e39c2d6dd8ca5c1b219", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN58Y686QH957QKXDC820.json b/.hop/records/prompts/P_06FN58Y686QH957QKXDC820.json deleted file mode 100644 index 64fd5fb..0000000 --- a/.hop/records/prompts/P_06FN58Y686QH957QKXDC820.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN58Y686QH957QKXDC820", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN58Y686QH957QKXDC820", - "prompt": "why would you have to put port 22? thats not how it works with github. wtf?", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:24:52.673537Z", - "metadata": { - "source_tree": "a91a4bd37315fa30673005970ce750755e9582be", - "git_commit": "092fec93f02614e919b8a75392044f86703d2293", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN596K5H32W6J8X4NNS4R.json b/.hop/records/prompts/P_06FN596K5H32W6J8X4NNS4R.json deleted file mode 100644 index 0367b79..0000000 --- a/.hop/records/prompts/P_06FN596K5H32W6J8X4NNS4R.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN596K5H32W6J8X4NNS4R", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN596K5H32W6J8X4NNS4R", - "prompt": "no thats gucking dumb i want it to work with the base url? so wtf do i need to do? go into the vps and make it fucking work", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:26:01.516159Z", - "metadata": { - "source_tree": "a91a4bd37315fa30673005970ce750755e9582be", - "git_commit": "c88206ebec78fd2abff1b971188a520ba39dedfa", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN5E0MF7G4PQ12VZTMB9G.json b/.hop/records/prompts/P_06FN5E0MF7G4PQ12VZTMB9G.json deleted file mode 100644 index 6213772..0000000 --- a/.hop/records/prompts/P_06FN5E0MF7G4PQ12VZTMB9G.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN5E0MF7G4PQ12VZTMB9G", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN5E0MF7G4PQ12VZTMB9G", - "prompt": "idk what this is, im going to disable the actions thing on our instance we dont have the server resources for i\n\nAttachment: /var/folders/58/b6yn15tx3jb7_6lw9m67f89m0000gn/T/TemporaryItems/NSIRD_screencaptureui_4Rixx8/Screenshot 2026-07-11 at 12.45.33 PM.png", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T19:47:03.417913Z", - "metadata": { - "source_tree": "52869fbc4277ef5d2db2794c6d7867d6c23c423c", - "git_commit": "c77bf19172e2fed26b577183282c27ce341c80d4", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN5HF796A99F3E5DR9PWR.json b/.hop/records/prompts/P_06FN5HF796A99F3E5DR9PWR.json deleted file mode 100644 index 7f8992f..0000000 --- a/.hop/records/prompts/P_06FN5HF796A99F3E5DR9PWR.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN5HF796A99F3E5DR9PWR", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN5HF796A99F3E5DR9PWR", - "prompt": "I dont think we need this in the read me, its too much. \"**Go install — developer path**\nIf Go 1.26 or newer is already installed:\n\ngo install githop.xyz/GnosysLabs/Hop/cmd/hop@latest\nhop skill install --force\nEnsure $(go env GOPATH)/bin is on PATH. Tagged module builds report the tag through hop version.\n\n**Build from source**\nCloning is supported, but it is the contributor/fallback path rather than the normal product installation:\n\ngit clone https://githop.xyz/GnosysLabs/Hop.git\ncd hop\ngo test ./...\ngo build -trimpath -o hop ./cmd/hop\nmkdir -p \"$HOME/.local/bin\"\ninstall -m 755 hop \"$HOME/.local/bin/hop\"\n\"$HOME/.local/bin/hop\" skill install --force\n**Verify the installation**\nhop version\nhop help\nThe packaged installers install two things:\n\nthe hop CLI; and\nthe embedded Hop agent skill at ${CODEX_HOME:-~/.codex}/skills/hop.\nNo project is modified during installation. A project receives its local .hop/ state only when Hop first runs inside that project.\n\nRelease installer URLs become available after the corresponding Gitea Release is published. Until the first public release exists, use the source build.\"\n\n\"To inspect the installer before running it:\n\ncurl -fsSLO https://githop.xyz/GnosysLabs/Hop/raw/branch/main/scripts/install.sh\nless install.sh\nsh install.sh\nPin a version or choose another destination with environment variables:\n\nHOP_VERSION=v0.1.0 HOP_INSTALL_DIR=\"$HOME/bin\" sh install.sh\"\n\n\"**Current status**\nThis repository contains the first local alpha kernel. It supports:\n\nExisting and unborn Git repositories\nCodex Desktop first-action prompt capture and session-aware follow-ups\nController-grade pre-delivery prompt capture\nIsolated detached worktrees per attempt\nImmutable checkpoints and proposals\nChecks bound to exact source trees\nReal three-way content merging, including compatible same-file edits\nAgent-ready reconciliation workspaces for genuine merge conflicts\nOptional validation on the final integrated tree\nCompare-and-swap acceptance\nSafe visible-root materialization on Desktop landing\nController-only internal acceptance plus explicit root synchronization\nForward-only undo of the latest accepted transition\nGit-compatible accepted commits under refs/hop/accepted\nSQLite WAL state graph and machine-readable JSON output\nPre-persistence credential redaction across prompts, summaries, and check evidence\nEmbedded, installable vendor-neutral agent skill with implicit invocation enabled\nIt does not yet include a trusted raw-prompt hook, project knowledge, claims, remote synchronization, a GUI, or semantic merging.\n\nStart with [Installation](https://githop.xyz/GnosysLabs/Hop#installation) and [Getting started](https://githop.xyz/GnosysLabs/Hop#getting-started), or browse the [documentation wiki](https://githop.xyz/GnosysLabs/Hop/src/branch/main/wiki/Home.md). See [the product blueprint](https://githop.xyz/GnosysLabs/Hop/src/branch/main/docs/product-blueprint.md) for the complete model, design principles, and phased roadmap.\"\n\n\"The agent's first project action is equivalent to:\n\nhop begin --agent codex --heredoc \u003c\u003c'HOP_PROMPT_EOF'\n\u003cthe current user message\u003e\nHOP_PROMPT_EOF\nThe agent—not the user—runs this command. It initializes Hop without changing the current Git branch, working tree, or index, stores the prompt, and returns the state IDs and isolated workspace. Follow-up messages in the same Codex task automatically continue the same Hop attempt.\n\nFor another agent runtime, export the embedded skill to that runtime's skills directory:\n\nhop skill install --path /path/to/agent/skills --force\nAfter editing the printed workspace, the agent runs this lifecycle automatically:\n\nhop check P_... -- go test ./...\nhop propose --summary \"Added password reset emails\" P_...\nhop land R_... -- go test ./...\nThe command supplied to land runs in a temporary worktree containing the exact final tree that would become accepted. If it fails, refs/hop/accepted and the SQLite accepted head do not move. After acceptance, Hop updates only the visible working files through a disposable Git index; HEAD, the active branch, and the real index do not move. Compatible edits to the same file are merged automatically. A genuine merge conflict becomes a fresh agent reconciliation workspace, which the skill resolves, checks, and lands without asking the user to manage source-control mechanics. The agent pauses only for an explicit review-first request, failed validation, visible-root divergence, unresolved product intent, or newly required destructive/external scope.\n\nland and accept are intentionally different:\n\nhop land R_... -- go test ./... # Desktop: accept and synchronize the selected folder\nhop accept R_... -- go test ./... # Controller: accept internally, leave the folder untouched\nhop sync # Catch a stale visible folder up to accepted state\nhop sync is also the upgrade path for projects accepted by an older Hop build. It synchronizes only when the folder still matches an accepted ancestor.\n\nInspect the project:\n\nhop status\nhop graph\nhop state P_...\nhop diff R_...\nhop history\nhop sync\nhop doctor\nInspect or hand the skill to a harness without installing it:\n\nhop skill print\nFor a harness or controller that can capture prompts before delivery, initialize and start explicitly:\n\nhop init\nhop start --agent codex --heredoc \u003c\u003c'HOP_PROMPT_EOF'\nAdd password reset emails\nHOP_PROMPT_EOF\neval \"$(hop env P_...)\"\nIn this mode, deliver the same message only after hop start succeeds. Use hop prompt --from P_... --heredoc for controller-managed follow-ups.\n\nUndo the latest accepted transition without rewriting history:\n\nhop undo\" \n\n**Safety boundary**\nHop uses Git's three-way content merge rather than treating shared paths as conflicts. Textual cleanliness still cannot prove behavioral compatibility, so automatic acceptance reruns the strongest relevant validation on the exact final tree. Manual acceptance remains available as an explicit review-first mode.\n\nDesktop landing fails closed when nonignored visible source does not exactly match an accepted Hop state or when an ignored/untracked path would be overwritten. It never uses reset --hard, moves HEAD, or rewrites the user's real index. The lower-level hop accept command deliberately retains controller-only behavior and does not update the visible root.\n\nAgent-reported scope and test claims are never used as the source of truth. Hop computes source trees and changed paths itself.\n\nNon-secret prompt text and check output are stored locally in SQLite without encryption. Before persistence, Hop redacts high-confidence provider tokens and contextual credentials, private keys, authorization headers, and credential-bearing URLs. The same boundary sanitizes proposal summaries plus recorded validation commands and output.\n\nDetection cannot recognize every private or future token format. Prefer environment variables or a secret manager, and rotate any real credential pasted into any agent prompt even when Hop reports that it redacted the value.\n\nSkill-driven Desktop capture stores the agent's verbatim transcription of the visible message and attachment references. It cannot prove byte-for-byte fidelity with Codex's raw submission. A trusted UserPromptSubmit hook is the future deterministic capture boundary; the skill remains the no-UI-change alpha workflow.\n\nthis is stuff that belongs in the wiki not in the readme", - "agent_name": "codex", - "status": "completed", - "created_at": "2026-07-11T20:02:09.353469Z", - "metadata": { - "source_tree": "4113b695efa32e7b1b81dd1f8e4c0ad1a7013fae", - "git_commit": "4a39b2438d956cc4e9f0d6544750772e016f0a8f", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN5JNW8825596P3JQACJG.json b/.hop/records/prompts/P_06FN5JNW8825596P3JQACJG.json deleted file mode 100644 index 8ea9a5a..0000000 --- a/.hop/records/prompts/P_06FN5JNW8825596P3JQACJG.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN5JNW8825596P3JQACJG", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN5J8NBKM5ES4ZMD5Y1CR", - "state_id": "P_06FN5JNW8825596P3JQACJG", - "prompt": "\"Hop correctly detected that this long-running task’s original workspace was based on an older accepted tree. It has created a reconciliation workspace; I’m preserving the current product code and applying only the README/wiki cleanup there, then I’ll revalidate and land it automatically.\" WHY DOES THIS HAPPEN WITH EVERY SINGLE FUCKING REQUEST?", - "agent_name": "codex", - "status": "accepted", - "response_summary": "Reconcile concise README with current accepted product", - "created_at": "2026-07-11T20:07:26.01873Z", - "metadata": { - "source_tree": "2c8e849cbf49c422470f5857fda59a1aaf9ac2eb", - "git_commit": "240d7927e029adfc01c0a83622a8720963a3cbec", - "attempt_head": "R_06FN5JXX64P7BMT8Q98HHE8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN5M2868H466RPTMYQ3B0.json b/.hop/records/prompts/P_06FN5M2868H466RPTMYQ3B0.json deleted file mode 100644 index dfce749..0000000 --- a/.hop/records/prompts/P_06FN5M2868H466RPTMYQ3B0.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN5M2868H466RPTMYQ3B0", - "task_id": "T_06FN3MBF98GWD4NA5PA1RWG", - "attempt_id": "AT_06FN3MBF98FBDSE1BZDP5DG", - "state_id": "P_06FN5M2868H466RPTMYQ3B0", - "prompt": "ok please fix it", - "agent_name": "codex", - "status": "completed", - "response_summary": "Roll completed agent sessions onto the latest accepted state", - "created_at": "2026-07-11T20:13:29.522241Z", - "metadata": { - "source_tree": "4dae249394e559de9d1d86b76b5bcb1b551cc109", - "git_commit": "4e5aa7120134cb7a017685e50c3ec8d1534ad816", - "attempt_head": "R_06FN5TJRZC3APECRM9VM2A8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN63779989R5PFV1QF8TR.json b/.hop/records/prompts/P_06FN63779989R5PFV1QF8TR.json deleted file mode 100644 index 5b2116d..0000000 --- a/.hop/records/prompts/P_06FN63779989R5PFV1QF8TR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN63779989R5PFV1QF8TR", - "task_id": "T_06FN637799RW5Q7WH9H6PJR", - "attempt_id": "AT_06FN63779BJRBK1VFQ94GRR", - "state_id": "P_06FN63779989R5PFV1QF8TR", - "prompt": "does our installer install the skill in .agents so it works with any agent? and, why is the readme so specific to codex? it should work with any agent", - "agent_name": "codex", - "status": "accepted", - "response_summary": "Install portable agent-neutral Hop skill and documentation", - "created_at": "2026-07-11T21:19:42.41027Z", - "metadata": { - "source_tree": "fcc3f3c78f5bbf6fae43651a99019e833bd88530", - "git_commit": "2f00b2dd3f8bc5e4c261563a9ac063d1716f0d47", - "attempt_head": "R_06FN69VET1STZNKPWK913K8", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN6BNGRJKMV6THWMBFVM8.json b/.hop/records/prompts/P_06FN6BNGRJKMV6THWMBFVM8.json deleted file mode 100644 index ec27b27..0000000 --- a/.hop/records/prompts/P_06FN6BNGRJKMV6THWMBFVM8.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN6BNGRJKMV6THWMBFVM8", - "task_id": "T_06FN6BNGRHYXR3WCQBDNVNR", - "attempt_id": "AT_06FN6BNGRKX5FS7MJ6VN8Z8", - "state_id": "P_06FN6BNGRJKMV6THWMBFVM8", - "prompt": "we will do MIT license, and, do I just post the token here or what?", - "agent_name": "codex", - "status": "accepted", - "response_summary": "License Hop under MIT", - "created_at": "2026-07-11T21:56:36.676853Z", - "metadata": { - "source_tree": "8423d5eeffbb7d7e3b416139417c06a552beca7d", - "git_commit": "68d8bf2f7d4e064ba21fb6dcd4fb72b01fd8722f", - "attempt_head": "R_06FN6C531M6TX6KCGEMG4E0", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN6CFVXS0Q22MHZRR17PR.json b/.hop/records/prompts/P_06FN6CFVXS0Q22MHZRR17PR.json deleted file mode 100644 index cbe8943..0000000 --- a/.hop/records/prompts/P_06FN6CFVXS0Q22MHZRR17PR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN6CFVXS0Q22MHZRR17PR", - "task_id": "T_06FN6CFVXVVAP1KZAJ2X62R", - "attempt_id": "AT_06FN6CFVXTRZ8ZDG28QR5Z0", - "state_id": "P_06FN6CFVXS0Q22MHZRR17PR", - "prompt": "before we do that, i think we should set up hop to push after every commit, my logic is taking the most steps out of the users need to do manually as possible, so having it auto push would alliviate manual work", - "agent_name": "codex", - "status": "accepted", - "response_summary": "Automatically push every accepted Hop transition", - "created_at": "2026-07-11T22:00:12.526502Z", - "metadata": { - "source_tree": "996e9d600f0b31af0db77ff6794370758544a417", - "git_commit": "9a3a6d152b93df531cc21753e5564045db4b75b2", - "attempt_head": "R_06FN6FDTK9G17RXFFT82ZYR", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN6GZKFM9KAYSX7A6TXP8.json b/.hop/records/prompts/P_06FN6GZKFM9KAYSX7A6TXP8.json deleted file mode 100644 index 5df71c2..0000000 --- a/.hop/records/prompts/P_06FN6GZKFM9KAYSX7A6TXP8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "P_06FN6GZKFM9KAYSX7A6TXP8", - "task_id": "T_06FN6GZKFMRV2HP5N7XMNSR", - "attempt_id": "AT_06FN6GZKFP22ZCFRXGB7V00", - "state_id": "P_06FN6GZKFM9KAYSX7A6TXP8", - "prompt": "lets finish what we were doing then investigate that, i have added the key to the keychain", - "agent_name": "codex", - "status": "accepted", - "created_at": "2026-07-11T22:19:50.013492Z", - "metadata": { - "source_tree": "3b5d48444b5e5290839774d383a7498d0190e708", - "git_commit": "7139b9e1a7e52e8748c130dc366472c9f10cb04e", - "attempt_head": "R_06FN6KJWAY5XWVMCVQHCXKR", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN6JQ4DBZHXABYYCA61E8.json b/.hop/records/prompts/P_06FN6JQ4DBZHXABYYCA61E8.json deleted file mode 100644 index 62a79fd..0000000 --- a/.hop/records/prompts/P_06FN6JQ4DBZHXABYYCA61E8.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN6JQ4DBZHXABYYCA61E8", - "task_id": "T_06FN6GZKFMRV2HP5N7XMNSR", - "attempt_id": "AT_06FN6GZKFP22ZCFRXGB7V00", - "state_id": "P_06FN6JQ4DBZHXABYYCA61E8", - "prompt": "why is it creating tokens? is there something in hop encouraging that behavior? i dont like that", - "agent_name": "codex", - "status": "accepted", - "response_summary": "Require user-provisioned release credentials", - "created_at": "2026-07-11T22:27:24.906795Z", - "metadata": { - "source_tree": "3b5d48444b5e5290839774d383a7498d0190e708", - "git_commit": "531775df8be5e87e57832473fc6a7ad8342b9b91", - "attempt_head": "R_06FN6KJWAY5XWVMCVQHCXKR", - "attempt_head_kind": "proposal" - } -} diff --git a/.hop/records/prompts/P_06FN72497T1AVJAQC7X7860.json b/.hop/records/prompts/P_06FN72497T1AVJAQC7X7860.json deleted file mode 100644 index 424d366..0000000 --- a/.hop/records/prompts/P_06FN72497T1AVJAQC7X7860.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN72497T1AVJAQC7X7860", - "task_id": "T_06FN6ZVNS25H7V4BRG743PG", - "attempt_id": "AT_06FN6ZVNS0W32KK9YQCRG88", - "state_id": "P_06FN72497T1AVJAQC7X7860", - "prompt": "we dont need to show internal agent instructions at all. also why do all the agent responses say \"Roll completed agent sessions onto the latest accepted state\"", - "agent_name": "codex", - "status": "proposed", - "response_summary": "Publish only user prompts with their own proposal outcomes", - "created_at": "2026-07-11T23:34:44.798223Z", - "metadata": { - "source_tree": "a7f12a311a193987f0691cd61e7d7efd313a61d0", - "git_commit": "61fef5fbb2288dfeb5fc02675644f2780b9305d4", - "attempt_head": "C_06FN72ADQRWFSQ8TP9Q9SY0", - "attempt_head_kind": "checkpoint" - } -} diff --git a/.hop/records/prompts/P_06FN83DN7R0B9X5TEV3YDC0.json b/.hop/records/prompts/P_06FN83DN7R0B9X5TEV3YDC0.json deleted file mode 100644 index 84dee3f..0000000 --- a/.hop/records/prompts/P_06FN83DN7R0B9X5TEV3YDC0.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN83DN7R0B9X5TEV3YDC0", - "task_id": "T_06FN83DN7SMZB9WJRZNZMTR", - "attempt_id": "AT_06FN83DN7VHVF5BVW6DEJ8R", - "state_id": "P_06FN83DN7R0B9X5TEV3YDC0", - "prompt": "# Files mentioned by the user:\n\n## Screenshot 2026-07-11 at 6.58.29 PM.png: /var/folders/58/b6yn15tx3jb7_6lw9m67f89m0000gn/T/TemporaryItems/NSIRD_screencaptureui_vyA0oe/Screenshot 2026-07-11 at 6.58.29 PM.png\n\n## My request for Codex:\nonce again, there are prompts here that arent mine!", - "agent_name": "codex", - "status": "proposed", - "response_summary": "Purge internal reconciliation prompts from portable records", - "created_at": "2026-07-12T02:00:12.350322Z", - "metadata": { - "source_tree": "27e94b6d19eb7e4107b127e3cec0ca41718053dc", - "git_commit": "74ba06de917236754f6146aca0e2d6e10438e455", - "attempt_head": "C_06FN83MVSDWQH772HR663SR", - "attempt_head_kind": "checkpoint" - } -} diff --git a/.hop/records/prompts/P_06FN892CVDG36AN981MGF00.json b/.hop/records/prompts/P_06FN892CVDG36AN981MGF00.json deleted file mode 100644 index a65fde0..0000000 --- a/.hop/records/prompts/P_06FN892CVDG36AN981MGF00.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN892CVDG36AN981MGF00", - "task_id": "T_06FN892CVE210TZR2ZQQ9JG", - "attempt_id": "AT_06FN892CVDGHFG3F889Q3YR", - "state_id": "P_06FN892CVDG36AN981MGF00", - "prompt": "implement that", - "agent_name": "codex", - "status": "proposed", - "response_summary": "Attribute accepted commits to the prompting user's configured Git identity", - "created_at": "2026-07-12T02:24:52.955741Z", - "metadata": { - "source_tree": "5895f21f33fd3d6d04b764c84dfeb6e0c4738c1f", - "git_commit": "787acb2330d8662320477d022da37ee4029697ac", - "attempt_head": "C_06FN89NDDXYFZK3XRRBQPVR", - "attempt_head_kind": "checkpoint" - } -} diff --git a/.hop/records/prompts/P_06FN8AWMR1EMWPB5787YS60.json b/.hop/records/prompts/P_06FN8AWMR1EMWPB5787YS60.json deleted file mode 100644 index 79b5904..0000000 --- a/.hop/records/prompts/P_06FN8AWMR1EMWPB5787YS60.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN8AWMR1EMWPB5787YS60", - "task_id": "T_06FN8AWMR0HY1RJY8AN2A3R", - "attempt_id": "AT_06FN8AWMR0WHBQ7ZMDZ0NWG", - "state_id": "P_06FN8AWMR1EMWPB5787YS60", - "prompt": "HOP IS SUPPOSED TO INTELLIGENTLY RECONCILE THIS!", - "agent_name": "codex", - "status": "proposed", - "response_summary": "Reconcile compatible remote advancement before publishing accepted states", - "created_at": "2026-07-12T02:32:50.112254Z", - "metadata": { - "source_tree": "b24e0fc4185ee2df0b827cbcddf7712e8f14ebff", - "git_commit": "59b8ca7c05edbdd50535a61ae9ca732516ce7f31", - "attempt_head": "C_06FN8BEYCESTJ4ZDR0XRNK0", - "attempt_head_kind": "checkpoint" - } -} diff --git a/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json b/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json deleted file mode 100644 index e35e875..0000000 --- a/.hop/records/prompts/P_06FN8C4PMD47GH10ZFQY2VR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "P_06FN8C4PMD47GH10ZFQY2VR", - "task_id": "T_06FN8C4PMFJRT8APV4G03Y8", - "attempt_id": "AT_06FN8C4PMFRJEE4B7KKQ740", - "state_id": "P_06FN8C4PMD47GH10ZFQY2VR", - "prompt": "\"I couldn’t safely start the repository change because Hop prompt capture timed out. Per the repository workflow, I stopped before inspecting or modifying files. No changes were made.\" what the fuck is this bullshit", - "agent_name": "codex", - "status": "proposed", - "response_summary": "Make prompt capture fast and resilient", - "created_at": "2026-07-12T02:38:18.275042Z", - "metadata": { - "source_tree": "7f3f889e96a317c157eedf4f5285af835865f6fb", - "git_commit": "ff62e89596a85ff3073ea348919b8d2b7125069f", - "attempt_head": "C_06FN8CQ0ZNZ60MRZ818MB9G", - "attempt_head_kind": "checkpoint" - } -} diff --git a/.hop/records/suppressed.json b/.hop/records/suppressed.json deleted file mode 100644 index ee6d346..0000000 --- a/.hop/records/suppressed.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "prompt_ids": [ - "P_06FN3SFFTFGNMTA6219GNX0", - "P_06FN5WHD61PV5ZABVFXDXS8", - "P_06FN6RMF4VH728C9Q6GTQNR", - "P_06FN6VQSN9P8R8NGASJQG7R", - "P_06FN6XEKNYR6BP9GQF78MVG", - "P_06FN6ZVNS3EYMSG7D7J17E8" - ] -} diff --git a/internal/hop/cli.go b/internal/hop/cli.go index 7f6475f..344c842 100644 --- a/internal/hop/cli.go +++ b/internal/hop/cli.go @@ -218,7 +218,7 @@ func RunCLIWithInput(args []string, stdin io.Reader, stdout, stderr io.Writer) i case "export": fs := flag.NewFlagSet("export", flag.ContinueOnError) fs.SetOutput(stderr) - output := fs.String("output", "", "write the portable prompt ledger beneath this directory") + output := fs.String("output", "", "write a private local prompt export beneath this directory") if err := fs.Parse(commandArgs); err != nil || len(fs.Args()) != 0 { if err == nil { fmt.Fprintln(stderr, "usage: hop export [--output PATH]") @@ -235,7 +235,7 @@ func RunCLIWithInput(args []string, stdin io.Reader, stdout, stderr io.Writer) i if root == "" { root = service.Root } - fmt.Fprintf(stdout, "Exported %d prompt records to %s\n", len(ledger.Prompts), filepath.Join(root, ".hop", "records", "prompts")) + fmt.Fprintf(stdout, "Exported %d private local prompt records to %s\n", len(ledger.Prompts), filepath.Join(root, ".hop", "records", "prompts")) } case "check": stateID, argv, ok := splitCommand(commandArgs) diff --git a/internal/hop/git.go b/internal/hop/git.go index f488ca5..b019b21 100644 --- a/internal/hop/git.go +++ b/internal/hop/git.go @@ -109,8 +109,8 @@ func EnsureRepository(path string) (*Repository, error) { return NewGitStore().Ensure(context.Background(), path) } -// OpenRepository opens the repository containing path and keeps Hop's local -// runtime private while leaving its portable record ledger versionable. +// OpenRepository opens the repository containing path and keeps all Hop state, +// including prompt exports, private to the local machine. func OpenRepository(path string) (*Repository, error) { return NewGitStore().Open(context.Background(), path) } @@ -254,9 +254,8 @@ func (r *Repository) GitDir() string { return r.gitDir } // worktrees. func (r *Repository) CommonGitDir() string { return r.commonGitDir } -// EnsureHopExcluded keeps the SQLite cache and disposable workspaces private -// without excluding .hop/records. The records directory is intentionally -// versionable so a repository can carry Hop's portable causal history. +// EnsureHopExcluded keeps the entire .hop directory private. Prompt exports can +// contain user requests and machine paths, so they must never enter snapshots. func (r *Repository) EnsureHopExcluded() error { infoDir := filepath.Join(r.commonGitDir, "info") if err := os.MkdirAll(infoDir, 0o755); err != nil { @@ -269,18 +268,20 @@ func (r *Repository) EnsureHopExcluded() error { } lines := make([]string, 0) for _, line := range strings.Split(string(contents), "\n") { - if strings.TrimSuffix(line, "\r") == ".hop/" { - continue // Migrate the legacy blanket exclusion. + normalized := strings.TrimSuffix(line, "\r") + switch normalized { + case ".hop/", ".hop/*", "!.hop/records/", "!.hop/records/**", + "# Hop local runtime; .hop/records is intentionally versioned.", + "# Hop private local runtime and prompt records.": + continue } lines = append(lines, line) } updated := strings.Join(lines, "\n") - if !strings.Contains(updated, ".hop/*\n") { - if updated != "" && !strings.HasSuffix(updated, "\n") { - updated += "\n" - } - updated += "# Hop local runtime; .hop/records is intentionally versioned.\n.hop/*\n!.hop/records/\n!.hop/records/**\n" + if updated != "" && !strings.HasSuffix(updated, "\n") { + updated += "\n" } + updated += "# Hop private local runtime and prompt records.\n.hop/\n" if updated == string(contents) { return nil } diff --git a/internal/hop/ledger.go b/internal/hop/ledger.go index e28cd5d..cd0d7cc 100644 --- a/internal/hop/ledger.go +++ b/internal/hop/ledger.go @@ -10,9 +10,8 @@ import ( "time" ) -// PortablePromptLedger is the versioned, repository-safe subset of Hop's -// local state. It deliberately excludes SQLite, workspace paths, check output, -// and every other machine-local runtime detail. +// PortablePromptLedger is an explicit local export of prompt state. It is kept +// beneath the ignored .hop directory because prompts and metadata can be private. type PortablePromptLedger struct { SchemaVersion int `json:"schema_version"` GeneratedAt time.Time `json:"generated_at"` @@ -52,9 +51,8 @@ type suppressedPromptManifest struct { PromptIDs []string `json:"prompt_ids"` } -// ExportPromptLedger writes immutable prompt files beneath -// .hop/records/prompts. Passing attemptIDs restricts the export to those -// attempts, which keeps concurrent proposals from writing the same files. +// ExportPromptLedger writes local prompt files beneath .hop/records/prompts. +// Passing attemptIDs restricts the export to those attempts. func (s *Service) ExportPromptLedger(ctx context.Context, destinationRoot string, attemptIDs ...string) (PortablePromptLedger, error) { return s.exportPromptLedger(ctx, destinationRoot, promptExportOptions{AttemptIDs: attemptIDs, PublishableOnly: true}) } @@ -199,7 +197,7 @@ func (s *Service) exportPromptLedger(ctx context.Context, destinationRoot string } if err := os.Rename(temporaryPath, outputPath); err != nil { _ = os.Remove(temporaryPath) - return PortablePromptLedger{}, fmt.Errorf("publish prompt record %s: %w", record.ID, err) + return PortablePromptLedger{}, fmt.Errorf("write prompt record %s: %w", record.ID, err) } } return ledger, nil diff --git a/internal/hop/service.go b/internal/hop/service.go index 221e96a..3ff3c78 100644 --- a/internal/hop/service.go +++ b/internal/hop/service.go @@ -34,9 +34,7 @@ func InitProject(ctx context.Context, path string) (*Service, State, error) { return nil, State{}, err } for _, trackedPath := range trackedHopPaths { - if !strings.HasPrefix(filepath.ToSlash(trackedPath), ".hop/records/") { - return nil, State{}, fmt.Errorf("cannot initialize Hop: local .hop runtime path is already tracked (for example %s)", trackedPath) - } + return nil, State{}, fmt.Errorf("cannot initialize Hop: private .hop path is already tracked (for example %s)", trackedPath) } hopDir := filepath.Join(root, ".hop") if err := os.MkdirAll(filepath.Join(hopDir, "workspaces"), 0o755); err != nil { @@ -600,11 +598,6 @@ func (s *Service) Propose(ctx context.Context, stateID, summary string) (Proposa return ProposalResult{}, fmt.Errorf("reconciliation must pass hop check on the resolved tree before proposing") } } - if _, err := s.exportPromptLedger(ctx, attempt.Workspace, promptExportOptions{ - AttemptIDs: []string{attempt.ID}, Status: "proposed", ResponseSummary: summary, - }); err != nil { - return ProposalResult{}, err - } commit, tree, err := workspaceRepo.Snapshot(ctx, "hop: proposal\n") if err != nil { return ProposalResult{}, err diff --git a/internal/hop/service_smoke_test.go b/internal/hop/service_smoke_test.go index c77448a..0dbe016 100644 --- a/internal/hop/service_smoke_test.go +++ b/internal/hop/service_smoke_test.go @@ -73,7 +73,7 @@ func TestInitRefusesAUserTrackedHopDirectory(t *testing.T) { writeTestFile(t, filepath.Join(root, ".hop", "user-owned.txt"), "do not overwrite\n") runGitTest(t, root, "add", "-f", ".hop/user-owned.txt") _, _, err := InitProject(context.Background(), root) - if err == nil || !strings.Contains(err.Error(), "local .hop runtime path is already tracked") { + if err == nil || !strings.Contains(err.Error(), "private .hop path is already tracked") { t.Fatalf("InitProject error = %v, want tracked local-runtime refusal", err) } contents, readErr := os.ReadFile(filepath.Join(root, ".hop", "user-owned.txt")) @@ -82,24 +82,15 @@ func TestInitRefusesAUserTrackedHopDirectory(t *testing.T) { } } -func TestInitAllowsTrackedPortableHopRecords(t *testing.T) { +func TestInitRefusesTrackedPromptRecords(t *testing.T) { root := t.TempDir() runGitTest(t, root, "init", "--quiet") writeTestFile(t, filepath.Join(root, ".hop", "records", "prompts.json"), `{"schema_version":1,"prompts":[]}`+"\n") runGitTest(t, root, "add", "-f", ".hop/records/prompts.json") - service, _, err := InitProject(context.Background(), root) - if err != nil { - t.Fatalf("InitProject error = %v", err) - } - t.Cleanup(func() { _ = service.Close() }) - - exclude, err := os.ReadFile(filepath.Join(root, ".git", "info", "exclude")) - if err != nil { - t.Fatal(err) - } - if strings.Contains(string(exclude), "\n.hop/\n") || !strings.Contains(string(exclude), "!.hop/records/**") { - t.Fatalf("portable ledger is not allowed by exclude file:\n%s", exclude) + _, _, err := InitProject(context.Background(), root) + if err == nil || !strings.Contains(err.Error(), "private .hop path is already tracked") { + t.Fatalf("InitProject error = %v, want tracked private-path refusal", err) } } @@ -228,20 +219,23 @@ func TestConcurrentInitInExistingGitRepository(t *testing.T) { if err != nil { t.Fatal(err) } - if count := strings.Count(string(exclude), ".hop/*\n"); count != 1 { - t.Fatalf("concurrent initialization wrote .hop/* exclusion %d times", count) + if count := strings.Count(string(exclude), ".hop/\n"); count != 1 { + t.Fatalf("concurrent initialization wrote .hop/ exclusion %d times", count) + } + if strings.Contains(string(exclude), "!.hop/records") { + t.Fatalf("concurrent initialization exposed prompt records:\n%s", exclude) } } -func TestProposeExportsPortablePromptLedger(t *testing.T) { +func TestProposeDoesNotPublishPromptLedger(t *testing.T) { ctx := context.Background() service, _ := newTestProject(t, map[string]string{"base.txt": "base\n"}) - result, err := service.CreatePrompt(ctx, "Publish a portable prompt ledger", "", "codex") + result, err := service.CreatePrompt(ctx, "Keep this prompt private", "", "codex") if err != nil { t.Fatal(err) } writeTestFile(t, filepath.Join(result.Workspace, "feature.txt"), "done\n") - proposal, err := service.Propose(ctx, result.Prompt.ID, "Publish the ledger") + proposal, err := service.Propose(ctx, result.Prompt.ID, "Land without publishing the prompt") if err != nil { t.Fatal(err) } @@ -251,16 +245,8 @@ func TestProposeExportsPortablePromptLedger(t *testing.T) { t.Fatal(err) } t.Cleanup(func() { _ = service.Repo.RemoveWorktree(ctx, materialized, true) }) - contents, err := os.ReadFile(filepath.Join(materialized, ".hop", "records", "prompts", result.Prompt.ID+".json")) - if err != nil { - t.Fatal(err) - } - var record PortablePromptRecord - if err := json.Unmarshal(contents, &record); err != nil { - t.Fatal(err) - } - if record.ID != result.Prompt.ID || record.Prompt != "Publish a portable prompt ledger" || record.Status != "proposed" || record.ResponseSummary != "Publish the ledger" { - t.Fatalf("unexpected portable prompt record: %#v", record) + if _, err := os.Stat(filepath.Join(materialized, ".hop")); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("proposal published private .hop content: %v", err) } } diff --git a/wiki/CLI-Reference.md b/wiki/CLI-Reference.md index de15b84..520da81 100644 --- a/wiki/CLI-Reference.md +++ b/wiki/CLI-Reference.md @@ -34,7 +34,7 @@ release. |---|---| | `hop accept PROPOSAL [-- COMMAND...]` | Accept internally without changing visible files | | `hop sync` | Materialize the current accepted tree from a safe accepted ancestor | -| `hop export [--output PATH]` | Write immutable, repository-safe prompt records to `.hop/records/prompts/` | +| `hop export [--output PATH]` | Write a private local prompt export to ignored `.hop/records/prompts/` | | `hop push` | Retry publishing the current accepted commit to its inferred upstream | | `hop undo` | Create a forward-only acceptance that restores the previous accepted tree | | `hop doctor [--repair]` | Validate database/object/ref consistency |