burhan-mop 0.1.5 → 0.1.7

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/.MOP/PROTOCOL.md CHANGED
@@ -31,7 +31,8 @@ Use this order:
31
31
  7. Coding/adventure language.
32
32
  8. GitHub project link. Required for `team`, optional for `solo`.
33
33
  9. GitHub username.
34
- 10. Git commit email. Use a GitHub-verified email or GitHub noreply email.
34
+ 10. Git commit email. Default to `github-noreply` so MOP derives the active
35
+ GitHub account email as `ID+USERNAME@users.noreply.github.com`.
35
36
  11. If `team`, ask join mode: `open`, `owner-approved`, or `invite`.
36
37
  12. Ask auto deploy: `Nak aktifkan auto deploy sekarang? Pilih provider:
37
38
  GitHub, Docker, Vercel.` If the user says later/no, answer with the defer
@@ -40,7 +41,7 @@ Use this order:
40
41
  After confirmation, run:
41
42
 
42
43
  ```bash
43
- node .MOP/scripts/mop-core.mjs setup --project-name "<name>" --name "<display>" --codename <codename> --password "<password>" --mode <solo|team> --conversation-language "<lang>" --coding-language "<lang>" --git-email "<github-verified-email>" [--git-name "<display>"] [--github-username "<github-login>"] [--github-url "<url>"] [--join-mode <mode>]
44
+ node .MOP/scripts/mop-core.mjs setup --project-name "<name>" --name "<display>" --codename <codename> --password "<password>" --mode <solo|team> --conversation-language "<lang>" --coding-language "<lang>" --git-email github-noreply [--git-name "<display>"] [--github-username "<github-login>"] [--github-url "<url>"] [--join-mode <mode>]
44
45
  ```
45
46
 
46
47
  ## Agent Naming Ceremony
@@ -60,6 +61,40 @@ Gate order:
60
61
  3. `AGENT_GATE`
61
62
  4. `ACTION`
62
63
 
64
+ ## Answer Contract
65
+
66
+ For every authenticated user-facing answer, the assistant must show the active
67
+ named agent before the content. This applies to short answers, explanations,
68
+ status updates, plans, code-change summaries, and final responses.
69
+
70
+ Required first line format:
71
+
72
+ ```text
73
+ agent: <agent-name> (<agent-role>) to <user>
74
+ ```
75
+
76
+ Rules:
77
+
78
+ - Do not answer authenticated work without an active named agent.
79
+ - If no active agent exists, ask the user to name the required agent and stop.
80
+ - After `agent route`, use `answerContract.firstLine` from the JSON response.
81
+ - Party Mode keeps the full party dialogue format, but the final handoff to the
82
+ user must still show the speaking agent.
83
+ - Never hide the selected agent in prose. The first visible line must identify
84
+ the agent.
85
+
86
+ Before answering, restore monthly memory:
87
+
88
+ ```bash
89
+ node .MOP/scripts/mop-core.mjs memory brief --actor <codename>
90
+ ```
91
+
92
+ After meaningful work or a useful answer, save memory:
93
+
94
+ ```bash
95
+ node .MOP/scripts/mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"
96
+ ```
97
+
63
98
  ## Agent Router
64
99
 
65
100
  After authentication and before the Agent Gate, route the user's task to one
@@ -88,6 +123,9 @@ Routing rules:
88
123
  genuinely requires several areas of expertise.
89
124
  - If the route reports `partyMode.active: true`, run Party Mode before the final
90
125
  answer. Name any missing participant agents first.
126
+ - The route JSON includes an `answerContract`. The assistant must restore
127
+ monthly memory, start the visible answer with `answerContract.firstLine`, and
128
+ save a one-line memory after meaningful work.
91
129
 
92
130
  Example:
93
131
 
@@ -282,6 +320,34 @@ The skill must call `mop-workflow.mjs help` and answer with the next action,
282
320
  lead agent, party agents, next artifact, and whether readiness/adversarial gates
283
321
  apply.
284
322
 
323
+ ## Monthly Memory
324
+
325
+ MOP keeps durable memory in two layers:
326
+
327
+ - `.MOP/STATE.json` ledger for compact structured state.
328
+ - `.MOP/memory/YYYY-MM.jsonl` for append-only monthly conversation memory.
329
+
330
+ The monthly memory file exists so a fresh chat can restore prior context without
331
+ the user explaining the whole project again. Each useful session must:
332
+
333
+ 1. Run `memory brief` before answering after authentication.
334
+ 2. Save one-line outcomes with `memory add`.
335
+ 3. Use `.MOP/memory/SESSION_BRIEF.md` as the short human-readable handoff.
336
+
337
+ Commands:
338
+
339
+ ```bash
340
+ node .MOP/scripts/mop-core.mjs memory brief --actor <codename>
341
+ node .MOP/scripts/mop-core.mjs memory restore --actor <codename>
342
+ node .MOP/scripts/mop-core.mjs memory add --actor <codename> --kind conversation --summary "<what happened>"
343
+ ```
344
+
345
+ Autosycn memory also writes to the monthly memory file:
346
+
347
+ ```bash
348
+ node .MOP/scripts/mop-autosycn.mjs memory --actor <codename> --summary "<what changed>"
349
+ ```
350
+
285
351
  ## Installer
286
352
 
287
353
  The package installer command is:
@@ -366,10 +432,13 @@ In team mode, an agent name is the identity.
366
432
  Autosycn is always available and should be used after meaningful state or file
367
433
  changes. It is intentionally identity-safe.
368
434
 
369
- Before first push for a member, configure the real Git identity:
435
+ Before first push for a member, configure the real GitHub identity. Default to
436
+ GitHub noreply so commits attach to the active user account without exposing
437
+ private email:
370
438
 
371
439
  ```bash
372
- node .MOP/scripts/mop-core.mjs member git-identity --actor <codename> --name "<display name>" --email "<github-verified-email>" --github-username "<github-login>"
440
+ gh auth login
441
+ node .MOP/scripts/mop-core.mjs member git-identity --actor <codename> --name "<display name>" --email github-noreply --github-username "<github-login>"
373
442
  ```
374
443
 
375
444
  Then run:
