From ecc4c88e9a4d7bf4a47347fd537ce69b3870c74c Mon Sep 17 00:00:00 2001 From: AnnuKumar Date: Sun, 12 Jul 2026 07:29:19 +0530 Subject: [PATCH] feat: implement Developer Commit Activity Adapter (#22) --- src/adapters/commit-adapter.mjs | 70 +++++++++++++++++++++++++++++++++ src/index.mjs | 3 +- test/commit-adapter.test.mjs | 38 ++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/adapters/commit-adapter.mjs create mode 100644 test/commit-adapter.test.mjs diff --git a/src/adapters/commit-adapter.mjs b/src/adapters/commit-adapter.mjs new file mode 100644 index 0000000..d5e1680 --- /dev/null +++ b/src/adapters/commit-adapter.mjs @@ -0,0 +1,70 @@ +/** + * Developer Commit Activity Adapter + * Normalizes raw git commit payloads into structured CCP context objects. + */ + +const RECOGNIZED_TECH_KEYWORDS = [ + { term: "react", token: "React", domain: "frontend" }, + { term: "vue", token: "Vue", domain: "frontend" }, + { term: "next.js", token: "Next.js", domain: "frontend" }, + { term: "node", token: "Node.js", domain: "backend" }, + { term: "python", token: "Python", domain: "backend" }, + { term: "rust", token: "Rust", domain: "systems" }, + { term: "docker", token: "Docker", domain: "devops" }, + { term: "kubernetes", token: "Kubernetes", domain: "devops" }, + { term: "aws", token: "AWS", domain: "cloud" } +]; + +/** + * Parses a raw git commit log text or webhook frame payload object. + * @param {Object|string} rawCommit - The input commit data string or object structure + * @returns {Object} Structured CCP frame properties mapping token metadata descriptors + */ +export function parseCommitActivity(rawCommit) { + let message = ""; + let author = "unknown"; + let hash = "unknown"; + let timestamp = new Date().toISOString(); + + // Handle either raw text lines or structured JSON objects cleanly + if (typeof rawCommit === "string") { + message = rawCommit; + } else if (rawCommit && typeof rawCommit === "object") { + message = rawCommit.message || rawCommit.commit?.message || ""; + author = rawCommit.author || rawCommit.commit?.author?.name || "unknown"; + hash = rawCommit.hash || rawCommit.sha || "unknown"; + if (rawCommit.timestamp || rawCommit.commit?.author?.date) { + timestamp = new Date(rawCommit.timestamp || rawCommit.commit?.author?.date).toISOString(); + } + } + + const normalizedMessage = message.toLowerCase(); + const technologiesDetected = []; + const domainTags = new Set(); + + // Run taxonomy scanning matrix extraction rules + for (const item of RECOGNIZED_TECH_KEYWORDS) { + if (normalizedMessage.includes(item.term)) { + technologiesDetected.push(item.token); + domainTags.add(item.domain); + } + } + + // Set an operational signal type category classification classification token + const category = technologiesDetected.length > 0 ? "developer_work" : "general"; + + return { + schema_version: "memact.ccp_observation.v1", + event_type: "developer_commit", + category, + title: `Commit activity: ${message.split("\n")[0].slice(0, 60)}`, + evidence: { + commit_hash: hash, + author, + raw_message: message, + technologies: [...new Set(technologiesDetected)], + domains: [...domainTags] + }, + occurred_at: timestamp + }; +} \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs index 82b94bb..52bf1c1 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1,3 +1,4 @@ export { createMemactClient } from "./client.mjs" export { MemactSDKError } from "./errors.mjs" -export { validateClientConfig } from "./config-validation.mjs" \ No newline at end of file +export { validateClientConfig } from "./config-validation.mjs" +export { parseCommitActivity } from "./adapters/commit-adapter.mjs" \ No newline at end of file diff --git a/test/commit-adapter.test.mjs b/test/commit-adapter.test.mjs new file mode 100644 index 0000000..11ab0f4 --- /dev/null +++ b/test/commit-adapter.test.mjs @@ -0,0 +1,38 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { parseCommitActivity } from "../src/adapters/commit-adapter.mjs" + +test("parseCommitActivity maps raw text log and extracts tech stacks correctly", () => { + const sampleLog = "feat: implement hooks system using react and node server paths"; + const result = parseCommitActivity(sampleLog); + + assert.equal(result.schema_version, "memact.ccp_observation.v1"); + assert.equal(result.category, "developer_work"); + assert.deepEqual(result.evidence.technologies, ["React", "Node.js"]); + assert.deepEqual(result.evidence.domains, ["frontend", "backend"]); +}); + +test("parseCommitActivity falls back to general category if no tech keywords match", () => { + const sampleLog = "docs: update readme text formatting rules"; + const result = parseCommitActivity(sampleLog); + + assert.equal(result.category, "general"); + assert.equal(result.evidence.technologies.length, 0); +}); + +test("parseCommitActivity normalizes full GitHub webhook payloads", () => { + const webhookMock = { + sha: "abcd1234efgh", + commit: { + message: "fix: resolve security flaw using rust backend logic", + author: { name: "Annu Kumar", date: "2026-07-12T00:00:00Z" } + } + }; + + const result = parseCommitActivity(webhookMock); + assert.equal(result.evidence.commit_hash, "abcd1234efgh"); + assert.equal(result.evidence.author, "Annu Kumar"); + assert.deepEqual(result.evidence.technologies, ["Rust"]); + // FIXED: Adjusted string comparison token layout to match standard millisecond formatting passes + assert.equal(result.occurred_at, "2026-07-12T00:00:00.000Z"); +}); \ No newline at end of file