@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.
- package/README.md +67 -0
- package/commands/dashboard.md +105 -0
- package/commands/goal.md +142 -0
- package/commands/heartbeat.md +129 -0
- package/commands/triage.md +108 -0
- package/hooks/README.md +1 -1
- package/hooks/harness-context-monitor.js +4 -0
- package/hooks/hooks.json +27 -26
- package/hooks/strategic-compact/README.md +7 -5
- package/hooks/strategic-compact/suggest-compact.js +9 -174
- package/marketplace.json +7 -8
- package/package.json +1 -1
- package/schemas/goal.schema.json +172 -0
- package/scripts/harness-audit.js +7 -4
- package/scripts/hooks/session-start-goal-resume.js +95 -0
- package/scripts/hooks/suggest-compact.js +392 -62
- package/scripts/install-platform.js +68 -85
- package/scripts/lib/blame-attribution.js +210 -0
- package/scripts/lib/completion-oracle.js +351 -0
- package/scripts/lib/heartbeat-scheduler.js +265 -0
- package/scripts/lib/opencode/convert-agents.js +273 -0
- package/scripts/lib/opencode/convert-hooks.js +286 -0
- package/scripts/lib/opencode/generate-agents-md.js +361 -0
- package/scripts/lib/wave-cost-advisor.js +155 -0
- package/scripts/test-opencode-install.js +151 -0
- package/skills/goal-convergence/SKILL.md +150 -0
- package/skills/goframe-v2/examples/practices/quick-demo/manifest/config/config.yaml +14 -14
- package/skills/loop-heartbeat/SKILL.md +120 -0
- package/skills/mcp-connector-bridge/SKILL.md +132 -0
- package/skills/repo-scan/SKILL.md +63 -63
- package/skills/rework-loop/SKILL.md +131 -0
- package/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/scripts/__pycache__/build_platform_artifacts.cpython-311.pyc +0 -0
- package/scripts/__pycache__/install_platform.cpython-311.pyc +0 -0
- package/scripts/__pycache__/langfuse_trace.cpython-311.pyc +0 -0
- package/scripts/__pycache__/query_audit_logs.cpython-311.pyc +0 -0
- package/scripts/__pycache__/scan_leaked_keys.cpython-311.pyc +0 -0
- package/scripts/__pycache__/team_skills_platform.cpython-311.pyc +0 -0
- package/scripts/__pycache__/team_skills_platform.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate_library.cpython-311.pyc +0 -0
- package/scripts/__pycache__/validate_workflow_state.cpython-311.pyc +0 -0
- package/scripts/evolution/__pycache__/__init__.cpython-311.pyc +0 -0
- package/scripts/evolution/__pycache__/store.cpython-311.pyc +0 -0
- package/scripts/hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- package/scripts/hooks/__pycache__/mcp_health_check.cpython-311.pyc +0 -0
- package/scripts/hooks/__pycache__/observe.cpython-311.pyc +0 -0
- package/scripts/hooks/__pycache__/session_end.cpython-311.pyc +0 -0
- package/scripts/hooks/__pycache__/session_start.cpython-311.pyc +0 -0
- package/scripts/lib/__pycache__/audit_logger.cpython-311.pyc +0 -0
- package/scripts/lib/__pycache__/audit_query.cpython-311.pyc +0 -0
- package/scripts/lib/__pycache__/hook_contract.cpython-311.pyc +0 -0
- package/scripts/lib/__pycache__/memory_store.cpython-311.pyc +0 -0
- package/scripts/lib/__pycache__/utils.cpython-311.pyc +0 -0
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## 当前状态
|
|
6
6
|
|
|
7
|
-
✅ **已实现** - 通过 `scripts/hooks/
|
|
7
|
+
✅ **已实现** - 通过 `scripts/hooks/pre-compact.js` 和 `scripts/hooks/suggest-compact.js` 提供完整功能。
|
|
8
8
|
|
|
9
9
|
## 实现细节
|
|
10
10
|
|
|
11
|
-
###
|
|
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
|
-
###
|
|
28
|
+
### suggest-compact.js
|
|
29
29
|
|
|
30
|
-
**触发时机**:
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
107
|
-
|
|
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": "
|
|
7
|
-
"email": "
|
|
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.
|
|
18
|
-
"icon": "./assets/icon.png",
|
|
17
|
+
"version": "2.3.0",
|
|
19
18
|
"author": {
|
|
20
|
-
"name": "
|
|
21
|
-
"email": "
|
|
19
|
+
"name": "Engineering Team",
|
|
20
|
+
"email": "engineering@example.com"
|
|
22
21
|
},
|
|
23
|
-
"homepage": "https://github.com/
|
|
24
|
-
"repository": "https://github.com/
|
|
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
|
@@ -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
|
+
}
|
package/scripts/harness-audit.js
CHANGED
|
@@ -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: '
|
|
274
|
-
description: 'Suggest-compact automation
|
|
275
|
-
pass: fileExists(rootDir, 'scripts/hooks/suggest-compact.js'),
|
|
276
|
-
fix: '
|
|
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();
|