greprag 5.80.0 → 5.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capture-manifest.js +2 -1
- package/dist/codex-fast-hook.js +6 -0
- package/dist/codex-steering.js +1 -1
- package/dist/commands/app-model.js +0 -1
- package/dist/commands/arm-reminder.js +9 -7
- package/dist/commands/collision-check.js +7 -6
- package/dist/commands/corpus/client.js +13 -3
- package/dist/commands/delivery-reminder.js +35 -14
- package/dist/commands/deploy-gate.js +55 -0
- package/dist/commands/deploy-lock.js +100 -0
- package/dist/commands/deploy-record.js +145 -0
- package/dist/commands/deploy-verify.js +111 -0
- package/dist/commands/inbox-primer-reminder.js +5 -5
- package/dist/commands/inbox-watch.js +2 -4
- package/dist/commands/init.js +82 -0
- package/dist/commands/load.js +40 -0
- package/dist/commands/loadout-reminder.js +1 -1
- package/dist/commands/merge-guard.js +419 -0
- package/dist/commands/merge-lock.js +176 -0
- package/dist/commands/parity-reminder.js +53 -0
- package/dist/commands/persona-reminder.js +11 -0
- package/dist/commands/persona.js +50 -0
- package/dist/commands/procedure.js +77 -6
- package/dist/commands/reminder-registry.js +21 -5
- package/dist/commands/repodoc.js +433 -0
- package/dist/commands/search.js +149 -0
- package/dist/commands/skillgain.js +33 -25
- package/dist/delivery-lifecycle.js +16 -1
- package/dist/deploy-gate.js +355 -0
- package/dist/deploy-locks.js +339 -0
- package/dist/deploy-verify.js +209 -0
- package/dist/env-redaction.js +157 -0
- package/dist/harness-limits.js +17 -0
- package/dist/hook-runtime.js +11 -1
- package/dist/hook.js +170 -88
- package/dist/index.js +593 -567
- package/dist/inline-atom-episode.js +15 -7
- package/dist/inline-atom.js +8 -2
- package/dist/native-skill-adoption.js +11 -0
- package/dist/native-skill-mirror.js +8 -1
- package/dist/node-identity.bundle.js +1166 -0
- package/dist/opencode-plugin.bundle.js +307 -119
- package/dist/procedure-enabled.js +55 -0
- package/dist/procedure-runtime.js +6 -0
- package/dist/procedure-scope.js +190 -0
- package/dist/procedure-watch.js +29 -16
- package/dist/procedure.js +111 -5
- package/dist/project-anchor.js +1 -14
- package/dist/reminder-injector.js +11 -10
- package/dist/repodoc-client.js +296 -0
- package/dist/session-id.js +7 -8
- package/dist/skill-landing.js +57 -2
- package/dist/skill-mirror-client.js +14 -0
- package/dist/skill-mirror-files.js +18 -0
- package/package.json +2 -2
- package/scripts/bundle-node-identity.mjs +47 -0
- package/skill/templates/chip-spawn.md +7 -1
- package/skill/templates/delivery.md +105 -0
- package/skill/templates/prompt-audit.md +196 -0
- package/skill/templates/skill-change.md +25 -2
- package/dist/assistant-doctrine.js +0 -85
- package/dist/commands/assistant-reminder.js +0 -19
- package/dist/commands/assistant.js +0 -95
package/dist/hook.js
CHANGED
|
@@ -49,7 +49,6 @@ const proc_1 = require("./proc");
|
|
|
49
49
|
const docptr_refs_1 = require("./docptr-refs");
|
|
50
50
|
const project_anchor_1 = require("./project-anchor");
|
|
51
51
|
const mechanic_role_1 = require("./mechanic-role");
|
|
52
|
-
const assistant_doctrine_1 = require("./assistant-doctrine");
|
|
53
52
|
const session_id_1 = require("./session-id");
|
|
54
53
|
const hook_runtime_1 = require("./hook-runtime");
|
|
55
54
|
const harness_1 = require("./harness");
|
|
@@ -67,9 +66,12 @@ const email_pull_1 = require("./email-pull");
|
|
|
67
66
|
const watcher_registry_1 = require("./commands/watcher-registry");
|
|
68
67
|
// adr: adr/secret-scrubber.md — secret detection is canonical in @greprag/core.
|
|
69
68
|
// This imports the CLI MIRROR (the zero-dep hook can't import core at runtime).
|
|
70
|
-
// buildRedactionMap (the .env/process.env walker) is client-only and
|
|
71
|
-
// this
|
|
69
|
+
// buildRedactionMap (the .env/process.env walker) is client-only and lives in
|
|
70
|
+
// ./env-redaction beside this hook — the pure module is deliberately
|
|
71
|
+
// filesystem-free, and the walker's classification (a path is never a secret)
|
|
72
|
+
// is unit-tested there.
|
|
72
73
|
const secret_scrubber_1 = require("./secret-scrubber");
|
|
74
|
+
const env_redaction_1 = require("./env-redaction");
|
|
73
75
|
// adr: adr/opencode-monitor-relay.md 2026-06-06 (g) — recap renderer is the
|
|
74
76
|
// single source of truth for the recap body pushed at session start. Both
|
|
75
77
|
// Claude/Codex SessionStart and the opencode plugin use the same renderer so
|
|
@@ -85,6 +87,7 @@ const guard_1 = require("./guard");
|
|
|
85
87
|
// auto-silences when resolved (the arm-check pattern). The hook below assembles the
|
|
86
88
|
// live env (ALL I/O) and emits; the modules are pure.
|
|
87
89
|
const reminder_registry_1 = require("./commands/reminder-registry");
|
|
90
|
+
const persona_1 = require("./commands/persona");
|
|
88
91
|
const app_settings_1 = require("./app-settings");
|
|
89
92
|
const inbox_primer_reminder_1 = require("./commands/inbox-primer-reminder");
|
|
90
93
|
const skill_activation_manifest_1 = require("./skill-activation-manifest");
|
|
@@ -105,6 +108,7 @@ const context_governor_1 = require("./commands/context-governor");
|
|
|
105
108
|
// lives in ./commands/pipe-wrap. docs/pre-llm-compression.md, adr/pipe-wrap-hook.md
|
|
106
109
|
const pipe_wrap_1 = require("./commands/pipe-wrap");
|
|
107
110
|
const search_guard_1 = require("./commands/search-guard");
|
|
111
|
+
const merge_guard_1 = require("./commands/merge-guard");
|
|
108
112
|
// Governed compact (Chip D) — PreCompact renders the transcript to pixel-font
|
|
109
113
|
// PNG pages (lossless ~3x-density archive, CCR-indexed); SessionStart(compact)
|
|
110
114
|
// injects the pointer so the post-compact model can Read the verbatim record.
|
|
@@ -129,13 +133,16 @@ const procedure_1 = require("./procedure");
|
|
|
129
133
|
// per turn by the Stop hook (observeProcedureTurn), closed by negative
|
|
130
134
|
// continuity, distilled server-side, transitions applied to the local store.
|
|
131
135
|
const procedure_watch_1 = require("./procedure-watch");
|
|
136
|
+
const procedure_scope_1 = require("./procedure-scope");
|
|
132
137
|
const procedure_runtime_1 = require("./procedure-runtime");
|
|
138
|
+
const procedure_enabled_1 = require("./procedure-enabled");
|
|
133
139
|
const procedure_shadow_1 = require("./procedure-shadow");
|
|
134
140
|
const inline_atom_hook_1 = require("./inline-atom-hook");
|
|
135
141
|
const inline_atom_background_1 = require("./inline-atom-background");
|
|
136
142
|
const reminder_injector_1 = require("./reminder-injector");
|
|
137
143
|
const skill_landing_1 = require("./skill-landing");
|
|
138
144
|
const skill_mirror_client_1 = require("./skill-mirror-client");
|
|
145
|
+
const repodoc_client_1 = require("./repodoc-client");
|
|
139
146
|
const native_skill_pending_1 = require("./native-skill-pending");
|
|
140
147
|
const doc_mirror_client_1 = require("./doc-mirror-client");
|
|
141
148
|
const equipped_loadouts_cache_1 = require("./equipped-loadouts-cache");
|
|
@@ -620,60 +627,11 @@ function detectArtifacts(toolCalls, cwd) {
|
|
|
620
627
|
return events;
|
|
621
628
|
}
|
|
622
629
|
// ---------- Env-var scrubbing ---------------------------------------------
|
|
623
|
-
// Pattern + entropy detection
|
|
630
|
+
// Pattern + entropy detection lives in ./secret-scrubber — the CLI MIRROR of
|
|
624
631
|
// the canonical @greprag/core module (the zero-dep hook can't import core at
|
|
625
|
-
// runtime).
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
// buildRedactionMap below is CLIENT-ONLY (reads .env + process.env) and stays
|
|
629
|
-
// here on purpose: the pure module is filesystem-free. adr: adr/secret-scrubber.md
|
|
630
|
-
/** Build a redaction map of value → [REDACTED:VAR_NAME] from .env files and process.env. */
|
|
631
|
-
function buildRedactionMap(cwd) {
|
|
632
|
-
const entries = [];
|
|
633
|
-
const seenValues = new Set();
|
|
634
|
-
const addEntry = (key, value) => {
|
|
635
|
-
if (!value || value.length < 16)
|
|
636
|
-
return;
|
|
637
|
-
if (seenValues.has(value))
|
|
638
|
-
return;
|
|
639
|
-
seenValues.add(value);
|
|
640
|
-
entries.push([value, `[REDACTED:${key}]`]);
|
|
641
|
-
};
|
|
642
|
-
// Walk up looking for .env files
|
|
643
|
-
let dir = path.resolve(cwd);
|
|
644
|
-
for (let depth = 0; depth < 8; depth++) {
|
|
645
|
-
const envPath = path.join(dir, '.env');
|
|
646
|
-
if (fs.existsSync(envPath)) {
|
|
647
|
-
try {
|
|
648
|
-
const raw = fs.readFileSync(envPath, 'utf-8');
|
|
649
|
-
for (const line of raw.split('\n')) {
|
|
650
|
-
const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.+?)\s*$/);
|
|
651
|
-
if (!m)
|
|
652
|
-
continue;
|
|
653
|
-
let value = m[2].trim();
|
|
654
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
655
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
656
|
-
value = value.slice(1, -1);
|
|
657
|
-
}
|
|
658
|
-
addEntry(m[1], value);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
catch { /* skip */ }
|
|
662
|
-
}
|
|
663
|
-
const parent = path.dirname(dir);
|
|
664
|
-
if (parent === dir)
|
|
665
|
-
break;
|
|
666
|
-
dir = parent;
|
|
667
|
-
}
|
|
668
|
-
// Process env
|
|
669
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
670
|
-
if (typeof value === 'string')
|
|
671
|
-
addEntry(key, value);
|
|
672
|
-
}
|
|
673
|
-
// Sort by value length desc so longer values are replaced first
|
|
674
|
-
entries.sort((a, b) => b[0].length - a[0].length);
|
|
675
|
-
return entries;
|
|
676
|
-
}
|
|
632
|
+
// runtime). The CLIENT-ONLY .env/process.env walker (buildRedactionMap) lives
|
|
633
|
+
// in ./env-redaction; both are imported at the top of this file.
|
|
634
|
+
// adr: adr/secret-scrubber.md
|
|
677
635
|
// ---------- Field-size cap ------------------------------------------------
|
|
678
636
|
function capField(text) {
|
|
679
637
|
if (!text || text.length <= MAX_FIELD_CHARS)
|
|
@@ -773,6 +731,14 @@ function collectDocEvents(filesTouched, cwd) {
|
|
|
773
731
|
/** One observed turn of the active watch. Never throws (the caller also
|
|
774
732
|
* guards); every network call fail-open. */
|
|
775
733
|
async function observeProcedureTurn(cfg, anchor, turn, cwd, redaction) {
|
|
734
|
+
// Master switch. Off since 2026-09-11. No watch can open while it is off, but
|
|
735
|
+
// a watch left open by the last enabled session must not keep observing and
|
|
736
|
+
// must never distill — so clear it rather than resume it.
|
|
737
|
+
// adr: adr/procedure-system-off.md
|
|
738
|
+
if (!procedure_enabled_1.PROCEDURE_SYSTEM_ENABLED) {
|
|
739
|
+
(0, procedure_watch_1.clearProcedureWatch)(anchor.projectId);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
776
742
|
const watch = (0, procedure_watch_1.readProcedureWatch)(anchor.projectId);
|
|
777
743
|
if (!watch) {
|
|
778
744
|
// File exists but is unreadable/corrupt — drop it so it can't wedge the observer.
|
|
@@ -868,7 +834,7 @@ async function observeProcedureTurn(cfg, anchor, turn, cwd, redaction) {
|
|
|
868
834
|
}
|
|
869
835
|
else {
|
|
870
836
|
const store = (0, procedure_1.readProcedureStore)(anchor.projectId);
|
|
871
|
-
const { store: nextStore, action } = (0, procedure_watch_1.applyDistillTransition)(store, next, outcome, distilled, (0, procedure_watch_1.learnTriggersForVerb)(next.verb, store));
|
|
837
|
+
const { store: nextStore, action } = (0, procedure_watch_1.applyDistillTransition)(store, next, outcome, distilled, (0, procedure_watch_1.learnTriggersForVerb)(next.verb, store), (0, procedure_scope_1.projectCheckoutRoots)(cwd));
|
|
872
838
|
if (action !== 'discarded')
|
|
873
839
|
(0, procedure_1.writeProcedureStore)(anchor.projectId, nextStore);
|
|
874
840
|
}
|
|
@@ -1110,7 +1076,7 @@ async function store(input, source = 'claude-code') {
|
|
|
1110
1076
|
? (0, turn_provenance_1.provenanceElisionMarker)(provenance, (0, turn_provenance_1.provenanceHint)(provenance, turn.userPrompt))
|
|
1111
1077
|
: turn.userPrompt;
|
|
1112
1078
|
// Env-var scrub
|
|
1113
|
-
const redaction = buildRedactionMap(cwd);
|
|
1079
|
+
const redaction = (0, env_redaction_1.buildRedactionMap)(cwd);
|
|
1114
1080
|
const userPrompt = capField((0, secret_scrubber_1.scrubString)(userPromptForStore, redaction));
|
|
1115
1081
|
const agentResponse = capField((0, secret_scrubber_1.scrubString)(turn.agentResponse, redaction));
|
|
1116
1082
|
// Strip internal-only fields (_output, _useId) before sending to API,
|
|
@@ -1218,6 +1184,17 @@ async function store(input, source = 'claude-code') {
|
|
|
1218
1184
|
});
|
|
1219
1185
|
}
|
|
1220
1186
|
}
|
|
1187
|
+
// Repo chunk index (adr/node-identity-port.md): the chunks are DERIVED from a
|
|
1188
|
+
// document plus the node graph committed beside it, so they are rebuilt when
|
|
1189
|
+
// the document moves rather than when someone remembers to run a push. Same
|
|
1190
|
+
// event as docptr and the skill mirror — the turn already knows what it wrote.
|
|
1191
|
+
// Only a repository that has been indexed at least once participates, and
|
|
1192
|
+
// within it only documents whose graph actually moved. Best-effort, never
|
|
1193
|
+
// throws: a stale index is a worse search result, a thrown Stop hook is lost
|
|
1194
|
+
// work.
|
|
1195
|
+
await (0, repodoc_client_1.maybeRebuildChunks)({
|
|
1196
|
+
filesTouched: turn.filesTouched, cwd, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey,
|
|
1197
|
+
});
|
|
1221
1198
|
}
|
|
1222
1199
|
// ---------- Main ---------------------------------------------------------
|
|
1223
1200
|
// ---------- Recap (SessionStart) ------------------------------------------
|
|
@@ -1329,13 +1306,56 @@ function shouldEmitDriftWarning(storedId, derivedId) {
|
|
|
1329
1306
|
return (Date.now() - lastMs) >= DRIFT_WARNING_TTL_MS;
|
|
1330
1307
|
}
|
|
1331
1308
|
function grokSidecarHead(short, full) {
|
|
1332
|
-
const arm = (0, session_id_1.armMonitorCommand)(short, null, false,
|
|
1309
|
+
const arm = (0, session_id_1.armMonitorCommand)(short, null, false, 'grok');
|
|
1333
1310
|
return (0, session_id_1.buildSessionIdContext)(short, (0, session_id_1.readIdentityAlias)())
|
|
1334
1311
|
+ `\n\nARM (Grok monitor persistent:true): \`${arm}\`\n`
|
|
1335
1312
|
+ `Send with --from-session ${full || short} (full UUID or 16-hex, never 8-hex).\n`
|
|
1336
1313
|
+ 'Chip: `greprag load grok-chip-spawn` then `greprag grok spawn`. Helper: spawn_subagent. Parent keeps ONE watch. Peers: `greprag inbox watchers` then `greprag send`.\n'
|
|
1337
1314
|
+ 'If a greprag inbox monitor is already listed, do not start another. Instant exit = already armed.\n\n';
|
|
1338
1315
|
}
|
|
1316
|
+
/** SessionStart(persona) / PostCompact(persona) — Persona on its own hook, with
|
|
1317
|
+
* its own inline budget.
|
|
1318
|
+
*
|
|
1319
|
+
* The harness caps EACH hook invocation at ~2048 bytes independently, so a
|
|
1320
|
+
* dedicated hook buys Persona a full allowance nothing else competes for. That
|
|
1321
|
+
* matters because Persona is the one block that shapes every token the agent
|
|
1322
|
+
* emits; the rest of the announce is reference material `greprag load` serves
|
|
1323
|
+
* on demand. It also means the operator can grow their Persona without silently
|
|
1324
|
+
* pushing another announce section out.
|
|
1325
|
+
*
|
|
1326
|
+
* Registered on SessionStart AND PostCompact, for Claude Code and Codex alike.
|
|
1327
|
+
* PostCompact is not an optional extra: a compaction wipes SessionStart context,
|
|
1328
|
+
* so without it a long session silently loses its speaking rules mid-flight.
|
|
1329
|
+
*
|
|
1330
|
+
* The emitted `hookEventName` is the CONSTANT 'SessionStart', never the payload's
|
|
1331
|
+
* event name. `PostCompact` is a hook REGISTRATION point, not a value the harness
|
|
1332
|
+
* accepts in `hookSpecificOutput` — Claude Code validates that field against a
|
|
1333
|
+
* fixed enum and REJECTS the whole payload when it echoes 'PostCompact', so the
|
|
1334
|
+
* persona is dropped on exactly the path this hook exists to cover. Every other
|
|
1335
|
+
* greprag PostCompact hook (`recompact`, `session-id`) already emits 'SessionStart'
|
|
1336
|
+
* for this reason.
|
|
1337
|
+
*
|
|
1338
|
+
* Fetch failures stay silent — a missing Persona is not worth a startup error.
|
|
1339
|
+
* adr: adr/persona.md, adr/announce-inline-budget.md */
|
|
1340
|
+
async function personaHook(input) {
|
|
1341
|
+
const cwd = input.cwd || process.cwd();
|
|
1342
|
+
const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
|
|
1343
|
+
if (!cfg.enabled || !cfg.apiKey)
|
|
1344
|
+
return;
|
|
1345
|
+
try {
|
|
1346
|
+
const res = await fetch(`${cfg.apiUrl}/v1/persona`, {
|
|
1347
|
+
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
|
1348
|
+
});
|
|
1349
|
+
if (!res.ok)
|
|
1350
|
+
return;
|
|
1351
|
+
const data = await res.json();
|
|
1352
|
+
const announce = typeof data.announce === 'string' ? data.announce.trim() : '';
|
|
1353
|
+
if (!announce)
|
|
1354
|
+
return;
|
|
1355
|
+
(0, hook_runtime_1.writeAdditionalContext)('SessionStart', (0, persona_1.fitPersonaAnnounce)(announce));
|
|
1356
|
+
}
|
|
1357
|
+
catch { /* never block session start on Persona */ }
|
|
1358
|
+
}
|
|
1339
1359
|
/** Where the full, unabridged announce is parked for on-demand reading.
|
|
1340
1360
|
* adr: adr/announce-inline-budget.md */
|
|
1341
1361
|
function announceCachePath(short) {
|
|
@@ -1362,9 +1382,9 @@ function buildInlineAnnounce(blocks, short) {
|
|
|
1362
1382
|
catch { /* cache is a convenience; never block session start */ }
|
|
1363
1383
|
}
|
|
1364
1384
|
// Reserve room for the pointer line before fitting, so adding it can never be
|
|
1365
|
-
// what pushes the payload over the cap.
|
|
1366
|
-
|
|
1367
|
-
const { kept, droppedIds } = (0, reminder_registry_1.fitAnnounceBudget)(blocks, reminder_registry_1.ANNOUNCE_INLINE_BUDGET,
|
|
1385
|
+
// what pushes the payload over the cap. The number lives beside the budget in
|
|
1386
|
+
// reminder-registry so the `mustInline` build guard charges the same reserve.
|
|
1387
|
+
const { kept, droppedIds } = (0, reminder_registry_1.fitAnnounceBudget)(blocks, reminder_registry_1.ANNOUNCE_INLINE_BUDGET, reminder_registry_1.ANNOUNCE_POINTER_RESERVE);
|
|
1368
1388
|
if (!droppedIds.length)
|
|
1369
1389
|
return kept.map(b => b.text).join('\n\n') || null;
|
|
1370
1390
|
const names = droppedIds.join(', ');
|
|
@@ -1373,9 +1393,14 @@ function buildInlineAnnounce(blocks, short) {
|
|
|
1373
1393
|
+ `Read them with \`greprag announce\`.]`;
|
|
1374
1394
|
return [...kept.map(b => b.text), pointer].join('\n\n');
|
|
1375
1395
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1396
|
+
/** `grokExtra` is sidecar-ONLY text. The Grok sidecar is a FILE the agent reads
|
|
1397
|
+
* (adr/grok-platform.md) — it has no inline cap, so a block that cannot fit the
|
|
1398
|
+
* inline budget still belongs there whole. Persona is the one such block today.
|
|
1399
|
+
* adr: adr/persona.md */
|
|
1400
|
+
function writeRecapOutput(text, mode, grokShort, grokFull, grokExtra) {
|
|
1401
|
+
if (grokShort) {
|
|
1402
|
+
(0, grok_session_1.writeGrokSidecar)(grokShort, grokSidecarHead(grokShort, grokFull) + (grokExtra || '') + (text || ''));
|
|
1403
|
+
}
|
|
1379
1404
|
if (!text)
|
|
1380
1405
|
return;
|
|
1381
1406
|
if (mode === 'additionalContext') {
|
|
@@ -1667,11 +1692,38 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1667
1692
|
return null;
|
|
1668
1693
|
}
|
|
1669
1694
|
})();
|
|
1695
|
+
// Parity units THIS harness still owes. The lead is filtered out server-side by
|
|
1696
|
+
// state, and diverged units are dropped here — two-sided advancement needs the
|
|
1697
|
+
// operator, not an unattended port. adr: adr/parity-system.md
|
|
1698
|
+
const parityPendingPromise = (async () => {
|
|
1699
|
+
try {
|
|
1700
|
+
const res = await fetch(`${cfg.apiUrl}/v1/parity/list`, {
|
|
1701
|
+
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
|
1702
|
+
});
|
|
1703
|
+
if (!res.ok)
|
|
1704
|
+
return undefined;
|
|
1705
|
+
const data = await res.json();
|
|
1706
|
+
const pending = (data.units || [])
|
|
1707
|
+
.filter(u => !u.divergedHint && u.lead?.harness && u.lead.harness !== platform)
|
|
1708
|
+
.flatMap(u => (u.followers || [])
|
|
1709
|
+
.filter(f => f.state === 'stale' && f.harness === platform && f.path)
|
|
1710
|
+
.map(f => ({
|
|
1711
|
+
unit: String(u.name || ''),
|
|
1712
|
+
summary: String(u.summary || ''),
|
|
1713
|
+
path: String(f.path),
|
|
1714
|
+
leadHarness: String(u.lead?.harness || ''),
|
|
1715
|
+
})));
|
|
1716
|
+
return pending.length ? pending : undefined;
|
|
1717
|
+
}
|
|
1718
|
+
catch {
|
|
1719
|
+
return undefined;
|
|
1720
|
+
}
|
|
1721
|
+
})();
|
|
1670
1722
|
const updateAvailablePromise = checkForUpdate();
|
|
1671
1723
|
const bodyPromise = (!opts.compact && anchor.sessionStartRecap)
|
|
1672
1724
|
? (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor)
|
|
1673
1725
|
: Promise.resolve('');
|
|
1674
|
-
const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, personaAnnounce, updateAvailable, body,] = await Promise.all([
|
|
1726
|
+
const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, personaAnnounce, parityPending, updateAvailable, body,] = await Promise.all([
|
|
1675
1727
|
matchsetPromise,
|
|
1676
1728
|
corpusApiDocsPromise,
|
|
1677
1729
|
docPointersPromise,
|
|
@@ -1680,6 +1732,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1680
1732
|
mirroredSkillsPromise,
|
|
1681
1733
|
loadoutStatePromise,
|
|
1682
1734
|
personaPromise,
|
|
1735
|
+
parityPendingPromise,
|
|
1683
1736
|
updateAvailablePromise,
|
|
1684
1737
|
bodyPromise,
|
|
1685
1738
|
]);
|
|
@@ -1699,12 +1752,6 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1699
1752
|
const top = behind.reduce((a, b) => (isNewer(a.minCliVersion, b.minCliVersion) ? b : a));
|
|
1700
1753
|
return { from: top.fromHandle || 'another tenant', needed: top.minCliVersion, current: installed };
|
|
1701
1754
|
})();
|
|
1702
|
-
// Assistant doctrine auto-load — fires ONLY for the flagged assistant project
|
|
1703
|
-
// (isAssistantProject), so a normal project sees zero change. The hook owns the
|
|
1704
|
-
// doctrine-file read; the assistant-doctrine module routes the text. adr: adr/assistant-role.md
|
|
1705
|
-
const assistantDoctrine = (0, project_anchor_1.isAssistantProject)(anchor)
|
|
1706
|
-
? (0, assistant_doctrine_1.buildAssistantDoctrineContext)(cwd)
|
|
1707
|
-
: null;
|
|
1708
1755
|
// Active Procedure installs (adr/procedure-install-control-plane.md). Local,
|
|
1709
1756
|
// operator-owned state; missing/corrupt installs fail closed to no announce.
|
|
1710
1757
|
const procedureAnnounces = (() => {
|
|
@@ -1717,7 +1764,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1717
1764
|
})();
|
|
1718
1765
|
// ── THE single SessionStart announce assembly (docs/reminder-interrupt.md §Registry
|
|
1719
1766
|
// spec) ──────────────────────────────────────────────────────────────────────────────
|
|
1720
|
-
// Every agent-facing announce — setup-warning, front-desk,
|
|
1767
|
+
// Every agent-facing announce — setup-warning, front-desk, delivery-control,
|
|
1721
1768
|
// watcher-arm, mechanic — is now a registry module. The hook did the I/O above; here it
|
|
1722
1769
|
// stuffs the results into ONE env and collectAnnounces renders them in registry order.
|
|
1723
1770
|
// Loaded once + re-fired on compact (recap matcher ''). The Mechanic kill switch drops
|
|
@@ -1739,19 +1786,12 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1739
1786
|
short: announceShort, turnCount: 0, stress: 0, armed: false, sessionUnread: 0,
|
|
1740
1787
|
ownerPid: hasLocalMonitorLifecycle ? sessionOwnerPid : null,
|
|
1741
1788
|
alias: (0, session_id_1.readIdentityAlias)(),
|
|
1742
|
-
assistant: (() => { try {
|
|
1743
|
-
return (0, project_anchor_1.isAssistantProject)(anchor);
|
|
1744
|
-
}
|
|
1745
|
-
catch {
|
|
1746
|
-
return false;
|
|
1747
|
-
} })(),
|
|
1748
1789
|
mechanic: announceMechanic,
|
|
1749
1790
|
mechanicLive: announceMechanicLive,
|
|
1750
1791
|
platform,
|
|
1751
1792
|
projectName: anchor.projectName,
|
|
1752
1793
|
deliveryRepo: (0, project_anchor_1.computeGitDerivedProjectId)(cwd) !== null,
|
|
1753
1794
|
setupWarning,
|
|
1754
|
-
assistantDoctrine,
|
|
1755
1795
|
corpusApiDocs,
|
|
1756
1796
|
docPointers,
|
|
1757
1797
|
enrichmentDown,
|
|
@@ -1760,11 +1800,36 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1760
1800
|
procedureAnnounces,
|
|
1761
1801
|
equippedLoadouts,
|
|
1762
1802
|
personaAnnounce,
|
|
1803
|
+
parityPending,
|
|
1763
1804
|
updateAvailable,
|
|
1764
1805
|
activeCommandDrift: activeCommandDrift(),
|
|
1765
1806
|
loadoutUpdateRequired,
|
|
1766
1807
|
};
|
|
1767
|
-
|
|
1808
|
+
// Derive the registry for THIS harness first, so a module's `harnesses:` tag is
|
|
1809
|
+
// honored here the way it already is on the opencode path.
|
|
1810
|
+
let announceReg = (0, app_settings_1.filterEnabledInterrupts)((0, reminder_registry_1.harnessModules)(platform, reminder_registry_1.REGISTRY));
|
|
1811
|
+
// Persona NEVER rides this inline path, on any harness or any event. Every
|
|
1812
|
+
// harness that reaches recap/recompact delivers it through the dedicated
|
|
1813
|
+
// `greprag-hook persona` hook (Claude Code + Codex, SessionStart AND
|
|
1814
|
+
// PostCompact) or, for Grok, through the uncapped sidecar file below — each
|
|
1815
|
+
// with a full ANNOUNCE_HARNESS_CAP allowance to itself. Competing inside
|
|
1816
|
+
// ANNOUNCE_INLINE_BUDGET never worked: the operator's 1,886-byte persona is
|
|
1817
|
+
// larger than the whole budget, so `fitAnnounceBudget` deferred it WHOLE on
|
|
1818
|
+
// every path except Claude Code SessionStart.
|
|
1819
|
+
//
|
|
1820
|
+
// Unconditional is the point. The previous attempt scoped this by harness and
|
|
1821
|
+
// broke Codex PostCompact, because `recompact` is one subcommand shared by
|
|
1822
|
+
// Claude Code and Codex and cannot tell which one invoked it. A rule that needs
|
|
1823
|
+
// no harness knowledge cannot be got wrong by a harness it cannot see.
|
|
1824
|
+
//
|
|
1825
|
+
// OpenCode is unaffected: its plugin never calls recap(), it renders
|
|
1826
|
+
// `persona-announce` from the same registry straight into the system prompt,
|
|
1827
|
+
// and that path has no inline cap. So the module stays registered for everyone.
|
|
1828
|
+
// adr: adr/persona.md, adr/announce-inline-budget.md
|
|
1829
|
+
const personaSidecar = platform === 'grok' && personaAnnounce
|
|
1830
|
+
? personaAnnounce.trim() + '\n\n'
|
|
1831
|
+
: '';
|
|
1832
|
+
announceReg = announceReg.filter(m => m.id !== 'persona-announce');
|
|
1768
1833
|
if ((0, reminder_injector_1.mechanicKilled)())
|
|
1769
1834
|
announceReg = announceReg.filter(m => m.id !== 'mechanic-friction');
|
|
1770
1835
|
if (!announceShort)
|
|
@@ -1783,7 +1848,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1783
1848
|
// that the rest existed. adr: adr/announce-inline-budget.md
|
|
1784
1849
|
const announceBlock = buildInlineAnnounce((0, reminder_registry_1.collectAnnounceBlocks)(announceEnv, announceReg), (0, session_id_1.truncateSessionId)(input.session_id));
|
|
1785
1850
|
if (opts.compact) {
|
|
1786
|
-
writeRecapOutput(announceBlock ? announceBlock + '\n' : '', mode, grokShort, input.session_id);
|
|
1851
|
+
writeRecapOutput(announceBlock ? announceBlock + '\n' : '', mode, grokShort, input.session_id, personaSidecar);
|
|
1787
1852
|
return;
|
|
1788
1853
|
}
|
|
1789
1854
|
const preamble = () => (announceBlock ? announceBlock + '\n' : '');
|
|
@@ -1792,7 +1857,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1792
1857
|
// hook globally. Setup warnings still fire above.
|
|
1793
1858
|
if (!anchor.sessionStartRecap) {
|
|
1794
1859
|
const out = preamble();
|
|
1795
|
-
writeRecapOutput(out, mode, grokShort, input.session_id);
|
|
1860
|
+
writeRecapOutput(out, mode, grokShort, input.session_id, personaSidecar);
|
|
1796
1861
|
return;
|
|
1797
1862
|
}
|
|
1798
1863
|
// Recap body is hourlies-only — daily summaries used to render here (one
|
|
@@ -1809,7 +1874,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1809
1874
|
// if they're pending.
|
|
1810
1875
|
if (!body) {
|
|
1811
1876
|
const out = preamble();
|
|
1812
|
-
writeRecapOutput(out, mode, grokShort, input.session_id);
|
|
1877
|
+
writeRecapOutput(out, mode, grokShort, input.session_id, personaSidecar);
|
|
1813
1878
|
return;
|
|
1814
1879
|
}
|
|
1815
1880
|
const parts = [];
|
|
@@ -1828,7 +1893,7 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1828
1893
|
? body
|
|
1829
1894
|
: '[greprag memory — session recap not inlined (over the SessionStart cap). '
|
|
1830
1895
|
+ 'Read it with `greprag memory recap`.]');
|
|
1831
|
-
writeRecapOutput(parts.join('\n') + '\n', mode, grokShort, input.session_id);
|
|
1896
|
+
writeRecapOutput(parts.join('\n') + '\n', mode, grokShort, input.session_id, personaSidecar);
|
|
1832
1897
|
}
|
|
1833
1898
|
/** Arm-state detection moved LOCAL (2026-06-04). The former `isSessionArmed`
|
|
1834
1899
|
* asked the server's watcher registry "does this session have a live socket?"
|
|
@@ -1975,9 +2040,14 @@ async function notify(input, source = 'claude-code', chipContext) {
|
|
|
1975
2040
|
// proven-registered `notify` hook — so arming never depends on a freshly-merged
|
|
1976
2041
|
// subcommand reaching the operator's settings. Fires whatever's deficient (stacking),
|
|
1977
2042
|
// and auto-silences each module the instant its deficiency resolves.
|
|
2043
|
+
// Claude Code inbox delivery is TURN-BASED (2026-09-04). The Monitor watcher is
|
|
2044
|
+
// retired here: Claude Code has native in-harness session messaging, so greprag's
|
|
2045
|
+
// job is only to land OUTSIDE mail — and it does that on the same UserPromptSubmit
|
|
2046
|
+
// steering Codex uses. adr: adr/monitor-resilience.md
|
|
2047
|
+
const inbox = await (0, codex_steering_1.buildCodexInboxSteering)(input, cfg);
|
|
1978
2048
|
const reminders = await (0, reminder_injector_1.injectReminders)(input, cfg, short);
|
|
1979
2049
|
const inlineAtoms = (0, inline_atom_hook_1.buildPendingInlineAtomContext)(input, short);
|
|
1980
|
-
const parts = [reminders, inlineAtoms].filter((p) => !!p);
|
|
2050
|
+
const parts = [inbox, reminders, inlineAtoms].filter((p) => !!p);
|
|
1981
2051
|
if (parts.length)
|
|
1982
2052
|
(0, hook_runtime_1.writeAdditionalContext)('UserPromptSubmit', parts.join('\n\n'));
|
|
1983
2053
|
}
|
|
@@ -2256,7 +2326,7 @@ async function main() {
|
|
|
2256
2326
|
const validSubs = new Set([
|
|
2257
2327
|
'store', 'recap', 'recompact', 'notify', 'mail', 'friction-reminder', 'session-id', 'pre-spawn-check', 'armcheck',
|
|
2258
2328
|
'collision-check', 'drain', 'coordinate-gate', 'codex-coordinate-gate', 'procedure-check',
|
|
2259
|
-
'guard', 'guard-refresh', 'guard-flush',
|
|
2329
|
+
'guard', 'guard-refresh', 'guard-flush', 'persona',
|
|
2260
2330
|
'context-probe', 'context-check', 'crush-wrap',
|
|
2261
2331
|
'pre-compact', 'archive-pointer',
|
|
2262
2332
|
'codex-store', 'codex-notify', 'codex-inbox', 'codex-recap',
|
|
@@ -2431,7 +2501,10 @@ async function main() {
|
|
|
2431
2501
|
// silence on any non-qualifying command. A narrow rg preflight denial runs
|
|
2432
2502
|
// before wrap because generated/temp crawls are harmful before output exists.
|
|
2433
2503
|
// adr: adr/pipe-wrap-hook.md
|
|
2434
|
-
|
|
2504
|
+
// The merge guard runs FIRST: it is a deny, and denying a command must not
|
|
2505
|
+
// depend on whether a later rewrite step happens to fire.
|
|
2506
|
+
const out = (0, merge_guard_1.runMergeGuard)(input)
|
|
2507
|
+
|| (0, search_guard_1.runSearchGuard)(input)
|
|
2435
2508
|
|| (0, pipe_wrap_1.runPipeWrap)(input);
|
|
2436
2509
|
if (out)
|
|
2437
2510
|
process.stdout.write(JSON.stringify(out) + '\n');
|
|
@@ -2444,6 +2517,15 @@ async function main() {
|
|
|
2444
2517
|
if (message)
|
|
2445
2518
|
process.stdout.write(JSON.stringify({ systemMessage: message }) + '\n');
|
|
2446
2519
|
}
|
|
2520
|
+
else if (subcommand === 'persona') {
|
|
2521
|
+
// Persona rides its OWN hook, not the announce. The harness caps each hook's
|
|
2522
|
+
// inline output at ~2048 bytes independently, so a dedicated invocation gives
|
|
2523
|
+
// Persona a full allowance that no other announce section can crowd out.
|
|
2524
|
+
// Persona governs every token the agent emits; everything else in the
|
|
2525
|
+
// announce is reference material that `greprag load` can serve on demand.
|
|
2526
|
+
// adr: adr/announce-inline-budget.md
|
|
2527
|
+
await personaHook(input);
|
|
2528
|
+
}
|
|
2447
2529
|
else if (subcommand === 'archive-pointer') {
|
|
2448
2530
|
// SessionStart(matcher=compact) — the one documented post-compact channel
|
|
2449
2531
|
// whose output reaches the model. Points at the PNG archive.
|