-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathissue_tracker.js
More file actions
162 lines (142 loc) · 5.14 KB
/
issue_tracker.js
File metadata and controls
162 lines (142 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env node
// issue_tracker.js -- Track evolution issues in a Feishu Doc
//
// Creates a persistent Feishu document on first run, then appends
// new issues discovered by the evolver in each cycle.
//
// Config (env vars):
// EVOLVER_ISSUE_DOC_TOKEN -- Feishu doc token (auto-created if not set)
// OPENCLAW_MASTER_ID -- Master's open_id for edit permission grant
//
// Usage from wrapper:
// const tracker = require('./issue_tracker');
// await tracker.recordIssues(signals, cycleTag, sessionSummary);
//
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const WORKSPACE_ROOT = path.resolve(__dirname, '../..');
const STATE_FILE = path.join(WORKSPACE_ROOT, 'memory', 'evolver_issue_doc.json');
const CREATE_SCRIPT = path.join(WORKSPACE_ROOT, 'skills', 'feishu-doc', 'create.js');
const APPEND_SCRIPT = path.join(WORKSPACE_ROOT, 'skills', 'feishu-doc', 'append_simple.js');
function loadState() {
try {
if (fs.existsSync(STATE_FILE)) {
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
}
} catch (e) {}
return null;
}
function saveState(state) {
try {
const dir = path.dirname(STATE_FILE);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
} catch (e) {
console.error('[IssueTracker] Failed to save state:', e.message);
}
}
function ensureDoc() {
// Check if we already have a doc token
let state = loadState();
if (state && state.doc_token) return state.doc_token;
// Check env var
const envToken = process.env.EVOLVER_ISSUE_DOC_TOKEN;
if (envToken) {
saveState({ doc_token: envToken, created_at: new Date().toISOString() });
return envToken;
}
// Create new doc
if (!fs.existsSync(CREATE_SCRIPT)) {
console.error('[IssueTracker] feishu-doc/create.js not found, cannot create issue doc');
return null;
}
try {
const masterId = process.env.OPENCLAW_MASTER_ID || '';
const grantArg = masterId ? ` --grant "${masterId}"` : '';
const result = execSync(
`node "${CREATE_SCRIPT}" --title "Evolver Issue Tracker"${grantArg}`,
{ encoding: 'utf8', timeout: 30000, cwd: WORKSPACE_ROOT }
);
const doc = JSON.parse(result);
const token = doc.doc_token;
if (!token) throw new Error('No doc_token in response');
console.log(`[IssueTracker] Created issue doc: ${doc.url}`);
saveState({
doc_token: token,
url: doc.url,
created_at: new Date().toISOString(),
granted_to: doc.granted_to
});
return token;
} catch (e) {
console.error('[IssueTracker] Failed to create doc:', e.message);
return null;
}
}
function appendToDoc(docToken, markdown) {
if (!fs.existsSync(APPEND_SCRIPT)) {
console.error('[IssueTracker] feishu-doc/append_simple.js not found');
return false;
}
try {
const tmpFile = path.join('/tmp', `evolver_issue_${Date.now()}.md`);
fs.writeFileSync(tmpFile, markdown);
execSync(
`node "${APPEND_SCRIPT}" --doc_token "${docToken}" --file "${tmpFile}"`,
{ encoding: 'utf8', timeout: 30000, cwd: WORKSPACE_ROOT }
);
try { fs.unlinkSync(tmpFile); } catch (_) {}
return true;
} catch (e) {
console.error('[IssueTracker] Failed to append:', e.message);
return false;
}
}
async function recordIssues(signals, cycleTag, extraContext) {
if (!signals || signals.length === 0) return;
// Only record actionable signals (skip cosmetic ones)
const actionable = signals.filter(s =>
s !== 'stable_success_plateau' &&
s !== 'user_missing' &&
s !== 'memory_missing'
);
if (actionable.length === 0) return;
const docToken = ensureDoc();
if (!docToken) return;
const now = new Date().toISOString();
const lines = [
`### Cycle #${cycleTag} | ${now}`,
'',
'**Signals detected:**',
...actionable.map(s => `- \`${s}\``),
];
if (extraContext) {
lines.push('', '**Context:**', extraContext.slice(0, 500));
}
lines.push('', '---', '');
const markdown = lines.join('\n');
const ok = appendToDoc(docToken, markdown);
if (ok) {
console.log(`[IssueTracker] Recorded ${actionable.length} issues for Cycle #${cycleTag}`);
}
}
function getDocUrl() {
const state = loadState();
return state && state.url ? state.url : null;
}
if (require.main === module) {
// CLI test: node issue_tracker.js --test
const args = process.argv.slice(2);
if (args.includes('--test')) {
recordIssues(
['log_error', 'unsupported_input_type', 'errsig:test error'],
'TEST',
'Manual test of issue tracker'
).then(() => console.log('Done. Doc URL:', getDocUrl()));
} else {
console.log('Usage: node issue_tracker.js --test');
console.log('State:', JSON.stringify(loadState(), null, 2));
}
}
module.exports = { recordIssues, getDocUrl, ensureDoc };