@colin4k1024/tsp 2.5.0 → 2.5.2

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.
Files changed (53) hide show
  1. package/README.md +67 -0
  2. package/commands/dashboard.md +105 -0
  3. package/commands/goal.md +142 -0
  4. package/commands/heartbeat.md +129 -0
  5. package/commands/triage.md +108 -0
  6. package/hooks/README.md +1 -1
  7. package/hooks/harness-context-monitor.js +4 -0
  8. package/hooks/hooks.json +27 -26
  9. package/hooks/strategic-compact/README.md +7 -5
  10. package/hooks/strategic-compact/suggest-compact.js +9 -174
  11. package/marketplace.json +7 -8
  12. package/package.json +1 -1
  13. package/schemas/goal.schema.json +172 -0
  14. package/scripts/harness-audit.js +7 -4
  15. package/scripts/hooks/session-start-goal-resume.js +95 -0
  16. package/scripts/hooks/suggest-compact.js +392 -62
  17. package/scripts/install-platform.js +68 -85
  18. package/scripts/lib/blame-attribution.js +210 -0
  19. package/scripts/lib/completion-oracle.js +351 -0
  20. package/scripts/lib/heartbeat-scheduler.js +265 -0
  21. package/scripts/lib/opencode/convert-agents.js +273 -0
  22. package/scripts/lib/opencode/convert-hooks.js +286 -0
  23. package/scripts/lib/opencode/generate-agents-md.js +361 -0
  24. package/scripts/lib/wave-cost-advisor.js +155 -0
  25. package/scripts/test-opencode-install.js +151 -0
  26. package/skills/goal-convergence/SKILL.md +150 -0
  27. package/skills/goframe-v2/examples/practices/quick-demo/manifest/config/config.yaml +14 -14
  28. package/skills/loop-heartbeat/SKILL.md +120 -0
  29. package/skills/mcp-connector-bridge/SKILL.md +132 -0
  30. package/skills/repo-scan/SKILL.md +63 -63
  31. package/skills/rework-loop/SKILL.md +131 -0
  32. package/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  33. package/scripts/__pycache__/build_platform_artifacts.cpython-311.pyc +0 -0
  34. package/scripts/__pycache__/install_platform.cpython-311.pyc +0 -0
  35. package/scripts/__pycache__/langfuse_trace.cpython-311.pyc +0 -0
  36. package/scripts/__pycache__/query_audit_logs.cpython-311.pyc +0 -0
  37. package/scripts/__pycache__/scan_leaked_keys.cpython-311.pyc +0 -0
  38. package/scripts/__pycache__/team_skills_platform.cpython-311.pyc +0 -0
  39. package/scripts/__pycache__/team_skills_platform.cpython-313.pyc +0 -0
  40. package/scripts/__pycache__/validate_library.cpython-311.pyc +0 -0
  41. package/scripts/__pycache__/validate_workflow_state.cpython-311.pyc +0 -0
  42. package/scripts/evolution/__pycache__/__init__.cpython-311.pyc +0 -0
  43. package/scripts/evolution/__pycache__/store.cpython-311.pyc +0 -0
  44. package/scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  45. package/scripts/hooks/__pycache__/mcp_health_check.cpython-311.pyc +0 -0
  46. package/scripts/hooks/__pycache__/observe.cpython-311.pyc +0 -0
  47. package/scripts/hooks/__pycache__/session_end.cpython-311.pyc +0 -0
  48. package/scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
  49. package/scripts/lib/__pycache__/audit_logger.cpython-311.pyc +0 -0
  50. package/scripts/lib/__pycache__/audit_query.cpython-311.pyc +0 -0
  51. package/scripts/lib/__pycache__/hook_contract.cpython-311.pyc +0 -0
  52. package/scripts/lib/__pycache__/memory_store.cpython-311.pyc +0 -0
  53. package/scripts/lib/__pycache__/utils.cpython-311.pyc +0 -0
@@ -4,11 +4,11 @@
4
4
 
5
5
  ## 当前状态
6
6
 
7
- ✅ **已实现** - 通过 `scripts/hooks/pre_compact.py` 和 `scripts/hooks/suggest_compact.py` 提供完整功能。
7
+ ✅ **已实现** - 通过 `scripts/hooks/pre-compact.js` 和 `scripts/hooks/suggest-compact.js` 提供完整功能。
8
8
 
