engineering-memory 1.7.0 → 1.9.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/dispatcher/sections.mjs +2 -0
- package/install/commands.mjs +39 -10
- package/package.json +1 -1
- package/runtime/dist/src/config.js +9 -1
- package/runtime/dist/src/git/git-inspector.js +140 -17
- package/runtime/dist/src/git/pre-commit.js +16 -0
- package/runtime/dist/src/git/verification-gate.js +14 -1
- package/runtime/dist/src/mcp/tool-definitions.js +101 -5
- package/runtime/dist/src/project/repository.js +1 -1
- package/runtime/dist/src/runtime/active-context-store.js +13 -7
- package/runtime/dist/src/runtime/api-client.js +62 -12
- package/runtime/dist/src/runtime/bridge-service.js +357 -21
- package/runtime/dist/src/runtime/principal-state.js +3 -2
- package/runtime/dist/src/runtime/recovery-error.js +9 -0
- package/runtime/dist/src/runtime/task-branch-store.js +111 -0
- package/skill/SKILL.md +2 -0
- package/skill/references/lifecycle.md +15 -9
- package/skill/references/memory-updates.md +3 -1
- package/skill/references/questionnaires.md +6 -4
- package/skill/references/scaffolding.md +9 -8
- package/bin/entry-point.test.mjs +0 -29
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class BridgeRecoveryError extends Error {
|
|
2
|
+
recovery;
|
|
3
|
+
constructor(message, recovery) {
|
|
4
|
+
super(`${message} Recovery: call ${recovery}.`);
|
|
5
|
+
this.recovery = recovery;
|
|
6
|
+
this.name = 'BridgeRecoveryError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=recovery-error.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { NativeCommandRunner } from '../utilities/process.js';
|
|
2
|
+
import { assertSafeToPersist } from './offline-outbox.js';
|
|
3
|
+
import { BridgeRecoveryError } from './recovery-error.js';
|
|
4
|
+
const decisionRef = 'refs/worktree/engineering-memory-task';
|
|
5
|
+
export class TaskBranchStore {
|
|
6
|
+
repoRoot;
|
|
7
|
+
runner;
|
|
8
|
+
constructor(repoRoot, runner = new NativeCommandRunner()) {
|
|
9
|
+
this.repoRoot = repoRoot;
|
|
10
|
+
this.runner = runner;
|
|
11
|
+
}
|
|
12
|
+
async read() {
|
|
13
|
+
const reference = await this.runner.run('git', ['rev-parse', '--verify', '--quiet', decisionRef], { cwd: this.repoRoot });
|
|
14
|
+
if (reference.exitCode === 1)
|
|
15
|
+
return null;
|
|
16
|
+
if (reference.exitCode !== 0)
|
|
17
|
+
throw new BridgeRecoveryError('Git could not read the worktree task reservation. Retry the branch operation.', 'task.branch');
|
|
18
|
+
const oid = reference.stdout.trim();
|
|
19
|
+
const blob = await this.runner.run('git', ['cat-file', 'blob', oid], { cwd: this.repoRoot });
|
|
20
|
+
if (blob.exitCode !== 0)
|
|
21
|
+
throw new BridgeRecoveryError('Git could not read the recorded branch decision. Retry the branch operation.', 'task.branch');
|
|
22
|
+
let decision;
|
|
23
|
+
try {
|
|
24
|
+
decision = JSON.parse(blob.stdout);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new BridgeRecoveryError('The worktree branch record is invalid. Use a separate worktree to continue this task.', 'task.branch');
|
|
28
|
+
}
|
|
29
|
+
if (!decision)
|
|
30
|
+
throw new BridgeRecoveryError('The worktree branch record is invalid. Use a separate worktree to continue this task.', 'task.branch');
|
|
31
|
+
if (typeof decision.projectId !== 'string' ||
|
|
32
|
+
typeof decision.externalTaskId !== 'string' ||
|
|
33
|
+
!(decision.branch === null || typeof decision.branch === 'string')) {
|
|
34
|
+
throw new BridgeRecoveryError('The worktree branch record is invalid. Use a separate worktree to continue this task.', 'task.branch');
|
|
35
|
+
}
|
|
36
|
+
return { oid, decision };
|
|
37
|
+
}
|
|
38
|
+
async reserve(decision) {
|
|
39
|
+
assertSafeToPersist({ ...decision });
|
|
40
|
+
const existing = await this.read();
|
|
41
|
+
if (existing) {
|
|
42
|
+
this.assertSame(existing.decision, decision);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const oid = await this.hash(decision);
|
|
46
|
+
const written = await this.runner.run('git', ['update-ref', '--no-deref', decisionRef, oid, '0'.repeat(oid.length)], { cwd: this.repoRoot });
|
|
47
|
+
if (written.exitCode !== 0) {
|
|
48
|
+
const winner = await this.read();
|
|
49
|
+
if (winner) {
|
|
50
|
+
this.assertSame(winner.decision, decision);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
throw new BridgeRecoveryError('Another branch operation is in progress. Retry the same task.branch operation.', 'task.branch');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async bind(projectId, externalTaskId, taskId) {
|
|
57
|
+
const current = await this.read();
|
|
58
|
+
if (!current ||
|
|
59
|
+
current.decision.projectId !== projectId ||
|
|
60
|
+
current.decision.externalTaskId !== externalTaskId)
|
|
61
|
+
throw new BridgeRecoveryError('The worktree reservation changed while opening the task. Resume it before editing.', 'session.resume');
|
|
62
|
+
if (current.decision.taskId === taskId)
|
|
63
|
+
return;
|
|
64
|
+
const oid = await this.hash({ ...current.decision, taskId });
|
|
65
|
+
const result = await this.runner.run('git', ['update-ref', '--no-deref', decisionRef, oid, current.oid], { cwd: this.repoRoot });
|
|
66
|
+
if (result.exitCode !== 0) {
|
|
67
|
+
const latest = await this.read();
|
|
68
|
+
if (latest?.decision.taskId !== taskId)
|
|
69
|
+
throw new BridgeRecoveryError('The worktree reservation changed while opening the task. Resume it before editing.', 'session.resume');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async cancelUnopened(projectId, externalTaskId) {
|
|
73
|
+
const current = await this.read();
|
|
74
|
+
if (!current ||
|
|
75
|
+
current.decision.taskId ||
|
|
76
|
+
current.decision.projectId !== projectId ||
|
|
77
|
+
current.decision.externalTaskId !== externalTaskId)
|
|
78
|
+
return;
|
|
79
|
+
const result = await this.runner.run('git', ['update-ref', '--no-deref', '-d', decisionRef, current.oid], { cwd: this.repoRoot });
|
|
80
|
+
if (result.exitCode !== 0 && (await this.read())?.oid === current.oid)
|
|
81
|
+
throw new BridgeRecoveryError('The failed branch operation could not release its reservation. Retry task.branch with the same task identifier.', 'task.branch');
|
|
82
|
+
}
|
|
83
|
+
async release(taskId) {
|
|
84
|
+
const current = await this.read();
|
|
85
|
+
if (!current || current.decision.taskId !== taskId)
|
|
86
|
+
return;
|
|
87
|
+
const result = await this.runner.run('git', ['update-ref', '--no-deref', '-d', decisionRef, current.oid], { cwd: this.repoRoot });
|
|
88
|
+
if (result.exitCode !== 0 && (await this.read())?.decision.taskId === taskId) {
|
|
89
|
+
throw new BridgeRecoveryError('The task has finished but its worktree reservation could not be released. Resume the task to retry cleanup.', 'session.resume');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
assertSame(current, requested) {
|
|
93
|
+
if (current.projectId !== requested.projectId ||
|
|
94
|
+
current.externalTaskId !== requested.externalTaskId) {
|
|
95
|
+
throw new BridgeRecoveryError(`This worktree belongs to task ${current.externalTaskId}. Resume that task, or call task.branch with worktreePath and a branch name for the new task.`, 'task.branch');
|
|
96
|
+
}
|
|
97
|
+
if (current.branch !== requested.branch) {
|
|
98
|
+
throw new BridgeRecoveryError(`This task selected ${current.branch ?? 'detached HEAD'}. Return to that branch before continuing.`, 'task.branch');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async hash(decision) {
|
|
102
|
+
const result = await this.runner.run('git', ['hash-object', '-w', '--stdin'], {
|
|
103
|
+
cwd: this.repoRoot,
|
|
104
|
+
input: JSON.stringify(decision),
|
|
105
|
+
});
|
|
106
|
+
if (result.exitCode !== 0)
|
|
107
|
+
throw new BridgeRecoveryError('Git could not save the branch decision. Retry the same branch operation.', 'task.branch');
|
|
108
|
+
return result.stdout.trim();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=task-branch-store.js.map
|
package/skill/SKILL.md
CHANGED
|
@@ -11,6 +11,8 @@ Read [lifecycle.md](references/lifecycle.md) before acting in a bound repository
|
|
|
11
11
|
|
|
12
12
|
Mandatory behavior:
|
|
13
13
|
|
|
14
|
+
Use the host's native questionnaire for every question to the user, including implementation choices, names, clarification, branch/worktree decisions and delivery. In Codex use request_user_input when available; in Claude use AskUserQuestion. 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. Existing answers remain valid through retries and handoffs.
|
|
15
|
+
|
|
14
16
|
1. Locate `.engineering-memory/project.json` from the working directory toward the repository root. When it exists, the project's knowledge is in the backend and not in the working tree, so answer nothing about the project before bootstrapping — a file search that finds no design link, no screen record and no rule is reporting what the repository lacks, not what the project knows.
|
|
15
17
|
2. Call `session.entry` before answering anything in a repository, and act on what it reports before the message itself: sign in when it says so, ask for organization and project when nothing has been decided, and stay completely silent about Engineering Memory in a repository where the user switched it off. Record every one of those answers with `session.set_decision`, and only ever from something the user actually said.
|
|
16
18
|
3. Before planning or editing, call `session.bootstrap`. After compaction, a new chat, interruption, or handoff, call `session.resume` first.
|
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
Sign-in decides nothing beyond who the user is. The organization and the project are chosen after it, through the questionnaires in `questionnaires.md`, and both listings end with an option to create a new one. Ask for both whenever this session has not already confirmed them, and ask again the moment the user says they want to change either — changing the organization always means choosing the project again.
|
|
6
6
|
|
|
7
|
-
Treat `.engineering-memory/project.json` as the binding authority. The marker contains only `projectId` and `schemaVersion`. Never infer a binding from a directory name or Git remote when a marker exists.
|
|
7
|
+
Treat `.engineering-memory/project.json` as the binding authority. The marker contains only `projectId` and `schemaVersion`. Never infer a binding from a directory name or Git remote when a marker exists. Schema 1 markers retain the legacy commit-aware repository identity so existing bindings keep working. Schema 2 markers use the canonical remote or main-worktree identity, which stays unchanged when an unborn repository receives its first commit. Never rewrite or upgrade a marker by hand.
|
|
8
|
+
|
|
9
|
+
An archived project is recoverable state, not a missing or conflicting binding. When an owner receives `project.restore`, use the `projectId` and `expectedVersion` in its data and call that operation before retrying. A non-owner receives `project.member_list` instead: list the members, identify a project owner and explain that the owner must restore the exact project before this session can retry. Use `project.list` with `includeArchived: true` when an owner must first select the archived project. Never create or bind a replacement project to escape archive state.
|
|
8
10
|
|
|
9
11
|
For a bound repository, call `session.bootstrap` before producing a plan or changing files. Supply the current repository root, project ID, task ID or stable local task slug, objective, task kind, task mode, and current Git diff hash. Use `read_only` for review, diagnosis, planning, or reporting without write authority; use `scaffold` when the task applies organization architecture templates to a new project; use `write` when the request authorizes repository changes. The bridge records the read-only Git diff hash as the immutable task baseline, including a pre-existing dirty worktree. Use the returned task ID, task version, context session ID, pinned revisions, project profile, engineering rules, prior task documents, quality gates, and current deviations.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Every new write or scaffold task settles its branch through the native questionnaire before it opens, including when HEAD is already on a feature branch. Offer a task branch or an explicit choice to stay on the current branch. Call `task.branch` with the exact `externalTaskId` and either `name` or `keepCurrent`, then call `session.bootstrap` in its returned `repoRoot`. An existing branch name checks it out. Creating a branch remains optional; recording the user's choice does not. A read-only task needs no branch decision, but `context.prepare_change` with `transitionToWrite` requires one before editing.
|
|
14
|
+
|
|
15
|
+
The choice belongs to that task and worktree, survives retry and client restart, and cannot be consumed by a different task. Reuse an existing answer rather than asking again. A named branch stays fixed through prepare, verify, close and commit; closing never replaces it. Return with `task.branch` using the same task slug and recorded branch when a gate reports a mismatch. Legacy tasks without a recorded branch and tasks explicitly kept on detached HEAD remain compatible; they are not restricted to a named branch. Older clients remain supported during the minimum-version compatibility window.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Parallel development needs separate working directories. If another task owns the current worktree, offer a separate worktree through the questionnaire. Call `task.branch` with `externalTaskId`, `name`, and `worktreePath`; run bootstrap and all subsequent file, test and Git commands in its returned `repoRoot`. This creates or reuses an appropriate Git worktree and preserves the repository's project marker schema. Never switch another task's working directory. Reservations are shared by clients using the same worktree; close or abandon releases that task's reservation. An interrupted close can release it through resume.
|
|
14
18
|
|
|
15
19
|
A repository holds as many tasks as the people working in it. Never treat somebody else's unfinished task as a reason this one cannot proceed: no task waits on another task's review, reconciliation, verification or close, and nothing that is already verified or closed is undone by what happens elsewhere. When `session.resume` reports more than one live task for this repository, it lists them and the right move is to ask the user which one this is, never to guess and never to adopt the one that happens to be most recent.
|
|
16
20
|
|
|
@@ -45,12 +49,14 @@ When the request covers both halves of a linked product — a flow that needs en
|
|
|
45
49
|
as screens — and the user's discipline covers both, it is one work item and two tasks: one in
|
|
46
50
|
each repository. Verification and the commit gate stay per repository because a commit is.
|
|
47
51
|
|
|
48
|
-
Ask which side to start on
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
Ask which side to start on and select the work item before opening the first task. When the work
|
|
53
|
+
moves to the other side, open its task with the same `workItemId` and that repository's root —
|
|
54
|
+
every tool takes `repoRoot`, and both stay open at once. The backend accepts that id only from the
|
|
55
|
+
current project or a directly linked project the account can work in, and derives the display key
|
|
56
|
+
from the work item. `context.prepare_change` then returns `siblingTasks`: the other task's
|
|
57
|
+
objective, status and recent checkpoints, so a decision taken on one side is visible on the other
|
|
58
|
+
without anyone repeating it. A legacy key is never an authorization boundary; key-only tasks can
|
|
59
|
+
see siblings only inside directly linked projects where the account is already a member.
|
|
54
60
|
|
|
55
61
|
Do not open the second task speculatively. Open it when the work actually reaches that side.
|
|
56
62
|
|
|
@@ -4,12 +4,14 @@ Backend resources are immutable revisions. The local agent drafts structured con
|
|
|
4
4
|
|
|
5
5
|
Use `memory.propose_revision` for project profiles, engineering rules, service contracts, localization contracts, navigation contracts, state contracts, screen logic, component mappings, Figma mappings, current deviations, quality gates, task history, and architecture templates.
|
|
6
6
|
|
|
7
|
-
Knowledge is layered. `scope: product` proposes a change to the shared engineering core that every organization reads, `scope: organization` proposes one that only this organization reads and which hides the product text for that key, and `scope: project` proposes a record for this project alone. The nearest layer wins when context is delivered. A product-scope proposal is the way a developer improves the product itself from inside their own project, and it requires nothing but the skill.
|
|
7
|
+
Knowledge is layered. `scope: product` proposes a change to the shared engineering core that every organization reads, `scope: organization` proposes one that only this organization reads and which hides the product text for that key, and `scope: project` proposes a record for this project alone. The nearest layer wins when context is delivered. A product-scope proposal is the way a developer improves the product itself from inside their own project, and it requires nothing but the skill. It remains inactive for the configured product release principal to review; waiting in that platform queue does not prevent the contributor's task from verifying or closing.
|
|
8
8
|
|
|
9
9
|
An architecture template is organization-scoped source, not prose. When a task establishes or changes a shared architecture structure that the templates carry, the template is stale and needs its own revision. Its manifest and content must stay in step: one `files[]` entry per `## file:` block, same path, byte count, SHA-256 and order. See `scaffolding.md`.
|
|
10
10
|
|
|
11
11
|
Every proposal must include the current `baseRevision`. A conflict means the resource changed after context pinning. Do not overwrite it. Refresh context, compare revisions, and ask the user when the merge changes intent.
|
|
12
12
|
|
|
13
|
+
When a revision changes which stacks receive the resource, send the complete selector as `metadata.appliesToStacks`. An absent selector preserves the resource's current stack scope; `[]` deliberately makes it universal. A concrete source template keeps the family selector, such as `["spring-boot"]`, and declares its exact machine-readable `metadata.compatibility`: language and supported language versions, framework and supported framework release lines, build tools and namespaces. The backend compares that contract with the approved project profile `runtimeContract` before returning source. Version-neutral Spring engineering rules remain `["spring-boot"]` and carry no source compatibility gate. Never broaden or delete a compatibility contract merely so an older project can use Engineering Memory; deliver the compatible rules and leave incompatible scaffolding unselected.
|
|
14
|
+
|
|
13
15
|
Permanent proposals remain inactive until an authorized user explicitly approves them. Call `memory.review_proposal` only after receiving that decision through the native questionnaire.
|
|
14
16
|
|
|
15
17
|
A proposal created in an earlier session, or created by a bundle import, is found with `memory.list_proposals`. It reports every proposal still waiting for review that this project or its organization can reach, with the reason, the current and base revision numbers, and whether the proposal now needs a rebase. Pass a single `proposalId` to read that proposal's proposed text before putting the decision to the user. Never present a decision the user cannot see the content of, and never approve on the strength of the summary alone. Approval makes the current context lease stale. Call `context.refresh`, reread the changed rule, prepare a new change lease, and rerun affected validation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Native Questionnaires
|
|
2
2
|
|
|
3
|
-
Use Codex
|
|
3
|
+
Use the host's native questionnaire for every question to the user, including implementation choices, names, clarification, branch/worktree decisions and delivery. In Codex use request_user_input when available; in Claude use AskUserQuestion. 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. Existing answers remain valid through retries and handoffs. Web pages are limited to sign in, sign up, initial password change, and email verification.
|
|
4
4
|
|
|
5
5
|
## Authentication
|
|
6
6
|
|
|
@@ -30,6 +30,8 @@ A new organization is not empty. It reads the product engineering core immediate
|
|
|
30
30
|
|
|
31
31
|
Once the organization is chosen, call `project.list` and offer that organization's projects, with **create a new project** last. Ignore projects belonging to other organizations; a listing that mixes them is how work lands in the wrong place. Creating one follows the unbound-repository flow below.
|
|
32
32
|
|
|
33
|
+
The normal list omits archived projects. If repository entry, setup, binding or task open reports `project.restore`, use the project id and expected version returned with that refusal and restore it before continuing. If it reports `project.member_list`, the current member cannot restore the project: call that operation, identify the project owners in its result and tell the user which owner must perform the versioned restore. If an owner does not yet have the project id and expected version, call `project.list` with `includeArchived: true` and let them select the archived project. Archive state never authorizes creating a duplicate project.
|
|
34
|
+
|
|
33
35
|
Ask both questions again whenever a chat starts in a repository whose binding you have not confirmed in this session.
|
|
34
36
|
|
|
35
37
|
## Switching Organization or Project
|
|
@@ -50,17 +52,17 @@ Ask whether the current repository belongs to an existing accessible project, sh
|
|
|
50
52
|
|
|
51
53
|
For a repository the backend has not seen before, ask whether this is an existing codebase import or a greenfield project. Before a marker or backend task exists, an existing-codebase import may perform one bounded local read-only structural and Figma discovery pass. It must not edit code. Use the findings in a native questionnaire to confirm the initial project profile and the repository-relative patterns that identify new memory resources. Send them as `discoveryUnits`, one unit per kind the project actually has: a client project declares `screen_logic` and `component_mapping`, and a server-side project declares `module_logic`, `data_model` and `api_endpoint` against its own layout. Never ask a server-side project for screen patterns, and never invent a unit for a kind the repository does not contain. Then call `project.setup`; the backend creates the project, owner membership, active profile revision, and pinned discovery policy atomically. Write the returned marker only after that succeeds, then start the normal `session.bootstrap` lifecycle. Later discoveries are reviewable memory proposals.
|
|
52
54
|
|
|
53
|
-
Greenfield setup asks for project name, framework, the response envelope, the exception model, authentication needs, localization languages, storage policy, and the initial discovery patterns. A client project is also asked for the Figma library and screen links if available, the design token sources, the page architecture and the navigation pattern; a server-side project is asked instead for the data layer, the migration tool and how configuration and secrets arrive. Ask what the project is before deciding which list applies.
|
|
55
|
+
Greenfield setup asks for project name, framework, the response envelope, the exception model, authentication needs, localization languages, storage policy, and the initial discovery patterns. A client project is also asked for the Figma library and screen links if available, the design token sources, the page architecture and the navigation pattern; a server-side project is asked instead for the data layer, the migration tool and how configuration and secrets arrive. A Spring project must also name Maven or Gradle, its Java release, its exact Spring Boot version and its `javax` or `jakarta` namespace before any architecture template is offered; `Spring Boot` alone is not enough information to select compatible source. Record those confirmed values in `initialProjectProfile.metadata.runtimeContract` as `language: "java"`, `languageVersion`, `framework: "spring-boot"`, `frameworkVersion`, `buildTool` and `namespace`. Never infer or invent this object. The backend compares it with each source module and returns no incompatible module; an absent contract means broad Spring rules still apply but no constrained source template is offered. Ask what the project is before deciding which list applies. Do not write a marker unless the setup response contains the active initial profile and discovery policy.
|
|
54
56
|
|
|
55
57
|
After setup, ask whether to build the project from the organization architecture templates. If the user accepts, follow `scaffolding.md`: confirm the optional modules, then confirm the package name and the concrete class name behind every rename placeholder in one questionnaire, then ask for each required asset role and each tenant-specific value the templates deliberately leave open.
|
|
56
58
|
|
|
57
59
|
## Project Membership
|
|
58
60
|
|
|
59
|
-
Ask for the registered email and intended role. Show owner, maintainer, member, and reader effects. Confirm before calling `project.member_add`.
|
|
61
|
+
Ask for the registered email and intended role. Show owner, maintainer, member, and reader effects. Confirm before calling `project.member_add`. A project grant is valid only while the same account belongs to the parent organization. If the call reports `organization.member_upsert`, do not retry the project write: an organization owner must first confirm the organization role and discipline and call that recovery. When the current account is not an organization owner, explain that coordination requirement instead of pretending the project grant succeeded. Retry `project.member_add` only after the parent grant exists.
|
|
60
62
|
|
|
61
63
|
## Branch
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
Ask once for every new write or scaffold task, on any branch, and before a read-only task transitions to writing. Offer a new or existing task branch, or explicitly continuing on the current branch; use the naming convention from the returned rules. When another task owns the worktree, offer a separate directory and branch for parallel work. Pass the exact `externalTaskId` and either `name` or `keepCurrent` to `task.branch`; for a separate worktree also supply `worktreePath`. Continue in the returned `repoRoot`. Retries and handoffs reuse the stored choice. Read-only analysis does not create a branch or consume another task's answer.
|
|
64
66
|
|
|
65
67
|
## Flow Entry and Exit
|
|
66
68
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Greenfield Scaffolding
|
|
2
2
|
|
|
3
|
-
Architecture templates are source modules stored as approved engineering memory. The product
|
|
3
|
+
Architecture templates are source modules stored as approved engineering memory. The product may ship one or more explicitly compatible starter generations for a stack and an organization may hold its own, which takes precedence; a template never belongs to a single project. General support for a stack does not imply that every starter generation is available. A greenfield project reproduces the team architecture from compatible modules instead of re-deriving it from prose rules. The backend stores, versions and authorizes them; it never generates code. The agent applies the rename map and writes every file itself.
|
|
4
4
|
|
|
5
5
|
## Organization first
|
|
6
6
|
|
|
@@ -13,13 +13,14 @@ Call `organization.list` and ask the user which organization the project belongs
|
|
|
13
13
|
1. Open the task in `scaffold` mode and complete the normal bootstrap, discovery and `context.prepare_change` steps. Prepare the full set of intended paths before the first write.
|
|
14
14
|
2. Call `architecture.plan`. It returns each module's manifest in apply order with its dependencies, rename map, string replacements, pubspec dependencies, asset contract and tenant-specific points. It carries no file bodies.
|
|
15
15
|
3. Present the optional modules through the native questionnaire. A package-shaped project usually skips the application modules; an application usually takes them.
|
|
16
|
-
4.
|
|
17
|
-
5.
|
|
18
|
-
6.
|
|
19
|
-
7.
|
|
20
|
-
8.
|
|
21
|
-
9.
|
|
22
|
-
10.
|
|
16
|
+
4. Treat the modules returned by the backend as the compatibility boundary: it compares each manifest's `compatibility` object with the approved project profile `runtimeContract` before returning it. Read both contracts back and confirm them in the questionnaire; never add a module the plan withheld or substitute a conversational guess. For Spring, the comparison covers Java release, exact Spring Boot release line, build tool and `javax` or `jakarta` namespace. If the plan is empty, say that no compatible starter exists and continue without scaffolding; never upgrade or downgrade the project to make a template fit.
|
|
17
|
+
5. Confirm the naming decisions in one questionnaire: package name and the concrete class name behind every rename placeholder. Never invent a name the user did not choose.
|
|
18
|
+
6. For each module in `applyOrder`, call `architecture.module`, apply the rename map and string replacements, write the files, then call `architecture.record_application` with the template path and the written path of every file. Respect `dependsOn`; do not reorder modules.
|
|
19
|
+
7. Merge every module's `packageDependencies` into the project's dependency manifest — `pubspec.yaml`, `package.json`, whatever the stack uses. Keep the existing constraint when a dependency already exists and report the conflict.
|
|
20
|
+
8. Satisfy the `assetContract`. Ask the user for each required asset role. Never invent an asset, never ship a placeholder binary, and never copy a licensed font from another project.
|
|
21
|
+
9. Resolve every `tenantSpecific` point through the questionnaire: base URLs, backend header contracts, storage key prefixes, supported locales, bundle identifiers. These are deliberately absent from the template. Do not guess them.
|
|
22
|
+
10. Run the stack's generator, compiler or type checker against every emitted source file before calling the scaffold complete. If the required local compiler is unavailable or below the selected template's declared release, stop with the exact tool and version needed rather than claiming the source is valid. This refusal applies only to applying that template, not to using Engineering Memory with an existing project. Then run code generation, localization generation, static analysis and tests that the project requires.
|
|
23
|
+
11. Reconcile each applied template resource with a `scaffold_applied` reconciliation carrying a short reason, then run `task.verify`.
|
|
23
24
|
|
|
24
25
|
## What scaffold mode does and does not relax
|
|
25
26
|
|
package/bin/entry-point.test.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import test from 'node:test';
|
|
4
|
-
|
|
5
|
-
const entryPoint = new URL('./engineering-memory.mjs', import.meta.url);
|
|
6
|
-
const manifest = new URL('../package.json', import.meta.url);
|
|
7
|
-
|
|
8
|
-
test('the published entry point tells the installer which version it is', async () => {
|
|
9
|
-
const source = await readFile(entryPoint, 'utf8');
|
|
10
|
-
|
|
11
|
-
assert.match(
|
|
12
|
-
source,
|
|
13
|
-
/'--client-version',\s*await publishedVersion\(\)/,
|
|
14
|
-
'The installer is never told the version, so every install reports itself as unknown and no update is ever offered',
|
|
15
|
-
);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('the published entry point tells the installer which backend it was built for', async () => {
|
|
19
|
-
const source = await readFile(entryPoint, 'utf8');
|
|
20
|
-
|
|
21
|
-
assert.match(source, /'--api-url',\s*await publishedApiUrl\(\)/);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('the manifest carries a version the entry point can stamp', async () => {
|
|
25
|
-
const declared = JSON.parse(await readFile(manifest, 'utf8'));
|
|
26
|
-
|
|
27
|
-
assert.match(String(declared.version), /^\d+\.\d+\.\d+$/);
|
|
28
|
-
assert.equal(declared.name, 'engineering-memory');
|
|
29
|
-
});
|