@devness/useai 0.4.27 → 0.5.0

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 +70 -16
  2. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -112,7 +112,7 @@ var VERSION;
112
112
  var init_version = __esm({
113
113
  "../shared/dist/constants/version.js"() {
114
114
  "use strict";
115
- VERSION = "0.4.22";
115
+ VERSION = "0.5.0";
116
116
  }
117
117
  });
118
118
 
@@ -1263,9 +1263,9 @@ var init_tools = __esm({
1263
1263
  init_dist();
1264
1264
  USEAI_INSTRUCTIONS_TEXT = [
1265
1265
  "## UseAI Session Tracking",
1266
- "- At the START of every response, call `useai_start` with the appropriate task_type.",
1266
+ "- At the START of every response, call `useai_start` with the appropriate task_type. Also generate a short session `title` (generic, no project names) and `private_title` (detailed, can include specifics) from the user's prompt.",
1267
1267
  "- For LONG sessions (15+ min), call `useai_heartbeat` periodically.",
1268
- "- At the END of every response, call `useai_end` with languages used, files_touched_count, and milestones (generic descriptions only \u2014 no project names, file paths, or company names)."
1268
+ "- At the END of every response, call `useai_end` with languages used, files_touched_count, milestones (generic descriptions only \u2014 no project names, file paths, or company names), and an `evaluation` object honestly assessing: prompt_quality (1-5), context_provided (1-5), task_outcome, iteration_count, independence_level (1-5), scope_quality (1-5), tools_leveraged count."
1269
1269
  ].join("\n");
1270
1270
  MCP_HTTP_URL = DAEMON_MCP_URL;
1271
1271
  MCP_HTTP_ENTRY = { type: "http", url: MCP_HTTP_URL };
@@ -1584,6 +1584,9 @@ var init_session_state = __esm({
1584
1584
  sessionRecordCount;
1585
1585
  clientName;
1586
1586
  sessionTaskType;
1587
+ sessionTitle;
1588
+ sessionPrivateTitle;
1589
+ sessionPromptWordCount;
1587
1590
  project;
1588
1591
  chainTipHash;
1589
1592
  signingKey;
@@ -1595,6 +1598,9 @@ var init_session_state = __esm({
1595
1598
  this.sessionRecordCount = 0;
1596
1599
  this.clientName = "unknown";
1597
1600
  this.sessionTaskType = "coding";
1601
+ this.sessionTitle = null;
1602
+ this.sessionPrivateTitle = null;
1603
+ this.sessionPromptWordCount = null;
1598
1604
  this.project = null;
1599
1605
  this.chainTipHash = GENESIS_HASH;
1600
1606
  this.signingKey = null;
@@ -1607,6 +1613,9 @@ var init_session_state = __esm({
1607
1613
  this.sessionRecordCount = 0;
1608
1614
  this.chainTipHash = GENESIS_HASH;
1609
1615
  this.sessionTaskType = "coding";
1616
+ this.sessionTitle = null;
1617
+ this.sessionPrivateTitle = null;
1618
+ this.sessionPromptWordCount = null;
1610
1619
  this.detectProject();
1611
1620
  }
1612
1621
  detectProject() {
@@ -1618,6 +1627,15 @@ var init_session_state = __esm({
1618
1627
  setTaskType(type) {
1619
1628
  this.sessionTaskType = type;
1620
1629
  }
1630
+ setTitle(title) {
1631
+ this.sessionTitle = title;
1632
+ }
1633
+ setPrivateTitle(title) {
1634
+ this.sessionPrivateTitle = title;
1635
+ }
1636
+ setPromptWordCount(count) {
1637
+ this.sessionPromptWordCount = count;
1638
+ }
1621
1639
  incrementHeartbeat() {
1622
1640
  this.heartbeatCount++;
1623
1641
  }
@@ -1691,20 +1709,27 @@ function resolveClient(server2, session2) {
1691
1709
  function registerTools(server2, session2) {
1692
1710
  server2.tool(
1693
1711
  "useai_start",
1694
- "Start tracking an AI coding session. Call this at the beginning of every response.",
1712
+ `Start tracking an AI coding session. Call this at the beginning of every response. Generate a session title from the user's prompt: a generic public "title" (no project/file names) and a detailed "private_title" (can include specifics).`,
1695
1713
  {
1696
- task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?")
1714
+ task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?"),
1715
+ title: z2.string().optional().describe(`Short public session title derived from the user's prompt. No project names, file paths, or identifying details. Example: "Fix authentication bug"`),
1716
+ private_title: z2.string().optional().describe('Detailed session title for private records. Can include project names and specifics. Example: "Fix JWT refresh in UseAI login flow"')
1697
1717
  },
1698
- async ({ task_type }) => {
1718
+ async ({ task_type, title, private_title }) => {
1699
1719
  session2.reset();
1700
1720
  resolveClient(server2, session2);
1701
1721
  session2.setTaskType(task_type ?? "coding");
1702
- const record = session2.appendToChain("session_start", {
1722
+ session2.setTitle(title ?? null);
1723
+ session2.setPrivateTitle(private_title ?? null);
1724
+ const chainData = {
1703
1725
  client: session2.clientName,
1704
1726
  task_type: session2.sessionTaskType,
1705
1727
  project: session2.project,
1706
1728
  version: VERSION
1707
- });
1729
+ };
1730
+ if (title) chainData.title = title;
1731
+ if (private_title) chainData.private_title = private_title;
1732
+ const record = session2.appendToChain("session_start", chainData);
1708
1733
  return {
1709
1734
  content: [
1710
1735
  {
@@ -1737,7 +1762,7 @@ function registerTools(server2, session2) {
1737
1762
  );
1738
1763
  server2.tool(
1739
1764
  "useai_end",
1740
- `End the current AI coding session and record milestones. Each milestone needs TWO titles: (1) a generic public "title" safe for public display (NEVER include project names, file names, class names, or any identifying details), and (2) an optional detailed "private_title" for the user's own records that CAN include project names, file names, and specific details. GOOD title: "Implemented user authentication". GOOD private_title: "Added JWT auth to UseAI API server". BAD title: "Fixed bug in Acme auth service".`,
1765
+ 'End the current AI coding session and record milestones. Each milestone needs TWO titles: (1) a generic public "title" safe for public display (NEVER include project names, file names, class names, or any identifying details), and (2) an optional detailed "private_title" for the user\'s own records that CAN include project names, file names, and specific details. GOOD title: "Implemented user authentication". GOOD private_title: "Added JWT auth to UseAI API server". BAD title: "Fixed bug in Acme auth service". Also provide an `evaluation` object assessing the session: prompt_quality (1-5), context_provided (1-5), task_outcome (completed/partial/abandoned/blocked), iteration_count, independence_level (1-5), scope_quality (1-5), and tools_leveraged count. Score honestly based on the actual interaction.',
1741
1766
  {
1742
1767
  task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
1743
1768
  languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
@@ -1747,9 +1772,18 @@ function registerTools(server2, session2) {
1747
1772
  private_title: z2.string().optional().describe("Detailed description for the user's private records. CAN include project names, file names, and specific details. Example: 'Added private/public milestone support to UseAI MCP server'"),
1748
1773
  category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
1749
1774
  complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
1750
- })).optional().describe("What was accomplished this session? List each distinct piece of work completed. Provide both a generic public title and an optional detailed private_title.")
1775
+ })).optional().describe("What was accomplished this session? List each distinct piece of work completed. Provide both a generic public title and an optional detailed private_title."),
1776
+ evaluation: z2.object({
1777
+ prompt_quality: z2.number().min(1).max(5).describe("How clear, specific, and complete was the initial prompt? 1=vague/ambiguous, 5=crystal clear with acceptance criteria"),
1778
+ context_provided: z2.number().min(1).max(5).describe("Did the user provide relevant context (files, errors, constraints)? 1=no context, 5=comprehensive context"),
1779
+ task_outcome: z2.enum(["completed", "partial", "abandoned", "blocked"]).describe("Was the primary task achieved?"),
1780
+ iteration_count: z2.number().min(1).describe("Number of user-to-AI turns in this session"),
1781
+ independence_level: z2.number().min(1).max(5).describe("How self-directed was the user? 1=needed constant guidance, 5=gave clear spec and let AI execute"),
1782
+ scope_quality: z2.number().min(1).max(5).describe("Was the task well-scoped? 1=vague or impossibly broad, 5=precise and achievable"),
1783
+ tools_leveraged: z2.number().min(0).describe("Count of distinct AI capabilities used (code gen, debugging, refactoring, testing, docs, etc.)")
1784
+ }).optional().describe("AI-assessed evaluation of this session. Score honestly based on the actual interaction.")
1751
1785
  },
1752
- async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
1786
+ async ({ task_type, languages, files_touched_count, milestones: milestonesInput, evaluation }) => {
1753
1787
  const duration = session2.getSessionDuration();
1754
1788
  const now = (/* @__PURE__ */ new Date()).toISOString();
1755
1789
  const finalTaskType = task_type ?? session2.sessionTaskType;
@@ -1759,7 +1793,8 @@ function registerTools(server2, session2) {
1759
1793
  task_type: finalTaskType,
1760
1794
  languages: languages ?? [],
1761
1795
  files_touched: files_touched_count ?? 0,
1762
- heartbeat_count: session2.heartbeatCount
1796
+ heartbeat_count: session2.heartbeatCount,
1797
+ ...evaluation ? { evaluation } : {}
1763
1798
  });
1764
1799
  const sealData = JSON.stringify({
1765
1800
  session_id: session2.sessionId,
@@ -1768,6 +1803,10 @@ function registerTools(server2, session2) {
1768
1803
  languages: languages ?? [],
1769
1804
  files_touched: files_touched_count ?? 0,
1770
1805
  project: session2.project,
1806
+ title: session2.sessionTitle ?? void 0,
1807
+ private_title: session2.sessionPrivateTitle ?? void 0,
1808
+ prompt_word_count: session2.sessionPromptWordCount ?? void 0,
1809
+ evaluation: evaluation ?? void 0,
1771
1810
  started_at: new Date(session2.sessionStartTime).toISOString(),
1772
1811
  ended_at: now,
1773
1812
  duration_seconds: duration,
@@ -1798,6 +1837,10 @@ function registerTools(server2, session2) {
1798
1837
  languages: languages ?? [],
1799
1838
  files_touched: files_touched_count ?? 0,
1800
1839
  project: session2.project ?? void 0,
1840
+ title: session2.sessionTitle ?? void 0,
1841
+ private_title: session2.sessionPrivateTitle ?? void 0,
1842
+ prompt_word_count: session2.sessionPromptWordCount ?? void 0,
1843
+ evaluation: evaluation ?? void 0,
1801
1844
  started_at: new Date(session2.sessionStartTime).toISOString(),
1802
1845
  ended_at: now,
1803
1846
  duration_seconds: duration,
@@ -1850,11 +1893,12 @@ function registerTools(server2, session2) {
1850
1893
  const durationStr = formatDuration(duration);
1851
1894
  const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
1852
1895
  const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
1896
+ const evalStr = evaluation ? ` \xB7 eval: ${evaluation.task_outcome} (prompt: ${evaluation.prompt_quality}/5)` : "";
1853
1897
  return {
1854
1898
  content: [
1855
1899
  {
1856
1900
  type: "text",
1857
- text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}`
1901
+ text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}${evalStr}`
1858
1902
  }
1859
1903
  ]
1860
1904
  };
@@ -1945,6 +1989,16 @@ function readBody(req) {
1945
1989
  req.on("error", reject);
1946
1990
  });
1947
1991
  }
1992
+ function deduplicateSessions(sessions2) {
1993
+ const map = /* @__PURE__ */ new Map();
1994
+ for (const s of sessions2) {
1995
+ const existing = map.get(s.session_id);
1996
+ if (!existing || s.duration_seconds > existing.duration_seconds) {
1997
+ map.set(s.session_id, s);
1998
+ }
1999
+ }
2000
+ return [...map.values()];
2001
+ }
1948
2002
  function calculateStreak(sessions2) {
1949
2003
  if (sessions2.length === 0) return 0;
1950
2004
  const days = /* @__PURE__ */ new Set();
@@ -1971,7 +2025,7 @@ function calculateStreak(sessions2) {
1971
2025
  }
1972
2026
  function handleLocalSessions(_req, res) {
1973
2027
  try {
1974
- const sessions2 = readJson(SESSIONS_FILE, []);
2028
+ const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
1975
2029
  json(res, 200, sessions2);
1976
2030
  } catch (err) {
1977
2031
  json(res, 500, { error: err.message });
@@ -1979,7 +2033,7 @@ function handleLocalSessions(_req, res) {
1979
2033
  }
1980
2034
  function handleLocalStats(_req, res) {
1981
2035
  try {
1982
- const sessions2 = readJson(SESSIONS_FILE, []);
2036
+ const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
1983
2037
  let totalSeconds = 0;
1984
2038
  let filesTouched = 0;
1985
2039
  const byClient = {};
@@ -2050,7 +2104,7 @@ async function handleLocalSync(req, res) {
2050
2104
  "Content-Type": "application/json",
2051
2105
  "Authorization": `Bearer ${token}`
2052
2106
  };
2053
- const sessions2 = readJson(SESSIONS_FILE, []);
2107
+ const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
2054
2108
  const sessionsRes = await fetch("https://api.useai.dev/api/sync", {
2055
2109
  method: "POST",
2056
2110
  headers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devness/useai",
3
- "version": "0.4.27",
3
+ "version": "0.5.0",
4
4
  "description": "Track your AI-assisted development workflow. MCP server that records usage metrics across all your AI tools.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -39,6 +39,7 @@
39
39
  "@types/node": "^22.13.4",
40
40
  "tsup": "^8.0.0",
41
41
  "typescript": "^5.7.3",
42
+ "vitest": "^4.0.18",
42
43
  "@useai/shared": "0.3.0"
43
44
  },
44
45
  "repository": {
@@ -54,6 +55,8 @@
54
55
  "dev": "tsc --watch",
55
56
  "daemon": "pnpm run build && node dist/index.js daemon --port 19201",
56
57
  "bundle": "tsup src/index.ts --format esm --target node18 --no-splitting",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
57
60
  "typecheck": "tsc --noEmit",
58
61
  "clean": "rm -rf dist"
59
62
  }