9
9
  ## 实现细节
10
10
 
11
- ### pre_compact.py
11
+ ### pre-compact.js
12
12
 
13
13
  **触发时机**: 上下文压缩前自动调用
14
14
 
@@ -25,12 +25,14 @@
25
25
  - 总是丢弃的类型:`tool_result`, `search_result`, `exploration_trace`(超过 500 字符时)
26
26
  - 其他根据上下文大小决定
27
27
 
28
- ### suggest_compact.py
28
+ ### suggest-compact.js
29
29
 
30
- **触发时机**: 上下文使用超过 70% 时自动调用
30
+ **触发时机**: `hooks/hooks.json` 的 `pre:all:strategic-compact` 在工具调用前运行;当真实上下文使用超过 70% 时注入 `/compact` 建议。
31
31
 
32
32
  **功能**:
33
- - 计算上下文使用率
33
+ - 优先读取 Claude hook 输入里的 `context_window.used_percentage` / `context_window.remaining_percentage`
34
+ - 其次读取 `CLAUDE_CONTEXT_SIZE` / `CLAUDE_CONTEXT_LIMIT`
35
+ - 最后读取 `harness-statusline.js` 写入的 `/tmp/harness-ctx-{session_id}.json`
34
36
  - 评估紧迫度:`low` (< 70%) | `medium` (70-85%) | `high` (85-95%) | `critical` (> 95%)
35
37
  - 估算可节省的 token
36
38
  - 提供具体压缩建议:
@@ -1,100 +1,14 @@
1
1
  #!/usr/bin/env node
2
- // suggest-compact.js — SuggestCompact hook
3
- // JS equivalent of scripts/hooks/suggest_compact.py
4
- //
5
- // Called when context is getting large. Analyses usage ratio and outputs
6
- // compact suggestions + reorganisation plan.
7
- //
8
- // Reads context metrics from:
9
- // 1. stdin JSON ({ context_window: { used_percentage, remaining_percentage } })
10
- // 2. env vars (CLAUDE_CONTEXT_SIZE, CLAUDE_CONTEXT_LIMIT)
11
- // 3. bridge file /tmp/harness-ctx-{session_id}.json (written by harness-statusline.js)
12
- //
13
- // Silent-fail: any error → process.exit(0)
2
+ /**
3
+ * Compatibility entry for older strategic-compact hook paths.
4
+ *
5
+ * The canonical implementation lives in scripts/hooks/suggest-compact.js so
6
+ * hooks.json, installers, tests, and audits share one behavior.
7
+ */
14
8
 
15
9
  'use strict';
16
10
 
17
- const fs = require('fs');
18
- const path = require('path');
19
- const os = require('os');
20
-
21
- // Urgency thresholds (context usage %)
22
- const URGENCY = [
23
- [95, 'critical'],
24
- [85, 'high'],
25
- [70, 'medium'],
26
- [ 0, 'low'],
27
- ];
28
-
29
- function getUrgency(usagePct) {
30
- for (const [threshold, label] of URGENCY) {
31
- if (usagePct > threshold) return label;
32
- }
33
- return 'low';
34
- }
35
-
36
- function buildSuggestions(usagePct, contextSize) {
37
- const suggestions = [];
38
- let savings = 0;
39
-
40
- if (usagePct > 85) {
41
- const s = Math.round(contextSize * 0.15);
42
- suggestions.push({
43
- action: 'summarize',
44
- target: 'early conversation history',
45
- reason: 'Preserve decisions and conclusions, compress exploration traces',
46
- estimated_tokens_saved: s,
47
- });
48
- savings += s;
49
- }
50
-
51
- if (usagePct > 70) {
52
- const s = Math.round(contextSize * 0.10);
53
- suggestions.push({
54
- action: 'discard',
55
- target: 'tool results and search outputs',
56
- reason: 'These are reference-only, not critical for continuity',
57
- estimated_tokens_saved: s,
58
- });
59
- savings += s;
60
- }
61
-
62
- if (usagePct > 60) {
63
- const s = Math.round(contextSize * 0.08);
64
- suggestions.push({
65
- action: 'reorganize',
66
- target: 'specialist outputs',
67
- reason: 'Move key conclusions to session summary, keep full outputs in memory store',
68
- estimated_tokens_saved: s,
69
- });
70
- savings += s;
71
- }
72
-
73
- return { suggestions, savings };
74
- }
75
-
76
- function buildReorganizationPlan() {
77
- return {
78
- phase_1: {
79
- action: 'save_decisions_to_memory',
80
- description: 'Extract all decisions and save to ~/.claude/memory/error_experience/decisions/',
81
- },
82
- phase_2: {
83
- action: 'save_pending_items',
84
- description: 'Extract pending items and next hints to session summary',
85
- },
86
- phase_3: {
87
- action: 'compress_conversation',
88
- description: 'Keep system prompts and last 20 exchanges, summarize middle',
89
- },
90
- phase_4: {
91
- action: 'compact_tool_results',
92
- description: 'Replace long tool outputs with summaries: [File X read, Y lines]',
93
- },
94
- };
95
- }
96
-
97
- // ─── main ───────────────────────────────────────────────────────────────────
11
+ const { buildHookOutput } = require('../../scripts/hooks/suggest-compact');
98
12
 
