-
Notifications
You must be signed in to change notification settings - Fork 731
feat: add first-class Factory Droids support #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d287e68
1295bd6
e032ad8
5f65977
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "name": "agentmemory", | ||
| "owner": { | ||
| "name": "Rohit Ghumare", | ||
| "github": "rohitg00" | ||
| }, | ||
| "plugins": [ | ||
| { | ||
| "name": "agentmemory", | ||
| "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. Factory Droids integration.", | ||
| "source": "./plugin" | ||
| } | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "name": "agentmemory", | ||
| "version": "0.9.11", | ||
| "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 51 MCP tools, 4 skills, real-time viewer.", | ||
| "author": { | ||
|
Comment on lines
+1
to
+5
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in e032ad8: added test/factory-plugin.test.ts to verify plugin/.factory-plugin/plugin.json exists, matches the root package.json version, and resolves its skills, mcpServers, and hooks paths. |
||
| "name": "Rohit Ghumare", | ||
| "url": "https://github.com/rohitg00" | ||
| }, | ||
| "license": "Apache-2.0", | ||
| "homepage": "https://github.com/rohitg00/agentmemory", | ||
| "repository": "https://github.com/rohitg00/agentmemory", | ||
| "factoryVersion": ">=0.1.0", | ||
| "tags": ["memory", "persistence", "mcp", "knowledge-graph"], | ||
| "skills": "../skills/", | ||
| "mcpServers": "../.mcp.json", | ||
| "hooks": "../hooks/hooks.factory.json" | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "name": "session-start", | ||
| "event": "SessionStart", | ||
| "description": "Initialize session and inject context", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/session-start.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "user-prompt-submit", | ||
|
Comment on lines
+8
to
+14
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in e032ad8: the Factory plugin tests now validate every hooks.factory.json command uses the expected ${FACTORY_PLUGIN_ROOT}/scripts/*.mjs pattern and that each referenced script exists on disk. |
||
| "event": "UserPromptSubmit", | ||
| "description": "Capture user prompts", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/prompt-submit.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "pre-tool-use", | ||
| "event": "PreToolUse", | ||
| "description": "Capture tool intent", | ||
| "enabled": true, | ||
| "conditions": { | ||
| "toolNames": ["Edit", "Write", "Read", "Glob", "Grep", "bash", "run_command", "edit_file", "write_file"] | ||
| }, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/pre-tool-use.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "post-tool-use", | ||
| "event": "PostToolUse", | ||
| "description": "Capture tool output", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/post-tool-use.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "post-tool-use-failure", | ||
| "event": "PostToolUseFailure", | ||
| "description": "Capture tool errors", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/post-tool-failure.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "pre-compact", | ||
| "event": "PreCompact", | ||
| "description": "Hook before memory compaction", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/pre-compact.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "subagent-start", | ||
| "event": "SubagentStart", | ||
| "description": "Capture subagent execution", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/subagent-start.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "subagent-stop", | ||
| "event": "SubagentStop", | ||
| "description": "Capture subagent completion", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/subagent-stop.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "notification", | ||
| "event": "Notification", | ||
| "description": "Capture notifications", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/notification.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "task-completed", | ||
| "event": "TaskCompleted", | ||
| "description": "Capture task completion", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/task-completed.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "stop", | ||
| "event": "Stop", | ||
| "description": "Capture stop events", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/stop.mjs\"" | ||
| } | ||
| }, | ||
| { | ||
| "name": "session-end", | ||
| "event": "SessionEnd", | ||
| "description": "Consolidate memory at end of session", | ||
| "enabled": true, | ||
| "action": { | ||
| "type": "command", | ||
| "command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/session-end.mjs\"" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { existsSync, readFileSync } from "node:fs"; | ||
| import { join, resolve } from "node:path"; | ||
|
|
||
| const repoRoot = resolve(__dirname, ".."); | ||
| const pluginRoot = join(repoRoot, "plugin"); | ||
| const factoryPluginRoot = join(pluginRoot, ".factory-plugin"); | ||
|
|
||
| function readJson<T = unknown>(path: string): T { | ||
| return JSON.parse(readFileSync(path, "utf-8")) as T; | ||
| } | ||
|
|
||
| describe("Factory Droids plugin manifest", () => { | ||
| it("ships .factory-plugin/plugin.json with name + version + references", () => { | ||
| const manifestPath = join(factoryPluginRoot, "plugin.json"); | ||
| expect(existsSync(manifestPath)).toBe(true); | ||
| const manifest = readJson<{ | ||
| name: string; | ||
| version: string; | ||
| description?: string; | ||
| factoryVersion?: string; | ||
| skills?: string; | ||
| mcpServers?: string; | ||
| hooks?: string; | ||
| }>(manifestPath); | ||
| expect(manifest.name).toBe("agentmemory"); | ||
| expect(manifest.name).toMatch(/^[a-z][a-z0-9-]*$/); | ||
| expect(manifest.version).toMatch(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/); | ||
| expect(manifest.factoryVersion).toBeDefined(); | ||
| expect(manifest.skills).toBeDefined(); | ||
| expect(manifest.mcpServers).toBeDefined(); | ||
| expect(manifest.hooks).toBeDefined(); | ||
| }); | ||
|
|
||
| it("manifest version matches main package.json", () => { | ||
| const pkgVer = readJson<{ version: string }>(join(repoRoot, "package.json")).version; | ||
| const factoryVer = readJson<{ version: string }>( | ||
| join(factoryPluginRoot, "plugin.json"), | ||
| ).version; | ||
| expect(factoryVer).toBe(pkgVer); | ||
| }); | ||
|
|
||
| it("all referenced manifest paths resolve to existing files / directories", () => { | ||
| const manifest = readJson<{ skills: string; mcpServers: string; hooks: string }>( | ||
| join(factoryPluginRoot, "plugin.json"), | ||
| ); | ||
| expect(existsSync(join(factoryPluginRoot, manifest.skills))).toBe(true); | ||
| expect(existsSync(join(factoryPluginRoot, manifest.mcpServers))).toBe(true); | ||
| expect(existsSync(join(factoryPluginRoot, manifest.hooks))).toBe(true); | ||
| }); | ||
|
|
||
| it("hooks.factory.json registers the same first-class lifecycle events as Claude Code", () => { | ||
| const hooks = readJson<{ hooks: Array<{ event: string }> }>( | ||
| join(pluginRoot, "hooks/hooks.factory.json"), | ||
| ); | ||
| const events = hooks.hooks.map((hook) => hook.event).sort(); | ||
| expect(events).toEqual([ | ||
| "Notification", | ||
| "PostToolUse", | ||
| "PostToolUseFailure", | ||
| "PreCompact", | ||
| "PreToolUse", | ||
| "SessionEnd", | ||
| "SessionStart", | ||
| "Stop", | ||
| "SubagentStart", | ||
| "SubagentStop", | ||
| "TaskCompleted", | ||
| "UserPromptSubmit", | ||
| ]); | ||
| }); | ||
|
|
||
| it("hook command scripts referenced in hooks.factory.json exist on disk", () => { | ||
| const hooks = readJson<{ | ||
| hooks: Array<{ action: { type: string; command: string } }>; | ||
| }>(join(pluginRoot, "hooks/hooks.factory.json")); | ||
| const scriptRefs = new Set<string>(); | ||
| for (const hook of hooks.hooks) { | ||
| expect(hook.action.type).toBe("command"); | ||
| const match = hook.action.command.match( | ||
| /^node "\$\{FACTORY_PLUGIN_ROOT\}\/(scripts\/[^"\s]+\.mjs)"$/, | ||
| ); | ||
| expect(match, `invalid Factory hook command: ${hook.action.command}`).not.toBeNull(); | ||
| if (match) scriptRefs.add(match[1]); | ||
| } | ||
| expect(scriptRefs.size).toBeGreaterThan(0); | ||
| for (const rel of scriptRefs) { | ||
| expect(existsSync(join(pluginRoot, rel)), `missing hook script: ${rel}`).toBe(true); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe("Factory marketplace.json (.factory-plugin/marketplace.json at repo root)", () => { | ||
| it("ships a marketplace manifest pointing at the plugin/ subdirectory", () => { | ||
| const marketplacePath = join(repoRoot, ".factory-plugin/marketplace.json"); | ||
| expect(existsSync(marketplacePath)).toBe(true); | ||
| const marketplace = readJson<{ | ||
| name: string; | ||
| plugins: Array<{ | ||
| name: string; | ||
| source: string; | ||
| }>; | ||
| }>(marketplacePath); | ||
| expect(marketplace.name).toBe("agentmemory"); | ||
| expect(marketplace.plugins).toHaveLength(1); | ||
| const entry = marketplace.plugins[0]; | ||
| expect(entry.name).toBe("agentmemory"); | ||
| expect(entry.source).toBe("./plugin"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in e032ad8: added marketplace coverage for .factory-plugin/marketplace.json, including presence, plugin name, and source path ./plugin.