@goodandready/dsh-goal 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1484,6 +1484,102 @@ window.__ModuleLoader__.load({
1484
1484
 
1485
1485
  }
1486
1486
 
1487
+
1488
+ .dsh-goal-intervene-card {
1489
+ padding: 12px;
1490
+ border-radius: 8px;
1491
+ background: var(--dsw-alias-bg-layer-2);
1492
+ border: 1px solid var(--dsw-alias-state-warning-primary);
1493
+ margin-bottom: 12px;
1494
+ }
1495
+
1496
+ .dsh-goal-preplan-card {
1497
+ padding: 12px;
1498
+ border-radius: 8px;
1499
+ background: var(--dsw-alias-bg-layer-2);
1500
+ border: 1px solid var(--dsw-alias-state-info-primary, var(--dsw-alias-border-l2));
1501
+ margin-bottom: 12px;
1502
+ }
1503
+
1504
+ .dsh-goal-activity-feed {
1505
+ padding: 10px 12px;
1506
+ border-radius: 8px;
1507
+ background: var(--dsw-alias-bg-layer-2);
1508
+ border: 1px solid var(--dsw-alias-border-l2);
1509
+ margin-top: 12px;
1510
+ }
1511
+
1512
+ .dsh-goal-activity-list {
1513
+ max-height: 140px;
1514
+ overflow-y: auto;
1515
+ display: flex;
1516
+ flex-direction: column;
1517
+ gap: 6px;
1518
+ }
1519
+
1520
+ .dsh-goal-activity-row {
1521
+ display: flex;
1522
+ align-items: center;
1523
+ gap: 8px;
1524
+ font-size: 11px;
1525
+ color: var(--dsw-alias-label-secondary);
1526
+ }
1527
+
1528
+ .dsh-goal-activity-dot {
1529
+ width: 6px;
1530
+ height: 6px;
1531
+ border-radius: 50%;
1532
+ background: var(--dsw-alias-state-info-primary, var(--dsw-alias-primary));
1533
+ flex-shrink: 0;
1534
+ }
1535
+
1536
+ .dsh-goal-activity-time {
1537
+ font-size: 10px;
1538
+ color: var(--dsw-alias-label-tertiary);
1539
+ flex-shrink: 0;
1540
+ }
1541
+
1542
+ .dsh-goal-activity-text {
1543
+ overflow: hidden;
1544
+ text-overflow: ellipsis;
1545
+ white-space: nowrap;
1546
+ flex: 1;
1547
+ }
1548
+
1549
+ .dsh-goal-retrospective-card {
1550
+ padding: 12px;
1551
+ border-radius: 8px;
1552
+ background: var(--dsw-alias-bg-layer-2);
1553
+ border: 1px solid var(--dsw-alias-border-l2);
1554
+ margin-top: 12px;
1555
+ }
1556
+
1557
+ .dsh-goal-chip-btn {
1558
+ padding: 3px 8px;
1559
+ border-radius: 9999px;
1560
+ background: var(--dsw-alias-bg-layer-2);
1561
+ border: 1px solid var(--dsw-alias-border-l2);
1562
+ color: var(--dsw-alias-label-primary);
1563
+ font-size: 11px;
1564
+ cursor: pointer;
1565
+ transition: all 0.15s ease;
1566
+ }
1567
+
1568
+ .dsh-goal-chip-btn:hover {
1569
+ background: var(--dsw-alias-bg-hover);
1570
+ border-color: var(--dsw-alias-border-l1);
1571
+ }
1572
+
1573
+ .dsh-goal-lock-badge {
1574
+ font-size: 10px;
1575
+ padding: 1px 6px;
1576
+ border-radius: 4px;
1577
+ background: var(--dsw-alias-state-warning-bg, var(--dsw-alias-bg-layer-4));
1578
+ color: var(--dsw-alias-state-warning-primary);
1579
+ border: 1px solid var(--dsw-alias-state-warning-primary);
1580
+ margin-left: 6px;
1581
+ }
1582
+
1487
1583
  `;
1488
1584
 
1489
1585
  function injectStylesOnce() {
@@ -1504,27 +1600,48 @@ window.__ModuleLoader__.load({
1504
1600
 
1505
1601
  }
1506
1602
 
1507
- // Synthesize audio chimes via Web Audio API (Item 6)
1508
-
1509
- function playGoalChime(isSuccess = true) {
1603
+ // Synthesize audio chimes via Web Audio API (Item 6) & soundScheme (#92)
1510
1604
 
1605
+ function playGoalChime(isSuccess = true, scheme = 'default') {
1511
1606
  if (typeof window === 'undefined') return;
1512
-
1513
1607
  try {
1514
-
1515
1608
  const AudioCtx = window.AudioContext || window.webkitAudioContext;
1516
-
1517
1609
  if (!AudioCtx) return;
1518
-
1519
1610
  const actx = new AudioCtx();
1520
-
1521
1611
  if (actx.state === 'suspended') {
1522
-
1523
1612
  actx.resume().catch(() => {});
1524
-
1525
1613
  }
1526
-
1527
1614
  const now = actx.currentTime;
1615
+ if (scheme === 'retro') {
1616
+ const notes = isSuccess ? [330, 440, 554, 659] : [300, 220, 160];
1617
+ notes.forEach((freq, idx) => {
1618
+ const osc = actx.createOscillator();
1619
+ const gain = actx.createGain();
1620
+ osc.type = 'square';
1621
+ osc.frequency.setValueAtTime(freq, now + idx * 0.08);
1622
+ gain.gain.setValueAtTime(0.08, now + idx * 0.08);
1623
+ gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.08 + 0.1);
1624
+ osc.connect(gain);
1625
+ gain.connect(actx.destination);
1626
+ osc.start(now + idx * 0.08);
1627
+ osc.stop(now + idx * 0.08 + 0.12);
1628
+ });
1629
+ return;
1630
+ }
1631
+ if (scheme === 'minimal') {
1632
+ const osc = actx.createOscillator();
1633
+ const gain = actx.createGain();
1634
+ osc.type = 'triangle';
1635
+ osc.frequency.setValueAtTime(isSuccess ? 880 : 330, now);
1636
+ gain.gain.setValueAtTime(0.001, now);
1637
+ gain.gain.exponentialRampToValueAtTime(0.12, now + 0.02);
1638
+ gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.35);
1639
+ osc.connect(gain);
1640
+ gain.connect(actx.destination);
1641
+ osc.start(now);
1642
+ osc.stop(now + 0.4);
1643
+ return;
1644
+ }
1528
1645
 
1529
1646
  // Pentatonic chords: success ascending C5-E5-G5-C6; failure descending G4-Eb4-C4
1530
1647
 
@@ -1570,6 +1687,17 @@ window.__ModuleLoader__.load({
1570
1687
 
1571
1688
  }
1572
1689
 
1690
+ // Voice announcement via Web Speech API (#92)
1691
+ function speakGoalAnnouncement(text, enabled = false) {
1692
+ if (!enabled || typeof window === 'undefined' || !window.speechSynthesis) return;
1693
+ try {
1694
+ const utter = new SpeechSynthesisUtterance(text);
1695
+ utter.rate = 1.0;
1696
+ utter.pitch = 1.0;
1697
+ window.speechSynthesis.speak(utter);
1698
+ } catch (err) { /* defensive */ }
1699
+ }
1700
+
1573
1701
  // --- REPORT GENERATOR FOR GITHUB / GITEA PR COMMENT ---
1574
1702
 
1575
1703
  function generateGitHubPRComment(state) {
@@ -1742,6 +1870,10 @@ window.__ModuleLoader__.load({
1742
1870
 
1743
1871
  const [nudgeSent, setNudgeSent] = useState(false);
1744
1872
 
1873
+ const [interveneText, setInterveneText] = useState('');
1874
+
1875
+ const [interveneSent, setInterveneSent] = useState(false);
1876
+
1745
1877
  const totalTokens = state.tokensUsage?.totalTokens || 0;
1746
1878
 
1747
1879
  const promptTokens = state.tokensUsage?.promptTokens || 0;
@@ -1982,6 +2114,64 @@ window.__ModuleLoader__.load({
1982
2114
 
1983
2115
  : null,
1984
2116
 
2117
+ // Smart Pre-planning Draft Card (#85)
2118
+ state.preplan && state.preplan.active && Array.isArray(state.preplan.draftMilestones) && state.preplan.draftMilestones.length > 0
2119
+ ? React.createElement(
2120
+ 'div',
2121
+ { className: 'dsh-goal-preplan-card' },
2122
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 13, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 } },
2123
+ React.createElement('span', null, '📋'),
2124
+ React.createElement('span', null, t('preplanTitle') || 'Smart Pre-plan Ready')
2125
+ ),
2126
+ React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
2127
+ `Generated ${state.preplan.draftMilestones.length} draft milestone(s). Review and approve to start.`
2128
+ ),
2129
+ React.createElement('div', { style: { display: 'flex', gap: 8 } },
2130
+ React.createElement('button', {
2131
+ type: 'button',
2132
+ className: 'dsh-goal-btn dsh-goal-btn-primary',
2133
+ onClick: () => onAction('preplan_approve', { sessionId: state.sessionId, approvedMilestones: state.preplan.draftMilestones })
2134
+ }, t('preplanApprove') || 'Approve & Start Plan')
2135
+ )
2136
+ )
2137
+ : null,
2138
+
2139
+ // Pause & Intervene Card (#83)
2140
+ state.state === 'PAUSED'
2141
+ ? React.createElement(
2142
+ 'div',
2143
+ { className: 'dsh-goal-intervene-card' },
2144
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 13, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 } },
2145
+ React.createElement('span', null, '⏸️'),
2146
+ React.createElement('span', null, t('interveneTitle') || 'Pause & Intervene')
2147
+ ),
2148
+ React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginBottom: 8 } },
2149
+ t('interveneDesc') || 'Goal is paused. Steer agent behavior with direct guidance before resuming.'
2150
+ ),
2151
+ React.createElement('textarea', {
2152
+ className: 'dsh-goal-card-input',
2153
+ style: { width: '100%', minHeight: 60, fontSize: 12, resize: 'vertical', marginBottom: 8 },
2154
+ placeholder: t('interveneDirectivePlaceholder') || 'Enter directive instructions for the agent...',
2155
+ value: interveneText,
2156
+ onChange: (e) => setInterveneText(e.target.value),
2157
+ }),
2158
+ React.createElement('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end' } },
2159
+ React.createElement('button', {
2160
+ type: 'button',
2161
+ className: 'dsh-goal-btn dsh-goal-btn-primary',
2162
+ disabled: !interveneText.trim() && !interveneSent,
2163
+ onClick: () => {
2164
+ if (interveneText.trim()) {
2165
+ onAction('intervene', { directive: interveneText.trim() });
2166
+ setInterveneSent(true);
2167
+ setTimeout(() => setInterveneSent(false), 2000);
2168
+ }
2169
+ }
2170
+ }, interveneSent ? (t('interveneApplied') || '✅ Applied') : (t('interveneApply') || 'Apply Directive & Resume'))
2171
+ )
2172
+ )
2173
+ : null,
2174
+
1985
2175
  React.createElement(
1986
2176
 
1987
2177
  'div',
@@ -2097,6 +2287,12 @@ window.__ModuleLoader__.load({
2097
2287
  },
2098
2288
 
2099
2289
  `${i + 1}. ${m.title}`,
2290
+ Array.isArray(m.dependsOn) && m.dependsOn.some((depId) => {
2291
+ const prereq = milestones.find((x) => x.id === depId || x.title === depId);
2292
+ return prereq && prereq.status !== 'completed';
2293
+ })
2294
+ ? React.createElement('span', { className: 'dsh-goal-lock-badge' }, `🔒 ${t('waitingPrereq') || 'waiting for prereq'}`)
2295
+ : null,
2100
2296
 
2101
2297
  ),
2102
2298
 
@@ -2155,6 +2351,96 @@ window.__ModuleLoader__.load({
2155
2351
 
2156
2352
  ),
2157
2353
 
2354
+ // Git Branch Action Controls (#86)
2355
+ state.gitBranch
2356
+ ? React.createElement(
2357
+ 'div',
2358
+ { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, padding: '8px 10px', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l2)', marginTop: 8 } },
2359
+ React.createElement('span', { className: 'dsh-goal-git-badge', style: { margin: 0 } }, `🌿 ${state.gitBranch}`),
2360
+ (isCompleted || state.state === 'CANCELLED')
2361
+ ? React.createElement(
2362
+ 'div',
2363
+ { style: { display: 'flex', gap: 6 } },
2364
+ React.createElement('button', {
2365
+ type: 'button',
2366
+ className: 'dsh-goal-btn',
2367
+ style: { fontSize: 11, padding: '2px 8px' },
2368
+ onClick: () => onAction('branch_action', { action: 'merge', sessionId: state.sessionId })
2369
+ }, t('mergeBranch') || 'Merge Branch'),
2370
+ React.createElement('button', {
2371
+ type: 'button',
2372
+ className: 'dsh-goal-btn',
2373
+ style: { fontSize: 11, padding: '2px 8px' },
2374
+ onClick: () => onAction('branch_action', { action: 'discard', sessionId: state.sessionId })
2375
+ }, t('discardBranch') || 'Discard Branch')
2376
+ )
2377
+ : null
2378
+ )
2379
+ : null,
2380
+
2381
+ // Live Activity Mini-Feed (#84)
2382
+ state.enableLiveActivityFeed !== false && Array.isArray(state.activityFeed) && state.activityFeed.length > 0
2383
+ ? React.createElement(
2384
+ 'div',
2385
+ { className: 'dsh-goal-activity-feed' },
2386
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 12, marginBottom: 6, color: 'var(--dsw-alias-label-primary)', display: 'flex', alignItems: 'center', gap: 6 } },
2387
+ React.createElement('span', null, '⚡'),
2388
+ React.createElement('span', null, t('liveActivityTitle') || 'Live Activity Feed'),
2389
+ React.createElement('span', { style: { fontSize: 10, color: 'var(--dsw-alias-label-tertiary)', fontWeight: 400 } }, `(${state.activityFeed.length})`)
2390
+ ),
2391
+ React.createElement(
2392
+ 'div',
2393
+ { className: 'dsh-goal-activity-list' },
2394
+ state.activityFeed.slice(-6).reverse().map((act, actIdx) =>
2395
+ React.createElement('div', { key: act.id || actIdx, className: 'dsh-goal-activity-row' },
2396
+ React.createElement('span', { className: 'dsh-goal-activity-dot' }),
2397
+ React.createElement('span', { className: 'dsh-goal-activity-text' }, act.details || act.tool || act.type)
2398
+ )
2399
+ )
2400
+ )
2401
+ )
2402
+ : null,
2403
+
2404
+ // Post-Goal Retrospective Card (#88)
2405
+ isCompleted && state.retrospective
2406
+ ? React.createElement(
2407
+ 'div',
2408
+ { className: 'dsh-goal-retrospective-card' },
2409
+ React.createElement('div', { style: { fontWeight: 600, fontSize: 13, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 6 } },
2410
+ React.createElement('span', null, '📊'),
2411
+ React.createElement('span', null, t('retrospectiveTitle') || 'Goal Retrospective')
2412
+ ),
2413
+ React.createElement(
2414
+ 'div',
2415
+ { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, fontSize: 11 } },
2416
+ React.createElement('div', null,
2417
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Duration'),
2418
+ React.createElement('div', { style: { fontWeight: 600 } }, state.retrospective.formattedDuration || state.formattedElapsed || '0s')
2419
+ ),
2420
+ React.createElement('div', null,
2421
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Tokens'),
2422
+ React.createElement('div', { style: { fontWeight: 600 } }, state.retrospective.tokensTotal ? `${state.retrospective.tokensTotal}` : '—')
2423
+ ),
2424
+ React.createElement('div', null,
2425
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Files Modified'),
2426
+ React.createElement('div', { style: { fontWeight: 600 } }, `${state.retrospective.filesModifiedCount || 0}`)
2427
+ ),
2428
+ React.createElement('div', null,
2429
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Tool Calls'),
2430
+ React.createElement('div', { style: { fontWeight: 600 } }, `${state.retrospective.toolCallsCount || 0}`)
2431
+ ),
2432
+ React.createElement('div', null,
2433
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Stall Recoveries'),
2434
+ React.createElement('div', { style: { fontWeight: 600 } }, `${state.retrospective.stallsEncountered || 0}`)
2435
+ ),
2436
+ React.createElement('div', null,
2437
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-tertiary)' } }, 'Milestones'),
2438
+ React.createElement('div', { style: { fontWeight: 600 } }, `${state.retrospective.milestonesCompleted || 0}/${state.retrospective.milestonesTotal || 0}`)
2439
+ )
2440
+ )
2441
+ )
2442
+ : null,
2443
+
2158
2444
  ),
2159
2445
 
2160
2446
  React.createElement(
@@ -2475,6 +2761,29 @@ window.__ModuleLoader__.load({
2475
2761
 
2476
2762
  }),
2477
2763
 
2764
+ React.createElement(
2765
+ 'div',
2766
+ { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8 } },
2767
+ [
2768
+ { id: 'quickfix', label: '⚡ Quick Fix', template: 'Fix issue: ' },
2769
+ { id: 'feature', label: '✨ Feature', template: 'Implement feature: ' },
2770
+ { id: 'refactor', label: '♻️ Refactor', template: 'Refactor module: ' },
2771
+ { id: 'repro', label: '🐛 Bug Repro', template: 'Reproduce and resolve bug: ' },
2772
+ { id: 'audit', label: '🔍 Audit', template: 'Deep audit codebase: ' },
2773
+ { id: 'security', label: '🛡️ Security', template: 'Security hardening for: ' },
2774
+ ].map((tpl) =>
2775
+ React.createElement(
2776
+ 'button',
2777
+ {
2778
+ key: tpl.id,
2779
+ type: 'button',
2780
+ className: 'dsh-goal-chip-btn',
2781
+ onClick: () => setTitle(tpl.template),
2782
+ },
2783
+ tpl.label
2784
+ )
2785
+ )
2786
+ ),
2478
2787
  ),
2479
2788
 
2480
2789
  React.createElement(
@@ -2701,11 +3010,13 @@ window.__ModuleLoader__.load({
2701
3010
 
2702
3011
  if (nextState.state === 'COMPLETED') {
2703
3012
 
2704
- playGoalChime(true);
3013
+ playGoalChime(true, nextState.soundScheme || 'default');
3014
+ speakGoalAnnouncement('Goal completed: ' + (nextState.title || ''), Boolean(nextState.enableVoiceAnnouncements));
2705
3015
 
2706
3016
  } else if (nextState.state === 'FAILED') {
2707
3017
 
2708
- playGoalChime(false);
3018
+ playGoalChime(false, nextState.soundScheme || 'default');
3019
+ speakGoalAnnouncement('Goal failed: ' + (nextState.title || ''), Boolean(nextState.enableVoiceAnnouncements));
2709
3020
 
2710
3021
  }
2711
3022
 
@@ -4456,6 +4767,19 @@ window.__ModuleLoader__.load({
4456
4767
  rollbackBtn: 'Rollback to Checkpoint',
4457
4768
  rollbackConfirm: 'Rollback working tree to this checkpoint?',
4458
4769
  rollbackSuccess: 'Rolled back to checkpoint',
4770
+ interveneTitle: 'Pause & Intervene',
4771
+ interveneDesc: 'Goal is paused. Steer agent behavior with direct guidance before resuming.',
4772
+ interveneDirectivePlaceholder: 'Enter directive instructions for the agent...',
4773
+ interveneApply: 'Apply Directive & Resume',
4774
+ interveneApplied: 'Directive Applied',
4775
+ liveActivityTitle: 'Live Activity Feed',
4776
+ preplanTitle: 'Smart Pre-plan Ready',
4777
+ preplanApprove: 'Approve & Start Plan',
4778
+ mergeBranch: 'Merge Branch',
4779
+ discardBranch: 'Discard Branch',
4780
+ retrospectiveTitle: 'Goal Retrospective',
4781
+ templatesTitle: 'Goal Templates',
4782
+ waitingPrereq: 'waiting for prereq',
4459
4783
 
4460
4784
  goalCompleted: 'Goal completed',
4461
4785
 
@@ -4582,6 +4906,19 @@ window.__ModuleLoader__.load({
4582
4906
  rollbackBtn: '回滚到该检查点',
4583
4907
  rollbackConfirm: '确定将工作区代码回滚至该里程碑检查点吗?',
4584
4908
  rollbackSuccess: '已成功回滚到检查点',
4909
+ interveneTitle: '暂停并干预',
4910
+ interveneDesc: '目标已暂停。在恢复前向智能体发送直接指引。',
4911
+ interveneDirectivePlaceholder: '输入给智能体的指导指令...',
4912
+ interveneApply: '应用指引并继续',
4913
+ interveneApplied: '已应用干预',
4914
+ liveActivityTitle: '实时动态',
4915
+ preplanTitle: '智能预规划就绪',
4916
+ preplanApprove: '批准并启动计划',
4917
+ mergeBranch: '合并分支',
4918
+ discardBranch: '放弃分支',
4919
+ retrospectiveTitle: '目标复盘报告',
4920
+ templatesTitle: '目标模板',
4921
+ waitingPrereq: '等待前置依赖',
4585
4922
 
4586
4923
  goalCompleted: '目标已完成',
4587
4924
 
@@ -0,0 +1,105 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ /**
4
+ * Format a sanitized git branch name from goal title
5
+ * @param {string} title
6
+ * @returns {string} branch name
7
+ */
8
+ export function sanitizeBranchSlug(title) {
9
+ const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
10
+ const slug = String(title || 'task')
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, '-')
13
+ .replace(/^-+|-+$/g, '')
14
+ .slice(0, 30) || 'goal';
15
+ return `goal/${dateStr}-${slug}`;
16
+ }
17
+
18
+ /**
19
+ * Get current git branch
20
+ * @param {string} cwd
21
+ * @returns {string} branch name or 'HEAD'
22
+ */
23
+ export function getCurrentGitBranch(cwd = process.cwd()) {
24
+ try {
25
+ return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
26
+ cwd,
27
+ encoding: 'utf8',
28
+ stdio: ['ignore', 'pipe', 'ignore'],
29
+ }).trim();
30
+ } catch (err) {
31
+ return 'main';
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Create a dedicated git branch for a goal
37
+ * @param {string} cwd
38
+ * @param {string} branchName
39
+ * @returns {boolean} success
40
+ */
41
+ export function createGoalBranch(cwd = process.cwd(), branchName) {
42
+ if (!branchName) return false;
43
+ try {
44
+ execFileSync('git', ['checkout', '-b', branchName], {
45
+ cwd,
46
+ encoding: 'utf8',
47
+ stdio: ['ignore', 'pipe', 'pipe'],
48
+ });
49
+ return true;
50
+ } catch (err) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Merge goal branch back into parent target branch
57
+ * @param {string} cwd
58
+ * @param {string} branchName
59
+ * @param {string} targetBranch
60
+ * @returns {boolean} success
61
+ */
62
+ export function mergeGoalBranch(cwd = process.cwd(), branchName, targetBranch = 'main') {
63
+ if (!branchName) return false;
64
+ try {
65
+ execFileSync('git', ['checkout', targetBranch], {
66
+ cwd,
67
+ encoding: 'utf8',
68
+ stdio: ['ignore', 'pipe', 'pipe'],
69
+ });
70
+ execFileSync('git', ['merge', '--no-ff', '-m', `chore(goal): merge ${branchName}`, branchName], {
71
+ cwd,
72
+ encoding: 'utf8',
73
+ stdio: ['ignore', 'pipe', 'pipe'],
74
+ });
75
+ return true;
76
+ } catch (err) {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Discard / delete a goal branch
83
+ * @param {string} cwd
84
+ * @param {string} branchName
85
+ * @param {string} targetBranch
86
+ * @returns {boolean} success
87
+ */
88
+ export function discardGoalBranch(cwd = process.cwd(), branchName, targetBranch = 'main') {
89
+ if (!branchName) return false;
90
+ try {
91
+ execFileSync('git', ['checkout', targetBranch], {
92
+ cwd,
93
+ encoding: 'utf8',
94
+ stdio: ['ignore', 'pipe', 'pipe'],
95
+ });
96
+ execFileSync('git', ['branch', '-D', branchName], {
97
+ cwd,
98
+ encoding: 'utf8',
99
+ stdio: ['ignore', 'pipe', 'pipe'],
100
+ });
101
+ return true;
102
+ } catch (err) {
103
+ return false;
104
+ }
105
+ }