99
13
  let input = '';
100
14
  const stdinTimeout = setTimeout(() => process.exit(0), 8000);
@@ -103,87 +17,8 @@ process.stdin.on('data', chunk => { input += chunk; });
103
17
  process.stdin.on('end', () => {
104
18
  clearTimeout(stdinTimeout);
105
19
  try {
106
- let data = {};
107
- try { data = JSON.parse(input || '{}'); } catch (_) { /* ignore */ }
108
-
109
- const sessionId = data.session_id || '';
110
-
111
- // ── Resolve context metrics (3 sources, first wins) ──
112
- let usagePct = null;
113
- let contextSize = 0;
114
- let contextLimit = 200000;
115
-
116
- // Source 1: stdin context_window
117
- const cw = data.context_window;
118
- if (cw && cw.used_percentage != null) {
119
- usagePct = cw.used_percentage;
120
- contextSize = Math.round((usagePct / 100) * contextLimit);
121
- }
122
-
123
- // Source 2: env vars
124
- if (usagePct == null) {
125
- const envSize = parseInt(process.env.CLAUDE_CONTEXT_SIZE || '0', 10);
126
- const envLimit = parseInt(process.env.CLAUDE_CONTEXT_LIMIT || '200000', 10);
127
- if (envSize > 0) {
128
- contextSize = envSize;
129
- contextLimit = envLimit;
130
- usagePct = Math.round((envSize / envLimit) * 100);
131
- }
132
- }
133
-
134
- // Source 3: bridge file from harness-statusline.js
135
- if (usagePct == null && sessionId) {
136
- const bridgePath = path.join(os.tmpdir(), `harness-ctx-${sessionId}.json`);
137
- if (fs.existsSync(bridgePath)) {
138
- try {
139
- const bridge = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
140
- usagePct = bridge.used_pct ?? null;
141
- contextSize = usagePct != null ? Math.round((usagePct / 100) * contextLimit) : 0;
142
- } catch (_) { /* ignore */ }
143
- }
144
- }
145
-
146
- if (usagePct == null) {
147
- process.exit(0); // No metrics available — nothing to suggest
148
- }
149
-
150
- const urgency = getUrgency(usagePct);
151
- const shouldCompact = usagePct > 70;
152
-
153
- const { suggestions, savings } = buildSuggestions(usagePct, contextSize);
154
- const reorganizationPlan = shouldCompact ? buildReorganizationPlan() : null;
155
-
156
- const payload = {
157
- should_compact: shouldCompact,
158
- urgency,
159
- context_usage_ratio: usagePct,
160
- estimated_token_savings: savings,
161
- suggestions,
162
- reorganization_plan: reorganizationPlan,
163
- };
164
-
165
- const _msg = `Compact suggestion: ${urgency} urgency, ~${savings} tokens could be saved`;
166
-
167
- // Build context advisory (only inject if compact is warranted)
168
- if (!shouldCompact) {
169
- process.exit(0);
170
- }
171
-
172
- const urgencyEmoji = { critical: '🔴', high: '🟠', medium: '🟡', low: '🟢' }[urgency] ?? '⚪';
173
- const ctx = [
174
- `## 上下文压缩建议 ${urgencyEmoji} (strategic-compact)`,
175
- `- 使用率: ${usagePct}% | 紧迫度: ${urgency} | 可节省: ~${savings} tokens`,
176
- ...suggestions.map(s => `- [${s.action}] ${s.target}: ${s.reason}`),
177
- ].join('\n');
178
-
179
- process.stdout.write(JSON.stringify({
180
- hookSpecificOutput: {
181
- hookEventName: 'SuggestCompact',
182
- additionalContext: ctx,
183
- },
184
- compactSuggestion: payload,
185
- }));
186
-
20
+ const output = buildHookOutput(input);
21
+ if (output) process.stdout.write(JSON.stringify(output));
187
22
  } catch (_) {
188
23
  process.exit(0);
189
24
  }
package/marketplace.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "name": "team-skills-platform",
4
4
  "description": "Role-based Team Skills platform with an ECC-style harness skeleton, specialist agents, commands, rules, hooks skeleton, contexts, and curated engineering skills.",
5
5
  "owner": {
6
- "name": "Colin4k1024",
7
- "email": "colin4k1024@gmail.com"
6
+ "name": "Engineering Team",
7
+ "email": "engineering@example.com"
8
8
  },
9
9
  "metadata": {
10
10
  "description": "Hybrid role platform plus ECC-style harness skeleton"
@@ -14,14 +14,13 @@
14
14
  "name": "team-skills-platform",
15
15
  "source": "./",
16
16
  "description": "Team-oriented workflow plugin with role agents, 27 specialist agents, ECC-inspired commands, layered rules, and hooks skeleton.",
17
- "version": "2.5.0",
18
- "icon": "./assets/icon.png",
17
+ "version": "2.3.0",
19
18
  "author": {
20
- "name": "Colin4k1024",
21
- "email": "colin4k1024@gmail.com"
19
+ "name": "Engineering Team",
20
+ "email": "engineering@example.com"
22
21
  },
23
- "homepage": "https://github.com/Colin4k1024/tsp",
24
- "repository": "https://github.com/Colin4k1024/tsp",
22
+ "homepage": "https://github.com/affaan-m/everything-claude-code",
23
+ "repository": "https://github.com/affaan-m/everything-claude-code",
25
24
  "license": "MIT",
26
25
  "keywords": [
27
26
  "team-skills",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colin4k1024/tsp",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "Open-source Team Skills Platform for role-based AI delivery workflows, shared skills, hooks, commands, and multi-platform installs.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,172 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "ECC Goal Definition",
4
+ "description": "Schema for goal-oriented autonomous loop with external completion oracle",
5
+ "type": "object",
6
+ "properties": {
7
+ "goalId": {
8
+ "type": "string",
9
+ "pattern": "^goal-[a-z0-9]{8}$",
10
+ "description": "Unique goal identifier"
11
+ },
12
+ "objective": {
13
+ "type": "string",
14
+ "minLength": 5,
15
+ "description": "Natural language description of the goal"
16
+ },
17
+ "stoppingConditions": {
18
+ "type": "array",
19
+ "minItems": 1,
20
+ "items": {
21
+ "type": "object",
22
+ "properties": {
23
+ "type": {
24
+ "type": "string",
25
+ "enum": ["test_pass", "lint_clean", "coverage_threshold", "build_pass", "custom_command"],
26
+ "description": "Type of stopping condition"
27
+ },
28
+ "command": {
29
+ "type": "string",
30
+ "minLength": 1,
31
+ "description": "Shell command to evaluate the condition"
32
+ },
33
+ "threshold": {
34
+ "type": "number",
35
+ "description": "Numeric threshold for the condition (e.g., coverage percentage)"
36
+ },
37
+ "description": {
38
+ "type": "string",
39
+ "description": "Human-readable description of what this condition checks"
40
+ }
41
+ },
42
+ "required": ["type", "command"],
43
+ "additionalProperties": false
44
+ }
45
+ },
46
+ "budget": {
47
+ "type": "object",
48
+ "properties": {
49
+ "maxIterations": {
50
+ "type": "integer",
51
+ "minimum": 1,
52
+ "maximum": 100,
53
+ "default": 15,
54
+ "description": "Maximum maker iterations before escalation"
55
+ },
56
+ "maxDuration": {
57
+ "type": "string",
58
+ "pattern": "^[0-9]+(m|h)$",
59
+ "default": "2h",
60
+ "description": "Maximum wall-clock time (e.g., '30m', '2h')"
61
+ },
62
+ "maxDollars": {
63
+ "type": "number",
64
+ "minimum": 0.1,
65
+ "default": 10,
66
+ "description": "Maximum cost in USD before pausing"
67
+ }
68
+ },
69
+ "additionalProperties": false
70
+ },
71
+ "oracle": {
72
+ "type": "object",
73
+ "properties": {
74
+ "model": {
75
+ "type": "string",
76
+ "default": "haiku",
77
+ "description": "Model to use for completion verification (must differ from maker)"
78
+ },
79
+ "allowedTools": {
80
+ "type": "array",
81
+ "items": { "type": "string" },
82
+ "default": ["Read", "Bash"],
83
+ "description": "Tools the oracle can use (read-only by convention)"
84
+ },
85
+ "prompt": {
86
+ "type": "string",
87
+ "description": "Custom oracle prompt override (optional)"
88
+ }
89
+ },
90
+ "additionalProperties": false
91
+ },
92
+ "state": {
93
+ "type": "string",
94
+ "enum": ["active", "paused", "converged", "escalated", "failed"],
95
+ "default": "active",
96
+ "description": "Current goal lifecycle state"
97
+ },
98
+ "currentIteration": {
99
+ "type": "integer",
100
+ "minimum": 0,
101
+ "default": 0
102
+ },
103
+ "createdAt": {
104
+ "type": "string",
105
+ "format": "date-time"
106
+ },
107
+ "updatedAt": {
108
+ "type": "string",
109
+ "format": "date-time"
110
+ },
111
+ "history": {
112
+ "type": "array",
113
+ "items": {
114
+ "type": "object",
115
+ "properties": {
116
+ "iteration": {
117
+ "type": "integer",
118
+ "minimum": 0
119
+ },
120
+ "makerSummary": {
121
+ "type": "string",
122
+ "description": "Brief summary of what the maker did this iteration"
123
+ },
124
+ "oracleVerdict": {
125
+ "type": "string",
126
+ "enum": ["pass", "fail", "partial"],
127
+ "description": "Oracle's assessment of stopping conditions"
128
+ },
129
+ "failReasons": {
130
+ "type": "array",
131
+ "items": { "type": "string" },
132
+ "description": "Specific reasons for failure"
133
+ },
134
+ "nextHint": {
135
+ "type": "string",
136
+ "description": "Oracle's guidance for the next iteration"
137
+ },
138
+ "timestamp": {
139
+ "type": "string",
140
+ "format": "date-time"
141
+ },
142
+ "costDollars": {
143
+ "type": "number",
144
+ "description": "Cost of this iteration"
145
+ }
146
+ },
147
+ "required": ["iteration", "oracleVerdict", "timestamp"],
148
+ "additionalProperties": false
149
+ }
150
+ },
151
+ "escalation": {
152
+ "type": "object",
153
+ "properties": {
154
+ "reason": {
155
+ "type": "string",
156
+ "enum": ["budget_exhausted", "repeated_failure", "oracle_uncertain", "manual"],
157
+ "description": "Why the goal was escalated"
158
+ },
159
+ "details": {
160
+ "type": "string"
161
+ },
162
+ "escalatedAt": {
163
+ "type": "string",
164
+ "format": "date-time"
165
+ }
166
+ },
167
+ "additionalProperties": false
168
+ }
169
+ },
170
+ "required": ["goalId", "objective", "stoppingConditions", "state"],
171
+ "additionalProperties": false
172
+ }
@@ -203,6 +203,9 @@ function getRepoChecks(rootDir) {
203
203
  const commandPrimary = safeRead(rootDir, 'commands/harness-audit.md').trim();
204
204
  const commandParity = safeRead(rootDir, '.opencode/commands/harness-audit.md').trim();
205
205
  const hooksJson = safeRead(rootDir, 'hooks/hooks.json');
206
+ const hasStrategicCompactHook =
207
+ hooksJson.includes('pre:all:strategic-compact') &&
208
+ hooksJson.includes('scripts/hooks/suggest-compact.js');
206
209
 
207
210
  return [
208
211
  {
@@ -270,10 +273,10 @@ function getRepoChecks(rootDir) {
270
273
  category: 'Context Efficiency',
271
274
  points: 3,
272
275
  scopes: ['repo', 'hooks'],
273
- path: 'scripts/hooks/suggest-compact.js',
274
- description: 'Suggest-compact automation hook exists',
275
- pass: fileExists(rootDir, 'scripts/hooks/suggest-compact.js'),
276
- fix: 'Implement scripts/hooks/suggest-compact.js for context pressure hints.',
276
+ path: 'hooks/hooks.json',
277
+ description: 'Suggest-compact automation is registered for strategic context pressure hints',
278
+ pass: fileExists(rootDir, 'scripts/hooks/suggest-compact.js') && hasStrategicCompactHook,
279
+ fix: 'Register pre:all:strategic-compact in hooks/hooks.json and point it at scripts/hooks/suggest-compact.js.',
277
280
  },
278
281
  {
279
282
  id: 'context-model-route',
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * session-start-goal-resume.js
6
+ *
7
+ * SessionStart hook that checks for active goals and injects a resume reminder.
8
+ *
9
+ * Part of the Loop Engineering upgrade (Phase 1.3: Inter-session state bridge).
10
+ * When active goals exist from a previous session, this hook adds context to the
11
+ * session start output so the user knows they can resume with `/goal resume`.
12
+ *
13
+ * Behavior:
14
+ * 1. Scans ~/.claude/goals/ for goal files with state "active" or "paused"
15
+ * 2. If found, appends a summary to the hook output
16
+ * 3. Non-blocking: failures are silently ignored
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ function getGoalsDir() {
23
+ const home = process.env.HOME || process.env.USERPROFILE || '';
24
+ return path.join(home, '.claude', 'goals');
25
+ }
26
+
27
+ function scanActiveGoals() {
28
+ const dir = getGoalsDir();
29
+ if (!fs.existsSync(dir)) return [];
30
+
31
+ try {
32
+ return fs.readdirSync(dir)
33
+ .filter(f => f.endsWith('.json'))
34
+ .map(f => {
35
+ try {
36
+ return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf-8'));
37
+ } catch {
38
+ return null;
39
+ }
40
+ })
41
+ .filter(g => g && (g.state === 'active' || g.state === 'paused'))
42
+ .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
43
+ } catch {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ function formatGoalSummary(goal) {
49
+ const stateIcon = goal.state === 'active' ? '▶' : '⏸';
50
+ const progress = `${goal.currentIteration}/${goal.budget?.maxIterations || 15}`;
51
+ const lastHint = goal.history?.length > 0
52
+ ? goal.history[goal.history.length - 1].nextHint || ''
53
+ : '';
54
+ return ` ${stateIcon} [${goal.goalId}] "${goal.objective}" (iter ${progress})${lastHint ? `\n Last hint: ${lastHint.slice(0, 120)}` : ''}`;
55
+ }
56
+
57
+ function main() {
58
+ let raw = '';
59
+ try {
60
+ raw = fs.readFileSync(0, 'utf8');
61
+ } catch {
62
+ // stdin may not be available
63
+ }
64
+
65
+ const activeGoals = scanActiveGoals();
66
+
67
+ if (activeGoals.length === 0) {
68
+ process.stdout.write(raw);
69
+ return;
70
+ }
71
+
72
+ // Parse the input event and add goal context
73
+ let event;
74
+ try {
75
+ event = JSON.parse(raw);
76
+ } catch {
77
+ process.stdout.write(raw);
78
+ return;
79
+ }
80
+
81
+ const goalSummary = activeGoals.map(formatGoalSummary).join('\n');
82
+ const message = `\n[Loop Engineering] ${activeGoals.length} active goal(s) from previous session:\n${goalSummary}\n\nResume with: /goal resume\n`;
83
+
84
+ // Inject into session context if possible
85
+ if (event && typeof event === 'object') {
86
+ if (!event.additionalContext) {
87
+ event.additionalContext = '';
88
+ }
89
+ event.additionalContext += message;
90
+ }
91
+
92
+ process.stdout.write(JSON.stringify(event));
93
+ }
94
+
95
+ main();