engineering-memory 1.11.20 → 1.11.21

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.
Files changed (30) hide show
  1. package/bin/engineering-memory.mjs +14 -24
  2. package/install/localization.generated.mjs +18 -0
  3. package/package.json +1 -1
  4. package/runtime/build.json +1 -1
  5. package/runtime/dist/src/auth/browser-auth.js +15 -4
  6. package/runtime/dist/src/config.js +1 -0
  7. package/runtime/dist/src/git/git-inspector.js +10 -0
  8. package/runtime/dist/src/localization/catalogue.generated.js +786 -0
  9. package/runtime/dist/src/mcp/delivery-tools.js +15 -48
  10. package/runtime/dist/src/mcp/onboarding-tools.js +289 -618
  11. package/runtime/dist/src/mcp/questionnaire-tools.js +23 -26
  12. package/runtime/dist/src/mcp/status-meaning-tools.js +12 -86
  13. package/runtime/dist/src/mcp/status-remap-tools.js +252 -0
  14. package/runtime/dist/src/mcp/tool-annotations.js +1 -0
  15. package/runtime/dist/src/mcp/tool-definitions.js +33 -55
  16. package/runtime/dist/src/mcp/workflow-tools.js +17 -108
  17. package/runtime/dist/src/mcp/worktree-tools.js +168 -250
  18. package/runtime/dist/src/runtime/bridge-service.js +100 -94
  19. package/runtime/dist/src/runtime/create-bridge-service.js +4 -2
  20. package/runtime/dist/src/runtime/language-store.js +6 -0
  21. package/runtime/dist/src/runtime/release-notes.js +6 -6
  22. package/runtime/dist/src/runtime/release-report.js +30 -30
  23. package/runtime/dist/src/runtime/task-start.js +75 -73
  24. package/runtime/dist/src/runtime/texts.js +135 -0
  25. package/runtime/dist/src/runtime/worktree-pool.js +30 -5
  26. package/runtime/dist/src/runtime/worktree-preparation.js +3 -2
  27. package/runtime/dist/src/utilities/process.js +7 -0
  28. package/skill/references/lifecycle.md +21 -3
  29. package/skill/references/project-onboarding.md +5 -2
  30. package/skill/references/questionnaires.md +3 -1
@@ -6,36 +6,22 @@ import { fileURLToPath } from 'node:url';
6
6
  import { exists, readJson } from '../install/files.mjs';
7
7
  import { stageBridgeRuntime } from '../lib/stage-runtime.mjs';
8
8
  import { runInstaller } from '../install/cli.mjs';
9
+ import { installerTexts } from '../install/localization.generated.mjs';
9
10
 
10
11
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
12
  const require = createRequire(import.meta.url);
12
13
 
13
- const usage = `Usage:
14
- engineering-memory install
15
-
16
- Installs the skill and the local bridge, and points them at the Engineering
17
- Memory backend this package was published for. Nothing else is needed: you sign
18
- in the first time an agent uses it, and your organization and project come from
19
- your account.
20
-
21
- Options:
22
- --clients codex,claude which agent CLIs to install for
23
- --api-url <https-url> a different backend, for a self-hosted install
24
- --repo <repository-root> install the Git verification gate in this repository
25
- --hook-mode chain|verify-only|cancel
26
- --help
27
-
28
- Choose the hook mode through the Codex or Claude native questionnaire before
29
- passing a repository. The installer never opens a questionnaire or an
30
- authentication web page.`;
14
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale;
15
+ const texts =
16
+ installerTexts[locale] ??
17
+ installerTexts[locale.split('-')[0]] ??
18
+ installerTexts.en;
31
19
 
32
20
  async function publishedApiUrl() {
33
21
  const manifest = await readJson(join(packageRoot, 'package.json'));
34
22
  const apiUrl = manifest?.engineeringMemory?.apiUrl;
35
23
  if (typeof apiUrl !== 'string' || apiUrl.length === 0) {
36
- throw new Error(
37
- 'This package was built without a backend address. Pass --api-url.',
38
- );
24
+ throw new Error(texts['installer.noBackend']);
39
25
  }
40
26
  return apiUrl;
41
27
  }