@@ -390,6 +459,13 @@ Autosycn must:
390
459
  - Commit with `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, and
391
460
  `GIT_COMMITTER_EMAIL` set from member state.
392
461
  - Set local `git config user.name` and `user.email` before commit/merge.
462
+ - For member commits, derive `user.email` from the active GitHub CLI account as
463
+ `ID+USERNAME@users.noreply.github.com` when
464
+ `autosync.githubIdentity.useNoreplyForMemberCommits` is enabled.
465
+ - Refuse to commit or push if `gh api user` is authenticated as a different
466
+ GitHub username than the active member's configured `githubUsername`.
467
+ - Use the member GitHub identity for setup, preflight, memory, and work branch
468
+ commits. Use `BURHAN-MOP` only for merge guardian commits.
393
469
  - In team mode, `main` is the trunk and each user works on `dev/<codename>`.
394
470
  - Push to `dev/<codename>` in team mode and `main` in solo mode.
395
471
  - After a team push, BURHAN-MOP reviews `dev/<codename>` and merges it into
@@ -404,10 +480,11 @@ Autosycn must:
404
480
  - If `githubUsername` is configured, refuse to push unless `gh api user`
405
481
  verifies the same account.
406
482
 
407
- Important: GitHub commit attribution comes from commit email. GitHub push actor
408
- comes from the credential or SSH key used by `git push`; no script can fake that.
409
- If GitHub shows the AI/bot as pusher, fix `gh auth login`, Git Credential
410
- Manager, or the SSH key account.
483
+ Important: GitHub commit attribution comes from commit email. MOP uses the
484
+ active GitHub account noreply email for member commits by default. GitHub push
485
+ actor comes from the credential or SSH key used by `git push`; no script can
486
+ fake that. If GitHub shows the wrong pusher, fix `gh auth login`, Git
487
+ Credential Manager, or the SSH key account.
411
488
 
412
489
  ## Default Skill: auto-deploy
413
490
 
package/.MOP/STATE.json CHANGED
@@ -13,6 +13,31 @@
13
13
  "defaultTitle": "Core Agent",
14
14
  "gateOrder": "AUTH_GATE_THEN_AGENT_ROUTER_THEN_AGENT_GATE_THEN_ACTION"
15
15
  },
16
+ "answerPolicy": {
17
+ "requireVisibleAgent": true,
18
+ "visibleAgentFormat": "agent: <agent-name> (<agent-role>) to <user>",
19
+ "requireMemoryRestore": true,
20
+ "requireMemorySave": true,
21
+ "rules": [
22
+ "Every authenticated user-facing answer must start with the active named agent line.",
23
+ "If no active named agent exists, ask the user to name the required agent before answering the task.",
24
+ "Party Mode keeps its dialogue format, but the final handoff to the user must still show the speaking agent."
25
+ ]
26
+ },
27
+ "memoryPolicy": {
28
+ "enabled": true,
29
+ "directory": ".MOP/memory",
30
+ "sessionBrief": ".MOP/memory/SESSION_BRIEF.md",
31
+ "monthlyPattern": "YYYY-MM.jsonl",
32
+ "recentLimit": 20,
33
+ "restoreBeforeAnswer": true,
34
+ "saveAfterMeaningfulWork": true,
35
+ "rules": [
36
+ "Save one-line memories to a monthly JSONL file so new chats can restore context.",
37
+ "Generate SESSION_BRIEF.md from recent monthly memory entries.",
38
+ "Read memory brief before answering any authenticated task."
39
+ ]
40
+ },
16
41
  "agentRouter": {
17
42
  "enabled": true,
18
43
  "primaryAgentLimit": 1,
@@ -374,7 +399,18 @@
374
399
  }
375
400
  },
376
401
  "requireUserGitEmail": true,
377
- "verifyGhUserWhenConfigured": true
402
+ "verifyGhUserWhenConfigured": true,
403
+ "githubIdentity": {
404
+ "requireMatchedGhUser": true,
405
+ "useNoreplyForMemberCommits": true,
406
+ "noreplyFormat": "id-plus-login",
407
+ "rules": [
408
+ "Member commits must use the active member GitHub account, not an AI or guardian identity.",
409
+ "When gh is authenticated, derive member commit email as ID+USERNAME@users.noreply.github.com.",
410
+ "Refuse autosycn if the active gh account does not match the member githubUsername.",
411
+ "BURHAN-MOP identity is reserved for merge guardian commits only."
412
+ ]
413
+ }
378
414
  },
379
415
  "members": {},
380
416
  "installedSkills": [
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { spawnSync } from 'node:child_process';
@@ -109,6 +109,123 @@ function agentLedgerFields(agent) {
109
109
  } : {};
110
110
  }
111
111
 
112
+ function memoryPolicy(state) {
113
+ return state.memoryPolicy || {
114
+ enabled: true,
115
+ directory: '.MOP/memory',
116
+ sessionBrief: '.MOP/memory/SESSION_BRIEF.md',
117
+ monthlyPattern: 'YYYY-MM.jsonl',
118
+ recentLimit: 20
119
+ };
120
+ }
121
+
122
+ function answerPolicy(state) {
123
+ return state.answerPolicy || {
124
+ requireVisibleAgent: true,
125
+ visibleAgentFormat: 'agent: <agent-name> (<agent-role>) to <user>'
126
+ };
127
+ }
128
+
129
+ function monthKey(date = new Date()) {
130
+ return date.toISOString().slice(0, 7);
131
+ }
132
+
133
+ function memoryDirFor(state) {
134
+ return join(rootDir, memoryPolicy(state).directory || '.MOP/memory');
135
+ }
136
+
137
+ function monthlyMemoryPath(state, month = monthKey()) {
138
+ const policy = memoryPolicy(state);
139
+ const filename = (policy.monthlyPattern || 'YYYY-MM.jsonl').replace('YYYY-MM', month);
140
+ return join(memoryDirFor(state), filename);
141
+ }
142
+
143
+ function sessionBriefPath(state) {
144
+ return join(rootDir, memoryPolicy(state).sessionBrief || '.MOP/memory/SESSION_BRIEF.md');
145
+ }
146
+
147
+ function readJsonl(path) {
148
+ if (!existsSync(path)) return [];
149
+ return readFileSync(path, 'utf8')
150
+ .split(/\r?\n/)
151
+ .filter(Boolean)
152
+ .map((line) => {
153
+ try {
154
+ return JSON.parse(line);
155
+ } catch {
156
+ return null;
157
+ }
158
+ })
159
+ .filter(Boolean);
160
+ }
161
+
162
+ function memoryMonths(state) {
163
+ const dir = memoryDirFor(state);
164
+ if (!existsSync(dir)) return [];
165
+ return readdirSync(dir)
166
+ .filter((name) => /^\d{4}-\d{2}\.jsonl$/.test(name))
167
+ .map((name) => name.replace(/\.jsonl$/, ''))
168
+ .sort();
169
+ }
170
+
171
+ function latestMemoryEntries(state, limit = memoryPolicy(state).recentLimit || 20) {
172
+ const months = memoryMonths(state);
173
+ const selected = months.length ? months.slice(-3) : [monthKey()];
174
+ return selected
175
+ .flatMap((month) => readJsonl(monthlyMemoryPath(state, month)))
176
+ .sort((a, b) => String(a.at || '').localeCompare(String(b.at || '')))
177
+ .slice(-limit);
178
+ }
179
+
180
+ function answerLineFor(state, actor, agent = activeAgentFor(state, actor)) {
181
+ if (!agent) return 'agent: <name> (<role>) to <user>';
182
+ return (answerPolicy(state).visibleAgentFormat || 'agent: <agent-name> (<agent-role>) to <user>')
183
+ .replace('<agent-name>', agent.name)
184
+ .replace('<agent-role>', agent.role)
185
+ .replace('<agent-title>', agent.title || agent.role)
186
+ .replace('<user>', actor || 'user');
187
+ }
188
+
189
+ function appendMonthlyMemory(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
190
+ if (memoryPolicy(state).enabled === false) return;
191
+ const entry = { at: now(), actor, ...agentLedgerFields(agent), kind, summary };
192
+ const monthlyPath = monthlyMemoryPath(state);
193
+ mkdirSync(dirname(monthlyPath), { recursive: true });
194
+ writeFileSync(monthlyPath, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', flag: 'a' });
195
+ writeSessionBrief(state, actor);
196
+ }
197
+
198
+ function writeSessionBrief(state, actor) {
199
+ const path = sessionBriefPath(state);
200
+ const agent = activeAgentFor(state, actor);
201
+ const entries = latestMemoryEntries(state, memoryPolicy(state).recentLimit || 20);
202
+ const lines = [
203
+ '# MOP Session Brief',
204
+ '',
205
+ `Updated: ${now()}`,
206
+ `Actor: ${actor || state.activeMember || 'unknown'}`,
207
+ `Active agent: ${agent ? `${agent.name} (${agent.role})` : 'none'}`,
208
+ `Current month: ${monthKey()}`,
209
+ '',
210
+ '## Required Session Flow',
211
+ '',
212
+ '1. Read `.MOP/STATE.json` and follow `.MOP/PROTOCOL.md`.',
213
+ '2. Restore memory with `node .MOP/scripts/mop-core.mjs memory brief --actor <codename>`.',
214
+ '3. Run `agent route` for the user task before answering.',
215
+ `4. Start every authenticated answer with: \`${answerLineFor(state, actor, agent)}\``,
216
+ '5. Save a one-line memory after meaningful work.',
217
+ '',
218
+ '## Recent Memory',
219
+ '',
220
+ ...entries.map((entry) => {
221
+ const who = entry.agent ? `${entry.agent} (${entry.agentRole || 'agent'})` : entry.actor;
222
+ return `- ${entry.at} - ${who}: ${entry.summary}`;
223
+ })
224
+ ];
225
+ mkdirSync(dirname(path), { recursive: true });
226
+ writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
227
+ }
228
+
112
229
  function appendLedger(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
113
230
  state.ledger ||= [];
114
231
  state.ledger.push({ at: now(), actor, ...agentLedgerFields(agent), kind, summary });
@@ -130,22 +247,59 @@ function getMember(state, actor) {
130
247
  return member;
131
248
  }
132
249
 
250
+ function githubIdentityPolicy(state) {
251
+ return state.autosync?.githubIdentity || {
252
+ requireMatchedGhUser: true,
253
+ useNoreplyForMemberCommits: true,
254
+ noreplyFormat: 'id-plus-login'
255
+ };
256
+ }
257
+
258
+ function currentGhUser() {
259
+ const result = runOptional('gh', ['api', 'user', '--jq', '{login:.login,id:.id,email:.email}']);
260
+ if (!result.ok) return null;
261
+ try {
262
+ return JSON.parse(result.stdout || '{}');
263
+ } catch {
264
+ return null;
265
+ }
266
+ }
267
+
268
+ function githubNoreplyEmail(user) {
269
+ if (!user?.login || !user?.id) return '';
270
+ return `${user.id}+${user.login}@users.noreply.github.com`;
271
+ }
272
+
133
273
  function identityFor(state, actor) {
134
274
  const member = getMember(state, actor);
135
275
  const identity = member.gitIdentity || {};
276
+ const policy = githubIdentityPolicy(state);
277
+ const gh = currentGhUser();
136
278
  const name = identity.name || member.displayName || actor;
137
- const email = identity.email || member.github?.noreplyEmail || '';
279
+ const githubUsername = identity.githubUsername || member.github?.username || gh?.login || '';
280
+ if (gh?.login && githubUsername && policy.requireMatchedGhUser !== false && gh.login.toLowerCase() !== githubUsername.toLowerCase()) {
281
+ throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${githubUsername}. Refusing to commit or push as the wrong user.`);
282
+ }
283
+ let email = identity.email || member.github?.noreplyEmail || '';
284
+ if (policy.useNoreplyForMemberCommits !== false) {
285
+ email = githubNoreplyEmail(gh);
286
+ if (!email) {
287
+ throw new Error('GitHub noreply identity is required for member commits. Run gh auth login as the real user, or set autosync.githubIdentity.useNoreplyForMemberCommits=false.');
288
+ }
289
+ }
138
290
  if (!email && state.autosync?.requireUserGitEmail !== false) {
139
291
  throw new Error([
140
292
  `Missing git email for ${actor}.`,
141
- 'Set a GitHub-verified email or noreply email first:',
142
- `node .MOP/scripts/mop-core.mjs member git-identity --actor ${actor} --name "${name}" --email "<github-verified-email>" [--github-username "<username>"]`
293
+ 'Set a GitHub-verified email or let MOP derive GitHub noreply from gh:',
294
+ `node .MOP/scripts/mop-core.mjs member git-identity --actor ${actor} --name "${name}" --email github-noreply [--github-username "<username>"]`
143
295
  ].join(' '));
144
296
  }
145
297
  return {
146
298
  name,
147
299
  email,
148
- githubUsername: identity.githubUsername || member.github?.username || ''
300
+ githubUsername,
301
+ githubUserId: gh?.login?.toLowerCase() === githubUsername.toLowerCase() ? gh.id : identity.githubUserId,
302
+ emailSource: policy.useNoreplyForMemberCommits !== false ? 'github-noreply' : (identity.emailSource || 'manual')
149
303
  };
150
304
  }
151
305
 
@@ -218,15 +372,16 @@ function configureRemote(url, replaceRemote = false) {
218
372
  }
219
373
 
220
374
  function verifyGhUser(identity, state) {
221
- if (!identity.githubUsername || state.autosync?.verifyGhUserWhenConfigured === false) return 'skipped';
222
- const gh = runOptional('gh', ['api', 'user', '--jq', '.login']);
223
- if (!gh.ok) {
375
+ const policy = githubIdentityPolicy(state);
376
+ if (state.autosync?.verifyGhUserWhenConfigured === false && policy.requireMatchedGhUser === false) return 'skipped';
377
+ const gh = currentGhUser();
378
+ if (!gh?.login) {
224
379
  throw new Error('GitHub username is configured, but gh could not verify the active account. Run gh auth login as the real user or set autosync.verifyGhUserWhenConfigured=false for SSH-only workflows.');
225
380
  }
226
- if (gh.stdout.toLowerCase() !== identity.githubUsername.toLowerCase()) {
227
- throw new Error(`GitHub CLI authenticated as ${gh.stdout}, expected ${identity.githubUsername}. Refusing to push as the wrong account.`);
381
+ if (identity.githubUsername && gh.login.toLowerCase() !== identity.githubUsername.toLowerCase()) {
382
+ throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${identity.githubUsername}. Refusing to push as the wrong account.`);
228
383
  }
229
- return `verified:${gh.stdout}`;
384
+ return `verified:${gh.login}:${githubNoreplyEmail(gh) || 'email-unavailable'}`;
230
385
  }
