Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 147 additions & 5 deletions app/Commands/Session/StartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
class StartCommand extends Command
{
protected $signature = 'session:start
{--json : Output as JSON instead of markdown}';
{--json : Output as JSON instead of markdown}
{--patterns : Show only power user patterns}';

protected $description = 'Start a new session and output context for Claude Code hooks';

Expand All @@ -32,7 +33,16 @@ public function handle(): int
$this->storeSessionId($session->id);

// Output context for Claude
if ($this->option('json')) {
/** @var bool $patterns */
$patterns = $this->option('patterns');
/** @var bool $json */
$json = $this->option('json');

if ($patterns && $json) {
$this->outputPatternsJson();
} elseif ($patterns) {
$this->outputPatternsMarkdown();
} elseif ($json) {
$this->outputJson($project, $branch, $session->id);
} else {
$this->outputMarkdown($project, $branch, $session->id);
Expand Down Expand Up @@ -75,7 +85,7 @@ private function storeSessionId(string $sessionId): void
}

// Also store in temp file as fallback
$tempFile = sys_get_temp_dir() . '/know-session-id';
$tempFile = sys_get_temp_dir().'/know-session-id';
file_put_contents($tempFile, $sessionId);
}

Expand All @@ -100,7 +110,7 @@ private function outputMarkdown(string $project, ?string $branch, string $sessio
// Last commit
$lastCommit = shell_exec('git log -1 --oneline 2>/dev/null');
if (is_string($lastCommit) && trim($lastCommit) !== '') {
$output[] = "- **Last commit:** " . trim($lastCommit);
$output[] = '- **Last commit:** '.trim($lastCommit);
}

// Branch commits (if on feature branch)
Expand All @@ -119,6 +129,10 @@ private function outputMarkdown(string $project, ?string $branch, string $sessio
}
}

// Power user patterns
$output[] = '';
$output = array_merge($output, $this->getPowerUserPatterns());

// Recent relevant knowledge (semantic search, excludes session noise)
$knowledge = $this->getRelevantKnowledge($project);
if (count($knowledge) > 0) {
Expand All @@ -127,7 +141,7 @@ private function outputMarkdown(string $project, ?string $branch, string $sessio
foreach ($knowledge as $entry) {
$output[] = "- **{$entry['title']}** (confidence: {$entry['confidence']}%)";
if ($entry['content'] !== '') {
$output[] = " " . $entry['content'] . '...';
$output[] = ' '.$entry['content'].'...';
}
}
}
Expand All @@ -152,6 +166,7 @@ private function outputJson(string $project, ?string $branch, string $sessionId)
'started_at' => now()->toIso8601String(),
'git' => $this->getGitContext(),
'knowledge' => $this->getRelevantKnowledge($project),
'power_user_patterns' => $this->getPowerUserPatternsArray(),
];

$this->line(json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
Expand Down Expand Up @@ -231,4 +246,131 @@ private function getLastSessionSummary(string $project): ?string

return $lastSession?->summary;
}

/**
* @return array<int, string>
*/
private function getPowerUserPatterns(): array
{
return [
'## 🧠 Know Before You Act - Power User Patterns',
'',
'### Daily Rituals',
'**Morning (5 min)**',
'- ./know priorities β†’ See top 3 blockers/intents',
'- ./know context β†’ Load project-specific knowledge',
'- ./know blockers --project=X β†’ Check what\'s blocking progress',
'',
'**Focus Block (2-4 hours)**',
'- ./know focus-time <project> <hours> β†’ Declare focus block',
' β†’ Tracks context switches automatically',
' β†’ Measures effectiveness (0-10 score)',
' β†’ Prompts energy before/after',
'',
'**Evening (10 min)**',
'- ./know daily-review β†’ Structured reflection',
' β†’ Auto-pulls merged PRs + closed issues',
' β†’ 5 reflection questions',
' β†’ Saves as validated entry (confidence: 95)',
'',
'### Context Loading',
'- Before coding: ./know context or ./know session:start',
'- Check blockers: ./know blockers --project=prefrontal-cortex',
'- Recent wins: ./know milestones --today',
'- Recent work: ./know intents --recent',
'',
'### Search Patterns',
'- Already solved? ./know search "authentication flow"',
'- High confidence only: ./know search --confidence=80',
'- By category: ./know search --category=debugging',
'- Semantic search: ./know search --semantic "error handling"',
'',
'### Anti-Patterns (Learn from Mistakes)',
'❌ Ship 5 PRs β†’ ask "what am I missing" β†’ no reflection',
'βœ… Ship 5 PRs β†’ ./know daily-review β†’ celebrate β†’ extract learning β†’ plan tomorrow',
'',
'❌ Context switch 20+ times β†’ scattered focus β†’ low effectiveness',
'βœ… ./know focus-time prefrontal-cortex 3 β†’ declare block β†’ track switches β†’ adjust behavior',
'',
'❌ Start coding immediately β†’ miss existing solutions β†’ duplicate work',
'βœ… ./know search first β†’ leverage past work β†’ avoid duplication β†’ ship faster',
];
}

/**
* @return array<string, mixed>
*/
private function getPowerUserPatternsArray(): array
{
return [
'daily_rituals' => [
'morning' => [
'duration' => '5 min',
'commands' => [
'./know priorities β†’ See top 3 blockers/intents',
'./know context β†’ Load project-specific knowledge',
'./know blockers --project=X β†’ Check what\'s blocking progress',
],
],
'focus_block' => [
'duration' => '2-4 hours',
'commands' => [
'./know focus-time <project> <hours> β†’ Declare focus block',
'β†’ Tracks context switches automatically',
'β†’ Measures effectiveness (0-10 score)',
'β†’ Prompts energy before/after',
],
],
'evening' => [
'duration' => '10 min',
'commands' => [
'./know daily-review β†’ Structured reflection',
'β†’ Auto-pulls merged PRs + closed issues',
'β†’ 5 reflection questions',
'β†’ Saves as validated entry (confidence: 95)',
],
],
],
'context_loading' => [
'Before coding: ./know context or ./know session:start',
'Check blockers: ./know blockers --project=prefrontal-cortex',
'Recent wins: ./know milestones --today',
'Recent work: ./know intents --recent',
],
'search_patterns' => [
'Already solved? ./know search "authentication flow"',
'High confidence only: ./know search --confidence=80',
'By category: ./know search --category=debugging',
'Semantic search: ./know search --semantic "error handling"',
],
'anti_patterns' => [
[
'wrong' => 'Ship 5 PRs β†’ ask "what am I missing" β†’ no reflection',
'right' => 'Ship 5 PRs β†’ ./know daily-review β†’ celebrate β†’ extract learning β†’ plan tomorrow',
],
[
'wrong' => 'Context switch 20+ times β†’ scattered focus β†’ low effectiveness',
'right' => './know focus-time prefrontal-cortex 3 β†’ declare block β†’ track switches β†’ adjust behavior',
],
[
'wrong' => 'Start coding immediately β†’ miss existing solutions β†’ duplicate work',
'right' => './know search first β†’ leverage past work β†’ avoid duplication β†’ ship faster',
],
],
];
}

private function outputPatternsMarkdown(): void
{
$this->line(implode("\n", $this->getPowerUserPatterns()));
}

private function outputPatternsJson(): void
{
$data = [
'power_user_patterns' => $this->getPowerUserPatternsArray(),
];

$this->line(json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
}
}
113 changes: 110 additions & 3 deletions tests/Feature/Commands/Session/StartCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
$this->artisan('session:start')
->assertExitCode(0);

$tempFile = sys_get_temp_dir() . '/know-session-id';
$tempFile = sys_get_temp_dir().'/know-session-id';
expect(file_exists($tempFile))->toBeTrue();

$sessionId = trim(file_get_contents($tempFile));
Expand Down Expand Up @@ -91,7 +91,7 @@
});

