@empirical-gauzza/cli 1.0.9
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.
- package/bin/empirical.js +125 -0
- package/lib/auth.js +804 -0
- package/lib/constants.js +491 -0
- package/lib/deviceConfig.js +94 -0
- package/lib/doc.js +50 -0
- package/lib/doctor.js +116 -0
- package/lib/env.js +49 -0
- package/lib/fsUtils.js +90 -0
- package/lib/help.js +113 -0
- package/lib/http.js +179 -0
- package/lib/installWizard.js +383 -0
- package/lib/installers/claude.js +89 -0
- package/lib/installers/codex.js +78 -0
- package/lib/installers/copilot.js +77 -0
- package/lib/installers/detect.js +47 -0
- package/lib/installers/hooks.js +128 -0
- package/lib/installers/nativePlugin.js +87 -0
- package/lib/installers/promptBlocks.js +403 -0
- package/lib/kitCommands.js +622 -0
- package/lib/memoryCommands.js +528 -0
- package/lib/oauthClient.js +222 -0
- package/lib/pluginCommands.js +81 -0
- package/lib/pluginUpdateCheck.js +143 -0
- package/lib/policyCommands.js +144 -0
- package/lib/scheduledSync.js +331 -0
- package/lib/skillSync.js +508 -0
- package/lib/ui.js +280 -0
- package/lib/workspaceCommands.js +323 -0
- package/lib/workspaceContext.js +76 -0
- package/package.json +25 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log } = require('../ui');
|
|
6
|
+
const { ensureDir, writeFile, readJsonIfExists } = require('../fsUtils');
|
|
7
|
+
|
|
8
|
+
const CLAUDE_HOOK_SESSION_START_SOURCE = `#!/usr/bin/env node
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var spawnSync = require('child_process').spawnSync;
|
|
12
|
+
|
|
13
|
+
function commandExists(cmd) {
|
|
14
|
+
var probe = process.platform === 'win32'
|
|
15
|
+
? spawnSync('where', [cmd], { stdio: 'ignore', shell: true })
|
|
16
|
+
: spawnSync('which', [cmd], { stdio: 'ignore' });
|
|
17
|
+
return probe.status === 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
if (!commandExists('empirical')) {
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var result = spawnSync('empirical', [
|
|
26
|
+
'memory', 'query',
|
|
27
|
+
'--match', 'project context, conventions, recent decisions, user preferences',
|
|
28
|
+
'--top-k', '8'
|
|
29
|
+
], {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
timeout: 10000,
|
|
32
|
+
shell: process.platform === 'win32'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!result || result.status !== 0 || !result.stdout) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var results;
|
|
40
|
+
try {
|
|
41
|
+
results = JSON.parse(result.stdout);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var lines = results.slice(0, 8).map(function (r) {
|
|
51
|
+
var category = r && r.category ? r.category : 'memory';
|
|
52
|
+
var summary = r && r.summary ? r.summary : '';
|
|
53
|
+
return '- [' + category + '] ' + summary;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log('Empirical memory context for this session:\\n' + lines.join('\\n'));
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main();
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
function getEmpiricalHooksDir() {
|
|
64
|
+
return path.join(os.homedir(), '.empirical', 'hooks');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildHookCommand(scriptPath) {
|
|
68
|
+
return `"${process.execPath}" "${scriptPath}"`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function removeEmpiricalHookEntries(entries, scriptPath) {
|
|
72
|
+
if (!Array.isArray(entries)) return [];
|
|
73
|
+
return entries
|
|
74
|
+
.map((entry) => {
|
|
75
|
+
if (!entry || !Array.isArray(entry.hooks)) return entry;
|
|
76
|
+
const filteredHooks = entry.hooks.filter(
|
|
77
|
+
(h) => !(h && typeof h.command === 'string' && h.command.includes(scriptPath))
|
|
78
|
+
);
|
|
79
|
+
return { ...entry, hooks: filteredHooks };
|
|
80
|
+
})
|
|
81
|
+
.filter((entry) => entry && (!Array.isArray(entry.hooks) || entry.hooks.length > 0));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function installClaudeHooks() {
|
|
85
|
+
const hooksDir = getEmpiricalHooksDir();
|
|
86
|
+
ensureDir(hooksDir);
|
|
87
|
+
|
|
88
|
+
// Older installs registered a per-turn UserPromptSubmit hook here too. That traded an
|
|
89
|
+
// unconditional network call on every single prompt for determinism, which is wasteful —
|
|
90
|
+
// the native empirical-memory plugin now covers ad-hoc reads/writes via
|
|
91
|
+
// Bash without that per-turn tax. SessionStart remains: one cheap call per session boundary.
|
|
92
|
+
const userPromptScriptPath = path.join(hooksDir, 'claude-user-prompt-submit.js');
|
|
93
|
+
const sessionStartScriptPath = path.join(hooksDir, 'claude-session-start.js');
|
|
94
|
+
writeFile(sessionStartScriptPath, CLAUDE_HOOK_SESSION_START_SOURCE);
|
|
95
|
+
|
|
96
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
97
|
+
const settings = readJsonIfExists(settingsPath) || {};
|
|
98
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
99
|
+
settings.hooks = {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Clean up the old per-turn hook entry if a prior install left one behind.
|
|
103
|
+
if (Array.isArray(settings.hooks.UserPromptSubmit)) {
|
|
104
|
+
settings.hooks.UserPromptSubmit = removeEmpiricalHookEntries(settings.hooks.UserPromptSubmit, userPromptScriptPath);
|
|
105
|
+
if (settings.hooks.UserPromptSubmit.length === 0) {
|
|
106
|
+
delete settings.hooks.UserPromptSubmit;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
settings.hooks.SessionStart = removeEmpiricalHookEntries(settings.hooks.SessionStart, sessionStartScriptPath);
|
|
111
|
+
for (const matcher of ['startup', 'resume', 'compact']) {
|
|
112
|
+
settings.hooks.SessionStart.push({
|
|
113
|
+
matcher,
|
|
114
|
+
hooks: [{ type: 'command', command: buildHookCommand(sessionStartScriptPath), timeout: 15 }]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
119
|
+
log(' - Claude SessionStart hook installed for deterministic memory lookups.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
CLAUDE_HOOK_SESSION_START_SOURCE,
|
|
124
|
+
getEmpiricalHooksDir,
|
|
125
|
+
buildHookCommand,
|
|
126
|
+
removeEmpiricalHookEntries,
|
|
127
|
+
installClaudeHooks
|
|
128
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { runResolvedCommand, summarizeProcessOutput } = require('../fsUtils');
|
|
4
|
+
|
|
5
|
+
const EMPIRICAL_PLUGIN_SOURCE = process.env.EMPIRICAL_PLUGIN_SOURCE || 'mattgauzza/empirical-memory-plugin';
|
|
6
|
+
const CORE_PLUGIN_NAME = 'empirical-memory';
|
|
7
|
+
const OPTIONAL_PLUGIN_NAMES = ['empirical-work-history', 'empirical-scar-tissue', 'empirical-todo'];
|
|
8
|
+
|
|
9
|
+
function runPluginCommand(command, args, options = {}) {
|
|
10
|
+
return runResolvedCommand(command, args, { stdio: 'inherit', ...options });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function assertPluginInstallSucceeded(agentId, result) {
|
|
14
|
+
if (result && result.status === 0) return;
|
|
15
|
+
const detail = result ? summarizeProcessOutput(result) : 'no process result';
|
|
16
|
+
throw new Error(`${agentId} plugin installation failed.${detail ? ` Details: ${detail}` : ''}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function updateOrInstall(agentId, command, updateArgs, installArgs) {
|
|
20
|
+
const updateResult = runPluginCommand(command, updateArgs);
|
|
21
|
+
if (updateResult && updateResult.status === 0) return updateResult;
|
|
22
|
+
|
|
23
|
+
const installResult = runPluginCommand(command, installArgs);
|
|
24
|
+
assertPluginInstallSucceeded(agentId, installResult);
|
|
25
|
+
return installResult;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizePluginNames(pluginNames = [CORE_PLUGIN_NAME]) {
|
|
29
|
+
const names = Array.isArray(pluginNames) ? pluginNames : [pluginNames];
|
|
30
|
+
const normalized = [...new Set(names.map(String))];
|
|
31
|
+
if (!normalized.includes(CORE_PLUGIN_NAME)) normalized.unshift(CORE_PLUGIN_NAME);
|
|
32
|
+
const invalid = normalized.filter((name) => name !== CORE_PLUGIN_NAME && !OPTIONAL_PLUGIN_NAMES.includes(name));
|
|
33
|
+
if (invalid.length) throw new Error(`Unsupported Empirical plugin(s): ${invalid.join(', ')}`);
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function installNativePlugin(agentId, pluginNames = [CORE_PLUGIN_NAME]) {
|
|
38
|
+
const names = normalizePluginNames(pluginNames);
|
|
39
|
+
if (agentId === 'claude') {
|
|
40
|
+
// Marketplace registration is intentionally best-effort: native Claude returns non-zero
|
|
41
|
+
// when the same Git marketplace is already registered, but the install command is still
|
|
42
|
+
// authoritative and idempotent.
|
|
43
|
+
runPluginCommand('claude', ['plugin', 'marketplace', 'add', EMPIRICAL_PLUGIN_SOURCE], { stdio: 'pipe' });
|
|
44
|
+
for (const name of names) {
|
|
45
|
+
updateOrInstall(
|
|
46
|
+
agentId,
|
|
47
|
+
'claude',
|
|
48
|
+
['plugin', 'update', `${name}@empirical-memory`, '--scope', 'user'],
|
|
49
|
+
['plugin', 'install', name, '--scope', 'user']
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (agentId === 'codex') {
|
|
56
|
+
runPluginCommand('codex', ['plugin', 'marketplace', 'add', EMPIRICAL_PLUGIN_SOURCE], { stdio: 'pipe' });
|
|
57
|
+
// Refresh the Git marketplace snapshot before reinstalling an existing plugin.
|
|
58
|
+
runPluginCommand('codex', ['plugin', 'marketplace', 'upgrade', 'empirical-memory'], { stdio: 'pipe' });
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
const result = runPluginCommand('codex', ['plugin', 'add', `${name}@empirical-memory`]);
|
|
61
|
+
assertPluginInstallSucceeded(agentId, result);
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (agentId === 'copilot') {
|
|
67
|
+
// Copilot is deprecating direct repository installs. Register the GitHub
|
|
68
|
+
// marketplace first, then use the marketplace-qualified plugin name.
|
|
69
|
+
runPluginCommand('copilot', ['plugin', 'marketplace', 'add', EMPIRICAL_PLUGIN_SOURCE], { stdio: 'pipe' });
|
|
70
|
+
runPluginCommand('copilot', ['plugin', 'marketplace', 'update', 'empirical-memory'], { stdio: 'pipe' });
|
|
71
|
+
for (const name of names) {
|
|
72
|
+
const result = runPluginCommand('copilot', ['plugin', 'install', `${name}@empirical-memory`]);
|
|
73
|
+
assertPluginInstallSucceeded(agentId, result);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(`Unsupported plugin target: ${agentId}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
EMPIRICAL_PLUGIN_SOURCE,
|
|
83
|
+
CORE_PLUGIN_NAME,
|
|
84
|
+
OPTIONAL_PLUGIN_NAMES,
|
|
85
|
+
normalizePluginNames,
|
|
86
|
+
installNativePlugin
|
|
87
|
+
};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { log, warn, uiHeader, uiSuccess, uiDim, ask, askYesNo, askWorkflowMultiSelect } = require('../ui');
|
|
6
|
+
const INSTALL_PROMPT_OPTIONS_DEFAULTS = Object.freeze({
|
|
7
|
+
queryBeforeCodingTask: false,
|
|
8
|
+
requireMemoryInWorkflows: true,
|
|
9
|
+
requiredWorkflows: [
|
|
10
|
+
'research',
|
|
11
|
+
'debugging',
|
|
12
|
+
'spec_prd',
|
|
13
|
+
'status',
|
|
14
|
+
'handoff',
|
|
15
|
+
'personal_health',
|
|
16
|
+
'lead_management',
|
|
17
|
+
'assistant_ops'
|
|
18
|
+
],
|
|
19
|
+
allowDiscretionaryMemoryUsage: true,
|
|
20
|
+
stopOnMemoryToolFailure: true,
|
|
21
|
+
rememberCodingStyle: true,
|
|
22
|
+
rememberArchitectureDecisions: true,
|
|
23
|
+
recordWorkHistory: true,
|
|
24
|
+
recordScarTissue: true,
|
|
25
|
+
recordTodos: false,
|
|
26
|
+
storeInstructionArtifacts: false,
|
|
27
|
+
checkMemoryBeforeAskingContext: true,
|
|
28
|
+
durableWritesOnly: true
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const WORKFLOW_POLICY_OPTIONS = Object.freeze([
|
|
32
|
+
{ id: 'research', label: 'research' },
|
|
33
|
+
{ id: 'debugging', label: 'debugging' },
|
|
34
|
+
{ id: 'spec_prd', label: 'spec/prd' },
|
|
35
|
+
{ id: 'status', label: 'status' },
|
|
36
|
+
{ id: 'handoff', label: 'handoff' },
|
|
37
|
+
{ id: 'personal_health', label: 'personal health' },
|
|
38
|
+
{ id: 'lead_management', label: 'lead management' },
|
|
39
|
+
{ id: 'assistant_ops', label: 'assistant ops' }
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function normalizeRequiredWorkflows(value) {
|
|
43
|
+
const valid = new Set(WORKFLOW_POLICY_OPTIONS.map((option) => option.id));
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
return [...INSTALL_PROMPT_OPTIONS_DEFAULTS.requiredWorkflows];
|
|
46
|
+
}
|
|
47
|
+
const normalized = value.filter((entry) => valid.has(entry));
|
|
48
|
+
return normalized.length ? normalized : [...INSTALL_PROMPT_OPTIONS_DEFAULTS.requiredWorkflows];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function workflowIdsToLabels(workflowIds = []) {
|
|
52
|
+
const byId = new Map(WORKFLOW_POLICY_OPTIONS.map((option) => [option.id, option.label]));
|
|
53
|
+
return workflowIds.map((id) => byId.get(id) || id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeInstallPromptOptions(options = {}) {
|
|
57
|
+
return {
|
|
58
|
+
queryBeforeCodingTask:
|
|
59
|
+
options.queryBeforeCodingTask !== false,
|
|
60
|
+
requireMemoryInWorkflows:
|
|
61
|
+
options.requireMemoryInWorkflows !== false,
|
|
62
|
+
requiredWorkflows:
|
|
63
|
+
normalizeRequiredWorkflows(options.requiredWorkflows),
|
|
64
|
+
allowDiscretionaryMemoryUsage:
|
|
65
|
+
options.allowDiscretionaryMemoryUsage !== false,
|
|
66
|
+
stopOnMemoryToolFailure:
|
|
67
|
+
options.stopOnMemoryToolFailure !== false,
|
|
68
|
+
rememberCodingStyle:
|
|
69
|
+
options.rememberCodingStyle !== false,
|
|
70
|
+
rememberArchitectureDecisions:
|
|
71
|
+
options.rememberArchitectureDecisions !== false,
|
|
72
|
+
recordWorkHistory:
|
|
73
|
+
options.recordWorkHistory !== false,
|
|
74
|
+
recordScarTissue:
|
|
75
|
+
options.recordScarTissue !== false,
|
|
76
|
+
recordTodos:
|
|
77
|
+
options.recordTodos === true,
|
|
78
|
+
storeInstructionArtifacts:
|
|
79
|
+
options.storeInstructionArtifacts === true,
|
|
80
|
+
checkMemoryBeforeAskingContext:
|
|
81
|
+
options.checkMemoryBeforeAskingContext !== false,
|
|
82
|
+
durableWritesOnly:
|
|
83
|
+
options.durableWritesOnly !== false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildSharedEmpiricalPolicyLines(options = {}) {
|
|
88
|
+
const policy = normalizeInstallPromptOptions(options);
|
|
89
|
+
const lines = [];
|
|
90
|
+
|
|
91
|
+
if (policy.queryBeforeCodingTask) {
|
|
92
|
+
lines.push('- Before coding, call `query_memories` once for relevant repo/user context.');
|
|
93
|
+
}
|
|
94
|
+
if (policy.requireMemoryInWorkflows) {
|
|
95
|
+
const workflowLabels = workflowIdsToLabels(policy.requiredWorkflows);
|
|
96
|
+
lines.push(`- For these workflows (${workflowLabels.join(', ')}), call \`query_memories\` before substantive response.`);
|
|
97
|
+
}
|
|
98
|
+
if (policy.allowDiscretionaryMemoryUsage) {
|
|
99
|
+
lines.push('- Outside required workflows, use Empirical proactively when prior context is likely useful.');
|
|
100
|
+
} else {
|
|
101
|
+
lines.push('- Outside required workflows, use Empirical only when explicitly requested by the user.');
|
|
102
|
+
}
|
|
103
|
+
if (policy.stopOnMemoryToolFailure) {
|
|
104
|
+
lines.push('- If Empirical tools fail, stop and report the failure before making code changes.');
|
|
105
|
+
}
|
|
106
|
+
lines.push('- If a memory call returns auth/session errors (`Auth required`, `invalid_token`, `Authentication required`, `Transport send error`), treat it as expired MCP auth.');
|
|
107
|
+
lines.push('- Retry the same MCP tool call once before taking any other action.');
|
|
108
|
+
lines.push('- If retry fails, refresh Empirical MCP auth for the active client before continuing memory-dependent work:');
|
|
109
|
+
lines.push('- Codex CLI: run `codex mcp logout empirical` then `codex mcp login empirical`.');
|
|
110
|
+
lines.push('- Claude Code: run `/mcp`, reconnect/re-authenticate the Empirical server, then retry the failed tool call.');
|
|
111
|
+
lines.push('- Copilot CLI: complete the MCP OAuth reconnect/sign-in flow for Empirical in Copilot, then retry the failed tool call.');
|
|
112
|
+
lines.push('- After re-authentication, retry the original tool call once; if it still fails, stop and ask the user to repair MCP auth/connectivity.');
|
|
113
|
+
lines.push('- If memory conflicts with the latest repo/user instruction, follow repo/user instruction.');
|
|
114
|
+
if (policy.rememberCodingStyle) {
|
|
115
|
+
lines.push('- Persist durable coding style preferences and conventions when they are established.');
|
|
116
|
+
}
|
|
117
|
+
if (policy.rememberArchitectureDecisions) {
|
|
118
|
+
lines.push('- Persist durable architecture/design decisions for future sessions.');
|
|
119
|
+
}
|
|
120
|
+
if (policy.recordWorkHistory) {
|
|
121
|
+
lines.push('- After meaningful completed work, record a concise timestamped work-history summary with what changed, why, verification, and any next action.');
|
|
122
|
+
}
|
|
123
|
+
if (policy.recordScarTissue) {
|
|
124
|
+
lines.push('- When a recurring failure, sharp edge, or corrected misconception is discovered, record concise scar-tissue guidance with the symptom, cause, prevention, and verification.');
|
|
125
|
+
} else {
|
|
126
|
+
lines.push('- Do not create scar-tissue guidance unless the user explicitly asks for it.');
|
|
127
|
+
}
|
|
128
|
+
if (policy.storeInstructionArtifacts) {
|
|
129
|
+
lines.push('- Query Empirical first for relevant instruction artifacts (AGENTS.md/CLAUDE.md/skills-style guidance).');
|
|
130
|
+
lines.push('- If task needs exact syntax or implementation details, open the source file directly.');
|
|
131
|
+
lines.push('- Persist this type of durable instruction context to Empirical for recall: concise rules, conventions, decision summaries, source path, and optional version/hash pointer.');
|
|
132
|
+
lines.push('- Use update-first writes: when an artifact/topic already exists, update it instead of creating a new memory.');
|
|
133
|
+
lines.push('- Create a new memory only when materially new (new decision, new version/hash, or new scope).');
|
|
134
|
+
lines.push('- Apply a soft cap of 1-2 instruction-artifact writes per task; prefer merge/update over additional creates.');
|
|
135
|
+
lines.push('- Do not store full raw file dumps by default; store concise, durable extracts that are easy to query.');
|
|
136
|
+
}
|
|
137
|
+
if (policy.checkMemoryBeforeAskingContext) {
|
|
138
|
+
lines.push('- Check memory before asking the user to restate prior context.');
|
|
139
|
+
}
|
|
140
|
+
lines.push('- Use available Empirical MCP tools when appropriate: record_graph_memory, get_memory, list_memories, query_memories, add_note_by_query, update_memory_by_query, delete_memory_by_query, get_user.');
|
|
141
|
+
if (policy.durableWritesOnly) {
|
|
142
|
+
lines.push('- Persist only durable context; never store secrets, tokens, or one-off transient values.');
|
|
143
|
+
}
|
|
144
|
+
lines.push('- Keep writes concise, factual, and easy to query.');
|
|
145
|
+
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function workflowLabelsSummary(options = {}) {
|
|
150
|
+
const policy = normalizeInstallPromptOptions(options);
|
|
151
|
+
if (!policy.requireMemoryInWorkflows) {
|
|
152
|
+
return 'research, debugging, spec/PRD work, status updates, and handoffs';
|
|
153
|
+
}
|
|
154
|
+
return workflowIdsToLabels(policy.requiredWorkflows).join(', ');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildEmpiricalSkillFrontmatter(options = {}) {
|
|
158
|
+
return {
|
|
159
|
+
name: 'empirical-memory',
|
|
160
|
+
// Keep frontmatter deliberately simple: Copilot's YAML parser rejects an
|
|
161
|
+
// unquoted colon in a plain scalar, and does not support when_to_use.
|
|
162
|
+
description: 'Use Empirical CLI and MCP to recall and save durable project context across sessions and projects.'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildEmpiricalSkillBody(options = {}) {
|
|
167
|
+
const policyLines = buildSharedEmpiricalPolicyLines(options);
|
|
168
|
+
const workflows = workflowLabelsSummary(options);
|
|
169
|
+
return `## Empirical Memory - Use It Like grep/view
|
|
170
|
+
|
|
171
|
+
Use this skill for ${workflows}, whenever prior context may matter, and whenever the user asks to remember, recall, update, or forget information.
|
|
172
|
+
|
|
173
|
+
Prefer calling the \`empirical\` CLI directly via the available shell over the MCP tools when both are
|
|
174
|
+
available: \`empirical memory query|get|list|record|note|update|delete\`. The CLI doesn't require an
|
|
175
|
+
extra tool-discovery step the way the deferred Empirical MCP tools do. The MCP tools
|
|
176
|
+
(\`get_memory\`, \`list_memories\`, \`query_memories\`, \`record_graph_memory\`, \`add_note_by_query\`, \`update_memory_by_query\`,
|
|
177
|
+
\`delete_memory_by_query\`, \`get_user\`) remain available as a fallback or for structured/graph
|
|
178
|
+
queries the CLI doesn't cover.
|
|
179
|
+
|
|
180
|
+
### Policy
|
|
181
|
+
${policyLines.join('\n')}
|
|
182
|
+
|
|
183
|
+
### Authored Skills - push what you write
|
|
184
|
+
- If you author a new \`SKILL.md\` for the user, or substantially edit an existing one, during a
|
|
185
|
+
session, tell the user about \`empirical skill push <path/to/SKILL.md>\` afterward so the skill
|
|
186
|
+
can follow them across machines/projects instead of staying local to this one.
|
|
187
|
+
- Offer, don't auto-push: surface the option (or offer to run it) and get the user's
|
|
188
|
+
confirmation before running \`empirical skill push\`. Do not run it unprompted the moment a new
|
|
189
|
+
or edited \`SKILL.md\` is detected — it uploads content to the user's account, which is a step
|
|
190
|
+
above a purely local file write.
|
|
191
|
+
- Run \`empirical doc\` or \`empirical skill push --help\` via the available shell for exact flags/usage.
|
|
192
|
+
|
|
193
|
+
### Command reference
|
|
194
|
+
Run \`empirical doc memory\` via the available shell for exact flags/usage before guessing syntax.
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildEmpiricalSkillMarkdown(options = {}) {
|
|
199
|
+
const frontmatter = buildEmpiricalSkillFrontmatter(options);
|
|
200
|
+
const body = buildEmpiricalSkillBody(options);
|
|
201
|
+
return `---
|
|
202
|
+
name: ${frontmatter.name}
|
|
203
|
+
description: ${frontmatter.description}
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
${body}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function formatPromptOptionSummary(promptOptions) {
|
|
210
|
+
const entries = [
|
|
211
|
+
['Require memory in selected workflows', promptOptions.requireMemoryInWorkflows],
|
|
212
|
+
['Allow discretionary memory usage', promptOptions.allowDiscretionaryMemoryUsage],
|
|
213
|
+
['Stop on Empirical tool failure', promptOptions.stopOnMemoryToolFailure],
|
|
214
|
+
['Store coding style preferences', promptOptions.rememberCodingStyle],
|
|
215
|
+
['Store architecture decisions', promptOptions.rememberArchitectureDecisions],
|
|
216
|
+
['Record timestamped work history', promptOptions.recordWorkHistory],
|
|
217
|
+
['Record scar-tissue guidance', promptOptions.recordScarTissue],
|
|
218
|
+
['Enable project-based Todo skill', promptOptions.recordTodos],
|
|
219
|
+
['Store/recall instruction artifacts', promptOptions.storeInstructionArtifacts],
|
|
220
|
+
['Check memory before asking for context', promptOptions.checkMemoryBeforeAskingContext],
|
|
221
|
+
['Store durable context only', promptOptions.durableWritesOnly]
|
|
222
|
+
];
|
|
223
|
+
const lines = entries
|
|
224
|
+
.map(([label, enabled]) => ` ${enabled ? '[x]' : '[ ]'} ${label}`)
|
|
225
|
+
.join('\n');
|
|
226
|
+
const workflowLine = ` [${promptOptions.requireMemoryInWorkflows ? 'x' : ' '}] Required workflows: ${workflowIdsToLabels(promptOptions.requiredWorkflows).join(', ')}`;
|
|
227
|
+
return `${lines}\n${workflowLine}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function collectInstallerPromptOptions() {
|
|
231
|
+
if (!process.stdin.isTTY) {
|
|
232
|
+
return {
|
|
233
|
+
savePolicy: true,
|
|
234
|
+
promptOptions: { ...INSTALL_PROMPT_OPTIONS_DEFAULTS }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const home = os.homedir();
|
|
239
|
+
log(uiHeader('Global instruction files'));
|
|
240
|
+
log(` - Copilot: ${path.join(home, '.copilot', 'copilot-instructions.md')}`);
|
|
241
|
+
log(` - Codex: ${path.join(home, '.codex', 'AGENTS.md')}`);
|
|
242
|
+
log(` - Claude: ${path.join(home, '.claude', 'CLAUDE.md')}`);
|
|
243
|
+
log('');
|
|
244
|
+
|
|
245
|
+
const savePolicy = await askYesNo(
|
|
246
|
+
'Save these personalized Empirical behavior preferences for native skills?',
|
|
247
|
+
true
|
|
248
|
+
);
|
|
249
|
+
log('');
|
|
250
|
+
if (!savePolicy) {
|
|
251
|
+
log(uiSuccess('Personalized behavior policy not saved; native skills will use safe defaults.'));
|
|
252
|
+
log('');
|
|
253
|
+
return {
|
|
254
|
+
savePolicy: false,
|
|
255
|
+
promptOptions: { ...INSTALL_PROMPT_OPTIONS_DEFAULTS }
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
log(uiHeader('Empirical policy setup'));
|
|
260
|
+
log(uiDim('Answer a few questions to configure native Empirical skills.'));
|
|
261
|
+
log('');
|
|
262
|
+
|
|
263
|
+
const requireMemoryInWorkflows = await askYesNo(
|
|
264
|
+
'Require Empirical memory-first behavior in specific workflows?',
|
|
265
|
+
true
|
|
266
|
+
);
|
|
267
|
+
let requiredWorkflows = [...INSTALL_PROMPT_OPTIONS_DEFAULTS.requiredWorkflows];
|
|
268
|
+
if (requireMemoryInWorkflows) {
|
|
269
|
+
log('');
|
|
270
|
+
requiredWorkflows = await askWorkflowMultiSelect(
|
|
271
|
+
'Which workflows should require memory-first behavior? (all preselected)',
|
|
272
|
+
WORKFLOW_POLICY_OPTIONS,
|
|
273
|
+
INSTALL_PROMPT_OPTIONS_DEFAULTS.requiredWorkflows
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const allowDiscretionaryMemoryUsage = await askYesNo(
|
|
277
|
+
'Allow discretionary Empirical use outside required workflows?',
|
|
278
|
+
true
|
|
279
|
+
);
|
|
280
|
+
const stopOnMemoryToolFailure = await askYesNo(
|
|
281
|
+
'If Empirical tools fail, should the agent stop before code changes?',
|
|
282
|
+
true
|
|
283
|
+
);
|
|
284
|
+
const rememberCodingStyle = await askYesNo(
|
|
285
|
+
'Do you want Empirical to store coding style preferences?',
|
|
286
|
+
true
|
|
287
|
+
);
|
|
288
|
+
const rememberArchitectureDecisions = await askYesNo(
|
|
289
|
+
'Do you want Empirical to store architecture/design decisions?',
|
|
290
|
+
true
|
|
291
|
+
);
|
|
292
|
+
const recordWorkHistory = await askYesNo(
|
|
293
|
+
'Should Empirical record concise timestamped summaries of completed work for future agents?',
|
|
294
|
+
true
|
|
295
|
+
);
|
|
296
|
+
const recordScarTissue = await askYesNo(
|
|
297
|
+
'Should Empirical record recurring failure patterns and prevention guidance (scar tissue) for future agents?',
|
|
298
|
+
true
|
|
299
|
+
);
|
|
300
|
+
const recordTodos = await askYesNo(
|
|
301
|
+
'Install the project-based Todo skill for actionable follow-ups?',
|
|
302
|
+
false
|
|
303
|
+
);
|
|
304
|
+
const storeInstructionArtifacts = await askYesNo(
|
|
305
|
+
'Store and recall instruction artifacts (AGENTS.md/CLAUDE.md/skills-style rules) in Empirical?',
|
|
306
|
+
false
|
|
307
|
+
);
|
|
308
|
+
const checkMemoryBeforeAskingContext = await askYesNo(
|
|
309
|
+
'Should agents check memory before asking you to restate context?',
|
|
310
|
+
true
|
|
311
|
+
);
|
|
312
|
+
const durableWritesOnly = await askYesNo(
|
|
313
|
+
'Restrict writes to durable context only (never secrets/tokens/transient values)?',
|
|
314
|
+
true
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const promptOptions = normalizeInstallPromptOptions({
|
|
318
|
+
queryBeforeCodingTask: false,
|
|
319
|
+
requireMemoryInWorkflows,
|
|
320
|
+
requiredWorkflows,
|
|
321
|
+
allowDiscretionaryMemoryUsage,
|
|
322
|
+
stopOnMemoryToolFailure,
|
|
323
|
+
rememberCodingStyle,
|
|
324
|
+
rememberArchitectureDecisions,
|
|
325
|
+
recordWorkHistory,
|
|
326
|
+
recordScarTissue,
|
|
327
|
+
recordTodos,
|
|
328
|
+
storeInstructionArtifacts,
|
|
329
|
+
checkMemoryBeforeAskingContext,
|
|
330
|
+
durableWritesOnly
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
log('');
|
|
334
|
+
log(uiSuccess('Policy profile captured.'));
|
|
335
|
+
log(formatPromptOptionSummary(promptOptions));
|
|
336
|
+
log('');
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
savePolicy: true,
|
|
340
|
+
promptOptions
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function buildSkillFileContent({ mcpEnabled = false } = {}) {
|
|
345
|
+
if (mcpEnabled) {
|
|
346
|
+
return `# Empirical Memory Skill
|
|
347
|
+
|
|
348
|
+
Empirical memory is installed via MCP. Use MCP tools as the primary interface.
|
|
349
|
+
|
|
350
|
+
## MCP tools (primary)
|
|
351
|
+
|
|
352
|
+
- \`query_memories\` — search memories by natural language
|
|
353
|
+
- \`record_graph_memory\` — save a new memory
|
|
354
|
+
- \`add_note_by_query\` — append a note to an existing memory
|
|
355
|
+
- \`update_memory_by_query\` — update fields on an existing memory
|
|
356
|
+
- \`delete_memory_by_query\` — delete a memory by query
|
|
357
|
+
- \`get_user\` — get current user identity and workspace
|
|
358
|
+
|
|
359
|
+
## CLI fallback (when MCP is unavailable)
|
|
360
|
+
|
|
361
|
+
empirical doc List all available topics
|
|
362
|
+
empirical doc <topic> Load full CLI instructions for that topic
|
|
363
|
+
empirical doctor Verify connectivity and token status
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return `# Empirical Memory Skill
|
|
368
|
+
|
|
369
|
+
Empirical memory is installed in this environment.
|
|
370
|
+
|
|
371
|
+
## Discover available commands
|
|
372
|
+
|
|
373
|
+
empirical doc List all available topics
|
|
374
|
+
empirical doc <topic> Load full instructions for that topic
|
|
375
|
+
empirical doctor Verify connectivity and token status
|
|
376
|
+
|
|
377
|
+
## Topics
|
|
378
|
+
|
|
379
|
+
memory Query, record, update, and delete memories
|
|
380
|
+
user Get current user identity
|
|
381
|
+
workspace List and switch workspaces
|
|
382
|
+
auth Token types, bootstrap, and refresh
|
|
383
|
+
agents Integration modes and SKILL file paths
|
|
384
|
+
install Install Empirical for Codex / Copilot / Claude Code
|
|
385
|
+
|
|
386
|
+
Load any topic on demand — for example: \`empirical doc memory\` or \`empirical doc user\`.
|
|
387
|
+
`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
module.exports = {
|
|
391
|
+
INSTALL_PROMPT_OPTIONS_DEFAULTS,
|
|
392
|
+
WORKFLOW_POLICY_OPTIONS,
|
|
393
|
+
normalizeRequiredWorkflows,
|
|
394
|
+
workflowIdsToLabels,
|
|
395
|
+
normalizeInstallPromptOptions,
|
|
396
|
+
buildSharedEmpiricalPolicyLines,
|
|
397
|
+
workflowLabelsSummary,
|
|
398
|
+
buildEmpiricalSkillFrontmatter,
|
|
399
|
+
buildEmpiricalSkillBody,
|
|
400
|
+
buildEmpiricalSkillMarkdown,
|
|
401
|
+
formatPromptOptionSummary,
|
|
402
|
+
collectInstallerPromptOptions
|
|
403
|
+
};
|