231
386
 
232
387
  function runProjectCommand(command, env) {
@@ -392,10 +547,11 @@ function preflight(args) {
392
547
  }, null, 2));
393
548
  }
394
549
 
395
- function saveMemory(actor, summary) {
550
+ function saveMemory(actor, summary, kind = 'conversation') {
396
551
  const state = readState();
397
552
  const agent = requireActiveAgent(state, actor);
398
553
  appendLedger(state, actor, 'memory', summary, agent);
554
+ appendMonthlyMemory(state, actor, kind, summary, agent);
399
555
  writeState(state);
400
556
  return state;
401
557
  }
@@ -559,6 +715,7 @@ function status() {
559
715
  workBranchPrefix: state.autosync?.workBranchPrefix || 'dev',
560
716
  autoMergeToMain: state.autosync?.autoMergeToMain !== false,
561
717
  mergeGuardian: guardianConfig(state),
718
+ githubIdentity: githubIdentityPolicy(state),
562
719
  preflightBeforeWork: state.autosync?.preflightBeforeWork !== false,
563
720
  requireUserGitEmail: state.autosync?.requireUserGitEmail !== false,
564
721
  initialized: state.initialized,
@@ -573,7 +730,8 @@ function status() {
573
730
  displayName: member.displayName,
574
731
  gitIdentityConfigured: Boolean(member.gitIdentity?.email || member.github?.noreplyEmail),
575
732
  gitName: member.gitIdentity?.name || member.displayName || key,
576
- gitEmail: member.gitIdentity?.email || member.github?.noreplyEmail || ''
733
+ gitEmail: member.gitIdentity?.email || member.github?.noreplyEmail || '',
734
+ githubUsername: member.gitIdentity?.githubUsername || member.github?.username || ''
577
735
  }
578
736
  ]))