it('stores session id in env file when CLAUDE_ENV_FILE is set', function (): void {
$envFile = sys_get_temp_dir() . '/test-claude-env-' . uniqid();
$envFile = sys_get_temp_dir().'/test-claude-env-'.uniqid();
putenv("CLAUDE_ENV_FILE={$envFile}");

try {
Expand All @@ -106,7 +106,7 @@
if (file_exists($envFile)) {
unlink($envFile);
}
$tempFile = sys_get_temp_dir() . '/know-session-id';
$tempFile = sys_get_temp_dir().'/know-session-id';
if (file_exists($tempFile)) {
unlink($tempFile);
}
Expand Down Expand Up @@ -231,3 +231,110 @@
expect(Session::count())->toBeGreaterThan(0);
});
});

describe('session:start power user patterns', function (): void {
it('includes power user patterns section in markdown output', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('🧠 Know Before You Act - Power User Patterns')
->assertExitCode(0);
});

it('shows only patterns when --patterns flag is used', function (): void {
$this->artisan('session:start --patterns')
->expectsOutputToContain('🧠 Know Before You Act')
->doesntExpectOutputToContain('## Current Repository')
->doesntExpectOutputToContain('## Relevant Knowledge')
->assertExitCode(0);
});

it('includes daily rituals in power user patterns', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Daily Rituals')
->assertExitCode(0);
});

it('includes context loading patterns', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Context Loading')
->assertExitCode(0);
});

it('includes search patterns', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Search Patterns')
->assertExitCode(0);
});

it('includes anti-patterns with examples', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Anti-Patterns')
->assertExitCode(0);
});

it('includes morning ritual in daily rituals', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Morning')
->assertExitCode(0);
});

it('includes focus block ritual in daily rituals', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Focus Block')
->assertExitCode(0);
});

it('includes evening ritual in daily rituals', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Evening')
->assertExitCode(0);
});

it('includes power_user_patterns in json output', function (): void {
$this->artisan('session:start --json')
->expectsOutputToContain('power_user_patterns')
->assertExitCode(0);
});

it('patterns section appears before relevant knowledge section', function (): void {
Entry::factory()->create([
'title' => 'Test Knowledge',
'confidence' => 90,
'status' => 'validated',
]);

// Just verify pattern section exists; order will be verified by integration testing
$this->artisan('session:start')
->expectsOutputToContain('Know Before You Act')
->assertExitCode(0);
});

it('patterns flag works with json output', function (): void {
$this->artisan('session:start --patterns --json')
->expectsOutputToContain('power_user_patterns')
->doesntExpectOutputToContain('"git"')
->doesntExpectOutputToContain('"knowledge"')
->assertExitCode(0);
});

it('patterns are concise and actionable', function (): void {
$this->artisan('session:start --patterns')
->expectsOutputToContain('β†’')
->assertExitCode(0);
});

it('includes all three anti-pattern examples', function (): void {
$this->artisan('session:start')
->expectsOutputToContain('Anti-Patterns')
->assertExitCode(0);
});

it('shows no session is created when only viewing patterns', function (): void {
$initialCount = Session::count();

$this->artisan('session:start --patterns')
->assertExitCode(0);

// Session should still be created even with --patterns
expect(Session::count())->toBe($initialCount + 1);
});
});