Skip to content

Commit 2bba656

Browse files
author
StackMemory Bot (CLI)
committed
feat(conductor): GitButler virtual branch mode for workspace management
1 parent 2ca09af commit 2bba656

8 files changed

Lines changed: 276 additions & 33 deletions

File tree

.husky/post-checkout

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/bin/sh
2+
# GITBUTLER_MANAGED_HOOK_V1
3+
# This hook auto-cleans GitButler hooks when you checkout away from gitbutler/workspace.
4+
5+
PREV_HEAD=$1
6+
NEW_HEAD=$2
7+
BRANCH_CHECKOUT=$3
8+
9+
# Only act on branch checkouts (not file checkouts)
10+
if [ "$BRANCH_CHECKOUT" != "1" ]; then
11+
# Run user's hook if it exists
12+
if [ -x "$(dirname "$0")/post-checkout-user" ]; then
13+
exec "$(dirname "$0")/post-checkout-user" "$@"
14+
fi
15+
exit 0
16+
fi
17+
18+
# Get the new branch name
19+
NEW_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
20+
21+
# If we just left gitbutler/workspace (and aren't coming back to it)
22+
PREV_BRANCH=$(git name-rev --name-only "$PREV_HEAD" 2>/dev/null | sed 's|^remotes/||')
23+
if echo "$PREV_BRANCH" | grep -q "gitbutler/workspace"; then
24+
if [ "$NEW_BRANCH" != "gitbutler/workspace" ]; then
25+
echo ""
26+
echo "NOTE: You have left GitButler's managed workspace branch."
27+
echo "Cleaning up GitButler hooks..."
28+
29+
HOOKS_DIR=$(dirname "$0")
30+
31+
# Restore pre-commit - but only if it's GitButler-managed
32+
if [ -f "$HOOKS_DIR/pre-commit-user" ]; then
33+
mv "$HOOKS_DIR/pre-commit-user" "$HOOKS_DIR/pre-commit"
34+
echo " Restored: pre-commit"
35+
elif [ -f "$HOOKS_DIR/pre-commit" ]; then
36+
# Only remove if it's GitButler-managed (has our signature)
37+
if grep -q "GITBUTLER_MANAGED_HOOK_V1" "$HOOKS_DIR/pre-commit"; then
38+
rm "$HOOKS_DIR/pre-commit"
39+
echo " Removed: pre-commit (GitButler managed)"
40+
else
41+
echo " Warning: pre-commit hook is not GitButler-managed, leaving it untouched"
42+
fi
43+
fi
44+
45+
# Run user's post-checkout if it exists, then clean up
46+
if [ -x "$HOOKS_DIR/post-checkout-user" ]; then
47+
"$HOOKS_DIR/post-checkout-user" "$@"
48+
mv "$HOOKS_DIR/post-checkout-user" "$HOOKS_DIR/post-checkout"
49+
echo " Restored: post-checkout"
50+
else
51+
# Only remove self if we're GitButler-managed (we should be, but check anyway)
52+
if grep -q "GITBUTLER_MANAGED_HOOK_V1" "$HOOKS_DIR/post-checkout"; then
53+
rm "$HOOKS_DIR/post-checkout"
54+
echo " Removed: post-checkout (GitButler managed)"
55+
else
56+
echo " Warning: post-checkout hook is not GitButler-managed, leaving it untouched"
57+
fi
58+
fi
59+
60+
echo ""
61+
echo "To return to GitButler mode, run: but setup"
62+
echo ""
63+
exit 0
64+
fi
65+
fi
66+
67+
# Run user's hook if it exists
68+
if [ -x "$(dirname "$0")/post-checkout-user" ]; then
69+
exec "$(dirname "$0")/post-checkout-user" "$@"
70+
fi
71+
72+
exit 0