@@ -48,15 +34,19 @@ async function publishedVersion() {
48
34
  async function main(argumentList) {
49
35
  const [command, ...rest] = argumentList;
50
36
  if (!command || command === '--help' || command === 'help') {
51
- process.stdout.write(`${usage}\n`);
37
+ process.stdout.write(`${texts['installer.usage']}\n`);
52
38
  return;
53
39
  }
54
40
  if (command !== 'install') {
55
- throw new Error(`Unknown command: ${command}`);
41
+ throw new Error(
42
+ texts['installer.unknownCommand'].replace('{command}', command),
43
+ );
56
44
  }
57
45
  const skillSource = join(packageRoot, 'skill');
58
46
  if (!(await exists(join(skillSource, 'SKILL.md')))) {
59
- throw new Error(`Skill source is missing in ${skillSource}`);
47
+ throw new Error(
48
+ texts['installer.missingSkill'].replace('{path}', skillSource),
49
+ );
60
50
  }
61
51
  const staged = await stageBridgeRuntime(packageRoot, require);
62
52
  try {
@@ -0,0 +1,18 @@
1
+ export const installerTexts = {
2
+ en: {
3
+ 'installer.missingSkill': 'Skill source is missing in {path}',
4
+ 'installer.noBackend':
5
+ 'This package was built without a backend address. Pass --api-url.',
6
+ 'installer.unknownCommand': 'Unknown command: {command}',
7
+ 'installer.usage':
8
+ 'Usage:\n engineering-memory install\n\nInstalls the skill and the local bridge, and points them at the Engineering\nMemory backend this package was published for. Nothing else is needed: you sign\nin the first time an agent uses it, and your organization and project come from\nyour account.\n\nOptions:\n --clients codex,claude which agent CLIs to install for\n --api-url <https-url> a different backend, for a self-hosted install\n --repo <repository-root> install the Git verification gate in this repository\n --hook-mode chain|verify-only|cancel\n --help\n\nChoose the hook mode through the Codex or Claude native questionnaire before\npassing a repository. The installer never opens a questionnaire or an\nauthentication web page.',
9
+ },
10
+ tr: {
11
+ 'installer.missingSkill': 'Skill kaynağı {path} içinde yok',
12
+ 'installer.noBackend':
13
+ 'Bu paket bir backend adresi olmadan derlenmiş. --api-url ver.',
14
+ 'installer.unknownCommand': 'Bilinmeyen komut: {command}',
15
+ 'installer.usage':
16
+ "Kullanım:\n engineering-memory install\n\nSkill'i ve yerel bridge'i kurar, ikisini de bu paketin yayınlandığı Engineering\nMemory backend'ine bağlar. Başka bir şey gerekmez: bir ajan ilk kez kullandığında\ngiriş yaparsın; organizasyonun ve projen hesabından gelir.\n\nSeçenekler:\n --clients codex,claude hangi ajan CLI'ları için kurulacağı\n --api-url <https-url> kendi sunucunda çalışan başka bir backend\n --repo <repository-root> commit öncesi Git doğrulamasını bu depoya kurar\n --hook-mode chain|verify-only|cancel\n --help\n\nDepo vermeden önce hook modunu Codex veya Claude'un yerel soru formuyla seç.\nKurulum hiçbir zaman soru formu ya da giriş sayfası açmaz.",
17
+ },
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engineering-memory",
3
- "version": "1.11.20",
3
+ "version": "1.11.21",
4
4
  "description": "Installs the Engineering Memory skill and its local MCP bridge. Sign in after installing; your organization and project are resolved from your account.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,3 +1,3 @@
1
1
  {
2
- "gitHead": "f0d87f878f51b092855c584d810072297b4062f4"
2
+ "gitHead": "9df251d4cf095e0f9ed2e7da4e480bbf60142c81"
3
3
  }
@@ -1,14 +1,20 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { createServer } from 'node:http';
3
+ import * as z from 'zod/v4';
4
+ import { copies, escapeHtml, textDirection } from '../runtime/texts.js';
3
5
  import { endpoints } from '../config.js';
6
+ const text = z.string().min(1);
7
+ const signInWording = z.strictObject({ invalid: text, completed: text, failed: text });
4
8
  export class BrowserAuthCoordinator {
5
9
  client;
6
10
  credentials;
11
+ language;
7
12
  server = null;
8
13
  pending = null;
9
- constructor(client, credentials) {
14
+ constructor(client, credentials, language = async () => undefined) {
10
15
  this.client = client;
11
16
  this.credentials = credentials;
17
+ this.language = language;
12
18
  }
13
19
  async ensureAuthenticated(options = {}) {
14
20
  if (await this.credentials.get('access-token')) {
@@ -100,7 +106,7 @@ export class BrowserAuthCoordinator {
100
106
  !authorizationCode ||
101
107
  Date.parse(this.pending.expiresAt) <= Date.now()) {
102
108
  response.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
103
- response.end('<!doctype html><html><body>Authentication request is invalid.</body></html>');
109
+ response.end(await this.page('invalid'));
104
110
  return;
105
111
  }
106
112
  const envelope = await this.client.request(endpoints.authBrowserExchange, {
@@ -118,18 +124,23 @@ export class BrowserAuthCoordinator {
118
124
  await this.persistTokenPair(envelope.data.accessToken, envelope.data.refreshToken);
119
125
  this.pending = null;
120
126
  response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
121
- response.end('<!doctype html><html><body>Authentication completed. You can return to the coding agent.</body></html>');
127
+ response.end(await this.page('completed'));
122
128
  await this.stopServer();
123
129
  }
124
130
  catch {
125
131
  await this.clearCredentialsBestEffort();
126
132
  this.pending = null;
127
133
  response.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
128
- response.end('<!doctype html><html><body>Authentication could not be completed.</body></html>');
134
+ response.end(await this.page('failed'));
129
135
  await this.stopServer();
130
136
  process.stderr.write('Browser authentication failed\n');
131
137
  }
132
138
  }
139
+ async page(outcome) {
140
+ const told = await this.language().catch(() => undefined);
141
+ const { language, copy } = copies(signInWording, 'signIn', told)[0];
142
+ return `<!doctype html><html lang="${escapeHtml(language)}" dir="${textDirection(language)}"><body>${escapeHtml(copy[outcome])}</body></html>`;
143
+ }
133
144
  async persistTokenPair(accessToken, refreshToken) {
134
145
  try {
135
146
  await this.credentials.set('refresh-token', refreshToken);
@@ -30,6 +30,7 @@ export function apiStateRoot(config) {
30
30
  }
31
31
  export const endpoints = {
32
32
  releases: '/runtime/releases',
33
+ runtimeTexts: '/runtime/texts',
33
34
  projectGitPreferences: (projectId) => '/projects/' + projectId + '/git-preferences',
34
35
  worktreePolicy: '/runtime/worktree-policy',
35
36
  auditList: '/audit',
@@ -391,6 +391,16 @@ export class GitInspector {
391
391
  const result = await this.runner.run('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd: repoRoot });
392
392
  return result.exitCode === 0;
393
393
  }
394
+ async remoteRefContaining(repoRoot, commit) {
395
+ return await this.gitValue(repoRoot, [
396
+ 'for-each-ref',
397
+ '--count=1',
398
+ '--format=%(refname)',
399
+ '--contains',
400
+ commit,
401
+ 'refs/remotes/',
402
+ ]);
403
+ }
394
404
  async committedPaths(repoRoot, baseCommit) {
395
405
  const result = await this.runner.run('git', ['diff', '--name-only', '-z', '--find-renames', baseCommit, 'HEAD', '--'], { cwd: repoRoot });
396
406
  if (result.exitCode !== 0)