greprag 5.76.0 → 5.77.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.
@@ -113,5 +113,6 @@ async function doctorCodex(args, deps) {
113
113
  console.log(` PermissionRequest ctx: ${hasHook(hooks, 'PermissionRequest', 'codex-permission-context') ? 'yes' : 'no'}`);
114
114
  console.log(` SubagentStart metadata: ${hasHook(hooks, 'SubagentStart', 'codex-subagent-start') ? 'yes' : 'no'}`);
115
115
  console.log(` Stop store: ${hasHook(hooks, 'Stop', 'codex-store') ? 'yes' : 'no'}`);
116
+ console.log(` PostCompact re-announce: ${hasHook(hooks, 'PostCompact', 'recompact') ? 'yes' : 'no'}`);
116
117
  console.log(` PostCompact session-id: ${hasHook(hooks, 'PostCompact', 'session-id') ? 'yes' : 'no'}`);
117
118
  }
@@ -12,15 +12,15 @@ exports.buildDeliveryAnnounce = buildDeliveryAnnounce;
12
12
  // adr: adr/delivery-announce-pilot.md
13
13
  function coordinationLine(platform) {
14
14
  if (platform === 'codex') {
15
- return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, then use codex_app.send_message_to_thread once for ready or overlapping work. Delivery owner: rename this task `DEPLOY: <existing title>`; transfer the prefix on handoff; remove the prefix when delivery is done.';
15
+ return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, and inspect their current summaries/status. Use codex_app.send_message_to_thread only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups. Delivery owner: rename this task `DEPLOY: <existing title>` for operator visibility only; transfer the prefix on handoff; remove it before reporting delivery complete or abandoned.';
16
16
  }
17
17
  if (platform === 'claude-code') {
18
- return 'Claude Code: at the merge/deploy gate, discover peers in the same repo and send one concise delivery notice to peers with ready or overlapping work.';
18
+ return 'Claude Code: at the merge/deploy gate, inspect peers in the same repo and send one concise delivery notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
19
19
  }
20
20
  if (platform === 'opencode') {
21
- return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise delivery notice to peers with ready or overlapping work.';
21
+ return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
22
22
  }
23
- return 'At the merge/deploy gate, coordinate once with peers in the same repo who have ready or overlapping work.';
23
+ return 'At the merge/deploy gate, inspect peers in the same repo and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.';
24
24
  }
25
25
  function buildDeliveryAnnounce(env) {
26
26
  if (!env.deliveryRepo)
@@ -30,7 +30,7 @@ function buildDeliveryAnnounce(env) {
30
30
  `[DELIVERY — ${project}]`,
31
31
  'Commit useful, passing work.',
32
32
  coordinationLine(env.platform),
33
- 'Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Missing replies and unfinished peer work do not block delivery.',
33
+ 'Include visible ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Silence means nothing actionable and does not block delivery.',
34
34
  'Use this repo\'s delivery profile and verify production.',
35
35
  ].join('\n');
36
36
  }
@@ -719,6 +719,7 @@ function applyGrokHooks(config) {
719
719
  add('PreToolUse', '*', 'guard', 5, 'guard');
720
720
  add('Stop', '', 'store', 15, 'turn capture');
721
721
  add('Stop', '', 'drain', 8, 'inbox drain');
722
+ add('PostCompact', '', 'recompact', 15, 'Interrupt re-announce');
722
723
  add('PostCompact', '', 'session-id', 5, 'session-id');
723
724
  return changes;
724
725
  }
@@ -1411,14 +1412,27 @@ function applyCodexHooks(config) {
1411
1412
  else {
1412
1413
  changes.push('Codex Stop hook already configured (skipped)');
1413
1414
  }
1414
- const postCompactHook = {
1415
+ const postCompactAnnounceHook = {
1416
+ matcher: 'manual|auto',
1417
+ hooks: [commandHook('recompact', 10, 'Re-announcing GrepRAG primers')],
1418
+ };
1419
+ if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'recompact', postCompactAnnounceHook.matcher)) {
1420
+ if (!config.hooks.PostCompact)
1421
+ config.hooks.PostCompact = [];
1422
+ config.hooks.PostCompact.push(postCompactAnnounceHook);
1423
+ changes.push('Added Codex PostCompact hook (Interrupt re-announce)');
1424
+ }
1425
+ else {
1426
+ changes.push('Codex PostCompact Interrupt re-announce hook already configured (skipped)');
1427
+ }
1428
+ const postCompactSessionHook = {
1415
1429
  matcher: 'manual|auto',
1416
1430
  hooks: [commandHook('session-id', 3, 'Restoring GrepRAG session id')],
1417
1431
  };