.husky/pre-commit

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,43 @@
1-
# Use Node version from .nvmrc
2-
export NVM_DIR="$HOME/.nvm"
3-
if [ -s "$NVM_DIR/nvm.sh" ]; then
4-
. "$NVM_DIR/nvm.sh"
5-
nvm use 2>/dev/null
6-
elif [ -d "$HOME/.nvm/versions/node" ]; then
7-
NODE_VER=$(cat "$(git rev-parse --show-toplevel)/.nvmrc" 2>/dev/null || echo "20")
8-
NODE_PATH=$(ls -d "$HOME/.nvm/versions/node/v${NODE_VER}"* 2>/dev/null | head -1)
9-
[ -n "$NODE_PATH" ] && export PATH="$NODE_PATH/bin:$PATH"
1+
#!/bin/sh
2+
# GITBUTLER_MANAGED_HOOK_V1
3+
# This hook is managed by GitButler to prevent accidental commits on the workspace branch.
4+
# Your original pre-commit hook has been preserved as 'pre-commit-user'.
5+
6+
HOOKS_DIR=$(dirname "$0")
7+
8+
# Run user's hook first if it exists - if it fails, stop here
9+
if [ -x "$HOOKS_DIR/pre-commit-user" ]; then
10+
"$HOOKS_DIR/pre-commit-user" "$@" || exit $?
11+
fi
12+
13+
# Get the current branch name
14+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
15+
16+
if [ "$BRANCH" = "gitbutler/workspace" ]; then
17+
echo ""
18+
echo "GITBUTLER_ERROR: Cannot commit directly to gitbutler/workspace branch."
19+
echo ""
20+
echo "GitButler manages commits on this branch. Please use GitButler to commit your changes:"
21+
echo " - Use the GitButler app to create commits"
22+
echo " - Or run 'but commit' from the command line"
23+
echo ""
24+
echo "If you want to exit GitButler mode and use normal git:"
25+
echo " - Run 'but teardown' to switch to a regular branch"
26+
echo " - Or directly checkout another branch: git checkout <branch>"
27+
echo ""
28+
echo "If you no longer have the GitButler CLI installed, you can simply remove this hook and checkout another branch:"
29+
printf ' rm "%s/pre-commit"\n' "$HOOKS_DIR"
30+
echo ""
31+
exit 1
32+
fi
33+
34+
# Not on workspace branch - run user's original hook if it exists
35+
if [ -x "$HOOKS_DIR/pre-commit-user" ]; then
36+
echo ""
37+
echo "WARNING: GitButler's pre-commit hook is still installed but you're not on gitbutler/workspace."
38+
echo "If you're no longer using GitButler, you can restore your original hook:"
39+
printf ' mv "%s/pre-commit-user" "%s/pre-commit"\n' "$HOOKS_DIR" "$HOOKS_DIR"
40+
echo ""
1041
fi
1142

12-
npx lint-staged
13-
npm run build
43+
exit 0

.husky/pre-commit-user

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Use Node version from .nvmrc
2+
export NVM_DIR="$HOME/.nvm"
3+
if [ -s "$NVM_DIR/nvm.sh" ]; then
4+
. "$NVM_DIR/nvm.sh"
5+
nvm use 2>/dev/null
6+
elif [ -d "$HOME/.nvm/versions/node" ]; then
7+
NODE_VER=$(cat "$(git rev-parse --show-toplevel)/.nvmrc" 2>/dev/null || echo "20")
8+
NODE_PATH=$(ls -d "$HOME/.nvm/versions/node/v${NODE_VER}"* 2>/dev/null | head -1)
9+
[ -n "$NODE_PATH" ] && export PATH="$NODE_PATH/bin:$PATH"
10+
fi
11+
12+
npx lint-staged
13+
npm run build

scripts/dspy/optimized_state.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"token_budget": 4096,
1111
"session_summary": "Frames: 50, recent activity in project",
1212
"available_frames": "- c05e94b2-3400-438b-bda1-d040c41a4d06: \"StackMemory v0.3.0 Development\" (task, score: 0.50, events: 0)\n- 722d8b90-29d2-462d-8077-a1ce0920db58: \"Test tool call storage\" (task, score: 0.50, events: 0)\n- 5fc00ed8-96a9-430f-a05e-ab83e2a411ac: \"working-on-cli\" (task, score: 0.50, events: 0)\n- 67cb9f1b-a458-4d57-82c0-c29308e00e87: \"cli-session\" (task, score: 0.50, events: 0)\n- 278f2eab-6bd7-4693-b9dc-7d583acadb8c: \"test-frame\" (task, score: 0.50, events: 0)\n- 86001f96-262a-4dd0-9320-f1545bf07b37: \"frame-1\" (task, score: 0.50, events: 0)\n- 88129690-84d0-48f5-825d-29e1f1deae86: \"frame-2\" (task, score: 0.50, events: 0)\n- 4ec15099-8217-453c-97bb-78f9806295e0: \"frame-3\" (task, score: 0.50, events: 0)\n- 67268788-f362-45f9-a68b-31801196e56b: \"test-stack-trace-capture\" (task, score: 0.50, events: 0)\n- 0f48dc4c-3a4b-4a69-ba23-95ac3be8d6e4: \"test-stack-trace-capture\" (task, score: 0.50, events: 0)\n- bd55adfc-6dc6-4dfb-a726-fda040796486: \"cli-session\" (task, score: 0.50, events: 0)\n- 7179084c-e4cc-4fda-82e1-47165c3629fb: \"cli-session\" (task, score: 0.50, events: 0)\n- 2e6692f7-3fc0-4777-8578-b1674658bbdc: \"team_share\" (tool_scope, score: 0.50, events: 0)\n- b41fbc67-c9f1-4fa6-b4f0-f2d523b93b2d: \"team_share\" (tool_scope, score: 0.50, events: 0)\n- d1eabbf4-fdab-4460-985c-5413f7307004: \"team_share\" (tool_scope, score: 0.50, events: 0)",
13-
"key_decisions": "- Exit code is 0. The remaining 4 warnings are in `skill-storage.ts`, which is not...\n- I now have a complete picture. Here is the implementation plan.\n\n---\n\n## Impleme...\n- Perfect! I have all the key files. Let me create a comprehensive report of the s...\n- Perfect. Now I have all the information you need. Let me compile a comprehensive...\n- Now I have all the information I need. Let me provide a comprehensive analysis:\n...",
13+
"key_decisions": "- The lint script only runs on `.ts` files, not `.js` files. The next.config.js er...\n- Task completed by agent: Add web clipper ingest pipeline. Watch a raw/ directory...\n- Task completed by agent: Build Obsidian vault adapter for frame serialization. W...\n- Task completed by agent: Wire Obsidian adapter into config + CLI. Add obsidianVa...\n- Task completed by agent: Test board end-to-end. Launch board, create session, se...",
1414
"reasoning": "Frame 'StackMemory v0.3.0 Development' directly matches the query topic.",
1515
"frames_to_retrieve": "[{\"frameId\": \"c05e94b2-3400-438b-bda1-d040c41a4d06\", \"priority\": 9, \"reason\": \"Direct match\", \"includeEvents\": true, \"includeAnchors\": true}]",
1616
"confidence_score": 0.9
@@ -20,7 +20,7 @@
2020
"token_budget": 4096,
2121
"session_summary": "Frames: 50, recent activity in project",
2222
"available_frames": "- c05e94b2-3400-438b-bda1-d040c41a4d06: \"StackMemory v0.3.0 Development\" (task, score: 0.50, events: 0)\n- 722d8b90-29d2-462d-8077-a1ce0920db58: \"Test tool call storage\" (task, score: 0.50, events: 0)\n- 5fc00ed8-96a9-430f-a05e-ab83e2a411ac: \"working-on-cli\" (task, score: 0.50, events: 0)\n- 67cb9f1b-a458-4d57-82c0-c29308e00e87: \"cli-session\" (task, score: 0.50, events: 0)\n- 278f2eab-6bd7-4693-b9dc-7d583acadb8c: \"test-frame\" (task, score: 0.50, events: 0)\n- 86001f96-262a-4dd0-9320-f1545bf07b37: \"frame-1\" (task, score: 0.50, events: 0)\n- 88129690-84d0-48f5-825d-29e1f1deae86: \"frame-2\" (task, score: 0.50, events: 0)\n- 4ec15099-8217-453c-97bb-78f9806295e0: \"frame-3\" (task, score: 0.50, events: 0)\n- 67268788-f362-45f9-a68b-31801196e56b: \"test-stack-trace-capture\" (task, score: 0.50, events: 0)\n- 0f48dc4c-3a4b-4a69-ba23-95ac3be8d6e4: \"test-stack-trace-capture\" (task, score: 0.50, events: 0)\n- bd55adfc-6dc6-4dfb-a726-fda040796486: \"cli-session\" (task, score: 0.50, events: 0)\n- 7179084c-e4cc-4fda-82e1-47165c3629fb: \"cli-session\" (task, score: 0.50, events: 0)\n- 2e6692f7-3fc0-4777-8578-b1674658bbdc: \"team_share\" (tool_scope, score: 0.50, events: 0)\n- b41fbc67-c9f1-4fa6-b4f0-f2d523b93b2d: \"team_share\" (tool_scope, score: 0.50, events: 0)\n- d1eabbf4-fdab-4460-985c-5413f7307004: \"team_share\" (tool_scope, score: 0.50, events: 0)",
23-
"key_decisions": "- Exit code is 0. The remaining 4 warnings are in `skill-storage.ts`, which is not...\n- I now have a complete picture. Here is the implementation plan.\n\n---\n\n## Impleme...\n- Perfect! I have all the key files. Let me create a comprehensive report of the s...\n- Perfect. Now I have all the information you need. Let me compile a comprehensive...\n- Now I have all the information I need. Let me provide a comprehensive analysis:\n...",
23+
"key_decisions": "- The lint script only runs on `.ts` files, not `.js` files. The next.config.js er...\n- Task completed by agent: Add web clipper ingest pipeline. Watch a raw/ directory...\n- Task completed by agent: Build Obsidian vault adapter for frame serialization. W...\n- Task completed by agent: Wire Obsidian adapter into config + CLI. Add obsidianVa...\n- Task completed by agent: Test board end-to-end. Launch board, create session, se...",
2424
"reasoning": "Frame 'cli-session' directly matches the query topic.",
2525
"frames_to_retrieve": "[{\"frameId\": \"67cb9f1b-a458-4d57-82c0-c29308e00e87\", \"priority\": 9, \"reason\": \"Direct match\", \"includeEvents\": true, \"includeAnchors\": true}]",
2626
"confidence_score": 0.9

scripts/gepa/generations/gen-000/baseline.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ When adding or renaming GitHub Actions workflows that should be triggerable via
112112
| Workflow | Script path | Category |
113113
|---|---|---|
114114
| `weekly-start.yml` | `voyager/scripts/content-brief.mjs` + `voyager/scripts/content-audit.mjs` + `ops/fathom-social-content.mjs` + `ops/fathom-testimonial-scan.mjs` + `ops/perplexity-citation-audit.mjs` + `commit/profound-aeo-pulse.mjs` + `voyager/scripts/generate-blog-scaffold.mjs` + `ops/ahrefs-firehose-digest.mjs` + `ops/export-dripify.mjs` + `commit/prospect-discovery.mjs` + `ops/repush-clay-leads.mjs` + `ops/snitcher-outreach.mjs` | GHA cron (Mon) |
115-
| `weekly-end.yml` | `diag/fathom-demo-scorecard.mjs` + `commit/feedback/collect-*.mjs` | GHA cron (Fri) |
115+
| `weekly-end.yml` | `diag/fathom-demo-scorecard.mjs` + `commit/feedback/collect-*.mjs` + `commit/feedback/collect-ops-feedback.mjs` + `diag/weekly-retro.mjs` | GHA cron (Fri) |
116116
| `anneal-keywords.yml` | `commit/anneal-keywords.mjs` | GHA cron (Sun) |
117117
| `g2-review-monitor.yml` | `ops/g2-to-senja.mjs` | GHA cron (Daily) |
118118
| `testimonial-pipeline.yml` | `commit/testimonial-pipeline.mjs` | Manual |

scripts/gepa/generations/gen-001/baseline.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ When adding or renaming GitHub Actions workflows that should be triggerable via
112112
| Workflow | Script path | Category |
113113
|---|---|---|
114114
| `weekly-start.yml` | `voyager/scripts/content-brief.mjs` + `voyager/scripts/content-audit.mjs` + `ops/fathom-social-content.mjs` + `ops/fathom-testimonial-scan.mjs` + `ops/perplexity-citation-audit.mjs` + `commit/profound-aeo-pulse.mjs` + `voyager/scripts/generate-blog-scaffold.mjs` + `ops/ahrefs-firehose-digest.mjs` + `ops/export-dripify.mjs` + `commit/prospect-discovery.mjs` + `ops/repush-clay-leads.mjs` + `ops/snitcher-outreach.mjs` | GHA cron (Mon) |
115-
| `weekly-end.yml` | `diag/fathom-demo-scorecard.mjs` + `commit/feedback/collect-*.mjs` | GHA cron (Fri) |
115+
| `weekly-end.yml` | `diag/fathom-demo-scorecard.mjs` + `commit/feedback/collect-*.mjs` + `commit/feedback/collect-ops-feedback.mjs` + `diag/weekly-retro.mjs` | GHA cron (Fri) |
116116
| `anneal-keywords.yml` | `commit/anneal-keywords.mjs` | GHA cron (Sun) |
117117
| `g2-review-monitor.yml` | `ops/g2-to-senja.mjs` | GHA cron (Daily) |
118118
| `testimonial-pipeline.yml` | `commit/testimonial-pipeline.mjs` | Manual |

src/cli/commands/orchestrate.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,11 @@ export function createConductorCommands(): Command {
20452045
'--no-pr',
20462046
'Disable automatic GitHub PR creation after agent success'
20472047
)
2048+
.option(
2049+
'--workspace-mode <mode>',
2050+
'Workspace mode: "auto" (detect GitButler), "gitbutler", or "worktree"',
2051+
'auto'
2052+
)
20482053
.action(async (options) => {
20492054
// Ensure default prompt template exists on first start
20502055
ensureDefaultPromptTemplate();
@@ -2065,6 +2070,7 @@ export function createConductorCommands(): Command {
20652070
agentMode: options.mode === 'adapter' ? 'adapter' : 'cli',
20662071
model: options.model,
20672072
autoPR: options.pr,
2073+
workspaceMode: options.workspaceMode,
20682074
});
20692075

20702076
await conductor.start();

0 commit comments

Comments
 (0)