engineering-memory 0.1.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.
- package/bin/engineering-memory.mjs +120 -0
- package/dispatcher/managed-section.mjs +59 -0
- package/dispatcher/sections.mjs +14 -0
- package/install/api-url.mjs +39 -0
- package/install/cli.mjs +93 -0
- package/install/commands.mjs +140 -0
- package/install/files.mjs +416 -0
- package/install/git-hook.mjs +270 -0
- package/install/installer.mjs +279 -0
- package/install/mcp-registration.mjs +457 -0
- package/package.json +28 -0
- package/runtime/dist/src/auth/browser-auth.js +184 -0
- package/runtime/dist/src/auth/credential-store.js +181 -0
- package/runtime/dist/src/cache/etag-cache.js +123 -0
- package/runtime/dist/src/config.js +59 -0
- package/runtime/dist/src/git/git-inspector.js +375 -0
- package/runtime/dist/src/git/pre-commit.js +44 -0
- package/runtime/dist/src/git/verification-gate.js +221 -0
- package/runtime/dist/src/index.js +60 -0
- package/runtime/dist/src/journal/journal-store.js +1300 -0
- package/runtime/dist/src/mcp/server.js +11 -0
- package/runtime/dist/src/mcp/tool-definitions.js +405 -0
- package/runtime/dist/src/project/repository.js +79 -0
- package/runtime/dist/src/runtime/active-context-store.js +356 -0
- package/runtime/dist/src/runtime/api-client.js +229 -0
- package/runtime/dist/src/runtime/bridge-service.js +2226 -0
- package/runtime/dist/src/runtime/offline-outbox.js +274 -0
- package/runtime/dist/src/runtime/principal-state.js +97 -0
- package/runtime/dist/src/types.js +2 -0
- package/runtime/dist/src/utilities/files.js +189 -0
- package/runtime/dist/src/utilities/hash.js +19 -0
- package/runtime/dist/src/utilities/process.js +32 -0
- package/runtime/package-lock.json +137 -0
- package/runtime/package.json +32 -0
- package/skill/SKILL.md +29 -0
- package/skill/agents/openai.yaml +6 -0
- package/skill/references/lifecycle.md +102 -0
- package/skill/references/memory-updates.md +25 -0
- package/skill/references/questionnaires.md +98 -0
- package/skill/references/scaffolding.md +38 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dispatcherSections } from '../dispatcher/sections.mjs';
|
|
5
|
+
import { upsertManagedSection } from '../dispatcher/managed-section.mjs';
|
|
6
|
+
import { normalizeApiUrl } from './api-url.mjs';
|
|
7
|
+
import { runCommand } from './commands.mjs';
|
|
8
|
+
import {
|
|
9
|
+
exists,
|
|
10
|
+
installManagedBridgeRuntime,
|
|
11
|
+
installManagedDirectory,
|
|
12
|
+
InstallTransaction,
|
|
13
|
+
readJson,
|
|
14
|
+
readText,
|
|
15
|
+
replaceFile,
|
|
16
|
+
writeJson,
|
|
17
|
+
} from './files.mjs';
|
|
18
|
+
import { applyGitHook, planGitHook } from './git-hook.mjs';
|
|
19
|
+
import {
|
|
20
|
+
applyMcpRegistrations,
|
|
21
|
+
planMcpRegistrations,
|
|
22
|
+
} from './mcp-registration.mjs';
|
|
23
|
+
|
|
24
|
+
const currentDirectory = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const defaultClientRoot = resolve(currentDirectory, '..');
|
|
26
|
+
const defaultProductRoot = resolve(defaultClientRoot, '..');
|
|
27
|
+
|
|
28
|
+
export const defaultInstallPaths = Object.freeze({
|
|
29
|
+
skillSource: join(defaultClientRoot, 'skill-source', 'engineering-memory'),
|
|
30
|
+
bridgeRuntimeSource: join(defaultProductRoot, 'bridge'),
|
|
31
|
+
bridgeEntry: join(defaultProductRoot, 'bridge', 'dist', 'src', 'index.js'),
|
|
32
|
+
gateEntry: join(
|
|
33
|
+
defaultProductRoot,
|
|
34
|
+
'bridge',
|
|
35
|
+
'dist',
|
|
36
|
+
'src',
|
|
37
|
+
'git',
|
|
38
|
+
'pre-commit.js',
|
|
39
|
+
),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export async function installEngineeringMemory(options = {}) {
|
|
43
|
+
const homeDir = resolve(options.homeDir ?? homedir());
|
|
44
|
+
const skillSource = resolve(
|
|
45
|
+
options.skillSource ?? defaultInstallPaths.skillSource,
|
|
46
|
+
);
|
|
47
|
+
const bridgeRuntimeSource = resolve(
|
|
48
|
+
options.bridgeRuntimeSource ?? defaultInstallPaths.bridgeRuntimeSource,
|
|
49
|
+
);
|
|
50
|
+
const sourceBridgeEntry = resolve(
|
|
51
|
+
options.bridgeEntry ?? join(bridgeRuntimeSource, 'dist', 'src', 'index.js'),
|
|
52
|
+
);
|
|
53
|
+
const sourceGateEntry = resolve(
|
|
54
|
+
options.gateEntry ??
|
|
55
|
+
join(bridgeRuntimeSource, 'dist', 'src', 'git', 'pre-commit.js'),
|
|
56
|
+
);
|
|
57
|
+
const runtimePath = join(homeDir, '.engineering-memory', 'runtime', 'bridge');
|
|
58
|
+
const bridgeEntry = stableRuntimeEntry(
|
|
59
|
+
bridgeRuntimeSource,
|
|
60
|
+
runtimePath,
|
|
61
|
+
sourceBridgeEntry,
|
|
62
|
+
'Bridge entry',
|
|
63
|
+
);
|
|
64
|
+
const gateEntry = stableRuntimeEntry(
|
|
65
|
+
bridgeRuntimeSource,
|
|
66
|
+
runtimePath,
|
|
67
|
+
sourceGateEntry,
|
|
68
|
+
'Git gate entry',
|
|
69
|
+
);
|
|
70
|
+
const nodePath = resolve(options.nodePath ?? process.execPath);
|
|
71
|
+
const development = options.development === true;
|
|
72
|
+
const apiUrl = normalizeApiUrl(options.apiUrl, { development });
|
|
73
|
+
const selectedClients = normalizeClients(
|
|
74
|
+
options.selectedClients ?? ['codex', 'claude'],
|
|
75
|
+
);
|
|
76
|
+
const commandRunner = options.commandRunner ?? runCommand;
|
|
77
|
+
await validateInputs({
|
|
78
|
+
skillSource,
|
|
79
|
+
bridgeRuntimeSource,
|
|
80
|
+
sourceBridgeEntry,
|
|
81
|
+
sourceGateEntry,
|
|
82
|
+
nodePath,
|
|
83
|
+
});
|
|
84
|
+
const statePath = join(homeDir, '.engineering-memory', 'install-state.json');
|
|
85
|
+
const existingState = await readJson(statePath);
|
|
86
|
+
if (
|
|
87
|
+
existingState &&
|
|
88
|
+
(existingState.product !== 'engineering-memory' ||
|
|
89
|
+
existingState.schemaVersion !== 1)
|
|
90
|
+
) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Refusing to overwrite unmanaged installer state: ${statePath}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const codexDispatcherPath = join(homeDir, '.codex', 'AGENTS.md');
|
|
96
|
+
const claudeDispatcherPath = join(homeDir, '.claude', 'CLAUDE.md');
|
|
97
|
+
const clientTargets = {
|
|
98
|
+
codex: {
|
|
99
|
+
skillPath: join(homeDir, '.agents', 'skills', 'engineering-memory'),
|
|
100
|
+
dispatcherPath: codexDispatcherPath,
|
|
101
|
+
dispatcherSection: dispatcherSections.codex,
|
|
102
|
+
},
|
|
103
|
+
claude: {
|
|
104
|
+
skillPath: join(homeDir, '.claude', 'skills', 'engineering-memory'),
|
|
105
|
+
dispatcherPath: claudeDispatcherPath,
|
|
106
|
+
dispatcherSection: dispatcherSections.claude,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const clientPlans = await Promise.all(
|
|
110
|
+
selectedClients.map(async (clientName) => {
|
|
111
|
+
const target = clientTargets[clientName];
|
|
112
|
+
return {
|
|
113
|
+
clientName,
|
|
114
|
+
...target,
|
|
115
|
+
dispatcher: upsertManagedSection(
|
|
116
|
+
await readText(target.dispatcherPath),
|
|
117
|
+
target.dispatcherSection,
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
const mcpPlans = await planMcpRegistrations({
|
|
123
|
+
selectedClients,
|
|
124
|
+
bridgeEntry,
|
|
125
|
+
nodePath,
|
|
126
|
+
apiUrl,
|
|
127
|
+
state: existingState,
|
|
128
|
+
commandRunner,
|
|
129
|
+
});
|
|
130
|
+
const hookPlan = await planGitHook({
|
|
131
|
+
repoRoot: options.repoRoot ? resolve(options.repoRoot) : null,
|
|
132
|
+
mode: options.hookMode ?? 'cancel',
|
|
133
|
+
nodePath,
|
|
134
|
+
gateEntry,
|
|
135
|
+
apiUrl,
|
|
136
|
+
commandRunner,
|
|
137
|
+
state: existingState?.hook,
|
|
138
|
+
});
|
|
139
|
+
const transaction = new InstallTransaction();
|
|
140
|
+
let result;
|
|
141
|
+
try {
|
|
142
|
+
const runtimeHash = await installManagedBridgeRuntime(
|
|
143
|
+
bridgeRuntimeSource,
|
|
144
|
+
runtimePath,
|
|
145
|
+
transaction,
|
|
146
|
+
);
|
|
147
|
+
const mcp = {
|
|
148
|
+
...(existingState?.mcp ?? {}),
|
|
149
|
+
...(await applyMcpRegistrations(mcpPlans, commandRunner, transaction)),
|
|
150
|
+
};
|
|
151
|
+
let skillHash;
|
|
152
|
+
for (const plan of clientPlans) {
|
|
153
|
+
const installedHash = await installManagedDirectory(
|
|
154
|
+
skillSource,
|
|
155
|
+
plan.skillPath,
|
|
156
|
+
transaction,
|
|
157
|
+
);
|
|
158
|
+
skillHash ??= installedHash;
|
|
159
|
+
await replaceFile(plan.dispatcherPath, plan.dispatcher, transaction);
|
|
160
|
+
}
|
|
161
|
+
const hookResult = await applyGitHook(hookPlan, transaction);
|
|
162
|
+
const hook =
|
|
163
|
+
hookPlan.action === 'skip'
|
|
164
|
+
? (existingState?.hook ?? hookResult)
|
|
165
|
+
: hookResult;
|
|
166
|
+
const state = {
|
|
167
|
+
product: 'engineering-memory',
|
|
168
|
+
schemaVersion: 1,
|
|
169
|
+
skillHash,
|
|
170
|
+
runtime: {
|
|
171
|
+
path: runtimePath,
|
|
172
|
+
sourceHash: runtimeHash,
|
|
173
|
+
bridgeEntry,
|
|
174
|
+
gateEntry,
|
|
175
|
+
},
|
|
176
|
+
bridgeEntry,
|
|
177
|
+
nodePath,
|
|
178
|
+
apiUrl,
|
|
179
|
+
development,
|
|
180
|
+
mcp,
|
|
181
|
+
hook,
|
|
182
|
+
};
|
|
183
|
+
await writeJson(statePath, state, transaction);
|
|
184
|
+
result = {
|
|
185
|
+
homeDir,
|
|
186
|
+
statePath,
|
|
187
|
+
skillPaths: clientPlans.map((plan) => plan.skillPath),
|
|
188
|
+
dispatcherPaths: clientPlans.map((plan) => plan.dispatcherPath),
|
|
189
|
+
selectedClients,
|
|
190
|
+
apiUrl,
|
|
191
|
+
runtimePath,
|
|
192
|
+
bridgeEntry,
|
|
193
|
+
gateEntry,
|
|
194
|
+
hook: hookResult,
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
try {
|
|
198
|
+
await transaction.rollback();
|
|
199
|
+
} catch (rollbackError) {
|
|
200
|
+
throw new AggregateError(
|
|
201
|
+
[error, rollbackError],
|
|
202
|
+
'Installation failed and rollback was incomplete',
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
await transaction.commit();
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function normalizeClients(value) {
|
|
212
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
213
|
+
throw new Error('At least one client must be selected');
|
|
214
|
+
}
|
|
215
|
+
const result = [
|
|
216
|
+
...new Set(value.map((entry) => String(entry).toLowerCase())),
|
|
217
|
+
];
|
|
218
|
+
for (const entry of result) {
|
|
219
|
+
if (entry !== 'codex' && entry !== 'claude') {
|
|
220
|
+
throw new Error(`Unsupported client: ${entry}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function validateInputs({
|
|
227
|
+
skillSource,
|
|
228
|
+
bridgeRuntimeSource,
|
|
229
|
+
sourceBridgeEntry,
|
|
230
|
+
sourceGateEntry,
|
|
231
|
+
nodePath,
|
|
232
|
+
}) {
|
|
233
|
+
if (!(await exists(join(skillSource, 'SKILL.md')))) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Engineering Memory skill source is missing: ${skillSource}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (!(await exists(join(bridgeRuntimeSource, 'package.json')))) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Bridge runtime source is missing package.json: ${bridgeRuntimeSource}`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (!(await exists(sourceBridgeEntry))) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Built stdio bridge is missing: ${sourceBridgeEntry}. Build the bridge before installing.`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (!(await exists(sourceGateEntry))) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Built Git gate is missing: ${sourceGateEntry}. Build the bridge before installing.`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (!(await exists(nodePath))) {
|
|
254
|
+
throw new Error(`Node executable is missing: ${nodePath}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function stableRuntimeEntry(sourceRoot, destinationRoot, sourceEntry, label) {
|
|
259
|
+
const relativeEntry = relative(sourceRoot, sourceEntry);
|
|
260
|
+
if (
|
|
261
|
+
relativeEntry === '' ||
|
|
262
|
+
relativeEntry === '..' ||
|
|
263
|
+
relativeEntry.startsWith(`..\\`) ||
|
|
264
|
+
relativeEntry.startsWith('../') ||
|
|
265
|
+
isAbsolute(relativeEntry)
|
|
266
|
+
) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`${label} must be inside bridge runtime source: ${sourceRoot}`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
relativeEntry !== 'dist' &&
|
|
273
|
+
!relativeEntry.startsWith('dist\\') &&
|
|
274
|
+
!relativeEntry.startsWith('dist/')
|
|
275
|
+
) {
|
|
276
|
+
throw new Error(`${label} must be inside the built dist directory`);
|
|
277
|
+
}
|
|
278
|
+
return join(destinationRoot, relativeEntry);
|
|
279
|
+
}
|