1418
- if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactHook.matcher)) {
1432
+ if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactSessionHook.matcher)) {
1419
1433
  if (!config.hooks.PostCompact)
1420
1434
  config.hooks.PostCompact = [];
1421
- config.hooks.PostCompact.push(postCompactHook);
1435
+ config.hooks.PostCompact.push(postCompactSessionHook);
1422
1436
  changes.push('Added Codex PostCompact hook (session-id awareness)');
1423
1437
  }
1424
1438
  else {
@@ -1432,6 +1446,7 @@ function normalizeCodexHookCommands(hooks) {
1432
1446
  const desired = {
1433
1447
  recap: { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
1434
1448
  'codex-recap': { timeout: 10, statusMessage: 'Loading GrepRAG memory' },
1449
+ recompact: { timeout: 10, statusMessage: 'Re-announcing GrepRAG primers' },
1435
1450
  'session-id': { timeout: 3, statusMessage: 'Loading GrepRAG session id' },
1436
1451
  drain: { timeout: 5, statusMessage: 'Draining GrepRAG inbox' },
1437
1452
  'codex-notify': { timeout: 300, statusMessage: 'Checking GrepRAG inbox', runner: 'greprag-codex-hook' },
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OPENCODE_QUOTA_WAITLIST_ANNOUNCE = exports.opencodeRegistry = void 0;
4
4
  exports.buildOpenCodeEnv = buildOpenCodeEnv;
5
5
  exports.getOpenCodeAnnounces = getOpenCodeAnnounces;
6
+ exports.getOpenCodePersistentAnnounces = getOpenCodePersistentAnnounces;
6
7
  exports.getOpenCodeReminders = getOpenCodeReminders;
7
8
  const reminder_registry_1 = require("./reminder-registry");
8
9
  /** The opencode registry is DERIVED from the canon, never hand-whitelisted.
@@ -44,11 +45,18 @@ function buildOpenCodeEnv(params) {
44
45
  updateAvailable: params.updateAvailable ?? null,
45
46
  procedureAnnounces: params.procedureAnnounces ?? [],
46
47
  mirroredSkills: params.mirroredSkills,
48
+ personaAnnounce: params.personaAnnounce ?? null,
47
49
  };
48
50
  }
49
51
  function getOpenCodeAnnounces(env) {
50
52
  return [...(0, reminder_registry_1.collectAnnounces)(env, exports.opencodeRegistry), exports.OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
51
53
  }
54
+ /** OpenCode rebuilds output.system for every model call. Persona is the one
55
+ * startup primer that must remain behaviorally active on later calls, so the
56
+ * plugin replays this shared-registry module from its bounded session cache. */
57
+ function getOpenCodePersistentAnnounces(env) {
58
+ return (0, reminder_registry_1.collectAnnounces)(env, exports.opencodeRegistry.filter((m) => m.id === 'persona-announce'));
59
+ }
52
60
  function getOpenCodeReminders(env) {
53
61
  return (0, reminder_registry_1.collectReminders)(env, exports.opencodeRegistry);
54
62
  }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.personaAnnounceModule = void 0;
4
+ exports.personaAnnounceModule = {
5
+ id: 'persona-announce',
6
+ detect: () => ({ tier: 'silent' }),
7
+ announce: (env) => env.personaAnnounce || null,
8
+ reminder: () => null,
9
+ };
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /** greprag persona — tenant-scoped speaking instructions. adr: adr/persona.md
3
+ *
4
+ * Thin HTTP wrapper over /v1/persona. The server owns built-in template content;
5
+ * custom text is sent verbatim and injected without rewriting.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.runPersona = runPersona;
42
+ const fs = __importStar(require("fs"));
43
+ function getConfig() {
44
+ return {
45
+ apiUrl: process.env.GREPRAG_API_URL || 'https://api.greprag.com',
46
+ apiKey: process.env.GREPRAG_API_KEY || '',
47
+ };
48
+ }
49
+ function getFlag(args, flag) {
50
+ const idx = args.indexOf(flag);
51
+ return idx === -1 || idx + 1 >= args.length ? undefined : args[idx + 1];
52
+ }
53
+ const HELP = `greprag persona — tenant-scoped instructions for how agents speak to you
54
+
55
+ USAGE
56
+ greprag persona list
57
+ List built-in Persona templates.
58
+
59
+ greprag persona use <template>
60
+ Activate a built-in template, e.g. vibe-shaper.
61
+
62
+ greprag persona set <text>
63
+ greprag persona set --file <path>
64
+ greprag persona set --stdin
65
+ Save and activate a custom Persona verbatim.
66
+
67
+ greprag persona show
68
+ Show the active Persona.
69
+
70
+ greprag persona clear
71
+ Remove the active Persona.`;
72
+ async function api(method, pathname, body) {
73
+ const cfg = getConfig();
74
+ if (!cfg.apiKey) {
75
+ console.error('GREPRAG_API_KEY not set — run `greprag init` first.');
76
+ process.exit(1);
77
+ }
78
+ const res = await fetch(`${cfg.apiUrl}${pathname}`, {
79
+ method,
80
+ headers: {
81
+ 'Authorization': `Bearer ${cfg.apiKey}`,
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: body === undefined ? undefined : JSON.stringify(body),
85
+ });
86
+ let data = {};
87
+ try {
88
+ data = await res.json();
89
+ }
90
+ catch { /* non-JSON */ }
91
+ return { ok: res.ok, status: res.status, data };
92
+ }
93
+ function fail(data, status) {
94
+ console.error(`Error${status ? ` (${status})` : ''}: ${data.error || 'request failed'}`);
95
+ process.exit(1);
96
+ }
97
+ function readPersonaInput(args) {
98
+ const file = getFlag(args, '--file');
99
+ if (file) {
100
+ try {
101
+ return file === '-' ? fs.readFileSync(0, 'utf-8') : fs.readFileSync(file, 'utf-8');
102
+ }
103
+ catch (err) {
104
+ console.error(`persona set: could not read --file ${file}: ${err.message}`);
105
+ process.exit(1);
106
+ }
107
+ }
108
+ if (args.includes('--stdin')) {
109
+ try {
110
+ return fs.readFileSync(0, 'utf-8');
111
+ }
112
+ catch (err) {
113
+ console.error(`persona set: could not read stdin: ${err.message}`);
114
+ process.exit(1);
115
+ }
116
+ }
117
+ return args.filter((a) => a !== '--stdin').join(' ');
118
+ }
119
+ async function list() {
120
+ const { ok, status, data } = await api('GET', '/v1/persona/templates');
121
+ if (!ok)
122
+ fail(data, status);
123
+ const templates = data.templates || [];
124
+ if (!templates.length) {
125
+ console.log('No built-in Persona templates.');
126
+ return;
127
+ }
128
+ console.log('Built-in Persona templates:\n');
129
+ for (const t of templates) {
130
+ console.log(` ${t.id} — ${t.name}`);
131
+ console.log(` ${t.description}`);
132
+ }
133
+ }
134
+ async function useTemplate(id) {
135
+ if (!id) {
136
+ console.error('Usage: greprag persona use <template>');
137
+ process.exit(1);
138
+ }
139
+ const { ok, status, data } = await api('PUT', '/v1/persona/template', { templateId: id });
140
+ if (!ok)
141
+ fail(data, status);
142
+ const p = data.persona;
143
+ console.log(`Persona active: ${p.templateId} (built-in template)`);
144
+ }
145
+ async function set(args) {
146
+ const content = readPersonaInput(args);
147
+ const { ok, status, data } = await api('PUT', '/v1/persona/custom', { content });
148
+ if (!ok)
149
+ fail(data, status);
150
+ const p = data.persona;
151
+ console.log(`Persona active: custom (${p.content?.length ?? content.length} chars)`);
152
+ }
153
+ async function show() {
154
+ const { ok, status, data } = await api('GET', '/v1/persona');
155
+ if (!ok)
156
+ fail(data, status);
157
+ const p = data.persona;
158
+ if (!p) {
159
+ console.log('No Persona set.');
160
+ return;
161
+ }
162
+ const source = p.mode === 'template' ? `template ${p.templateId}` : 'custom';
163
+ console.log(`Persona (${source})`);
164
+ console.log('');
165
+ console.log(p.content || '');
166
+ }
167
+ async function clear() {
168
+ const { ok, status, data } = await api('DELETE', '/v1/persona');
169
+ if (!ok)
170
+ fail(data, status);
171
+ console.log(data.removed ? 'Persona cleared.' : 'No Persona was set.');
172
+ }
173
+ async function runPersona(args) {
174
+ const sub = args[0];
175
+ const rest = args.slice(1);
176
+ if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
177
+ console.log(HELP);
178
+ return;
179
+ }
180
+ switch (sub) {
181
+ case 'list': return list();
182
+ case 'use': return useTemplate(rest[0]);
183
+ case 'set': return set(rest);
184
+ case 'show': return show();
185
+ case 'clear': return clear();
186
+ default:
187
+ console.error(`Unknown persona command: ${sub}\n`);
188
+ console.log(HELP);
189
+ process.exit(1);
190
+ }
191
+ }
@@ -28,6 +28,7 @@ const skill_mirror_reminder_1 = require("./skill-mirror-reminder");
28
28
  const procedure_reminder_1 = require("./procedure-reminder");
29
29
  const loadout_reminder_1 = require("./loadout-reminder");
30
30
  const delivery_reminder_1 = require("./delivery-reminder");
31
+ const persona_reminder_1 = require("./persona-reminder");
31
32
  /** Registry order = display order. THE single agent-facing announce/reminder assembly:
32
33
  * the hook does I/O → fills ReminderEnv → collectAnnounces (SessionStart) / collectReminders
33
34
  * (per turn) render every module here in this order. A module may own either surface or both;
@@ -46,6 +47,7 @@ exports.REGISTRY = [
46
47
  skill_mirror_reminder_1.skillMirrorAnnounceModule, // native-equivalent activation index for load-only mirrored skills
47
48
  procedure_reminder_1.procedureAnnounceModule, // active project procedures, rendered generically from operator-owned installs
48
49
  delivery_reminder_1.deliveryControlModule, // repo-scoped announce-first Delivery System pilot (all harnesses)
50
+ persona_reminder_1.personaAnnounceModule, // tenant-scoped Persona, shared across projects/harnesses
49
51
  loadout_reminder_1.loadoutRegistrarModule, // equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
50
52
  setup_reminder_1.setupWarningModule,
51
53
  version_reminder_1.versionUpgradeModule, // Deficiency-gated announce — silent unless a newer release exists
@@ -191,6 +191,7 @@ function buildStatus(cwd, platform = 'all') {
191
191
  pre_tool_use_chip_guard: hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-pretooluse')
192
192
  || hasCodexHook(codexHooks.hooks?.PreToolUse, 'codex-chip-hook'),
193
193
  stop_store: hasCodexHook(codexHooks.hooks?.Stop, 'codex-store'),
194
+ post_compact_reannounce: hasCodexHook(codexHooks.hooks?.PostCompact, 'recompact'),
194
195
  post_compact_session_id: hasCodexHook(codexHooks.hooks?.PostCompact, 'session-id'),
195
196
  };
196
197
  const opencodePluginInstalled = fs.existsSync(opencodePluginPath);
package/dist/hook.js CHANGED
@@ -1613,11 +1613,29 @@ async function recap(input, mode = 'plain', opts = {}) {
1613
1613
  return empty;
1614
1614
  }
1615
1615
  })();
1616
+ // Tenant Persona primer. The server owns both template resolution and
1617
+ // the provenance envelope; harnesses transport it unchanged. adr: adr/persona.md
1618
+ const personaPromise = (async () => {
1619
+ try {
1620
+ const res = await fetch(`${cfg.apiUrl}/v1/persona`, {
1621
+ headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
1622
+ });
1623
+ if (!res.ok)
1624
+ return null;
1625
+ const data = await res.json();
1626
+ return typeof data.announce === 'string' && data.announce.length > 0
1627
+ ? data.announce
1628
+ : null;
1629
+ }
1630
+ catch {
1631
+ return null;
1632
+ }
1633
+ })();
1616
1634
  const updateAvailablePromise = checkForUpdate();
1617
1635
  const bodyPromise = (!opts.compact && anchor.sessionStartRecap)
1618
1636
  ? (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor)
1619
1637
  : Promise.resolve('');
1620
- const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, updateAvailable, body,] = await Promise.all([
1638
+ const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, personaAnnounce, updateAvailable, body,] = await Promise.all([
1621
1639
  matchsetPromise,
1622
1640
  corpusApiDocsPromise,
1623
1641
  docPointersPromise,
@@ -1625,6 +1643,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1625
1643
  skillGainsPromise,
1626
1644
  mirroredSkillsPromise,
1627
1645
  loadoutStatePromise,
1646
+ personaPromise,
1628
1647
  updateAvailablePromise,
1629
1648
  bodyPromise,
1630
1649
  ]);
@@ -1704,6 +1723,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1704
1723
  mirroredSkills,
1705
1724
  procedureAnnounces,
1706
1725
  equippedLoadouts,
1726
+ personaAnnounce,
1707
1727
  updateAvailable,
1708
1728
  activeCommandDrift: activeCommandDrift(),
1709
1729
  loadoutUpdateRequired,
@@ -2264,7 +2284,10 @@ async function main() {
2264
2284
  // opt out per-module with reannounceOnCompact:false) so the doctrine the compaction
2265
2285
  // wiped (incl. the watcher-arm announce) returns. additionalContext = the JSON form
2266
2286
  // PostCompact injects. adr: adr/monitor-resilience.md, docs/reminder-interrupt.md
2267
- await recap(input, 'additionalContext', { compact: true });
2287
+ await recap(input, 'additionalContext', {
2288
+ compact: true,
2289
+ platform: harness === 'grok' ? 'grok' : undefined,
2290
+ });
2268
2291
  }
2269
2292
  else if (subcommand === 'codex-recap') {
2270
2293
  await recap(input, 'additionalContext', { platform: 'codex' });
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ const doc_1 = require("./commands/doc");
66
66
  const parity_1 = require("./commands/parity");
67
67
  const agents_1 = require("./commands/agents");
68
68
  const loadout_1 = require("./commands/loadout");
69
+ const persona_1 = require("./commands/persona");
69
70
  const assistant_1 = require("./commands/assistant");
70
71
  const email_1 = require("./commands/email");
71
72
  const sms_1 = require("./commands/sms");
@@ -1568,6 +1569,14 @@ Loadout (gift a skill bundle to another tenant — offer, never auto-install):
1568
1569
  loadout unequip <name> [--delete-skills]
1569
1570
  Stop a loadout's auto-inflate.
1570
1571
 
1572
+ Persona (tenant-scoped speaking instructions):
1573
+ persona list List built-in Persona templates.
1574
+ persona use <template> Activate a built-in Persona template.
1575
+ persona set <text>|--file <f>|--stdin
1576
+ Save and activate a custom Persona verbatim.
1577
+ persona show Show the active Persona.
1578
+ persona clear Remove the active Persona.
1579
+
1571
1580
  GrepRAG Instructions (canonical global/repo CLAUDE.md + AGENTS.md):
1572
1581
  instructions pull <file> [--scope global|repo] [--push]
1573
1582
  Pull disk instructions into canon.
@@ -1639,6 +1648,7 @@ const HELP_ALL_GROUPS = [
1639
1648
  ['delivery', delivery_1.runDelivery],
1640
1649
  ['assistant', assistant_1.runAssistant],
1641
1650
  ['instructions', agents_1.runInstructions],
1651
+ ['persona', persona_1.runPersona],
1642
1652
  ['doc', doc_1.runDoc],
1643
1653
  ['skill', skill_1.runSkill],
1644
1654
  ['opencode', opencode],
@@ -1926,6 +1936,7 @@ async function main() {
1926
1936
  case 'instructions': return (0, agents_1.runInstructions)(subArgs); // Canonical GrepRAG Instructions + rendered followers
1927
1937
  case 'agents': return (0, agents_1.runAgents)(subArgs); // Deprecated alias for GrepRAG Instructions
1928
1938
  case 'loadout': return (0, loadout_1.runLoadout)(subArgs); // Loadout: cross-tenant skill-bundle gifting (docs/loadout.md)
1939
+ case 'persona': return (0, persona_1.runPersona)(subArgs); // tenant-scoped speaking instructions
1929
1940
  case 'assistant': return (0, assistant_1.runAssistant)(subArgs); // designate this project as the tenant's Assistant (role flag)
1930
1941
  case 'opencode': return opencode(subArgs);
1931
1942
  case 'grok': return (0, grok_spawn_1.runGrok)(subArgs);
@@ -2438,15 +2438,15 @@ var loadoutRegistrarModule = {
2438
2438
  // src/commands/delivery-reminder.ts
2439
2439
  function coordinationLine(platform) {
2440
2440
  if (platform === "codex") {
2441
- return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, then use codex_app.send_message_to_thread once for ready or overlapping work. Delivery owner: rename this task `DEPLOY: <existing title>`; transfer the prefix on handoff; remove the prefix when delivery is done.";
2441
+ return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, and inspect their current summaries/status. Use codex_app.send_message_to_thread only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups. Delivery owner: rename this task `DEPLOY: <existing title>` for operator visibility only; transfer the prefix on handoff; remove it before reporting delivery complete or abandoned.";
2442
2442
  }
2443
2443
  if (platform === "claude-code") {
2444
- return "Claude Code: at the merge/deploy gate, discover peers in the same repo and send one concise delivery notice to peers with ready or overlapping work.";
2444
+ return "Claude Code: at the merge/deploy gate, inspect peers in the same repo and send one concise delivery notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2445
2445
  }
2446
2446
  if (platform === "opencode") {
2447
- return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise delivery notice to peers with ready or overlapping work.";
2447
+ return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2448
2448
  }
2449
- return "At the merge/deploy gate, coordinate once with peers in the same repo who have ready or overlapping work.";
2449
+ return "At the merge/deploy gate, inspect peers in the same repo and send one concise notice only for visible evidence of an unlanded ready commit, overlapping integration, or a concrete blocker. Peers with nothing actionable stay silent; do not solicit or send negative acknowledgements or routine completion follow-ups.";
2450
2450
  }
2451
2451
  function buildDeliveryAnnounce(env) {
2452
2452
  if (!env.deliveryRepo)
@@ -2456,7 +2456,7 @@ function buildDeliveryAnnounce(env) {
2456
2456
  `[DELIVERY \u2014 ${project}]`,
2457
2457
  "Commit useful, passing work.",
2458
2458
  coordinationLine(env.platform),
2459
- "Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Missing replies and unfinished peer work do not block delivery.",
2459
+ "Include visible ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Silence means nothing actionable and does not block delivery.",
2460
2460
  "Use this repo's delivery profile and verify production."
2461
2461
  ].join("\n");
2462
2462
  }
@@ -2467,6 +2467,14 @@ var deliveryControlModule = {
2467
2467
  reminder: () => null
2468
2468
  };
2469
2469
 
2470
+ // src/commands/persona-reminder.ts
2471
+ var personaAnnounceModule = {
2472
+ id: "persona-announce",
2473
+ detect: () => ({ tier: "silent" }),
2474
+ announce: (env) => env.personaAnnounce || null,
2475
+ reminder: () => null
2476
+ };
2477
+
2470
2478
  // src/commands/reminder-registry.ts
2471
2479
  var REGISTRY = [
2472
2480
  osPrimerModule,
@@ -2488,6 +2496,8 @@ var REGISTRY = [
2488
2496
  // active project procedures, rendered generically from operator-owned installs
2489
2497
  deliveryControlModule,
2490
2498
  // repo-scoped announce-first Delivery System pilot (all harnesses)
2499
+ personaAnnounceModule,
2500
+ // tenant-scoped Persona, shared across projects/harnesses
2491
2501
  loadoutRegistrarModule,
2492
2502
  // equipped loadouts: one advisor announce each + per-turn keyword Match (docs/loadout.md)
2493
2503
  setupWarningModule,
@@ -2591,12 +2601,16 @@ function buildOpenCodeEnv(params) {
2591
2601
  corpusApiDocs: params.corpusApiDocs ?? [],
2592
2602
  updateAvailable: params.updateAvailable ?? null,
2593
2603
  procedureAnnounces: params.procedureAnnounces ?? [],
2594
- mirroredSkills: params.mirroredSkills
2604
+ mirroredSkills: params.mirroredSkills,
2605
+ personaAnnounce: params.personaAnnounce ?? null
2595
2606
  };
2596
2607
  }
2597
2608
  function getOpenCodeAnnounces(env) {
2598
2609
  return [...collectAnnounces(env, opencodeRegistry), OPENCODE_QUOTA_WAITLIST_ANNOUNCE];
2599
2610
  }
2611
+ function getOpenCodePersistentAnnounces(env) {
2612
+ return collectAnnounces(env, opencodeRegistry.filter((m) => m.id === "persona-announce"));
2613
+ }
2600
2614
  function getOpenCodeReminders(env) {
2601
2615
  return collectReminders(env, opencodeRegistry);
2602
2616
  }
@@ -4296,6 +4310,8 @@ async function fetchRecapInbox(anchor) {
4296
4310
  }
4297
4311
  var relayArmedBySession = /* @__PURE__ */ new Set();
4298
4312
  var announcedBySession = /* @__PURE__ */ new Set();
4313
+ var personaAnnounceBySession = /* @__PURE__ */ new Map();
4314
+ var PERSONA_SESSION_CACHE_MAX = 100;
4299
4315
  var turnCountBySession = /* @__PURE__ */ new Map();
4300
4316
  var procedurePromptKeyBySession = /* @__PURE__ */ new Map();
4301
4317
  var cachedRecap = null;
@@ -4333,6 +4349,32 @@ async function fetchSkillActivation(apiKey, cwd) {
4333
4349
  return void 0;
4334
4350
  }
4335
4351
  }
