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.
Files changed (2) hide show
  1. package/dist/index.js +53 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/index.ts
2
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
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 join2, resolve, dirname as dirname2 } from "path";
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
- console.error(JSON.stringify({
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 fullQ = `${cleanQuery} org:${entry.githubOrg}`;
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
- console.error(`[web-search] tier1 ${fw}: ${m}`);
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
- console.error(`[web-search] tier2: ${m}`);
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
- console.error(`[web-search] tier3: ${m}`);
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
- console.error(`[web-search] unexpected error: ${m}`);
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 = join2(homedir(), ".cache", "opencode-usage-coach", "pipeline.log");
440
+ var PIPE_LOG = join3(homedir2(), ".cache", "opencode-usage-coach", "pipeline.log");
427
441
  function pipeLog(msg) {
428
442
  try {
429
- mkdirSync2(dirname2(PIPE_LOG), { recursive: true });
430
- appendFileSync2(PIPE_LOG, `[SERVER] ${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
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 = join2(homedir(), ".cache", "opencode-usage-coach");
437
- var STATE_FILE = join2(STATE_DIR, "state.json");
438
- var LOG_FILE = join2(STATE_DIR, "coach.log");
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 join2(homedir(), ".cache", "opencode-usage-coach", "projects", h);
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 = join2(STATE_DIR, "state.json");
447
- LOG_FILE = join2(STATE_DIR, "coach.log");
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
- appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
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
- mkdirSync2(STATE_DIR, { recursive: true });
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 join2(STATE_DIR, "rules.md");
479
+ return join3(STATE_DIR, "rules.md");
466
480
  }
467
481
  function failuresFile() {
468
- return join2(STATE_DIR, "failures.ndjson");
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 join2(STATE_DIR, "impl-notes.md");
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
- mkdirSync2(STATE_DIR, { recursive: true });
528
- appendFileSync2(implNotesFile(), entry);
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 join2(STATE_DIR, sessionID || "_default", "harness.json");
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
- mkdirSync2(dirname2(f), { recursive: true });
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 join2(STATE_DIR, sessionID || "_default", "interview.json");
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
- mkdirSync2(dirname2(f), { recursive: true });
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 = join2(baseDir, mf);
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(join2(homedir(), ".config", "opencode-usage-coach", "harness.config.json")),
1299
- ...tryRead(join2(dir, "harness.config.json"))
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 = join2(homedir(), ".config", "opencode-usage-coach");
1304
- const configPath = join2(configDir, "harness.config.json");
1317
+ const configDir = join3(homedir2(), ".config", "opencode-usage-coach");
1318
+ const configPath = join3(configDir, "harness.config.json");
1305
1319
  try {
1306
- mkdirSync2(configDir, { recursive: true });
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
- mkdirSync2(STATE_DIR, { recursive: true });
2035
- appendFileSync2(failuresFile(), JSON.stringify(rec) + "\n");
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
- mkdirSync2(STATE_DIR, { recursive: true });
2161
- appendFileSync2(rulesFile(), `## Rule (${date})
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "opencode closed-loop usage coach — quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",