Initial commit with translated description
This commit is contained in:
163
issue_tracker.js
Normal file
163
issue_tracker.js
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/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, windowsHide: true }
|
||||
);
|
||||
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 os = require('os');
|
||||
const tmpFile = path.join(os.tmpdir(), `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, windowsHide: true }
|
||||
);
|
||||
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 };
|
||||
Reference in New Issue
Block a user