4352
+ async function fetchPersonaAnnounce(apiKey) {
4353
+ try {
4354
+ const response = await fetch(`${API_URL}/v1/persona`, {
4355
+ headers: { Authorization: `Bearer ${apiKey}` }
4356
+ });
4357
+ if (!response.ok)
4358
+ return null;
4359
+ const data = await response.json();
4360
+ return typeof data.announce === "string" && data.announce.length > 0 ? data.announce : null;
4361
+ } catch {
4362
+ return null;
4363
+ }
4364
+ }
4365
+ async function personaAnnounceForSession(sessionId, apiKey) {
4366
+ if (personaAnnounceBySession.has(sessionId)) {
4367
+ return personaAnnounceBySession.get(sessionId) ?? null;
4368
+ }
4369
+ const announce = await fetchPersonaAnnounce(apiKey);
4370
+ if (personaAnnounceBySession.size >= PERSONA_SESSION_CACHE_MAX) {
4371
+ const oldest = personaAnnounceBySession.keys().next().value;
4372
+ if (oldest)
4373
+ personaAnnounceBySession.delete(oldest);
4374
+ }
4375
+ personaAnnounceBySession.set(sessionId, announce);
4376
+ return announce;
4377
+ }
4336
4378
  function startSessionRelay(sessionId, serverUrl) {
4337
4379
  if (process.env.GREPRAG_OPENCODE_RELAY === "0")
4338
4380
  return;
@@ -4667,6 +4709,7 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4667
4709
  if (sid8) {
4668
4710
  const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
4669
4711
  turnCountBySession.set(sid8, turnCount);
4712
+ const firstAnnounce = !announcedBySession.has(sid8);
4670
4713
  const env = buildOpenCodeEnv({
4671
4714
  short: sid8,
4672
4715
  turnCount,
@@ -4682,9 +4725,12 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4682
4725
  return [];
4683
4726
  }
4684
4727
  })(),
4685
- mirroredSkills: !announcedBySession.has(sid8) ? await fetchSkillActivation(apiKey, workingDir) : void 0
4728
+ mirroredSkills: firstAnnounce ? await fetchSkillActivation(apiKey, workingDir) : void 0,
4729
+ // output.system is fresh on EVERY transform call. Resolve Persona
4730
+ // once/session, then replay the cached shared-registry announce.
4731
+ personaAnnounce: await personaAnnounceForSession(sid8, apiKey)
4686
4732
  });
