Skip to content

Commit 6d7ade1

Browse files
author
StackMemory Bot (CLI)
committed
fix(conductor): address code review findings
- Fix symphony-context.md -> conductor-context.md filename mismatch (functional bug) - Remove double JSON.parse in capture command (use tracked counts) - Rename OrchestratorStats -> ConductorStats - Pre-compute lowercased state arrays in constructor (avoid per-poll allocation) - Consolidate redundant overlap filtering (3x -> 1x) in preflight - Replace dynamic import('child_process') with static import - Delete deprecated linear-run.md skill (replaced by conductor)
1 parent a08edfd commit 6d7ade1

File tree

11 files changed

+1728
-83
lines changed

11 files changed

+1728
-83
lines changed

.claude/skills/linear-run.md

Lines changed: 0 additions & 63 deletions
This file was deleted.

scripts/git-hooks/post-commit-stackmemory.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,30 @@ record_commit_metrics() {
268268
return 0
269269
}
270270

271+
# Auto-capture context after commit
272+
capture_context() {
273+
if [ "$STACKMEMORY_ENABLED" != "true" ]; then
274+
return 0
275+
fi
276+
277+
local commit_info="$1"
278+
IFS='|' read -r commit_hash commit_msg commit_author branch files_changed <<< "$commit_info"
279+
280+
# Only capture on significant commits (3+ files or feature/fix commits)
281+
if [ "$files_changed" -lt 3 ] && ! echo "$commit_msg" | grep -iE "(feat|fix|refactor|complete|done)" >/dev/null; then
282+
return 0
283+
fi
284+
285+
log_info "Capturing context for session continuity..."
286+
if stackmemory snapshot save --task "$commit_msg" >/dev/null 2>&1; then
287+
log_success "Context captured"
288+
else
289+
log_warning "Context capture failed (non-critical)"
290+
fi
291+
292+
return 0
293+
}
294+
271295
# Main execution
272296
main() {
273297
log_info "📝 StackMemory post-commit hook starting..."
@@ -297,6 +321,9 @@ main() {
297321
record_commit_metrics "$commit_info"
298322
sync_with_linear "$commit_info"
299323

324+
# Auto-capture context for session continuity
325+
capture_context "$commit_info"
326+
300327
log_success "🎉 Post-commit processing completed!"
301328
return 0
302329
}

src/cli/commands/capture.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Snapshot CLI command.
3+
* Takes a point-in-time snapshot of what changed after a task completes.
4+
*/
5+
6+
import { Command } from 'commander';
7+
import chalk from 'chalk';
8+
import { ContextCapture } from '../../core/worktree/capture.js';
9+
10+
export function createSnapshotCommand(): Command {
11+
const cmd = new Command('snapshot')
12+
.alias('snap')
13+
.description('Point-in-time snapshot of work (what changed and why)');
14+
15+
// Capture current state
16+
cmd
17+
.command('save')
18+
.alias('s')
19+
.description('Save a snapshot of current branch state')
20+
.option('-t, --task <name>', 'Task name or description')
21+
.option(
22+
'-b, --base <branch>',
23+
'Base branch to diff against (default: auto-detect)'
24+
)
25+
.option(
26+
'-d, --decision <decisions...>',
27+
'Key decisions made during this task'
28+
)
29+
.option('--json', 'Output as JSON')
30+
.action((options) => {
31+
const capture = new ContextCapture();
32+
33+
const result = capture.capture({
34+
task: options.task,
35+
baseBranch: options.base,
36+
decisions: options.decision,
37+
});
38+
39+
if (options.json) {
40+
console.log(JSON.stringify(result, null, 2));
41+
return;
42+
}
43+
44+
console.log(chalk.green('\nSnapshot saved.\n'));
45+
console.log(chalk.gray(` Branch: ${result.branch}`));
46+
console.log(chalk.gray(` Base: ${result.baseBranch}`));
47+
console.log(chalk.gray(` Changed: ${result.filesChanged.length} files`));
48+
console.log(chalk.gray(` Created: ${result.filesCreated.length} files`));
49+
console.log(chalk.gray(` Deleted: ${result.filesDeleted.length} files`));
50+
console.log(chalk.gray(` Commits: ${result.commits.length}`));
51+
52+
if (result.decisions.length > 0) {
53+
console.log(chalk.cyan('\n Decisions:'));
54+
result.decisions.forEach((d) => console.log(chalk.gray(` - ${d}`)));
55+
}
56+
57+
if (result.duration) {
58+
console.log(chalk.gray(` Duration: ${result.duration}`));
59+
}
60+
61+
console.log(chalk.gray(`\n Saved: ${result.id}`));
62+
});
63+
64+
// List captures
65+
cmd
66+
.command('list')
67+
.alias('ls')
68+
.description('List recent snapshots')
69+
.option('-n, --limit <n>', 'Number of captures to show', '10')
70+
.option('--json', 'Output as JSON')
71+
.action((options) => {
72+
const capture = new ContextCapture();
73+
const captures = capture.list(parseInt(options.limit));
74+
75+
if (captures.length === 0) {
76+
console.log(chalk.yellow('No snapshots found.'));
77+
return;
78+
}
79+
80+
if (options.json) {
81+
console.log(JSON.stringify(captures, null, 2));
82+
return;
83+
}
84+
85+
console.log(chalk.cyan(`\nRecent Snapshots (${captures.length}):\n`));
86+
87+
for (const cap of captures) {
88+
const date = new Date(cap.timestamp).toLocaleDateString();
89+
const files = cap.filesChanged.length + cap.filesCreated.length;
90+
console.log(
91+
chalk.gray(
92+
` ${date} ${cap.branch.padEnd(30)} ${files} files ${cap.commits.length} commits`
93+
)
94+
);
95+
}
96+
});
97+
98+
// Show a specific capture or latest
99+
cmd
100+
.command('show [branch]')
101+
.description('Show snapshot details (latest or by branch)')
102+
.option('--json', 'Output as JSON')
103+
.action((branch, options) => {
104+
const capturer = new ContextCapture();
105+
const result = capturer.getLatest(branch);
106+
107+
if (!result) {
108+
console.log(
109+
chalk.yellow(
110+
branch
111+
? `No snapshot found for branch: ${branch}`
112+
: 'No snapshots found.'
113+
)
114+
);
115+
return;
116+
}
117+
118+
if (options.json) {
119+
console.log(JSON.stringify(result, null, 2));
120+
return;
121+
}
122+
123+
console.log(capturer.format(result));
124+
});
125+
126+
return cmd;
127+
}

src/cli/commands/orchestrate.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import { Command } from 'commander';
13+
import { execSync } from 'child_process';
1314
import { existsSync, mkdirSync, writeFileSync } from 'fs';
1415
import { join } from 'path';
1516
import { homedir, tmpdir } from 'os';
@@ -78,6 +79,8 @@ export function createConductorCommands(): Command {
7879
let framesJson = '[]';
7980
let anchorsJson = '[]';
8081
let eventsJson = '[]';
82+
let frameCount = 0;
83+
let anchorCount = 0;
8184

8285
// Extract context from workspace database
8386
if (existsSync(dbPath)) {
@@ -90,6 +93,7 @@ export function createConductorCommands(): Command {
9093
'SELECT frame_id, name, type, digest_text, created_at FROM frames ORDER BY created_at DESC LIMIT 20'
9194
)
9295
.all();
96+
frameCount = frames.length;
9397
framesJson = JSON.stringify(frames);
9498

9599
// Get anchors (decisions, facts, constraints)
@@ -98,6 +102,7 @@ export function createConductorCommands(): Command {
98102
"SELECT anchor_id, type, text, priority FROM anchors WHERE type IN ('DECISION', 'FACT', 'CONSTRAINT', 'RISK') ORDER BY priority DESC LIMIT 30"
99103
)
100104
.all();
105+
anchorCount = anchors.length;
101106
anchorsJson = JSON.stringify(anchors);
102107

103108
// Get recent events
@@ -126,7 +131,6 @@ export function createConductorCommands(): Command {
126131
// Also capture git state if available
127132
let metadata: Record<string, any> = { workspace, attempt };
128133
try {
129-
const { execSync } = await import('child_process');
130134
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
131135
cwd: workspace,
132136
encoding: 'utf8',
@@ -165,8 +169,6 @@ export function createConductorCommands(): Command {
165169
);
166170
globalDb.close();
167171

168-
const frameCount = JSON.parse(framesJson).length;
169-
const anchorCount = JSON.parse(anchorsJson).length;
170172
console.log(
171173
`Captured ${frameCount} frames, ${anchorCount} anchors for ${issueId} (attempt ${attempt})`
172174
);

0 commit comments

Comments
 (0)