engineering-memory 1.10.2 → 1.10.4

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.
@@ -0,0 +1,259 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { link, readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import * as z from 'zod/v4';
5
+ import { assertManagedPath, ensureManagedDirectory, isNodeError, readJson, removeFile, writeJson, } from '../utilities/files.js';
6
+ import { sha256, stableStringify } from '../utilities/hash.js';
7
+ import { assertSafeToPersist } from './offline-outbox.js';
8
+ const identifier = z.string().regex(/^[A-Za-z0-9_-]{1,100}$/);
9
+ export const questionnaireDefinitionSchema = z.strictObject({
10
+ questionnaireId: identifier,
11
+ message: z.string().trim().min(1).max(2000),
12
+ options: z
13
+ .array(z.strictObject({
14
+ id: z
15
+ .string()
16
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
17
+ .refine((value) => value !== '__other__'),
18
+ label: z.string().trim().min(1).max(240),
19
+ }))
20
+ .min(2)
21
+ .max(12)
22
+ .refine((values) => new Set(values.map((value) => value.id)).size === values.length),
23
+ allowFreeText: z.boolean().default(false),
24
+ textField: z
25
+ .strictObject({
26
+ title: z.string().trim().min(1).max(120),
27
+ maxLength: z.number().int().min(1).max(2000),
28
+ requiredForChoice: z
29
+ .string()
30
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
31
+ .optional(),
32
+ valueType: z.enum(['text', 'uuid_list']).optional(),
33
+ })
34
+ .optional(),
35
+ });
36
+ const scopeSchema = z.strictObject({
37
+ principalHash: z.string().regex(/^[0-9a-f]{64}$/),
38
+ repoFingerprint: z.string().regex(/^[0-9a-f]{64}$/),
39
+ projectId: z.string().uuid().nullable(),
40
+ });
41
+ const storedQuestionSchema = questionnaireDefinitionSchema.extend({
42
+ schemaVersion: z.literal(1),
43
+ scope: scopeSchema,
44
+ contentHash: z.string().regex(/^[0-9a-f]{64}$/),
45
+ requestKey: z.string().regex(/^questionnaire_[0-9a-f]{64}$/),
46
+ createdAt: z.string().datetime(),
47
+ });
48
+ const storedAnswerSchema = z.strictObject({
49
+ requestKey: z.string().regex(/^questionnaire_[0-9a-f]{64}$/),
50
+ answeredAt: z.string().datetime(),
51
+ answerAvailable: z.boolean(),
52
+ withdrawn: z.literal(true).optional(),
53
+ answer: z.strictObject({ choice: z.string(), text: z.string().optional() }).optional(),
54
+ });
55
+ export function questionnaireAnswerSchema(record) {
56
+ const textSchema = record.textField
57
+ ? record.textField.valueType === 'uuid_list'
58
+ ? z
59
+ .string()
60
+ .trim()
61
+ .min(2)
62
+ .max(record.textField.maxLength)
63
+ .refine((value) => {
64
+ try {
65
+ const parsed = JSON.parse(value);
66
+ if (!Array.isArray(parsed) || new Set(parsed).size !== parsed.length)
67
+ return false;
68
+ return z.array(z.string().uuid()).max(10).safeParse(parsed).success;
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }, 'Enter a JSON array containing valid project IDs.')
74
+ .optional()
75
+ : z.string().trim().min(1).max(record.textField.maxLength).optional()
76
+ : record.allowFreeText
77
+ ? z.string().trim().min(1).max(2000).optional()
78
+ : z.never().optional();
79
+ return z
80
+ .strictObject({
81
+ choice: z.enum([
82
+ ...record.options.map((option) => option.id),
83
+ ...(record.allowFreeText ? ['__other__'] : []),
84
+ ]),
85
+ text: z.preprocess((value) => (typeof value === 'string' && value.trim() === '' ? undefined : value), textSchema),
86
+ })
87
+ .refine((answer) => {
88
+ if (record.textField?.requiredForChoice === answer.choice)
89
+ return typeof answer.text === 'string';
90
+ if (answer.choice === '__other__')
91
+ return typeof answer.text === 'string';
92
+ return answer.text === undefined;
93
+ });
94
+ }
95
+ export class QuestionnaireStore {
96
+ root;
97
+ constructor(stateRoot) {
98
+ this.root = join(stateRoot, 'questionnaires');
99
+ }
100
+ async ask(scope, input) {
101
+ const definition = questionnaireDefinitionSchema.parse(input);
102
+ scopeSchema.parse(scope);
103
+ assertSafeToPersist(definition);
104
+ const contentHash = sha256(stableStringify(definition));
105
+ const question = {
106
+ ...definition,
107
+ schemaVersion: 1,
108
+ scope,
109
+ contentHash,
110
+ requestKey: `questionnaire_${sha256(`${stableStringify(scope)}\n${contentHash}\n${randomUUID()}`)}`,
111
+ createdAt: new Date().toISOString(),
112
+ };
113
+ await this.publish(this.pathFor(scope, definition.questionnaireId, 'pending.json'), question);
114
+ const record = await this.get(scope, definition.questionnaireId);
115
+ if (!record || record.contentHash !== contentHash) {
116
+ throw new Error('Questionnaire id was reused with different content. Resume the original question or use a new id.');
117
+ }
118
+ return record;
119
+ }
120
+ async get(scope, questionnaireId) {
121
+ const raw = await readJson(this.pathFor(scope, questionnaireId, 'pending.json'), this.root);
122
+ if (!raw)
123
+ return null;
124
+ const question = storedQuestionSchema.parse(raw);
125
+ const definition = questionnaireDefinitionSchema.parse({
126
+ questionnaireId: question.questionnaireId,
127
+ message: question.message,
128
+ options: question.options,
129
+ allowFreeText: question.allowFreeText,
130
+ textField: question.textField,
131
+ });
132
+ if (stableStringify(question.scope) !== stableStringify(scope) ||
133
+ question.questionnaireId !== questionnaireId ||
134
+ question.contentHash !== sha256(stableStringify(definition))) {
135
+ throw new Error('Questionnaire scope or content does not match its durable record.');
136
+ }
137
+ assertSafeToPersist(question);
138
+ const rawAnswer = await readJson(this.pathFor(scope, questionnaireId, 'answer.json'), this.root);
139
+ if (!rawAnswer)
140
+ return { ...question, status: 'pending' };
141
+ const answer = storedAnswerSchema.parse(rawAnswer);
142
+ if (answer.withdrawn) {
143
+ if (answer.requestKey !== question.requestKey || answer.answerAvailable || answer.answer)
144
+ throw new Error('Invalid questionnaire withdrawal receipt.');
145
+ return { ...question, ...answer, status: 'withdrawn' };
146
+ }
147
+ if (answer.requestKey !== question.requestKey ||
148
+ (answer.answerAvailable
149
+ ? !answer.answer || !question.options.some((option) => option.id === answer.answer.choice)
150
+ : !question.allowFreeText || answer.answer !== undefined)) {
151
+ throw new Error('Questionnaire answer does not match its durable question.');
152
+ }
153
+ if (answer.answer?.text !== undefined && !question.textField)
154
+ throw new Error('Questionnaire text answer does not match its durable question.');
155
+ if (question.textField?.requiredForChoice &&
156
+ question.textField.requiredForChoice === answer.answer?.choice &&
157
+ typeof answer.answer?.text !== 'string')
158
+ throw new Error('Questionnaire text answer is required for this choice.');
159
+ assertSafeToPersist(answer);
160
+ return { ...question, ...answer, status: 'answered' };
161
+ }
162
+ async pending(scope) {
163
+ const directory = this.scopeRoot(scope);
164
+ await ensureManagedDirectory(this.root, directory);
165
+ const entries = await readdir(directory, { withFileTypes: true });
166
+ const records = [];
167
+ for (const entry of entries) {
168
+ if (!/^[0-9a-f]{64}$/.test(entry.name))
169
+ continue;
170
+ if (!entry.isDirectory() || entry.isSymbolicLink())
171
+ throw new Error('Unsafe questionnaire directory.');
172
+ const path = await assertManagedPath(this.root, join(directory, entry.name, 'pending.json'), true);
173
+ const raw = await readJson(path, this.root);
174
+ if (!raw)
175
+ continue;
176
+ const question = storedQuestionSchema.parse(raw);
177
+ if (sha256(question.questionnaireId) !== entry.name)
178
+ throw new Error('Questionnaire directory does not match its id.');
179
+ const record = await this.get(scope, question.questionnaireId);
180
+ if (record?.status === 'pending')
181
+ records.push(record);
182
+ }
183
+ return records.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
184
+ }
185
+ async accept(scope, questionnaireId, requestKey, input) {
186
+ const record = await this.get(scope, questionnaireId);
187
+ if (!record || record.requestKey !== requestKey)
188
+ throw new Error('Questionnaire response is stale or belongs to a different scope.');
189
+ if (record.status === 'withdrawn')
190
+ throw new Error('Questionnaire was explicitly withdrawn. Start a new decision if the user resumes the work.');
191
+ const answer = questionnaireAnswerSchema(record).parse(input);
192
+ assertSafeToPersist(answer);
193
+ if (record.status === 'answered') {
194
+ if (record.answer && stableStringify(record.answer) !== stableStringify(answer))
195
+ throw new Error('Questionnaire was already answered differently.');
196
+ return { record, answer: record.answer, replayed: true };
197
+ }
198
+ const receipt = {
199
+ requestKey,
200
+ answeredAt: new Date().toISOString(),
201
+ answerAvailable: answer.choice !== '__other__',
202
+ ...(answer.choice !== '__other__'
203
+ ? { answer: { choice: answer.choice, ...(answer.text ? { text: answer.text } : {}) } }
204
+ : {}),
205
+ };
206
+ const published = await this.publish(this.pathFor(scope, questionnaireId, 'answer.json'), receipt);
207
+ const resolved = (await this.get(scope, questionnaireId));
208
+ if (resolved.status === 'withdrawn')
209
+ throw new Error('Questionnaire was explicitly withdrawn before this response.');
210
+ if (!published &&
211
+ resolved.answer &&
212
+ stableStringify(resolved.answer) !== stableStringify(answer))
213
+ throw new Error('Questionnaire was already answered differently.');
214
+ return { record: resolved, answer: published ? answer : resolved.answer, replayed: !published };
215
+ }
216
+ async withdraw(scope, questionnaireId) {
217
+ const record = await this.get(scope, questionnaireId);
218
+ if (!record)
219
+ throw new Error('No questionnaire exists in this scope.');
220
+ if (record.status !== 'pending')
221
+ return record;
222
+ await this.publish(this.pathFor(scope, questionnaireId, 'answer.json'), {
223
+ requestKey: record.requestKey,
224
+ answeredAt: new Date().toISOString(),
225
+ answerAvailable: false,
226
+ withdrawn: true,
227
+ });
228
+ return (await this.get(scope, questionnaireId));
229
+ }
230
+ scopeRoot(scope) {
231
+ scopeSchema.parse(scope);
232
+ return join(this.root, scope.principalHash, sha256(`${scope.repoFingerprint}\n${scope.projectId ?? ''}`));
233
+ }
234
+ pathFor(scope, questionnaireId, filename) {
235
+ identifier.parse(questionnaireId);
236
+ return join(this.scopeRoot(scope), sha256(questionnaireId), filename);
237
+ }
238
+ async publish(path, value) {
239
+ assertSafeToPersist(value);
240
+ const temporaryPath = `${path}.${randomUUID()}.tmp`;
241
+ await writeJson(temporaryPath, value, this.root);
242
+ try {
243
+ const target = await assertManagedPath(this.root, path, true);
244
+ try {
245
+ await link(temporaryPath, target);
246
+ return true;
247
+ }
248
+ catch (error) {
249
+ if (isNodeError(error) && error.code === 'EEXIST')
250
+ return false;
251
+ throw error;
252
+ }
253
+ }
254
+ finally {
255
+ await removeFile(temporaryPath, this.root);
256
+ }
257
+ }
258
+ }
259
+ //# sourceMappingURL=questionnaire-store.js.map
@@ -136,7 +136,14 @@ export async function ensureManagedDirectory(root, directory) {
136
136
  if (!isNodeError(error) || error.code !== 'ENOENT') {
137
137
  throw error;
138
138
  }
139
- await mkdir(current, { mode: 0o700 });
139
+ try {
140
+ await mkdir(current, { mode: 0o700 });
141
+ }
142
+ catch (creationError) {
143
+ if (!isNodeError(creationError) || creationError.code !== 'EEXIST') {
144
+ throw creationError;
145
+ }
146
+ }
140
147
  const created = await lstat(current);
141
148
  if (!created.isDirectory() || created.isSymbolicLink()) {
142
149
  throw new Error(`Managed directory could not be created safely: ${current}`);
package/skill/SKILL.md CHANGED
@@ -7,11 +7,11 @@ description: Enforces the Engineering Memory lifecycle for Codex and Claude in r
7
7
 
8
8
  This skill is a thin runtime protocol. It contains no proprietary engineering rules. The task-specific rules, project profile, screen logic, component mappings, service contracts, decisions, and quality gates must come from the Engineering Memory MCP server.
9
9
 
10
- Read [lifecycle.md](references/lifecycle.md) before acting in a bound repository. Read [questionnaires.md](references/questionnaires.md) when authentication, project binding, project creation, membership, correction scope, proposal review, or Git-hook choices require user input. Read [memory-updates.md](references/memory-updates.md) before changing screen, component, service, project, or organization memory. Read [scaffolding.md](references/scaffolding.md) before creating a project from the organization architecture templates or changing a template.
10
+ Read [lifecycle.md](references/lifecycle.md) before acting in a bound repository. Read [project-onboarding.md](references/project-onboarding.md) for greenfield, adopt, rework and refresh flows. Read [questionnaires.md](references/questionnaires.md) when authentication, project binding, project creation, membership, correction scope, proposal review, or Git-hook choices require user input. Read [memory-updates.md](references/memory-updates.md) before changing screen, component, service, project, or organization memory. Read [scaffolding.md](references/scaffolding.md) before creating a project from the organization architecture templates or changing a template.
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.
14
+ 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, read [questionnaires.md](references/questionnaires.md), then use `questionnaire.ask` to open a durable native MCP form and `questionnaire.resume` to return to the same unanswered question. 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.
15
15
 
16
16
  1. Discover the repository binding through `session.entry`. Bindings live in the user-level Engineering Memory state directory, outside the repository and installed runtime. No project settings file is required. For a bound repository, the project's knowledge is in the backend, so answer nothing about it before bootstrapping; the absence of local design files or records says nothing about its stored knowledge.
17
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.
@@ -22,6 +22,8 @@ A task nobody is going to finish is abandoned rather than inherited. Ask the use
22
22
 
23
23
  If an existing task is identified or execution resumes after compaction, call `session.resume`. Reconcile backend sequence, local outbox, Markdown projections, current Git diff, pinned revisions, and the active lease before any further action.
24
24
 
25
+ Reconcile pending questions too. A required question remains unanswered across a timeout, dismissed form, interruption or restart. Use `questionnaire.resume` for its recorded identifier and wait for an explicit valid response before dependent work. Follow `questionnaires.md`; do not create a replacement async prompt or consume an unrelated task's answer.
26
+
25
27
  ## Discovery
26
28
 
27
29
  Perform only read operations until the relevant current code, tests, Figma evidence, Git diff, and returned memory have been inspected.
@@ -81,26 +83,51 @@ Temporary code written to reach or force a path — a pinned state, a fixed serv
81
83
 
82
84
  Do not write task Markdown files directly. The bridge owns event IDs, expected task versions, atomic projections, outbox state, and synchronization.
83
85
 
84
- ## Adopting a Project That Already Exists
85
-
86
- A repository bound with a working system in it can be surveyed at any time, not only at the moment
87
- it is bound. When the user asks for its architecture to be learned, read, recorded or understood,
88
- that is a survey: open it as a read-only task and follow `engineering-core.adopting-an-existing-project`,
89
- which says what a survey records and what it must never do.
90
-
91
- Four things come out of it and nothing else: the discovery policy that matches the repository's
92
- actual layout, the architecture already in place recorded on the project profile under
93
- `architecture.adopted` with the pressure each structure relieves — or `inherited, reason not
94
- recorded` where nobody knows — one module record per module saying what it owns, and one current
95
- deviation per divergence, each carrying what the rule says, what the project does, both costs, and
96
- a decision or an explicit deferral. Everything else is written as later tasks reach it.
97
-
98
- The survey changes nothing in the tree. No formatting, no renames, no tidying of dead code it
99
- finds; leftovers are recorded as observations and decided like any other divergence.
100
-
101
- Where the same product has a client and a backend, they are two projects. Adopt them separately,
102
- then link them with `project.link` once both exist, and ask before linking rather than inferring it
103
- from the names.
86
+ ## Four-mode project onboarding
87
+
88
+ Use `references/project-onboarding.md` and `engineering-core.adopting-an-existing-project` for the
89
+ full flow. Project creation asks through a native questionnaire for `greenfield`, `adopt`, or
90
+ `rework`; later incremental work is `refresh`. Read-only maturity analysis can recommend a mode but
91
+ cannot select one. Adopt, rework discovery, and refresh use committed `HEAD` by default, walk an
92
+ exhaustive cross-stack source inventory, and classify results as evidence, inference, or unknown.
93
+ The inventory covers frontend pages, navigation, state, service classes and network layers; backend
94
+ modules, services, data and API endpoints; project architecture; and the contracts connecting both
95
+ sides.
96
+
97
+ Greenfield applies compatible organization architecture modules and gates them with the real local
98
+ toolchain. Adopt and refresh never modify source. Rework asks per project whether the target keeps
99
+ behavior or redesigns it before proposing phased branches and tasks. Existing membership, active
100
+ user, tenant scope, explicit assignments, Project-first strongest-lock-first ordering, supplied
101
+ `EntityManager` transactions, immutable revisions and scope authority remain in force.
102
+
103
+ The implemented onboarding operations are `project.inspect`, `project.initialize`, `memory.source`,
104
+ `memory.sync_start`, `memory.sync_status`, `memory.sync_inventory`, `memory.sync_plan`,
105
+ `memory.sync_next`, `memory.sync_submit`, `memory.sync_reopen`, `memory.sync_delta`,
106
+ `memory.sync_verify`, `memory.catalog`, `memory.read_revisions`, and `memory.review_batch`.
107
+ `memory.sync_start` snapshots a selected Git ref, persists its commit/tree/manifest hashes and file
108
+ count, and uploads all manifest pages including exclusions. `memory.source` serves only bounded pages
109
+ or exact file reads from that committed snapshot. `memory.sync_status` lists runs or paged units and
110
+ coverage counters without exposing claim tokens.
111
+
112
+ `memory.sync_plan` uploads semantic units and their paths and dependency keys. The initial plan must
113
+ cover included and removed behavior, but analyzing may add newly discovered cross-module flow units.
114
+ Existing unit identities and definitions remain immutable; every added unit needs the same complete
115
+ submission, exact memory approval references and final verification before the run can complete.
116
+ `memory.sync_next` claims a distinct unit for 15 minutes;
117
+ the saved claim token explicitly resumes or renews that claim. `memory.sync_submit` records evidence,
118
+ all behavior facets, gaps and exact proposal or unchanged revision references. No unit is autoapproved,
119
+ and no historical task or motivation may be invented. Rejected or gapped unfinished units use
120
+ `memory.sync_reopen` with the current expected version. `memory.sync_delta` compares a completed prior
121
+ run's content hashes, memory revisions and transitive dependencies.
122
+
123
+ `memory.review_batch` displays full proposal texts before a durable native questionnaire records the
124
+ exact proposal IDs, content digests and base revisions. Pending or dismissed approval writes nothing;
125
+ stale batches fail atomically. `memory.sync_verify` requires sealed inventory, complete semantic and
126
+ removed-file coverage, submitted evidence, no unresolved gaps or failed unknown execution, and
127
+ approved immutable references for changed memory. `questionnaire.ask` and `questionnaire.resume`
128
+ support `preparation: true` for missing-folder decisions; `project.initialize` uses that native
129
+ confirmation before preparing the folder and Git repository. Raw secrets, headers, customer payloads
130
+ and PII are never uploaded.
104
131
 
105
132
  ## The Task Baseline
106
133
 
@@ -0,0 +1,146 @@
1
+ # Project onboarding and memory refresh
2
+
3
+ ## Choose the journey before changing the repository
4
+
5
+ Call `project.inspect` for the selected directory, including a missing directory. Its maturity
6
+ classification is `missing`, `empty`, `scaffold` or `established`, supported by manifests and
7
+ architecture/state/service/network/data/screen evidence. These are observations, not a mode choice.
8
+ Explain the appropriate options to the user and use `project.onboard` (or `project.initialize` for
9
+ initial Git preparation). The tool opens native forms for the actual mode and profile; do not write
10
+ an answer in chat or infer a choice from the directory classification.
11
+
12
+ The modes are `greenfield`, `adopt`, `rework` and `refresh`. Initial onboarding offers the first three;
13
+ refresh is available for a selected existing project at any later time. Recognize the user's intent
14
+ semantically in their own phrasing, with no keyword trigger table. Rework asks preserve or redesign
15
+ explicitly. Choosing another mode than the proposed input returns that choice without changing files;
16
+ repeat with the same requestKey and the user's selected mode.
17
+
18
+ Use a stable requestKey per onboarding occurrence. Profile answers must be collected with native
19
+ forms; each independent field is reviewed separately with its current value visible. The user may
20
+ keep it, explicitly defer it, or request an edit in the same bounded native text field. An edited value
21
+ is persisted as part of the exact decision and reaches the final profile digest; it cannot be silently
22
+ replaced by a later chat value. The onboarding forms confirm the exact
23
+ stack/runtime/architecture, service/API/auth/storage/environment choices and existing backend project
24
+ IDs. For Flutter/React they additionally confirm Figma, color palette, font families and text styles,
25
+ design system, navigation, platforms and languages; for Nest/Spring they confirm migration strategy.
26
+ An explicit absence or deferral is acceptable; a made-up answer is not.
27
+ The local machine's installed runtime is validation capability, not an architecture preference.
28
+
29
+ `project.initialize` only prepares Git for the approved greenfield mode. It supports missing, empty
30
+ and barely-started non-Git directories without overwriting existing files; parent repositories and
31
+ symbolic links are protected. Choices persist in user-local Engineering Memory state outside the
32
+ repository and installed package. After creating/selecting the backend project, pass projectId to
33
+ `project.onboard` to store its exact choice in the project onboarding history. Include the confirmed
34
+ profile in the normal project/profile proposal; onboarding choices do not independently activate
35
+ engineering rules. `project.onboarding_status` reads those choices and any rework plans. Old projects
36
+ remain uninspected until they opt in.
37
+
38
+ If a native form is pending, preserve its identifier and resume it. A wizard can return the next
39
+ pending form after one answer is accepted; complete that form and call the originating onboarding
40
+ operation again to advance. Restart reuses answered questions and exact choice data. It never assumes
41
+ approval after a cancellation, empty response or timeout.
42
+
43
+ ## Greenfield architecture and links
44
+
45
+ Prepare and bind the repository through the existing project.setup flow, then use architecture.plan
46
+ and architecture.module with the approved project profile. Apply the matching Flutter, React, NestJS
47
+ or Spring templates through the scaffold task lifecycle. Replace technical demonstration inputs with
48
+ approved application profile values; a compile fixture is not a user product or a ready live API.
49
+ Flutter exposes injected service/transport, state lifecycle and cancellation, storage boundary,
50
+ navigation and profile-controlled palette/typography. Validate the actual selected toolchain.
51
+
52
+ List authorized backend/frontend projects when services may already exist. Offer the actual selected
53
+ project IDs in the native profile form. Create approved links with project.link; a selection alone
54
+ never changes a link, and fullstack discipline never replaces membership or link authority. Keep
55
+ unavailable service contracts explicit rather than inventing endpoint behavior.
56
+
57
+ ## Durable deep learning
58
+
59
+ Adopt, refresh and the learning stage of rework do not modify application source. They inventory and
60
+ learn the whole architecture: modules, service classes, network and API contracts, state management,
61
+ persistence/data models/migrations, navigation, screens/components, localization, deployment,
62
+ authentication, runtime/toolchain and cross-module/end-to-end flows. Record source facts, tested
63
+ behavior, inference and unknown historical reasons separately. Never fabricate historical tasks,
64
+ prior developer decisions or reasons the code cannot establish.
65
+
66
+ 1. `memory.sync_start` opens a durable native confirmation bound to the mode, committed source hash
67
+ and previousRunId/rework behavior. Only acceptance starts the run. It snapshots the selected HEAD
68
+ or explicit ref, uploads every manifest page with exclusion reasons and seals the inventory.
69
+ Dirty worktree changes never silently enter shared memory. Repeating an occurrence reuses its key;
70
+ another commit requires a new occurrence and its own exact confirmation.
71
+ 2. `memory.source` pages the committed manifest or bounded chunks of one exact-commit file. Follow
72
+ nextCursor/nextOffset until null. Exclusions are reviewable evidence; inspect suspicious exclusions
73
+ and correct the classifier rather than declaring an incomplete project fully learned. Large owned
74
+ files remain in scope; bounded Git reads at the same commit can supplement the tool.
75
+ 3. `memory.sync_plan` creates meaningful semantic units with module keys, knowledge kinds, source
76
+ paths and dependency unit keys. Declare applicable architecture/state/services/network/data/UI/flow
77
+ areas and justify absence with source evidence. File coverage is necessary but does not certify
78
+ semantic understanding. Do not collapse an application into a generic all-files summary.
79
+ 4. `memory.sync_next` leases one unit for fifteen minutes. Save claimToken and use distinct requestKeys
80
+ for parallel workers; retry the same key after response loss. `memory.sync_status` exposes progress
81
+ and missing units without exposing another worker's token. No database transaction spans analysis.
82
+ 5. `memory.sync_submit` attaches per-path source or actual execution evidence and explicit findings
83
+ for entry, exit, authority, effects, errors, cancellation, retry, persistence and dependencies.
84
+ Link concrete witnesses; lack of applicability needs evidence and a reason. A source read and a
85
+ successful test are separate claims. Automated structural checks cannot prove the prose is true;
86
+ review the complete module package against actual code and tests before approval.
87
+ 6. Reuse the normal KnowledgeResource → pending Proposal → immutable Revision model. Present complete
88
+ module records, source evidence, uncertainty, deviations and archival actions. `memory.review_batch`
89
+ binds native approval to exact IDs, content hashes and base revisions, and reviews atomically under
90
+ existing authority. Deferring makes no change; when the user wants to reconsider, use its returned
91
+ nextReviewAttempt. Retrying the old attempt preserves the deferral, not an invented approval.
92
+ 7. `memory.sync_reopen` uses expectedVersion to repair an unfinished unit. `memory.sync_verify` requires
93
+ sealed inventory, source/removed-path and declared-area coverage, valid evidence, resolved gaps and
94
+ approved exact memory references. It cannot replace the Git diff gate for any source-writing task.
95
+
96
+ ## Incremental refresh and Git evidence
97
+
98
+ Use a completed previousRunId. `memory.sync_delta` refuses an unsealed new inventory and pages changed
99
+ units and dependent flows. With repoRoot on the first page it adds bounded Git history/rename evidence;
100
+ `memory.source` with previousCommit and commit can read further source change pages. Commit IDs and
101
+ source trees establish provenance; commit messages do not establish developer intent. Squash or
102
+ rewritten/missing history falls back explicitly to the persisted file/blob comparison. A missing
103
+ commit is not proof of zero changes.
104
+
105
+ Review renames against existing resource identities and propose selector relocation rather than
106
+ unnecessarily creating duplicate records. Removed behavior requires reviewed archival with the old
107
+ identity; revision history is retained. Code deviating from a rule does not silently change that rule.
108
+ Known changed memory invalidates its units/dependants. New records are compared against selectors;
109
+ unscoped records may require conservative review because there is no defensible narrow scope.
110
+ A source/memory no-op reuses approved evidence with provenance and creates no gratuitous revisions.
111
+
112
+ At module boundaries read memory.sync_status with repoRoot. If newer commits exist, keep the pinned
113
+ inspection unchanged and start a new `memory.sync_start` refresh occurrence with the follow-up commit
114
+ and previousRunId; never silently move its baseline or call old coverage current. Read-only completion
115
+ is a coverage decision, not an implementation verification. Long-running analysis must checkpoint its
116
+ remaining units and decisions durably.
117
+
118
+ The core pack is intentionally bounded. Linked contracts remain inline while they fit the budget; when
119
+ the response reports `additionalMemory`, read the full authorized catalogue with
120
+ `memory.catalog({projectId, includeLinked: true, afterId, limit})`, then use the current session with
121
+ `memory.read_revisions` for the exact revision IDs needed by the task. Do not assume the core pack is
122
+ exhaustive. `memory.catalog` is the paging surface for linked project memory; it does not grant access
123
+ or change project links.
124
+
125
+ For source orientation, prefer the bounded summary returned by `project.inspect` and its nested stack
126
+ signals. Its `totalFiles`, `omittedFiles`, `prunedDirectories` and `historyTruncated` fields are part
127
+ of the evidence: a nonzero omission or truncated history requires bounded follow-up, not a claim that
128
+ the tree or history was fully read. Use the existing paged `memory.source` pages only for the committed
129
+ paths and symbols required by the chosen unit; do not request or serialize an unbounded full tree.
130
+
131
+ ## Rework target and execution
132
+
133
+ Complete deep learning first. Propose and approve target architecture in existing immutable knowledge;
134
+ redesign also requires the intended screens, flows and API/service contracts. Build a dependency-ordered
135
+ phase plan with acceptance criteria and branch names, covering every inspected unit by key. Present it
136
+ in full. `project.rework_plan` opens a native form for that exact source/target/phase digest and persists
137
+ real WorkItems and their dependencies; stale targets, incomplete learning or changed idempotency inputs
138
+ must refuse before partial creation. Deferred plans can be submitted under a new explicit requestKey
139
+ when the user requests review again.
140
+
141
+ Each phase opens an ordinary implementation task on its recorded branch in a separate worktree where
142
+ needed. Preserve all lease, history, reconciliation, self-review, validation, verify, close and delivery
143
+ gates. Cross-project phases use existing work_item.plan/confirm_plan with explicit scope authorization
144
+ and independent per-repository runs. Planning tasks does not perform their implementation.
145
+
146
+ A deferred individual field or final profile saves no onboarding choice. Retrying that request preserves its deferral. When the user explicitly resumes, use the returned `nextRequestKey` for fresh native decisions; never infer approval from a previous defer.