4687
- if (!announcedBySession.has(sid8)) {
4733
+ if (firstAnnounce) {
4688
4734
  announcedBySession.add(sid8);
4689
4735
  const announces = getOpenCodeAnnounces(env);
4690
4736
  if (announces.length > 0) {
@@ -4693,6 +4739,13 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4693
4739
  }
4694
4740
  dlog(`interrupt: pushed ${announces.length} announce(s) for sid=${sid8}`);
4695
4741
  }
4742
+ } else {
4743
+ const persistent = getOpenCodePersistentAnnounces(env);
4744
+ for (const line of persistent)
4745
+ pushSystemPrompt(output, line);
4746
+ if (persistent.length > 0) {
4747
+ dlog(`interrupt: replayed ${persistent.length} persistent announce(s) for sid=${sid8}`);
4748
+ }
4696
4749
  }
4697
4750
  const reminders = getOpenCodeReminders(env);
4698
4751
  if (reminders.length > 0) {
@@ -548,6 +548,8 @@ async function fetchRecapInbox(anchor) {
548
548
  // ============================================================================
549
549
  const relayArmedBySession = new Set();
550
550
  const announcedBySession = new Set();
551
+ const personaAnnounceBySession = new Map();
552
+ const PERSONA_SESSION_CACHE_MAX = 100;
551
553
  const turnCountBySession = new Map();
552
554
  const procedurePromptKeyBySession = new Map();
553
555
  /** Cached recap body per projectId. opencode fires
@@ -596,6 +598,35 @@ async function fetchSkillActivation(apiKey, cwd) {
596
598
  return undefined;
597
599
  }
598
600
  }
601
+ async function fetchPersonaAnnounce(apiKey) {
602
+ try {
603
+ const response = await fetch(`${API_URL}/v1/persona`, {
604
+ headers: { Authorization: `Bearer ${apiKey}` },
605
+ });
606
+ if (!response.ok)
607
+ return null;
608
+ const data = await response.json();
609
+ return typeof data.announce === 'string' && data.announce.length > 0
610
+ ? data.announce
611
+ : null;
612
+ }
613
+ catch {
614
+ return null;
615
+ }
616
+ }
617
+ async function personaAnnounceForSession(sessionId, apiKey) {
618
+ if (personaAnnounceBySession.has(sessionId)) {
619
+ return personaAnnounceBySession.get(sessionId) ?? null;
620
+ }
621
+ const announce = await fetchPersonaAnnounce(apiKey);
622
+ if (personaAnnounceBySession.size >= PERSONA_SESSION_CACHE_MAX) {
623
+ const oldest = personaAnnounceBySession.keys().next().value;
624
+ if (oldest)
625
+ personaAnnounceBySession.delete(oldest);
626
+ }
627
+ personaAnnounceBySession.set(sessionId, announce);
628
+ return announce;
629
+ }
599
630
  // ============================================================================
600
631
  // Per-session relay spawner — "anytime opencode is opened and being used"
601
632
  // ============================================================================
@@ -1002,6 +1033,7 @@ const GrepRAGMemoryPlugin = async (ctx) => {
1002
1033
  if (sid8) {
1003
1034
  const turnCount = (turnCountBySession.get(sid8) ?? 0) + 1;
1004
1035
  turnCountBySession.set(sid8, turnCount);
1036
+ const firstAnnounce = !announcedBySession.has(sid8);
1005
1037
  // Build env — only populate what's available in the opencode context
1006
1038
  const env = (0, opencode_interrupt_1.buildOpenCodeEnv)({
1007
1039
  short: sid8,
@@ -1019,12 +1051,15 @@ const GrepRAGMemoryPlugin = async (ctx) => {
1019
1051
  return [];
1020
1052
  }
1021
1053
  })(),
1022
- mirroredSkills: !announcedBySession.has(sid8)
1054
+ mirroredSkills: firstAnnounce
1023
1055
  ? await fetchSkillActivation(apiKey, workingDir)
1024
1056
  : undefined,
1057
+ // output.system is fresh on EVERY transform call. Resolve Persona
1058
+ // once/session, then replay the cached shared-registry announce.
1059
+ personaAnnounce: await personaAnnounceForSession(sid8, apiKey),
1025
1060
  });
1026
1061
  // Announces: inject on first fire per session (static primers)
1027
- if (!announcedBySession.has(sid8)) {
1062
+ if (firstAnnounce) {
1028
1063
  announcedBySession.add(sid8);
1029
1064
  const announces = (0, opencode_interrupt_1.getOpenCodeAnnounces)(env);
1030
1065
  if (announces.length > 0) {
@@ -1034,6 +1069,14 @@ const GrepRAGMemoryPlugin = async (ctx) => {
1034
1069
  dlog(`interrupt: pushed ${announces.length} announce(s) for sid=${sid8}`);
1035
1070
  }
1036
1071
  }
1072
+ else {
1073
+ const persistent = (0, opencode_interrupt_1.getOpenCodePersistentAnnounces)(env);
1074
+ for (const line of persistent)
1075
+ pushSystemPrompt(output, line);
1076
+ if (persistent.length > 0) {
1077
+ dlog(`interrupt: replayed ${persistent.length} persistent announce(s) for sid=${sid8}`);
1078
+ }
1079
+ }
1037
1080
  // Reminders: inject every turn (dynamic nudges)
1038
1081
  const reminders = (0, opencode_interrupt_1.getOpenCodeReminders)(env);
1039
1082
  if (reminders.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "greprag",
3
- "version": "5.76.0",
3
+ "version": "5.77.0",
4
4
  "description": "GrepRAG — agent memory for Claude Code, Codex, OpenCode, and Grok Build.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {