engineering-memory 1.11.10 → 1.11.12
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/dispatcher/sections.mjs +3 -2
- package/package.json +1 -1
- package/runtime/build.json +1 -1
- package/runtime/dist/src/config.js +1 -0
- package/runtime/dist/src/git/git-inspector.js +32 -1
- package/runtime/dist/src/mcp/onboarding-tools.js +918 -132
- package/runtime/dist/src/mcp/questionnaire-tools.js +55 -16
- package/runtime/dist/src/mcp/tool-annotations.js +1 -0
- package/runtime/dist/src/mcp/tool-definitions.js +105 -17
- package/runtime/dist/src/mcp/worktree-tools.js +416 -86
- package/runtime/dist/src/project/repository.js +15 -3
- package/runtime/dist/src/runtime/api-client.js +1 -0
- package/runtime/dist/src/runtime/bridge-service.js +308 -46
- package/runtime/dist/src/runtime/create-bridge-service.js +2 -0
- package/runtime/dist/src/runtime/language-store.js +25 -0
- package/runtime/dist/src/runtime/offline-outbox.js +4 -4
- package/runtime/dist/src/runtime/privacy-detector.js +14 -5
- package/runtime/dist/src/runtime/questionnaire-store.js +26 -7
- package/runtime/dist/src/runtime/task-start.js +115 -33
- package/runtime/dist/src/runtime/worktree-pool.js +41 -3
- package/skill/references/lifecycle.md +15 -0
- package/skill/references/questionnaires.md +17 -3
package/dispatcher/sections.mjs
CHANGED
|
@@ -2,12 +2,13 @@ const shared = `This section is managed by the Engineering Memory installer.
|
|
|
2
2
|
|
|
3
3
|
Repository bindings live in the user-level Engineering Memory state directory, not in the working tree. Do not require or create a repository settings file. Always discover the binding with \`session.entry\`; the absence of \`.engineering-memory/project.json\` does not mean the repository is unbound.
|
|
4
4
|
|
|
5
|
-
Call \`session.entry\` before you answer anything in a repository, whatever the user asked for. It reports whether they are signed in, whether this repository is bound, what they decided about it last time, and the one thing to do now.
|
|
5
|
+
Call \`session.entry\` before you answer anything in a repository, whatever the user asked for. It reports whether they are signed in, whether this repository is bound, what they decided about it last time, and the one thing to do now. Pass \`language\` on that call and on every tool that opens a form: the BCP-47 tag of the language the user is writing in, such as tr, en or pt-BR. It is remembered, so forms keep opening in that language. Act on what \`session.entry\` reports before the message itself:
|
|
6
6
|
|
|
7
7
|
- Not signed in: ask them to sign in and start the browser flow when they agree. Nothing about the project can be answered until that resolves.
|
|
8
8
|
- No decision recorded: ask which organization and then which project, whatever the message was. The other answer is switching Engineering Memory off in this repository, and it is remembered with \`session.set_decision\`.
|
|
9
9
|
- Switched off: say nothing about Engineering Memory, offer nothing, hint at nothing, however useful it would be for what was asked. They turned it off; raising it again is what this prevents. Record it back on only when they ask.
|
|
10
10
|
- Bound: continue into the lifecycle.
|
|
11
|
+
- Project lives elsewhere: binding the chosen project is refused because its code moved to another repository, or \`session.entry\` reports the project moved away from this checkout. Follow the skill's lifecycle reference: a project owner moves the project here with \`project.move_repository\`, anyone else is told that an owner has to. Never create a second project for moved code and never bind a different one to get past the refusal.
|
|
11
12
|
|
|
12
13
|
\`session.entry\` also reports this client's version. When it says an update is available, offer once to run \`npx engineering-memory@latest install\` and tell the user that the running bridge switches to the new release by itself on its next call, so nothing needs restarting; only a bridge older than 1.11.9 needs the client restarted once after that install. If they decline, record it with \`session.decline_update\` and never raise that version again. When it says an update is required, there is no choice to offer: say that this client is older than the backend supports, give the command, and do not carry on as though the lifecycle works.
|
|
13
14
|
|
|
@@ -27,7 +28,7 @@ Do not block independent task work on \`memory.propose_revision\` drafting, subm
|
|
|
27
28
|
|
|
28
29
|
A check whose answer decides the next action — delivery preconditions such as the commit hook, the remote and the branch; whether verification or close can pass; what a waiver allows — is run by the agent that takes that action, in its own turn. Hand work to another agent only when you have other real work to do while it runs, and never take the action a delegated check gates before its answer is back. Work another agent checked is neither the user's approval nor task verification. Size delegation to the task — a one- or two-file change needs no multi-agent workflow — and once work is delegated, wait for its real completion signal, never a sleep loop polling its output.
|
|
29
30
|
|
|
30
|
-
Use the host's native questionnaire for every question to the user, including implementation choices, names, clarification, branch/worktree decisions and delivery. For a required decision, use \`questionnaire.ask\` to open a durable native MCP form and \`questionnaire.resume\` to return to the same unanswered question. Read the skill's questionnaires reference first. Never use request_user_input_async for a required decision: it does not wait for an answer. When the durable form is unavailable, use a blocking native control only where the host permits it: request_user_input in Codex or AskUserQuestion in Claude. Never replace the questionnaire with a chat instruction such as 'type this', 'reply yes', or 'write X if you want Y'. Do not open a survey web page. If the required native control is unavailable or prohibited for that kind of question, follow the host's tool restrictions, explain the limitation, and continue only work already authorized; do not fabricate a survey or silently choose an answer. A timeout, dismissed form, empty response or ended turn is not an answer. Keep the decision pending and resume it; do not start dependent work or report it as resolved. Existing answers remain valid through retries and handoffs.
|
|
31
|
+
Use the host's native questionnaire for every question to the user, including implementation choices, names, clarification, branch/worktree decisions and delivery. For a required decision, use \`questionnaire.ask\` to open a durable native MCP form and \`questionnaire.resume\` to return to the same unanswered question. Read the skill's questionnaires reference first. Write every question in the user's language, say in \`context\` why it is asked, give in \`example\` one concrete thing the decision changes, and give every option a \`description\` of what happens next. Show a record by its title, never by an id or a hash. When the user says they did not understand, that is not an answer: explain in chat and show the same question again. Never use request_user_input_async for a required decision: it does not wait for an answer. When the durable form is unavailable, use a blocking native control only where the host permits it: request_user_input in Codex or AskUserQuestion in Claude. Never replace the questionnaire with a chat instruction such as 'type this', 'reply yes', or 'write X if you want Y'. Do not open a survey web page. If the required native control is unavailable or prohibited for that kind of question, follow the host's tool restrictions, explain the limitation, and continue only work already authorized; do not fabricate a survey or silently choose an answer. A timeout, dismissed form, empty response or ended turn is not an answer. Keep the decision pending and resume it; do not start dependent work or report it as resolved. Existing answers remain valid through retries and handoffs.
|
|
31
32
|
|
|
32
33
|
When a durable MCP form cannot be displayed, read its \`hostFallback\` or call \`questionnaire.resume\` with \`presentation: host_native\` to get the original question without reopening the MCP form. Display the same question, all choices and notices through a blocking native control only if the host permits that control for this decision. After an actual native answer, call \`questionnaire.answer_from_host\` with the unchanged questionnaireId, requestKey and contentHash, the hostTool name and the returned choice/text. This is an agent-reported relay, not MCP transport attestation. Then retry the owning operation; its authority, content and version checks still apply. Never relay prose consent, a default, an asynchronous response or a cancelled/declined/missing answer. Decline alone is not proof that the host cannot display forms. Keep the decision pending if no permitted native control can represent it.
|
|
33
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engineering-memory",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.12",
|
|
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",
|
package/runtime/build.json
CHANGED
|
@@ -67,6 +67,7 @@ export const endpoints = {
|
|
|
67
67
|
projectList: '/projects',
|
|
68
68
|
projectResolve: '/projects/resolve',
|
|
69
69
|
projectBind: (projectId) => `/projects/${projectId}/bind`,
|
|
70
|
+
projectRepository: (projectId) => `/projects/${projectId}/repository`,
|
|
70
71
|
projectUpdate: (projectId) => `/projects/${projectId}`,
|
|
71
72
|
workItemRuns: (projectId, workItemId) => `/projects/${projectId}/work-items/${workItemId}/runs`,
|
|
72
73
|
workItemRestore: (projectId, workItemId) => `/projects/${projectId}/work-items/${workItemId}/restore`,
|
|
@@ -4,6 +4,7 @@ import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
|
4
4
|
import { assertManagedPath, canonicalPath } from '../utilities/files.js';
|
|
5
5
|
import { sha256, stableStringify } from '../utilities/hash.js';
|
|
6
6
|
import { NativeCommandRunner } from '../utilities/process.js';
|
|
7
|
+
import { restrictedValueKind } from '../runtime/privacy-detector.js';
|
|
7
8
|
import { TaskBranchStore } from '../runtime/task-branch-store.js';
|
|
8
9
|
export const temporaryScaffoldingMarker = 'ENGINEERING-MEMORY-TEMPORARY';
|
|
9
10
|
export const temporaryScaffoldingPattern = `${temporaryScaffoldingMarker}:`;
|
|
@@ -35,6 +36,18 @@ export class GitInspector {
|
|
|
35
36
|
}
|
|
36
37
|
return sha256(identity);
|
|
37
38
|
}
|
|
39
|
+
async remoteIdentity(repoRoot) {
|
|
40
|
+
const remote = await this.gitValue(repoRoot, ['config', '--get', 'remote.origin.url']);
|
|
41
|
+
if (!remote)
|
|
42
|
+
return null;
|
|
43
|
+
const identity = canonicalRemoteIdentity(remote);
|
|
44
|
+
return !identity.startsWith('local/') &&
|
|
45
|
+
identity.length <= 500 &&
|
|
46
|
+
/^[a-z0-9][a-z0-9.-]*(:\d{1,5})?\/[^\s@\\]+$/.test(identity) &&
|
|
47
|
+
!restrictedValueKind(identity, 'remoteIdentity')
|
|
48
|
+
? identity
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
38
51
|
branchStore(repoRoot) {
|
|
39
52
|
return new TaskBranchStore(repoRoot, this.runner);
|
|
40
53
|
}
|
|
@@ -72,11 +85,13 @@ export class GitInspector {
|
|
|
72
85
|
throw new Error('The worktree directory already belongs to another repository or branch. Choose a different directory');
|
|
73
86
|
}
|
|
74
87
|
const exists = await this.branchExists(repoRoot, name);
|
|
88
|
+
const lost = (await this.lostRegistration(repoRoot, path, name)) ? ['-f'] : [];
|
|
75
89
|
const args = exists
|
|
76
|
-
? ['worktree', 'add', '--', path, name]
|
|
90
|
+
? ['worktree', 'add', ...lost, '--', path, name]
|
|
77
91
|
: [
|
|
78
92
|
'worktree',
|
|
79
93
|
'add',
|
|
94
|
+
...lost,
|
|
80
95
|
'--no-track',
|
|
81
96
|
'-b',
|
|
82
97
|
name,
|
|
@@ -89,6 +104,22 @@ export class GitInspector {
|
|
|
89
104
|
throw new Error(`Git could not create the worktree: ${result.stderr.trim()}`);
|
|
90
105
|
return await this.findRoot(path);
|
|
91
106
|
}
|
|
107
|
+
async lostRegistration(repoRoot, path, name) {
|
|
108
|
+
const listed = await this.runner.run('git', ['worktree', 'list', '--porcelain'], {
|
|
109
|
+
cwd: repoRoot,
|
|
110
|
+
});
|
|
111
|
+
if (listed.exitCode !== 0)
|
|
112
|
+
return false;
|
|
113
|
+
const registered = listed.stdout.split(/\r?\n\r?\n/).map((block) => ({
|
|
114
|
+
path: /^worktree (.+)$/m.exec(block)?.[1] ?? '',
|
|
115
|
+
branch: /^branch refs\/heads\/(.+)$/m.exec(block)?.[1] ?? null,
|
|
116
|
+
}));
|
|
117
|
+
const same = (left, right) => process.platform === 'win32'
|
|
118
|
+
? resolve(left).toLowerCase() === resolve(right).toLowerCase()
|
|
119
|
+
: resolve(left) === resolve(right);
|
|
120
|
+
return (registered.some((entry) => entry.path && same(entry.path, path)) &&
|
|
121
|
+
!registered.some((entry) => entry.branch === name && !same(entry.path, path)));
|
|
122
|
+
}
|
|
92
123
|
async fetchBranchCommit(repoRoot, remote, branch) {
|
|
93
124
|
await this.validateBranch(repoRoot, branch);
|
|
94
125
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(remote))
|