opencode-usage-coach 0.12.0 → 0.12.1
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/dist/index.js +53 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { mkdirSync as
|
|
2
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, appendFileSync as appendFileSync3, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
3
3
|
import { spawn, spawnSync } from "child_process";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
|
-
import { homedir } from "os";
|
|
6
|
-
import { join as
|
|
5
|
+
import { homedir as homedir2 } from "os";
|
|
6
|
+
import { join as join3, resolve, dirname as dirname2 } from "path";
|
|
7
7
|
import { tool } from "@opencode-ai/plugin";
|
|
8
8
|
|
|
9
9
|
// src/domain.ts
|
|
@@ -221,6 +221,18 @@ function autoLinkKeywords(nodeId, keywords, minOverlap = 2, maxLinks = 8) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
// src/web-search.ts
|
|
224
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
225
|
+
import { join as join2 } from "path";
|
|
226
|
+
import { homedir } from "os";
|
|
227
|
+
var WS_LOG_FILE = process.env.UC_STATE_DIR ? join2(process.env.UC_STATE_DIR, "web-search.log") : join2(homedir(), ".cache", "opencode-usage-coach", "web-search.log");
|
|
228
|
+
function wsLog(msg) {
|
|
229
|
+
try {
|
|
230
|
+
mkdirSync2(join2(WS_LOG_FILE, ".."), { recursive: true });
|
|
231
|
+
appendFileSync2(WS_LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
232
|
+
`);
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
}
|
|
224
236
|
var FRAMEWORK_DOCS = {
|
|
225
237
|
"react": { name: "React", docs: "https://react.dev", githubOrg: "facebook" },
|
|
226
238
|
"solid-js": { name: "Solid.js", docs: "https://solidjs.com", githubOrg: "solidjs" },
|
|
@@ -283,7 +295,7 @@ async function ghFetch(url, signal) {
|
|
|
283
295
|
const errs = ghError?.errors;
|
|
284
296
|
const errMsg = ghError?.message ?? ghErrorText.slice(0, 300);
|
|
285
297
|
const detail = errs ? errs.map((e) => typeof e === "string" ? e : `${e?.field ?? "?"}: ${e?.message ?? e?.code ?? JSON.stringify(e)}`).join("; ") : "";
|
|
286
|
-
|
|
298
|
+
wsLog(JSON.stringify({
|
|
287
299
|
level: "error",
|
|
288
300
|
module: "web-search",
|
|
289
301
|
event: "gh-fetch-error",
|
|
@@ -319,7 +331,9 @@ async function tier1OfficialDocs(query, fws, results, docRefs, seen, signal) {
|
|
|
319
331
|
const entry = FRAMEWORK_DOCS[fw];
|
|
320
332
|
if (!entry?.githubOrg) continue;
|
|
321
333
|
try {
|
|
322
|
-
const
|
|
334
|
+
const orgPart = ` org:${entry.githubOrg}`;
|
|
335
|
+
const truncated = cleanQuery.slice(0, GH_QUERY_MAX - orgPart.length);
|
|
336
|
+
const fullQ = `${truncated}${orgPart}`;
|
|
323
337
|
const url = `https://api.github.com/search/issues?q=${encodeURIComponent(fullQ)}&per_page=3`;
|
|
324
338
|
const data = await ghFetch(url, signal);
|
|
325
339
|
for (const item of data.items ?? []) {
|
|
@@ -332,7 +346,7 @@ async function tier1OfficialDocs(query, fws, results, docRefs, seen, signal) {
|
|
|
332
346
|
}
|
|
333
347
|
} catch (e) {
|
|
334
348
|
const m = errMessage(e);
|
|
335
|
-
|
|
349
|
+
wsLog(`[web-search] tier1 ${fw}: ${m}`);
|
|
336
350
|
errors.push(`tier1:${fw}:${m.slice(0, 80)}`);
|
|
337
351
|
}
|
|
338
352
|
}
|
|
@@ -355,7 +369,7 @@ async function tier2GitHubIssues(query, results, seen, signal) {
|
|
|
355
369
|
}
|
|
356
370
|
} catch (e) {
|
|
357
371
|
const m = errMessage(e);
|
|
358
|
-
|
|
372
|
+
wsLog(`[web-search] tier2: ${m}`);
|
|
359
373
|
errors.push(`tier2:${m.slice(0, 80)}`);
|
|
360
374
|
}
|
|
361
375
|
return errors;
|
|
@@ -379,7 +393,7 @@ async function tier3GitHubCode(query, results, seen, signal) {
|
|
|
379
393
|
}
|
|
380
394
|
} catch (e) {
|
|
381
395
|
const m = errMessage(e);
|
|
382
|
-
|
|
396
|
+
wsLog(`[web-search] tier3: ${m}`);
|
|
383
397
|
errors.push(`tier3:${m.slice(0, 80)}`);
|
|
384
398
|
}
|
|
385
399
|
return errors;
|
|
@@ -405,7 +419,7 @@ async function searchContext(query, frameworks, keyDeps, timeoutMs) {
|
|
|
405
419
|
}
|
|
406
420
|
} catch (e) {
|
|
407
421
|
const m = errMessage(e);
|
|
408
|
-
|
|
422
|
+
wsLog(`[web-search] unexpected error: ${m}`);
|
|
409
423
|
errors.push(`unexpected:${m.slice(0, 80)}`);
|
|
410
424
|
} finally {
|
|
411
425
|
clearTimeout(timer);
|
|
@@ -423,49 +437,49 @@ var DEFAULT_MAX_STEPS = Number(process.env.UC_MAX_STEPS ?? 30) || 30;
|
|
|
423
437
|
var WATCHDOG_POLL_MS = Math.max(1e3, Number(process.env.UC_WATCHDOG_POLL_MS ?? 3e3) || 3e3);
|
|
424
438
|
var WALL_TIMEOUT_MS = Math.max(1, Number(process.env.UC_WALL_TIMEOUT_MIN ?? 30) || 30) * 60 * 1e3;
|
|
425
439
|
var DEFAULT_MAX_QUESTIONS = Math.max(1, Math.round(Number(process.env.UC_MAX_QUESTIONS ?? 7)) || 7);
|
|
426
|
-
var PIPE_LOG =
|
|
440
|
+
var PIPE_LOG = join3(homedir2(), ".cache", "opencode-usage-coach", "pipeline.log");
|
|
427
441
|
function pipeLog(msg) {
|
|
428
442
|
try {
|
|
429
|
-
|
|
430
|
-
|
|
443
|
+
mkdirSync3(dirname2(PIPE_LOG), { recursive: true });
|
|
444
|
+
appendFileSync3(PIPE_LOG, `[SERVER] ${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
431
445
|
`);
|
|
432
446
|
} catch {
|
|
433
447
|
}
|
|
434
448
|
}
|
|
435
449
|
pipeLog(`MODULE LOADED | node=${process.version} | pid=${process.pid}`);
|
|
436
|
-
var STATE_DIR =
|
|
437
|
-
var STATE_FILE =
|
|
438
|
-
var LOG_FILE =
|
|
450
|
+
var STATE_DIR = join3(homedir2(), ".cache", "opencode-usage-coach");
|
|
451
|
+
var STATE_FILE = join3(STATE_DIR, "state.json");
|
|
452
|
+
var LOG_FILE = join3(STATE_DIR, "coach.log");
|
|
439
453
|
function projectStateDir(dir) {
|
|
440
454
|
const abs = resolve(dir || ".");
|
|
441
455
|
const h = createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
442
|
-
return
|
|
456
|
+
return join3(homedir2(), ".cache", "opencode-usage-coach", "projects", h);
|
|
443
457
|
}
|
|
444
458
|
function setStateDir(dir) {
|
|
445
459
|
STATE_DIR = process.env.UC_STATE_DIR ?? projectStateDir(dir);
|
|
446
|
-
STATE_FILE =
|
|
447
|
-
LOG_FILE =
|
|
460
|
+
STATE_FILE = join3(STATE_DIR, "state.json");
|
|
461
|
+
LOG_FILE = join3(STATE_DIR, "coach.log");
|
|
448
462
|
}
|
|
449
463
|
var NOOP_HOOKS = {};
|
|
450
464
|
function log(msg) {
|
|
451
465
|
try {
|
|
452
|
-
|
|
466
|
+
appendFileSync3(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
453
467
|
`);
|
|
454
468
|
} catch {
|
|
455
469
|
}
|
|
456
470
|
}
|
|
457
471
|
function writeState(c) {
|
|
458
472
|
try {
|
|
459
|
-
|
|
473
|
+
mkdirSync3(STATE_DIR, { recursive: true });
|
|
460
474
|
writeFileSync2(STATE_FILE, JSON.stringify({ ...c, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
461
475
|
} catch {
|
|
462
476
|
}
|
|
463
477
|
}
|
|
464
478
|
function rulesFile() {
|
|
465
|
-
return
|
|
479
|
+
return join3(STATE_DIR, "rules.md");
|
|
466
480
|
}
|
|
467
481
|
function failuresFile() {
|
|
468
|
-
return
|
|
482
|
+
return join3(STATE_DIR, "failures.ndjson");
|
|
469
483
|
}
|
|
470
484
|
function readRules() {
|
|
471
485
|
try {
|
|
@@ -477,7 +491,7 @@ function readRules() {
|
|
|
477
491
|
}
|
|
478
492
|
}
|
|
479
493
|
function implNotesFile() {
|
|
480
|
-
return
|
|
494
|
+
return join3(STATE_DIR, "impl-notes.md");
|
|
481
495
|
}
|
|
482
496
|
var IMPL_NOTE_INSTRUCTION = `
|
|
483
497
|
## Implementation Notes (important!)
|
|
@@ -524,8 +538,8 @@ ${notes}
|
|
|
524
538
|
Source: generate task "${shortTask}"
|
|
525
539
|
|
|
526
540
|
`;
|
|
527
|
-
|
|
528
|
-
|
|
541
|
+
mkdirSync3(STATE_DIR, { recursive: true });
|
|
542
|
+
appendFileSync3(implNotesFile(), entry);
|
|
529
543
|
} catch (e) {
|
|
530
544
|
log(`appendImplNotes err: ${String(e)}`);
|
|
531
545
|
}
|
|
@@ -716,7 +730,7 @@ function extractKeywords(text) {
|
|
|
716
730
|
}
|
|
717
731
|
}
|
|
718
732
|
function harnessFile(sessionID) {
|
|
719
|
-
return
|
|
733
|
+
return join3(STATE_DIR, sessionID || "_default", "harness.json");
|
|
720
734
|
}
|
|
721
735
|
function readHarness(sessionID) {
|
|
722
736
|
try {
|
|
@@ -730,7 +744,7 @@ function readHarness(sessionID) {
|
|
|
730
744
|
function writeHarness(sessionID, h) {
|
|
731
745
|
try {
|
|
732
746
|
const f = harnessFile(sessionID);
|
|
733
|
-
|
|
747
|
+
mkdirSync3(dirname2(f), { recursive: true });
|
|
734
748
|
h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
735
749
|
writeFileSync2(f, JSON.stringify(h, null, 2));
|
|
736
750
|
} catch {
|
|
@@ -753,7 +767,7 @@ function mutateHarness(sessionID, fn) {
|
|
|
753
767
|
return next;
|
|
754
768
|
}
|
|
755
769
|
function interviewFile(sessionID) {
|
|
756
|
-
return
|
|
770
|
+
return join3(STATE_DIR, sessionID || "_default", "interview.json");
|
|
757
771
|
}
|
|
758
772
|
function readInterview(sessionID) {
|
|
759
773
|
try {
|
|
@@ -767,7 +781,7 @@ function readInterview(sessionID) {
|
|
|
767
781
|
function writeInterview(sessionID, s) {
|
|
768
782
|
try {
|
|
769
783
|
const f = interviewFile(sessionID);
|
|
770
|
-
|
|
784
|
+
mkdirSync3(dirname2(f), { recursive: true });
|
|
771
785
|
s.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
772
786
|
writeFileSync2(f, JSON.stringify(s, null, 2));
|
|
773
787
|
} catch {
|
|
@@ -965,7 +979,7 @@ function parseFileList(rawList, baseDir) {
|
|
|
965
979
|
let testPattern;
|
|
966
980
|
let testFramework;
|
|
967
981
|
for (const mf of manifestFiles) {
|
|
968
|
-
const full =
|
|
982
|
+
const full = join3(baseDir, mf);
|
|
969
983
|
if (existsSync2(full)) {
|
|
970
984
|
try {
|
|
971
985
|
const content = JSON.parse(readFileSync2(full, "utf8"));
|
|
@@ -1295,15 +1309,15 @@ function readHarnessCfg(dir) {
|
|
|
1295
1309
|
return {};
|
|
1296
1310
|
};
|
|
1297
1311
|
return {
|
|
1298
|
-
...tryRead(
|
|
1299
|
-
...tryRead(
|
|
1312
|
+
...tryRead(join3(homedir2(), ".config", "opencode-usage-coach", "harness.config.json")),
|
|
1313
|
+
...tryRead(join3(dir, "harness.config.json"))
|
|
1300
1314
|
};
|
|
1301
1315
|
}
|
|
1302
1316
|
function writeHarnessCfg(updates) {
|
|
1303
|
-
const configDir =
|
|
1304
|
-
const configPath =
|
|
1317
|
+
const configDir = join3(homedir2(), ".config", "opencode-usage-coach");
|
|
1318
|
+
const configPath = join3(configDir, "harness.config.json");
|
|
1305
1319
|
try {
|
|
1306
|
-
|
|
1320
|
+
mkdirSync3(configDir, { recursive: true });
|
|
1307
1321
|
const existing = (() => {
|
|
1308
1322
|
try {
|
|
1309
1323
|
return JSON.parse(readFileSync2(configPath, "utf8"));
|
|
@@ -2031,8 +2045,8 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
|
|
|
2031
2045
|
async execute(args, _ctx) {
|
|
2032
2046
|
const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), task: args.task, prompt: args.prompt, gradeResult: args.gradeResult, model: args.model, revisions: args.revisions };
|
|
2033
2047
|
try {
|
|
2034
|
-
|
|
2035
|
-
|
|
2048
|
+
mkdirSync3(STATE_DIR, { recursive: true });
|
|
2049
|
+
appendFileSync3(failuresFile(), JSON.stringify(rec) + "\n");
|
|
2036
2050
|
} catch (e) {
|
|
2037
2051
|
log(`record_failure err: ${String(e)}`);
|
|
2038
2052
|
}
|
|
@@ -2157,8 +2171,8 @@ Keep it concrete and actionable.`;
|
|
|
2157
2171
|
const rule = out;
|
|
2158
2172
|
try {
|
|
2159
2173
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2160
|
-
|
|
2161
|
-
|
|
2174
|
+
mkdirSync3(STATE_DIR, { recursive: true });
|
|
2175
|
+
appendFileSync3(rulesFile(), `## Rule (${date})
|
|
2162
2176
|
${rule}
|
|
2163
2177
|
Origin: ${args.task}
|
|
2164
2178
|
|
package/package.json
CHANGED