579
737
  }, null, 2));
@@ -588,7 +746,8 @@ function main() {
588
746
  if (command === 'memory') {
589
747
  const actor = requireArg(args, 'actor');
590
748
  const summary = String(args.summary || args.reason || 'MOP conversation');
591
- saveMemory(actor, summary);
749
+ const kind = String(args.kind || 'conversation');
750
+ saveMemory(actor, summary, kind);
592
751
  console.log(`Memory saved for ${actor}.`);
593
752
  return;
594
753
  }
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
6
+ import { spawnSync } from 'node:child_process';
6
7
 
7
8
  const here = dirname(fileURLToPath(import.meta.url));
8
9
  const coreDir = resolve(here, '..');
@@ -71,6 +72,53 @@ function verifyPassword(password, salt, expectedHex) {
71
72
  return expected.length === actual.length && timingSafeEqual(actual, expected);
72
73
  }
73
74
 
75
+ function currentGhUser() {
76
+ const result = spawnSync('gh', ['api', 'user', '--jq', '{login:.login,id:.id,email:.email}'], {
77
+ cwd: rootDir,
78
+ encoding: 'utf8'
79
+ });
80
+ if (result.status !== 0) return null;
81
+ try {
82
+ return JSON.parse(result.stdout || '{}');
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ function githubNoreplyEmail(user) {
89
+ if (!user?.login || !user?.id) return '';
90
+ return `${user.id}+${user.login}@users.noreply.github.com`;
91
+ }
92
+
93
+ function resolveGitIdentityInput(state, actor, name, emailInput, githubUsernameInput) {
94
+ const policy = state.autosync?.githubIdentity || {};
95
+ const preferNoreply = policy.useNoreplyForMemberCommits !== false;
96
+ const gh = currentGhUser();
97
+ const githubUsername = githubUsernameInput || gh?.login || '';
98
+ if (gh?.login && githubUsername && policy.requireMatchedGhUser !== false && gh.login.toLowerCase() !== githubUsername.toLowerCase()) {
99
+ throw new Error(`GitHub CLI authenticated as ${gh.login}, expected ${githubUsername}. Run gh auth login as the real user.`);
100
+ }
101
+
102
+ let email = String(emailInput || '').trim();
103
+ const wantsNoreply = !email || ['auto', 'github', 'github-noreply', 'noreply'].includes(email.toLowerCase());
104
+ if (preferNoreply && wantsNoreply) {
105
+ email = githubNoreplyEmail(gh);
106
+ if (!email) {
107
+ throw new Error('Cannot derive GitHub noreply email. Run gh auth login or provide --git-email "<github-verified-email>".');
108
+ }
109
+ }
110
+ if (!email && state.autosync?.requireUserGitEmail !== false) {
111
+ throw new Error('Git email is required. Use --git-email github-noreply after gh auth login, or provide a GitHub-verified email.');
112
+ }
113
+ return {
114
+ name,
115
+ email,
116
+ githubUsername,
117
+ githubUserId: gh?.login?.toLowerCase() === githubUsername.toLowerCase() ? gh.id : undefined,
118
+ emailSource: wantsNoreply && email ? 'github-noreply' : 'manual'
119
+ };
120
+ }
121
+
74
122
  function activeAgentFor(state, actor) {
75
123
  const activeId = state.activeAgents?.[actor];
76
124
  if (!activeId) return null;
@@ -87,6 +135,103 @@ function agentLedgerFields(agent) {
87
135
  } : {};
88
136
  }
89
137
 
138
+ function memoryPolicy(state) {
139
+ return state.memoryPolicy || {
140
+ enabled: true,
141
+ directory: '.MOP/memory',
142
+ sessionBrief: '.MOP/memory/SESSION_BRIEF.md',
143
+ monthlyPattern: 'YYYY-MM.jsonl',
144
+ recentLimit: 20
145
+ };
146
+ }
147
+
148
+ function answerPolicy(state) {
149
+ return state.answerPolicy || {
150
+ requireVisibleAgent: true,
151
+ visibleAgentFormat: 'agent: <agent-name> (<agent-role>) to <user>',
152
+ requireMemoryRestore: true,
153
+ requireMemorySave: true
154
+ };
155
+ }
156
+
157
+ function monthKey(date = new Date()) {
158
+ return date.toISOString().slice(0, 7);
159
+ }
160
+
161
+ function relativeFromRoot(path) {
162
+ return path.replace(rootDir, '').replace(/^[\\/]/, '').replaceAll('\\', '/');
163
+ }
164
+
165
+ function memoryDirFor(state) {
166
+ return join(rootDir, memoryPolicy(state).directory || '.MOP/memory');
167
+ }
168
+
169
+ function monthlyMemoryPath(state, month = monthKey()) {
170
+ const policy = memoryPolicy(state);
171
+ const filename = (policy.monthlyPattern || 'YYYY-MM.jsonl').replace('YYYY-MM', month);
172
+ return join(memoryDirFor(state), filename);
173
+ }
174
+
175
+ function sessionBriefPath(state) {
176
+ return join(rootDir, memoryPolicy(state).sessionBrief || '.MOP/memory/SESSION_BRIEF.md');
177
+ }
178
+
179
+ function readJsonl(path) {
180
+ if (!existsSync(path)) return [];
181
+ return readFileSync(path, 'utf8')
182
+ .split(/\r?\n/)
183
+ .filter(Boolean)
184
+ .map((line) => {
185
+ try {
186
+ return JSON.parse(line);
187
+ } catch {
188
+ return null;
189
+ }
190
+ })
191
+ .filter(Boolean);
192
+ }
193
+
194
+ function memoryMonths(state) {
195
+ const dir = memoryDirFor(state);
196
+ if (!existsSync(dir)) return [];
197
+ return readdirSync(dir)
198
+ .filter((name) => /^\d{4}-\d{2}\.jsonl$/.test(name))
199
+ .map((name) => name.replace(/\.jsonl$/, ''))
200
+ .sort();
201
+ }
202
+
203
+ function latestMemoryEntries(state, limit = memoryPolicy(state).recentLimit || 20) {
204
+ const months = memoryMonths(state);
205
+ const selected = months.length ? months.slice(-3) : [monthKey()];
206
+ return selected
207
+ .flatMap((month) => readJsonl(monthlyMemoryPath(state, month)))
208
+ .sort((a, b) => String(a.at || '').localeCompare(String(b.at || '')))
209
+ .slice(-limit);
210
+ }
211
+
212
+ function answerContractFor(state, actor, agent = activeAgentFor(state, actor)) {
213
+ const policy = answerPolicy(state);
214
+ const format = policy.visibleAgentFormat || 'agent: <agent-name> (<agent-role>) to <user>';
215
+ const firstLine = agent
216
+ ? format
217
+ .replace('<agent-name>', agent.name)
218
+ .replace('<agent-role>', agent.role)
219
+ .replace('<agent-title>', agent.title || agent.role)
220
+ .replace('<user>', actor || 'user')
221
+ : '';
222
+ return {
223
+ required: policy.requireVisibleAgent !== false,
224
+ firstLine,
225
+ beforeAnswer: `node .MOP/scripts/mop-core.mjs memory brief --actor ${actor || '<codename>'}`,
226
+ afterAnswer: `node .MOP/scripts/mop-core.mjs memory add --actor ${actor || '<codename>'} --kind conversation --summary "<one-line outcome>"`,
227
+ rules: [
228
+ 'Do not answer authenticated work without an active named agent.',
229
+ 'Every user-facing answer must show the active agent line first.',
230
+ 'Restore monthly memory before answering and save a one-line memory after meaningful work.'
231
+ ]
232
+ };
233
+ }
234
+
90
235
  function requireActiveAgent(state, actor, role = 'core', title = 'Core Agent') {
91
236
  const agent = activeAgentFor(state, actor);
92
237
  if (agent) return agent;
@@ -102,6 +247,48 @@ function appendLedger(state, actor, kind, summary, agent = activeAgentFor(state,
102
247
  state.ledger.push({ at: now(), actor, ...agentLedgerFields(agent), kind, summary });
103
248
  }
104
249
 
250
+ function appendMonthlyMemory(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
251
+ if (memoryPolicy(state).enabled === false) return null;
252
+ const entry = { at: now(), actor, ...agentLedgerFields(agent), kind, summary };
253
+ const monthlyPath = monthlyMemoryPath(state);
254
+ mkdirSync(dirname(monthlyPath), { recursive: true });
255
+ writeFileSync(monthlyPath, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', flag: 'a' });
256
+ writeSessionBrief(state, actor);
257
+ return { entry, monthlyPath };
258
+ }
259
+
260
+ function writeSessionBrief(state, actor) {
261
+ const path = sessionBriefPath(state);
262
+ const agent = activeAgentFor(state, actor);
263
+ const entries = latestMemoryEntries(state, memoryPolicy(state).recentLimit || 20);
264
+ const contract = answerContractFor(state, actor, agent);
265
+ const lines = [
266
+ '# MOP Session Brief',
267
+ '',
268
+ `Updated: ${now()}`,
269
+ `Actor: ${actor || state.activeMember || 'unknown'}`,
270
+ `Active agent: ${agent ? `${agent.name} (${agent.role})` : 'none'}`,
271
+ `Current month: ${monthKey()}`,
272
+ '',
273
+ '## Required Session Flow',
274
+ '',
275
+ '1. Read `.MOP/STATE.json` and follow `.MOP/PROTOCOL.md`.',
276
+ '2. Authenticate if required.',
277
+ '3. Run `agent route` for the user task before answering.',
278
+ `4. Start every authenticated answer with: \`${contract.firstLine || 'agent: <name> (<role>) to <user>'}\``,
279
+ '5. Save a one-line memory after meaningful work.',
280
+ '',
281
+ '## Recent Memory',
282
+ '',
283
+ ...entries.map((entry) => {
284
+ const who = entry.agent ? `${entry.agent} (${entry.agentRole || 'agent'})` : entry.actor;
285
+ return `- ${entry.at} - ${who}: ${entry.summary}`;
286
+ })
287
+ ];
288
+ mkdirSync(dirname(path), { recursive: true });
289
+ writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
290
+ }
291
+
105
292
  const routeRules = [
106
293
  {
107
294
  role: 'memory',
@@ -363,7 +550,7 @@ function setup(args) {
363
550
  const codingLanguage = String(args['coding-language'] || 'English');
364
551
  const githubUrl = String(args['github-url'] || '');
365
552
  const gitName = String(args['git-name'] || displayName);
366
- const gitEmail = String(args['git-email'] || '');
553
+ const gitEmail = String(args['git-email'] || 'github-noreply');
367
554
  const githubUsername = String(args['github-username'] || '');
368
555
  const joinMode = String(args['join-mode'] || 'owner-approved');
369
556
 
@@ -371,12 +558,10 @@ function setup(args) {
371
558
  if (password.length < 8) throw new Error('Password must be at least 8 characters.');
372
559
  if (!['solo', 'team'].includes(mode)) throw new Error('Mode must be solo or team.');
373
560
  if (mode === 'team' && !githubUrl) throw new Error('Team mode requires --github-url.');
374
- if (state.autosync?.requireUserGitEmail !== false && !gitEmail) {
375
- throw new Error('Git email is required so commits are attributed to the real user, not the AI tool.');
376
- }
377
561
  if (!['open', 'owner-approved', 'invite'].includes(joinMode)) {
378
562
  throw new Error('Join mode must be open, owner-approved, or invite.');
379
563
  }
564
+ const gitIdentity = resolveGitIdentityInput(state, codename, gitName, gitEmail, githubUsername);
380
565
 
381
566
  const { passwordHash, passwordSalt } = hashPassword(password);
382
567
  state.initialized = true;
@@ -406,11 +591,7 @@ function setup(args) {
406
591
  conversation: conversationLanguage,
407
592
  coding: codingLanguage
408
593
  },
409
- gitIdentity: {
410
- name: gitName,
411
- email: gitEmail,
412
- githubUsername
413
- },
594
+ gitIdentity,
414
595
  joinedAt: now()
415
596
  }
416
597
  };
@@ -435,6 +616,12 @@ function login(args) {
435
616
  console.log(`Active member: ${codename}`);
436
617
  if (!activeAgentFor(state, codename) && state.agentPolicy?.requiredAfterAuth !== false) {
437
618
  console.log(`Agent diperlukan. Jalankan: node .MOP/scripts/mop-core.mjs agent activate --actor ${codename} --role ${state.agentPolicy?.defaultRole || 'core'} --title "${state.agentPolicy?.defaultTitle || 'Core Agent'}" --name "<agent-name>"`);
619
+ } else {
620
+ console.log(JSON.stringify({
621
+ next: 'restore-memory-and-route-task',
622
+ memoryRestore: `node .MOP/scripts/mop-core.mjs memory brief --actor ${codename}`,
623
+ answerContract: answerContractFor(state, codename)
624
+ }, null, 2));
438
625
  }
439
626
  }
440
627
 
@@ -553,6 +740,11 @@ function agentRoute(args) {
553
740
  route,
554
741
  partyAgents,
555
742
  activeAgent: null,
743
+ answerContract: null,
744
+ monthlyMemory: {
745
+ restoreCommand: `node .MOP/scripts/mop-core.mjs memory brief --actor ${actor}`,
746
+ saveCommand: `node .MOP/scripts/mop-core.mjs memory add --actor ${actor} --kind conversation --summary "<one-line outcome>"`
747
+ },
556
748
  nextAction: null
557
749
  };
558
750
 
@@ -567,6 +759,7 @@ function agentRoute(args) {
567
759
  role: agent.role,
568
760
  title: agent.title
569
761
  };
762
+ response.answerContract = answerContractFor(state, actor, agent);
570
763
  if (route.partyMode.active && missingPartyAgents.length) {
571
764
  response.ok = false;
572
765
  response.nextAction = 'name-required-party-agents';
@@ -596,6 +789,73 @@ function agentList() {
596
789
  }, null, 2));
597
790
  }
598
791
 
792
+ function memoryAdd(args) {
793
+ const state = readState();
794
+ if (!state.initialized) throw new Error('MOP is not initialized.');
795
+ const actor = slug(requireArg(args, 'actor'));
796
+ if (!state.members?.[actor]) throw new Error('Unknown actor.');
797
+ const agent = requireActiveAgent(state, actor);
798
+ const summary = String(args.summary || args._?.join(' ') || '').trim();
799
+ const kind = String(args.kind || 'conversation');
800
+ if (!summary) throw new Error('Missing --summary');
801
+
802
+ appendLedger(state, actor, 'memory', summary, agent);
803
+ const saved = appendMonthlyMemory(state, actor, kind, summary, agent);
804
+ writeState(state);
805
+ console.log(JSON.stringify({
806
+ ok: true,
807
+ actor,
808
+ agent: agent.name,
809
+ kind,
810
+ summary,
811
+ monthlyMemory: saved ? relativeFromRoot(saved.monthlyPath) : 'disabled',
812
+ sessionBrief: relativeFromRoot(sessionBriefPath(state)),
813
+ answerContract: answerContractFor(state, actor, agent)
814
+ }, null, 2));
815
+ }
816
+
817
+ function memoryBrief(args) {
818
+ const state = readState();
819
+ if (!state.initialized) {
820
+ console.log('MOP belum di-setup. Jalankan /mop-setup.');
821
+ return;
822
+ }
823
+ const actor = slug(String(args.actor || state.activeMember || ''));
824
+ if (!actor) throw new Error('Missing --actor');
825
+ if (!state.members?.[actor]) throw new Error('Unknown actor.');
826
+ const agent = activeAgentFor(state, actor);
827
+ const month = String(args.month || monthKey());
828
+ const limit = Number(args.limit || memoryPolicy(state).recentLimit || 20);
829
+ const currentEntries = readJsonl(monthlyMemoryPath(state, month)).slice(-limit);
830
+ const recentEntries = latestMemoryEntries(state, limit);
831
+ if (agent) writeSessionBrief(state, actor);
832
+ console.log(JSON.stringify({
833
+ ok: true,
834
+ actor,
835
+ activeAgent: agent ? {
836
+ id: agent.id,
837
+ name: agent.name,
838
+ role: agent.role,
839
+ title: agent.title
840
+ } : null,
841
+ answerContract: answerContractFor(state, actor, agent),
842
+ memory: {
843
+ month,
844
+ monthPath: relativeFromRoot(monthlyMemoryPath(state, month)),
845
+ sessionBrief: relativeFromRoot(sessionBriefPath(state)),
846
+ currentMonthEntries: currentEntries,
847
+ recentEntries
848
+ },
849
+ next: agent
850
+ ? 'Use answerContract.firstLine before answering, then save memory after meaningful work.'
851
+ : `Agent diperlukan. Jalankan: node .MOP/scripts/mop-core.mjs agent activate --actor ${actor} --role ${state.agentPolicy?.defaultRole || 'core'} --title "${state.agentPolicy?.defaultTitle || 'Core Agent'}" --name "<agent-name>"`
852
+ }, null, 2));
853
+ }
854
+
855
+ function memoryRestore(args) {
856
+ return memoryBrief(args);
857
+ }
858
+
599
859
  function memberGitIdentity(args) {
600
860
  const state = readState();
601
861
  if (!state.initialized) throw new Error('MOP is not initialized.');
@@ -604,12 +864,12 @@ function memberGitIdentity(args) {
604
864
  if (!member) throw new Error('Unknown actor.');
605
865
  const agent = requireActiveAgent(state, actor);
606
866
  const name = String(args.name || member.displayName || actor);
607
- const email = requireArg(args, 'email');
867
+ const email = String(args.email || 'github-noreply');
608
868
  const githubUsername = String(args['github-username'] || member.gitIdentity?.githubUsername || '');
609
- member.gitIdentity = { name, email, githubUsername };
869
+ member.gitIdentity = resolveGitIdentityInput(state, actor, name, email, githubUsername);
610
870
  appendLedger(state, actor, 'git-identity', `Updated git identity for ${actor}.`, agent);
611
871
  writeState(state);
612
- console.log(`Git identity set for ${actor}: ${name} <${email}>${githubUsername ? ` github=${githubUsername}` : ''}`);
872
+ console.log(`Git identity set for ${actor}: ${member.gitIdentity.name} <${member.gitIdentity.email}>${member.gitIdentity.githubUsername ? ` github=${member.gitIdentity.githubUsername}` : ''}`);
613
873
  }
614
874
 
615
875
  function validate() {
@@ -621,8 +881,13 @@ function validate() {
621
881
  if (!Array.isArray(state.agentCatalog)) errors.push('agentCatalog must be array');
622
882
  if (state.activeAgents && typeof state.activeAgents !== 'object') errors.push('activeAgents must be object');
623
883
  if (state.agentPolicy && typeof state.agentPolicy !== 'object') errors.push('agentPolicy must be object');
884
+ if (state.answerPolicy && typeof state.answerPolicy !== 'object') errors.push('answerPolicy must be object');
885
+ if (state.memoryPolicy && typeof state.memoryPolicy !== 'object') errors.push('memoryPolicy must be object');
624
886
  if (state.agentRouter && typeof state.agentRouter !== 'object') errors.push('agentRouter must be object');
625
887
  if (state.partyMode && typeof state.partyMode !== 'object') errors.push('partyMode must be object');
888
+ if (state.autosync?.githubIdentity && typeof state.autosync.githubIdentity !== 'object') {
889
+ errors.push('autosync.githubIdentity must be object');
890
+ }
626
891
  if (state.projectRootPolicy && typeof state.projectRootPolicy !== 'object') errors.push('projectRootPolicy must be object');
627
892
  if (state.projectRootPolicy?.rules && !Array.isArray(state.projectRootPolicy.rules)) {
628
893
  errors.push('projectRootPolicy.rules must be array');
@@ -703,6 +968,8 @@ function status() {
703
968
  return [codename, agent ? { name: agent.name, role: agent.role, id: agent.id } : { id: agentId, missing: true }];
704
969
  })),
705
970
  agentPolicy: state.agentPolicy || {},
971
+ answerPolicy: answerPolicy(state),
972
+ memoryPolicy: memoryPolicy(state),
706
973
  agentRouter: state.agentRouter || {},
707
974
  partyMode: state.partyMode || {},
708
975
  workflow: {
@@ -756,19 +1023,25 @@ function main() {
756
1023
  if (command === 'agent' && subcommand === 'require') return agentRequire(args);
757
1024
  if (command === 'agent' && subcommand === 'route') return agentRoute(args);
758
1025
  if (command === 'agent' && subcommand === 'list') return agentList();
1026
+ if (command === 'memory' && subcommand === 'add') return memoryAdd(args);
1027
+ if (command === 'memory' && subcommand === 'brief') return memoryBrief(args);
1028
+ if (command === 'memory' && subcommand === 'restore') return memoryRestore(args);
759
1029
 
760
1030
  console.log(`Usage:
761
1031
  node .MOP/scripts/mop-core.mjs status
762
1032
  node .MOP/scripts/mop-core.mjs validate
763
- node .MOP/scripts/mop-core.mjs setup --project-name NAME --name DISPLAY --codename CODE --password PASS --mode solo|team --conversation-language LANG --coding-language LANG --git-email EMAIL [--git-name NAME] [--github-username USER] [--github-url URL]
1033
+ node .MOP/scripts/mop-core.mjs setup --project-name NAME --name DISPLAY --codename CODE --password PASS --mode solo|team --conversation-language LANG --coding-language LANG [--git-email github-noreply|EMAIL] [--git-name NAME] [--github-username USER] [--github-url URL]
764
1034
  node .MOP/scripts/mop-core.mjs login --codename CODE --password PASS
765
- node .MOP/scripts/mop-core.mjs member git-identity --actor CODE --name NAME --email EMAIL [--github-username USER]
1035
+ node .MOP/scripts/mop-core.mjs member git-identity --actor CODE --name NAME [--email github-noreply|EMAIL] [--github-username USER]
766
1036
  node .MOP/scripts/mop-core.mjs agent activate --actor CODE --role ROLE --title TITLE --name NAME
767
1037
  node .MOP/scripts/mop-core.mjs agent use --actor CODE --name NAME
768
1038
  node .MOP/scripts/mop-core.mjs agent current --actor CODE
769
1039
  node .MOP/scripts/mop-core.mjs agent require --actor CODE [--role ROLE] [--title TITLE]
770
1040
  node .MOP/scripts/mop-core.mjs agent route --actor CODE --task "task text"
771
- node .MOP/scripts/mop-core.mjs agent list`);
1041
+ node .MOP/scripts/mop-core.mjs agent list
1042
+ node .MOP/scripts/mop-core.mjs memory brief --actor CODE [--month YYYY-MM]
1043
+ node .MOP/scripts/mop-core.mjs memory add --actor CODE --kind conversation --summary "what happened"
1044
+ node .MOP/scripts/mop-core.mjs memory restore --actor CODE`);
772
1045
  }
773
1046
 
774
1047
  try {
package/.agents/AGENTS.md CHANGED
@@ -14,6 +14,13 @@ Before doing anything, read `.MOP/STATE.json` and follow
14
14
  - After login, run the Agent Router: every conversation and action must route
15
15
  to one primary named agent. Check with `mop-core.mjs agent route --actor
16
16
  <codename> --task "<user task>"`.
17
+ - Before answering, restore monthly memory with
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed
20
+ `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work, save memory with
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
17
24
  - If the router says clarification is needed, ask the clarifying questions
18
25
  before implementation.
19
26
  - If the router activates Party Mode, show visible agent-to-agent dialogue using
@@ -27,6 +34,10 @@ Before doing anything, read `.MOP/STATE.json` and follow
27
34
  - For risky changes, run adversarial review before implementation or merge.
28
35
  - When a role agent first appears, ask the user to name it before using it as a
29
36
  personal agent.
37
+ - Autosycn member commits must use the active member GitHub account. By default,
38
+ MOP derives `ID+USERNAME@users.noreply.github.com` from `gh api user` and
39
+ refuses mismatched GitHub accounts. `BURHAN-MOP` identity is reserved for
40
+ merge guardian commits only.
30
41
 
31
42
  ## Operating Rules
32
43
 
package/AGENTS.md CHANGED
@@ -14,6 +14,13 @@ follow `.MOP/PROTOCOL.md`.
14
14
  It selects one primary role and may recommend any number of support roles
15
15
  when genuinely needed. If no named agent exists for a needed role, ask the
16
16
  user to name it and save it with `agent activate`.
17
+ - Before answering an authenticated task, restore monthly memory:
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed named agent
20
+ line from `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work or a useful answer, save memory:
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"`.
17
24
 
18
25
  | Role | Name | Description |
19
26
  | :--- | :--- | :--- |
@@ -39,8 +46,8 @@ follow `.MOP/PROTOCOL.md`.
39
46
 
40
47
  For `/mop-setup`, ask in order: project name (default folder name), owner display
41
48
  name, owner codename, password, solo/team, conversation language, coding
42
- language, GitHub link, GitHub username, Git commit email, join mode if team,
43
- then whether to activate auto-deploy now or later.
49
+ language, GitHub link, GitHub username, Git commit email (`github-noreply` by
50
+ default), join mode if team, then whether to activate auto-deploy now or later.
44
51
 
45
52
  Agent role/template names are not personal AI names. When a role agent is first
46
53
  needed, ask the user to name it and save it with `mop-core.mjs agent activate`.
@@ -49,10 +56,15 @@ different agents.
49
56
  The active named agent must be recorded on every memory/ledger action. Do not
50
57
  activate irrelevant agents; route to one primary agent first, then add support
51
58
  agents through Party Mode only when useful.
59
+ If the assistant cannot show the active agent line, it must stop and repair the
60
+ agent route instead of answering invisibly.
52
61
 
53
62
  Default skill: `autosycn`. After meaningful changes, use
54
63
  `.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
55
64
  real member Git identity from state, never with the AI tool identity.
65
+ Member commits must use the active member GitHub account; by default MOP derives
66
+ `ID+USERNAME@users.noreply.github.com` from `gh api user` and refuses mismatched
67
+ GitHub accounts. `BURHAN-MOP` identity is only for merge guardian commits.
56
68
  In team mode, run `preflight --actor <codename>` before starting work; work goes
57
69
  to `dev/<codename>`. Every small or large change is pushed there first, then
58
70
  BURHAN-MOP reviews and merges to `main` only when checks pass.
package/CLAUDE.md CHANGED
@@ -14,6 +14,13 @@ follow `.MOP/PROTOCOL.md`.
14
14
  It selects one primary role and may recommend any number of support roles
15
15
  when genuinely needed. If no named agent exists for a needed role, ask the
16
16
  user to name it and save it with `agent activate`.
17
+ - Before answering an authenticated task, restore monthly memory:
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed named agent
20
+ line from `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work or a useful answer, save memory:
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"`.
17
24
  - If the router marks the task as ambiguous, the named primary agent must ask
18
25
  clarifying questions before implementation.
19
26
  - If the router returns `partyMode.active: true`, use Party Mode. Show
@@ -34,10 +41,15 @@ different agents.
34
41
  The active named agent must be recorded on every memory/ledger action. Do not
35
42
  activate irrelevant agents; route to one primary agent first, then add support
36
43
  agents through Party Mode only when useful.
44
+ If the assistant cannot show the active agent line, it must stop and repair the
45
+ agent route instead of answering invisibly.
37
46
 
38
47
  Default skill: `autosycn`. After meaningful changes, use
39
48
  `.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
40
49
  real member Git identity from state, never with the AI tool identity.
50
+ Member commits must use the active member GitHub account; by default MOP derives
51
+ `ID+USERNAME@users.noreply.github.com` from `gh api user` and refuses mismatched
52
+ GitHub accounts. `BURHAN-MOP` identity is only for merge guardian commits.
41
53
  In team mode, run `preflight --actor <codename>` before starting work; work goes
42
54
  to `dev/<codename>`. Every small or large change is pushed there first, then
43
55
  BURHAN-MOP reviews and merges to `main` only when checks pass.
package/GEMINI.md CHANGED
@@ -9,6 +9,16 @@ rules are imported below.
9
9
 
10
10
  - First action still applies: read `.MOP/STATE.json` and follow
11
11
  `.MOP/PROTOCOL.md`.
12
+ - After authentication, run `mop-core.mjs memory brief --actor <codename>` and
13
+ `mop-core.mjs agent route --actor <codename> --task "<user task>"` before
14
+ answering.
15
+ - Every authenticated answer must start with `answerContract.firstLine`, for
16
+ example: `agent: <agent-name> (<agent-role>) to <user>`.
17
+ - Save a one-line memory after meaningful work with
18
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
19
+ - Autosycn member commits must use the active member GitHub account. MOP derives
20
+ `ID+USERNAME@users.noreply.github.com` from `gh api user` by default and
21
+ refuses mismatched GitHub accounts.
12
22
  - Treat the current workspace root as the project root; do not create a nested
13
23
  project wrapper folder for app work unless the user explicitly asks for it.
14
24
  - Use `.gemini/settings.json` for context filenames and MCP server setup.
package/README.bm.md CHANGED
@@ -142,7 +142,7 @@ dan merge hanya bila workflow selamat.
142
142
  | --- | --- |
143
143
  | npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
144
144
  | command latest | `npx burhan-mop install` |
145
- | GitHub release | [`v0.1.5`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.5) |
145
+ | GitHub release | [`v0.1.7`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.7) |
146
146
  | Node | `>=20` |
147
147
 
148
148
  ## Links
package/README.md CHANGED
@@ -141,7 +141,7 @@ and merges only when the workflow is safe.
141
141
  | --- | --- |
142
142
  | npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
143
143
  | latest command | `npx burhan-mop install` |
144
- | GitHub release | [`v0.1.5`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.5) |
144
+ | GitHub release | [`v0.1.7`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.7) |
145
145
  | Node | `>=20` |
146
146
 
147
147
  ## Links
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "burhan-mop",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "MOP portable agent memory core installer and CLI.",
5
5
  "type": "module",
6
6
  "author": "BURHANDEV ENTERPRISE",