@haposoft/cafekit 0.3.12 → 0.4.0
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/README.md +83 -28
- package/bin/install.js +125 -1
- package/package.json +5 -3
- package/src/claude/hooks/agent.cjs +203 -0
- package/src/claude/hooks/lib/color.cjs +95 -0
- package/src/claude/hooks/lib/config.cjs +831 -0
- package/src/claude/hooks/lib/context.cjs +616 -0
- package/src/claude/hooks/lib/counter.cjs +103 -0
- package/src/claude/hooks/lib/detect.cjs +474 -0
- package/src/claude/hooks/lib/git.cjs +143 -0
- package/src/claude/hooks/lib/parser.cjs +182 -0
- package/src/claude/hooks/session.cjs +360 -0
- package/src/claude/hooks/usage.cjs +179 -0
- package/src/claude/migration-manifest.json +27 -2
- package/src/claude/settings/status.settings.json +54 -0
- package/src/claude/status.cjs +539 -0
- package/src/common/skills/code/SKILL.md +55 -0
- package/src/common/skills/code/references/execution-loop.md +21 -0
- package/src/common/skills/review/SKILL.md +46 -0
- package/src/common/skills/review/references/review-focus.md +28 -0
- package/src/common/skills/spec-design/SKILL.md +66 -0
- package/src/common/skills/spec-design/references/design-discovery.md +46 -0
- package/src/common/skills/spec-init/SKILL.md +61 -0
- package/src/common/skills/spec-requirements/SKILL.md +59 -0
- package/src/common/skills/spec-requirements/references/requirements-workflow.md +36 -0
- package/src/common/skills/spec-tasks/SKILL.md +60 -0
- package/src/common/skills/spec-tasks/references/task-sizing.md +36 -0
- package/src/common/skills/test/SKILL.md +40 -0
package/README.md
CHANGED
|
@@ -43,6 +43,44 @@ CafeKit is a **multi-platform** CLI tool that installs a structured workflow for
|
|
|
43
43
|
- **📦 No global install** - Use directly with `npx`
|
|
44
44
|
- **🚀 Future-proof** - Easy to add support for new AI editors
|
|
45
45
|
|
|
46
|
+
## Claude Code Statusline (Claude Code Only)
|
|
47
|
+
|
|
48
|
+
CafeKit automatically installs an enhanced statusline for Claude Code that provides real-time session context.
|
|
49
|
+
|
|
50
|
+
**What it shows:**
|
|
51
|
+
- **Context usage** - Percentage and token count (e.g., `23% 45K/200K`)
|
|
52
|
+
- **Session timer** - Elapsed time since session start
|
|
53
|
+
- **Git status** - Current branch and dirty state indicator
|
|
54
|
+
- **Active agents** - Count of running subagents
|
|
55
|
+
- **Todo items** - Count of pending tasks
|
|
56
|
+
|
|
57
|
+
**Installation:**
|
|
58
|
+
- Automatically installed when running `npx @haposoft/cafekit` in a Claude Code project
|
|
59
|
+
- Merges with existing `settings.json` configuration without overwriting user settings
|
|
60
|
+
- Safe to re-run - preserves non-CafeKit statusline configurations
|
|
61
|
+
- Upgrade mode (`--upgrade`) refreshes managed runtime files
|
|
62
|
+
|
|
63
|
+
**Configuration:**
|
|
64
|
+
The statusline is configured via `.claude/settings.json`:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"statusLine": {
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/status.cjs\"",
|
|
70
|
+
"padding": 0
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Runtime files installed:**
|
|
76
|
+
- `.claude/status.cjs` - Main statusline script
|
|
77
|
+
- `.claude/hooks/session.cjs` - Session initialization hook
|
|
78
|
+
- `.claude/hooks/agent.cjs` - Subagent context injection hook
|
|
79
|
+
- `.claude/hooks/usage.cjs` - Usage tracking hook
|
|
80
|
+
- `.claude/hooks/lib/*.cjs` - Shared utilities (color, parser, git, config, etc.)
|
|
81
|
+
|
|
82
|
+
**Note:** This feature is Claude Code exclusive and not available for Antigravity.
|
|
83
|
+
|
|
46
84
|
## Installation
|
|
47
85
|
|
|
48
86
|
### Prerequisites
|
|
@@ -63,7 +101,8 @@ The installer will:
|
|
|
63
101
|
2. **Prompt** you to select platform if not detected
|
|
64
102
|
3. **Copy** workflow commands to the appropriate directory
|
|
65
103
|
4. **Install** shared skills for spec-driven development
|
|
66
|
-
5. **
|
|
104
|
+
5. **[Claude Code only]** Install unprefixed skill directories (`spec-init`, `spec-requirements`, `spec-design`, `spec-tasks`, `code`, `test`, `review`) that expose `hapo:`-prefixed skill names
|
|
105
|
+
6. **Ensure dependencies** for `code - test - review` by installing missing command/agent templates
|
|
67
106
|
|
|
68
107
|
Installer modes:
|
|
69
108
|
- **Default install mode**: `npx @haposoft/cafekit` (skip existing files)
|
|
@@ -72,33 +111,40 @@ Installer modes:
|
|
|
72
111
|
|
|
73
112
|
**Example output (Claude Code):**
|
|
74
113
|
```
|
|
75
|
-
CafeKit
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
CafeKit Installer v0.3.12
|
|
115
|
+
========================================
|
|
116
|
+
|
|
117
|
+
Installing for: Claude Code
|
|
118
|
+
Mode: install (skip existing files)
|
|
119
|
+
|
|
120
|
+
Claude Code (.claude/)
|
|
121
|
+
----------------------------------------
|
|
122
|
+
✓ Skill installed: specs
|
|
123
|
+
✓ Skill installed: spec-init
|
|
124
|
+
✓ Skill installed: spec-requirements
|
|
125
|
+
✓ Skill installed: spec-design
|
|
126
|
+
✓ Skill installed: spec-tasks
|
|
127
|
+
✓ Skill installed: code
|
|
128
|
+
✓ Skill installed: test
|
|
129
|
+
✓ Skill installed: review
|
|
130
|
+
✓ Copied: spec-init.md
|
|
131
|
+
✓ Copied: spec-requirements.md
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
╔════════════════════════════════════════════════════════╗
|
|
135
|
+
║ Installation Complete! ║
|
|
136
|
+
╚════════════════════════════════════════════════════════╝
|
|
137
|
+
|
|
138
|
+
Installed Skills: Yes ✓
|
|
98
139
|
|
|
99
140
|
Next steps:
|
|
100
|
-
|
|
101
|
-
|
|
141
|
+
1. Start your AI editor
|
|
142
|
+
|
|
143
|
+
For Claude Code:
|
|
144
|
+
Run: /spec-init <feature-name>
|
|
145
|
+
Or use skill: /hapo:spec-init <feature-description>
|
|
146
|
+
|
|
147
|
+
2. Follow the workflow: requirements - design - tasks - code - test - review
|
|
102
148
|
|
|
103
149
|
Documentation: https://github.com/haposoft/cafekit
|
|
104
150
|
```
|
|
@@ -565,7 +611,15 @@ User clicks -> dispatch action -> update context -> localStorage -> re-render
|
|
|
565
611
|
│ ├── spec-status.md
|
|
566
612
|
│ └── docs.md # Docs workflows
|
|
567
613
|
└── skills/
|
|
568
|
-
|
|
614
|
+
├── specs/
|
|
615
|
+
├── impact-analysis/
|
|
616
|
+
├── spec-init/
|
|
617
|
+
├── spec-requirements/
|
|
618
|
+
├── spec-design/
|
|
619
|
+
├── spec-tasks/
|
|
620
|
+
├── code/
|
|
621
|
+
├── test/
|
|
622
|
+
└── review/
|
|
569
623
|
```
|
|
570
624
|
|
|
571
625
|
**Antigravity** (`.agent/`):
|
|
@@ -583,7 +637,8 @@ User clicks -> dispatch action -> update context -> localStorage -> re-render
|
|
|
583
637
|
│ ├── docs-init.md # Docs workflows
|
|
584
638
|
│ └── docs-update.md
|
|
585
639
|
├── skills/
|
|
586
|
-
│
|
|
640
|
+
│ ├── specs/
|
|
641
|
+
│ └── impact-analysis/
|
|
587
642
|
└── rules/
|
|
588
643
|
└── GEMINI.md # System rules (always_on)
|
|
589
644
|
```
|
package/bin/install.js
CHANGED
|
@@ -19,6 +19,13 @@ const path = require('path');
|
|
|
19
19
|
const readline = require('readline');
|
|
20
20
|
const packageJson = require('../package.json');
|
|
21
21
|
|
|
22
|
+
function validateManifestV2(manifest) {
|
|
23
|
+
if (!manifest || manifest.version !== 2) return false;
|
|
24
|
+
if (!manifest.runtime?.files || !Array.isArray(manifest.runtime.files)) return false;
|
|
25
|
+
if (!manifest.settings?.template) return false;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
function loadClaudeMigrationManifest() {
|
|
23
30
|
const manifestPath = path.join(__dirname, '../src/claude/migration-manifest.json');
|
|
24
31
|
|
|
@@ -27,7 +34,15 @@ function loadClaudeMigrationManifest() {
|
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
try {
|
|
30
|
-
|
|
37
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
38
|
+
|
|
39
|
+
if (!validateManifestV2(manifest)) {
|
|
40
|
+
console.error('✗ Invalid manifest v2 schema - missing required fields');
|
|
41
|
+
console.error(' Expected: version=2, runtime.files[], settings.template');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return manifest;
|
|
31
46
|
} catch (error) {
|
|
32
47
|
console.warn(`⚠ Failed to parse Claude migration manifest: ${error.message}`);
|
|
33
48
|
return null;
|
|
@@ -649,6 +664,110 @@ function copyGeminiFile(platformKey, results, options = {}) {
|
|
|
649
664
|
}
|
|
650
665
|
}
|
|
651
666
|
|
|
667
|
+
// Copy Claude runtime files (statusline bundle)
|
|
668
|
+
function copyClaudeRuntimeFiles(platformKey, results, options = {}) {
|
|
669
|
+
if (platformKey !== 'claude') return;
|
|
670
|
+
|
|
671
|
+
const manifest = CLAUDE_MIGRATION_MANIFEST;
|
|
672
|
+
if (!manifest?.runtime?.files) return;
|
|
673
|
+
|
|
674
|
+
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
675
|
+
const srcBase = path.join(__dirname, '../src/claude');
|
|
676
|
+
const targetBase = path.join(PLATFORMS.claude.folder);
|
|
677
|
+
|
|
678
|
+
manifest.runtime.files.forEach(relPath => {
|
|
679
|
+
const srcPath = path.join(srcBase, relPath);
|
|
680
|
+
const targetPath = path.join(targetBase, relPath);
|
|
681
|
+
|
|
682
|
+
if (!fs.existsSync(srcPath)) {
|
|
683
|
+
console.log(` ⚠ Runtime file not found: ${relPath}`);
|
|
684
|
+
results.missingDependencies++;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const targetExists = fs.existsSync(targetPath);
|
|
689
|
+
const shouldCopy = shouldOverwriteManagedFiles || !targetExists;
|
|
690
|
+
|
|
691
|
+
if (shouldCopy) {
|
|
692
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
693
|
+
fs.copyFileSync(srcPath, targetPath);
|
|
694
|
+
|
|
695
|
+
if (targetExists) {
|
|
696
|
+
console.log(` ↻ Runtime updated: ${relPath}`);
|
|
697
|
+
results.updated++;
|
|
698
|
+
} else {
|
|
699
|
+
console.log(` ✓ Runtime installed: ${relPath}`);
|
|
700
|
+
results.copied++;
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
results.skipped++;
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Merge Claude settings.json
|
|
709
|
+
function mergeClaudeSettings(platformKey, results, options = {}) {
|
|
710
|
+
if (platformKey !== 'claude') return;
|
|
711
|
+
|
|
712
|
+
const manifest = CLAUDE_MIGRATION_MANIFEST;
|
|
713
|
+
if (!manifest?.settings?.template) return;
|
|
714
|
+
|
|
715
|
+
const templatePath = path.join(__dirname, '../src/claude', manifest.settings.template);
|
|
716
|
+
const targetPath = path.join(PLATFORMS.claude.folder, 'settings.json');
|
|
717
|
+
|
|
718
|
+
if (!fs.existsSync(templatePath)) {
|
|
719
|
+
console.log(` ⚠ Settings template not found: ${manifest.settings.template}`);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const managedSettings = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
724
|
+
let existingSettings = {};
|
|
725
|
+
|
|
726
|
+
if (fs.existsSync(targetPath)) {
|
|
727
|
+
existingSettings = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const mergedSettings = { ...existingSettings };
|
|
731
|
+
|
|
732
|
+
// Merge statusLine
|
|
733
|
+
if (managedSettings.statusLine) {
|
|
734
|
+
const existingCommand = existingSettings.statusLine?.command || '';
|
|
735
|
+
const isCafeKitOwned = existingCommand.includes('status.cjs') || existingCommand.includes('statusline.cjs');
|
|
736
|
+
|
|
737
|
+
if (options.upgrade || !existingSettings.statusLine || isCafeKitOwned) {
|
|
738
|
+
mergedSettings.statusLine = managedSettings.statusLine;
|
|
739
|
+
console.log(` ✓ Settings: statusLine merged`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Merge hooks
|
|
744
|
+
if (managedSettings.hooks) {
|
|
745
|
+
mergedSettings.hooks = mergedSettings.hooks || {};
|
|
746
|
+
|
|
747
|
+
Object.keys(managedSettings.hooks).forEach(eventName => {
|
|
748
|
+
const managedHooks = managedSettings.hooks[eventName];
|
|
749
|
+
const existingHooks = mergedSettings.hooks[eventName] || [];
|
|
750
|
+
const mergedHooks = [...existingHooks];
|
|
751
|
+
|
|
752
|
+
managedHooks.forEach(managedHook => {
|
|
753
|
+
const managedCommand = managedHook.hooks?.[0]?.command || '';
|
|
754
|
+
const isDuplicate = mergedHooks.some(existingHook => {
|
|
755
|
+
return existingHook.hooks?.some(h => h.command === managedCommand);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (!isDuplicate) {
|
|
759
|
+
mergedHooks.push(managedHook);
|
|
760
|
+
console.log(` ✓ Settings: hook ${eventName} merged`);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
mergedSettings.hooks[eventName] = mergedHooks;
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
fs.writeFileSync(targetPath, JSON.stringify(mergedSettings, null, 2), 'utf8');
|
|
769
|
+
}
|
|
770
|
+
|
|
652
771
|
// ═══════════════════════════════════════════════════════════
|
|
653
772
|
// MAIN
|
|
654
773
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -718,6 +837,8 @@ async function main() {
|
|
|
718
837
|
// Copy ROUTING.md for Claude Code platform
|
|
719
838
|
if (platformKey === 'claude') {
|
|
720
839
|
copyRoutingFile(platformKey, results, installerOptions);
|
|
840
|
+
copyClaudeRuntimeFiles(platformKey, results, installerOptions);
|
|
841
|
+
mergeClaudeSettings(platformKey, results, installerOptions);
|
|
721
842
|
}
|
|
722
843
|
|
|
723
844
|
// Copy GEMINI.md for Antigravity platform
|
|
@@ -756,6 +877,9 @@ async function main() {
|
|
|
756
877
|
const platform = PLATFORMS[platformKey];
|
|
757
878
|
console.log(`\n For ${platform.name}:`);
|
|
758
879
|
console.log(` Run: ${platform.commandPrefix}spec-init <feature-name>`);
|
|
880
|
+
if (platformKey === 'claude') {
|
|
881
|
+
console.log(' Or use skill: /hapo:spec-init <feature-description>');
|
|
882
|
+
}
|
|
759
883
|
}
|
|
760
884
|
|
|
761
885
|
console.log('\n 2. Follow the workflow: requirements - design - tasks - code - test - review');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Spec-Driven Development workflow for AI coding assistants. Supports Claude Code and Antigravity with spec-first
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Spec-Driven Development workflow for AI coding assistants. Supports Claude Code and Antigravity with spec-first workflows plus Claude Code hapo: skills.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"ai-coding",
|
|
33
33
|
"specification",
|
|
34
34
|
"requirements",
|
|
35
|
-
"sdd"
|
|
35
|
+
"sdd",
|
|
36
|
+
"skills",
|
|
37
|
+
"hapo"
|
|
36
38
|
],
|
|
37
39
|
"engines": {
|
|
38
40
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SubagentStart Hook - Injects context to subagents (Optimized)
|
|
4
|
+
*
|
|
5
|
+
* Fires: When a subagent (Task tool call) is started
|
|
6
|
+
* Purpose: Inject minimal context using env vars from SessionStart
|
|
7
|
+
* Target: ~200 tokens (down from ~350)
|
|
8
|
+
*
|
|
9
|
+
* Exit Codes:
|
|
10
|
+
* 0 - Success (non-blocking, allows continuation)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Crash wrapper
|
|
14
|
+
try {
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const {
|
|
18
|
+
loadConfig,
|
|
19
|
+
resolveNamingPattern,
|
|
20
|
+
getGitBranch,
|
|
21
|
+
getGitRoot,
|
|
22
|
+
resolvePlanPath,
|
|
23
|
+
getReportsPath,
|
|
24
|
+
normalizePath,
|
|
25
|
+
extractTaskListId,
|
|
26
|
+
isHookEnabled
|
|
27
|
+
} = require('./lib/config.cjs');
|
|
28
|
+
const { resolveSkillsVenv } = require('./lib/context.cjs');
|
|
29
|
+
|
|
30
|
+
// Early exit if hook disabled in config
|
|
31
|
+
if (!isHookEnabled('subagent-init')) {
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get agent-specific context from config
|
|
37
|
+
*/
|
|
38
|
+
function getAgentContext(agentType, config) {
|
|
39
|
+
const agentConfig = config.subagent?.agents?.[agentType];
|
|
40
|
+
if (!agentConfig?.contextPrefix) return null;
|
|
41
|
+
return agentConfig.contextPrefix;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build trust verification section if enabled
|
|
46
|
+
*/
|
|
47
|
+
function buildTrustVerification(config) {
|
|
48
|
+
if (!config.trust?.enabled || !config.trust?.passphrase) return [];
|
|
49
|
+
return [
|
|
50
|
+
``,
|
|
51
|
+
`## Trust Verification`,
|
|
52
|
+
`Passphrase: "${config.trust.passphrase}"`
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Main hook execution
|
|
58
|
+
*/
|
|
59
|
+
async function main() {
|
|
60
|
+
try {
|
|
61
|
+
const stdin = fs.readFileSync(0, 'utf-8').trim();
|
|
62
|
+
if (!stdin) process.exit(0);
|
|
63
|
+
|
|
64
|
+
const payload = JSON.parse(stdin);
|
|
65
|
+
const agentType = payload.agent_type || 'unknown';
|
|
66
|
+
const agentId = payload.agent_id || 'unknown';
|
|
67
|
+
|
|
68
|
+
// Load config for trust verification, naming, and agent-specific context
|
|
69
|
+
const config = loadConfig({ includeProject: false, includeAssertions: false });
|
|
70
|
+
|
|
71
|
+
// Use payload.cwd if provided for git operations (monorepo support)
|
|
72
|
+
// This ensures subagent resolves paths relative to its own CWD, not process.cwd()
|
|
73
|
+
// Issue #327: Use trim() to handle empty string edge case
|
|
74
|
+
const effectiveCwd = payload.cwd?.trim() || process.cwd();
|
|
75
|
+
|
|
76
|
+
// Compute naming pattern directly (don't rely on env vars which may not propagate)
|
|
77
|
+
// Pass effectiveCwd to git commands to support monorepo/submodule scenarios
|
|
78
|
+
const gitBranch = getGitBranch(effectiveCwd);
|
|
79
|
+
const gitRoot = getGitRoot(effectiveCwd);
|
|
80
|
+
// Issue #327: Use CWD as base for subdirectory workflow support
|
|
81
|
+
// Git root is kept for reference but CWD determines where files are created
|
|
82
|
+
const baseDir = effectiveCwd;
|
|
83
|
+
|
|
84
|
+
// Debug logging for path resolution troubleshooting
|
|
85
|
+
if (process.env.CK_DEBUG) {
|
|
86
|
+
console.error(`[subagent-init] effectiveCwd=${effectiveCwd}, gitRoot=${gitRoot}, baseDir=${baseDir}`);
|
|
87
|
+
}
|
|
88
|
+
const namePattern = resolveNamingPattern(config.plan, gitBranch);
|
|
89
|
+
|
|
90
|
+
// Resolve plan and reports path - use absolute paths based on CWD (Issue #327)
|
|
91
|
+
// Use session_id from payload to resolve active plan context (Issue #321)
|
|
92
|
+
const sessionId = payload.session_id || process.env.CK_SESSION_ID || null;
|
|
93
|
+
const resolved = resolvePlanPath(sessionId, config);
|
|
94
|
+
const reportsPath = getReportsPath(resolved.path, resolved.resolvedBy, config.plan, config.paths, baseDir);
|
|
95
|
+
const activePlan = resolved.resolvedBy === 'session' ? resolved.path : '';
|
|
96
|
+
const suggestedPlan = resolved.resolvedBy === 'branch' ? resolved.path : '';
|
|
97
|
+
|
|
98
|
+
// Extract task list ID for Claude Code Tasks coordination (shared helper, DRY)
|
|
99
|
+
const taskListId = extractTaskListId(resolved);
|
|
100
|
+
const plansPath = path.join(baseDir, normalizePath(config.paths?.plans) || 'plans');
|
|
101
|
+
const docsPath = path.join(baseDir, normalizePath(config.paths?.docs) || 'docs');
|
|
102
|
+
const thinkingLanguage = config.locale?.thinkingLanguage || '';
|
|
103
|
+
const responseLanguage = config.locale?.responseLanguage || '';
|
|
104
|
+
// Auto-default thinkingLanguage to 'en' when only responseLanguage is set
|
|
105
|
+
const effectiveThinking = thinkingLanguage || (responseLanguage ? 'en' : '');
|
|
106
|
+
|
|
107
|
+
// Build compact context (~200 tokens)
|
|
108
|
+
const lines = [];
|
|
109
|
+
|
|
110
|
+
// Subagent identification
|
|
111
|
+
lines.push(`## Subagent: ${agentType}`);
|
|
112
|
+
lines.push(`ID: ${agentId} | CWD: ${effectiveCwd}`);
|
|
113
|
+
lines.push(``);
|
|
114
|
+
|
|
115
|
+
// Plan context (from env vars)
|
|
116
|
+
lines.push(`## Context`);
|
|
117
|
+
if (activePlan) {
|
|
118
|
+
lines.push(`- Plan: ${activePlan}`);
|
|
119
|
+
if (taskListId) {
|
|
120
|
+
lines.push(`- Task List: ${taskListId} (shared with session)`);
|
|
121
|
+
}
|
|
122
|
+
} else if (suggestedPlan) {
|
|
123
|
+
lines.push(`- Plan: none | Suggested: ${suggestedPlan}`);
|
|
124
|
+
} else {
|
|
125
|
+
lines.push(`- Plan: none`);
|
|
126
|
+
}
|
|
127
|
+
lines.push(`- Reports: ${reportsPath}`);
|
|
128
|
+
lines.push(`- Paths: ${plansPath}/ | ${docsPath}/`);
|
|
129
|
+
lines.push(``);
|
|
130
|
+
|
|
131
|
+
// Language (thinking + response, if configured)
|
|
132
|
+
const hasThinking = effectiveThinking && effectiveThinking !== responseLanguage;
|
|
133
|
+
if (hasThinking || responseLanguage) {
|
|
134
|
+
lines.push(`## Language`);
|
|
135
|
+
if (hasThinking) {
|
|
136
|
+
lines.push(`- Thinking: Use ${effectiveThinking} for reasoning (logic, precision).`);
|
|
137
|
+
}
|
|
138
|
+
if (responseLanguage) {
|
|
139
|
+
lines.push(`- Response: Respond in ${responseLanguage} (natural, fluent).`);
|
|
140
|
+
}
|
|
141
|
+
lines.push(``);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Resolve Python venv path for subagent instructions
|
|
145
|
+
const skillsVenv = resolveSkillsVenv();
|
|
146
|
+
|
|
147
|
+
// Core rules (minimal)
|
|
148
|
+
lines.push(`## Rules`);
|
|
149
|
+
lines.push(`- Reports → ${reportsPath}`);
|
|
150
|
+
lines.push(`- YAGNI / KISS / DRY`);
|
|
151
|
+
lines.push(`- Concise, list unresolved Qs at end`);
|
|
152
|
+
// Python venv rules (if venv exists)
|
|
153
|
+
if (skillsVenv) {
|
|
154
|
+
lines.push(`- Python scripts in .claude/skills/: Use \`${skillsVenv}\``);
|
|
155
|
+
lines.push(`- Never use global pip install`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Naming templates (computed directly for reliable injection)
|
|
159
|
+
lines.push(``);
|
|
160
|
+
lines.push(`## Naming`);
|
|
161
|
+
lines.push(`- Report: ${path.join(reportsPath, `${agentType}-${namePattern}.md`)}`);
|
|
162
|
+
lines.push(`- Plan dir: ${path.join(plansPath, namePattern)}/`);
|
|
163
|
+
|
|
164
|
+
// Trust verification (if enabled)
|
|
165
|
+
lines.push(...buildTrustVerification(config));
|
|
166
|
+
|
|
167
|
+
// Agent-specific context (if configured)
|
|
168
|
+
const agentContext = getAgentContext(agentType, config);
|
|
169
|
+
if (agentContext) {
|
|
170
|
+
lines.push(``);
|
|
171
|
+
lines.push(`## Agent Instructions`);
|
|
172
|
+
lines.push(agentContext);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// CRITICAL: SubagentStart requires hookSpecificOutput.additionalContext format
|
|
176
|
+
const output = {
|
|
177
|
+
hookSpecificOutput: {
|
|
178
|
+
hookEventName: "SubagentStart",
|
|
179
|
+
additionalContext: lines.join('\n')
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
console.log(JSON.stringify(output));
|
|
184
|
+
process.exit(0);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(`SubagentStart hook error: ${error.message}`);
|
|
187
|
+
process.exit(0); // Fail-open
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main();
|
|
192
|
+
} catch (e) {
|
|
193
|
+
// Minimal crash logging (zero deps — only Node builtins)
|
|
194
|
+
try {
|
|
195
|
+
const fs = require('fs');
|
|
196
|
+
const p = require('path');
|
|
197
|
+
const logDir = p.join(__dirname, '.logs');
|
|
198
|
+
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
199
|
+
fs.appendFileSync(p.join(logDir, 'hook-log.jsonl'),
|
|
200
|
+
JSON.stringify({ ts: new Date().toISOString(), hook: p.basename(__filename, '.cjs'), status: 'crash', error: e.message }) + '\n');
|
|
201
|
+
} catch (_) {}
|
|
202
|
+
process.exit(0); // fail-open
|
|
203
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ANSI Terminal Colors - Cross-platform color support for statusline
|
|
6
|
+
* Supports NO_COLOR, FORCE_COLOR, COLORTERM auto-detection
|
|
7
|
+
* @module colors
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ANSI escape codes (8-color basic palette)
|
|
11
|
+
const RESET = '\x1b[0m';
|
|
12
|
+
const DIM = '\x1b[2m';
|
|
13
|
+
const RED = '\x1b[31m';
|
|
14
|
+
const GREEN = '\x1b[32m';
|
|
15
|
+
const YELLOW = '\x1b[33m';
|
|
16
|
+
const MAGENTA = '\x1b[35m';
|
|
17
|
+
const CYAN = '\x1b[36m';
|
|
18
|
+
|
|
19
|
+
// Detect color support at module load (cached)
|
|
20
|
+
// Claude Code statusline runs via pipe but output displays in TTY - default to true
|
|
21
|
+
const shouldUseColor = (() => {
|
|
22
|
+
if (process.env.NO_COLOR) return false;
|
|
23
|
+
if (process.env.FORCE_COLOR) return true;
|
|
24
|
+
// Default true for statusline context (Claude Code handles TTY display)
|
|
25
|
+
return true;
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
// Detect 256-color support via COLORTERM
|
|
29
|
+
const has256Color = (() => {
|
|
30
|
+
const ct = process.env.COLORTERM;
|
|
31
|
+
return ct === 'truecolor' || ct === '24bit' || ct === '256color';
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Wrap text with ANSI color code
|
|
36
|
+
* @param {string} text - Text to colorize
|
|
37
|
+
* @param {string} code - ANSI escape code
|
|
38
|
+
* @returns {string} Colorized text or plain text if colors disabled
|
|
39
|
+
*/
|
|
40
|
+
function colorize(text, code) {
|
|
41
|
+
if (!shouldUseColor) return String(text);
|
|
42
|
+
return `${code}${text}${RESET}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function green(text) { return colorize(text, GREEN); }
|
|
46
|
+
function yellow(text) { return colorize(text, YELLOW); }
|
|
47
|
+
function red(text) { return colorize(text, RED); }
|
|
48
|
+
function cyan(text) { return colorize(text, CYAN); }
|
|
49
|
+
function magenta(text) { return colorize(text, MAGENTA); }
|
|
50
|
+
function dim(text) { return colorize(text, DIM); }
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get color code based on context percentage threshold
|
|
54
|
+
* @param {number} percent - Context usage percentage (0-100)
|
|
55
|
+
* @returns {string} ANSI color code
|
|
56
|
+
*/
|
|
57
|
+
function getContextColor(percent) {
|
|
58
|
+
if (percent >= 85) return RED;
|
|
59
|
+
if (percent >= 70) return YELLOW;
|
|
60
|
+
return GREEN;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate colored progress bar for context window
|
|
65
|
+
* Uses ▰▱ characters (smooth horizontal rectangles) for consistent rendering
|
|
66
|
+
* @param {number} percent - Usage percentage (0-100)
|
|
67
|
+
* @param {number} width - Bar width in characters (default 12)
|
|
68
|
+
* @returns {string} Unicode progress bar with threshold-based colors
|
|
69
|
+
*/
|
|
70
|
+
function coloredBar(percent, width = 12) {
|
|
71
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
72
|
+
const filled = Math.round((clamped / 100) * width);
|
|
73
|
+
const empty = width - filled;
|
|
74
|
+
|
|
75
|
+
if (!shouldUseColor) {
|
|
76
|
+
return '▰'.repeat(filled) + '▱'.repeat(empty);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const color = getContextColor(percent);
|
|
80
|
+
return `${color}${'▰'.repeat(filled)}${DIM}${'▱'.repeat(empty)}${RESET}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
RESET,
|
|
85
|
+
green,
|
|
86
|
+
yellow,
|
|
87
|
+
red,
|
|
88
|
+
cyan,
|
|
89
|
+
magenta,
|
|
90
|
+
dim,
|
|
91
|
+
getContextColor,
|
|
92
|
+
coloredBar,
|
|
93
|
+
shouldUseColor,
|
|
94
|
+
has256Color
|
|
95
|
+
};
|