Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/adapters/commit-adapter.mjs
Original file line number Diff line number Diff line change
@@ -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
};
}
1 change: 1 addition & 0 deletions src/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { createMemactClient, MemactClient } from "./client.mjs"
export { MemactSDKError } from "./errors.mjs"
export { validateClientConfig } from "./config-validation.mjs"
export { parseCommitActivity } from "./adapters/commit-adapter.mjs"
export { parseMediaPlayback } from "./adapters/media-adapter.mjs"
export { MemactProvider, useContextClaim } from "./react.mjs"
38 changes: 38 additions & 0 deletions test/commit-adapter.test.mjs
Original file line number Diff line number Diff line change
@@ -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");
});