greprag 5.83.2 → 5.85.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.
Files changed (35) hide show
  1. package/dist/commands/agent-doctrine-file.js +5 -128
  2. package/dist/commands/agents.js +125 -846
  3. package/dist/commands/app-model.js +1 -1
  4. package/dist/commands/delivery-reminder.js +1 -1
  5. package/dist/commands/discord.js +12 -61
  6. package/dist/commands/inbox-watch.js +0 -13
  7. package/dist/commands/init.js +17 -29
  8. package/dist/commands/load.js +15 -35
  9. package/dist/commands/parity.js +1 -1
  10. package/dist/commands/reminder-registry.js +1 -8
  11. package/dist/commands/session.js +2 -2
  12. package/dist/commands/skill-mirror-reminder.js +1 -19
  13. package/dist/commands/skill.js +236 -448
  14. package/dist/hook.js +31 -54
  15. package/dist/index.js +19 -152
  16. package/dist/native-adapter-refresh.js +15 -18
  17. package/dist/native-skill-mirror.js +11 -89
  18. package/dist/opencode-plugin.bundle.js +82 -282
  19. package/dist/reminder-injector.js +1 -1
  20. package/dist/skill-activation-manifest.js +14 -37
  21. package/dist/skill-git-ingest.js +297 -0
  22. package/dist/skill-landing.js +7 -173
  23. package/dist/skill-mirror-client.js +4 -251
  24. package/dist/skill-staleness.js +3 -6
  25. package/package.json +1 -1
  26. package/skill/commander/SKILL.md +10 -12
  27. package/skill/greprag/SKILL.md +1 -1
  28. package/skill/greprag/docs/discord-handoff.md +6 -30
  29. package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
  30. package/skill/templates/chip-spawn.md +6 -2
  31. package/skill/templates/delivery.md +16 -0
  32. package/skill/templates/os.md +1 -1
  33. package/skill/templates/prompt-audit.md +26 -49
  34. package/skill/templates/skill-change.md +15 -11
  35. package/dist/commands/sms.js +0 -215
package/dist/hook.js CHANGED
@@ -141,8 +141,8 @@ const procedure_shadow_1 = require("./procedure-shadow");
141
141
  const inline_atom_hook_1 = require("./inline-atom-hook");
142
142
  const inline_atom_background_1 = require("./inline-atom-background");
143
143
  const reminder_injector_1 = require("./reminder-injector");
144
- const skill_landing_1 = require("./skill-landing");
145
- const skill_mirror_client_1 = require("./skill-mirror-client");
144
+ const skill_git_ingest_1 = require("./skill-git-ingest");
145
+ const agents_1 = require("./commands/agents");
146
146
  const repodoc_client_1 = require("./repodoc-client");
147
147
  const native_skill_pending_1 = require("./native-skill-pending");
148
148
  const doc_mirror_client_1 = require("./doc-mirror-client");
@@ -851,41 +851,6 @@ async function fetchEnrichmentDown(apiUrl, apiKey) {
851
851
  return null;
852
852
  }
853
853
  }
854
- /** SessionStart pass: the Skill Learning Loop's LANDING leg
855
- * (docs/skill-learning-loop.md — the write-back is CODE, not agent
856
- * voluntarism). Fetch pending gains → auto-land reference-tier into each
857
- * skill's docs/learnings.md → POST landed statuses back → return the report
858
- * for the announce. Undefined on any error → announce silent, gains stay
859
- * pending for the next session start. */
860
- async function fetchAndLandSkillGains(apiUrl, apiKey, anchor, cwd) {
861
- try {
862
- const res = await fetch(`${apiUrl}/v1/skillgain/${anchor.projectId}/pending`, {
863
- headers: { 'Authorization': `Bearer ${apiKey}` },
864
- });
865
- if (!res.ok)
866
- return undefined;
867
- const data = await res.json();
868
- const pending = (data.gains || []).filter(g => g.nodeId && g.skill && g.text);
869
- if (pending.length === 0)
870
- return undefined;
871
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
872
- const { report, statusUpdates } = (0, skill_landing_1.landPendingGains)(pending, cwd, homeDir, anchor.projectName);
873
- if (statusUpdates.length > 0) {
874
- try {
875
- await fetch(`${apiUrl}/v1/skillgain/${anchor.projectId}/status`, {
876
- method: 'POST',
877
- headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
878
- body: JSON.stringify({ projectName: anchor.projectName, updates: statusUpdates }),
879
- });
880
- }
881
- catch { /* statuses re-sync next start — the stamp keeps lands idempotent */ }
882
- }
883
- return report.landed.length > 0 ? report : undefined;
884
- }
885
- catch {
886
- return undefined;
887
- }
888
- }
889
854
  /** Manifest-liveness policy — mirror of core's assessLiveness threshold (the CLI
890
855
  * is a zero-dep artifact, so the tiny policy is duplicated, like
891
856
  * DOCPTR_ANNOUNCE_LIMIT). A doc must cite ≥3 in-repo paths with ≥60% dead before
@@ -1156,18 +1121,6 @@ async function store(input, source = 'claude-code') {
1156
1121
  }
1157
1122
  catch { /* fail-open — an inline-atom miss never fails the Stop hook */ }
1158
1123
  }
1159
- // Skill mirror (docs/load-system.md Tier 3): a skill-load turn shadows that
1160
- // skill's current markdown into the tenant's load store — upload ONLY on
1161
- // change (local hash state), so an unchanged skill costs zero network.
1162
- // Follower semantics: files stay canonical. Best-effort, never throws.
1163
- if (provenance === 'skill-injection') {
1164
- const mirrorSkill = (0, turn_provenance_1.provenanceHint)(provenance, turn.userPrompt);
1165
- if (mirrorSkill) {
1166
- await (0, skill_mirror_client_1.maybeMirrorSkill)({
1167
- skill: mirrorSkill, cwd, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey,
1168
- });
1169
- }
1170
- }
1171
1124
  // Repo chunk index (adr/node-identity-port.md): the chunks are DERIVED from a
1172
1125
  // document plus the node graph committed beside it, so they are rebuilt when
1173
1126
  // the document moves rather than when someone remembers to run a push. Same
@@ -1525,7 +1478,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1525
1478
  return;
1526
1479
  // Ensure the machine's desk-line is up (idempotent singleton). One per machine,
1527
1480
  // not per session — it long-polls the cloud and answers "what's truly live?"
1528
- // from local pidfile truth, so the Front Desk never reads a stale registry.
1481
+ // from local pidfile truth, so the live-session list never comes from a stale registry.
1529
1482
  // The lock makes this a no-op on every SessionStart after the first.
1530
1483
  // adr: adr/desk-line-relay.md
1531
1484
  try {
@@ -1602,10 +1555,9 @@ async function recap(input, mode = 'plain', opts = {}) {
1602
1555
  // Enrichment-health signal (fix c2eb8777) — the live Gemini probe verdict.
1603
1556
  // Null when healthy or unreachable; a positive failure fires the outage announce.
1604
1557
  const enrichmentDownPromise = fetchEnrichmentDown(cfg.apiUrl, cfg.apiKey);
1605
- // Skill Learning Loop landing leg (docs/skill-learning-loop.md): pending
1606
- // gains → auto-land references into learnings docs, hold rule/scope
1607
- // proposals for the announce. Best-effort; undefined → announce silent.
1608
- const skillGainsPromise = fetchAndLandSkillGains(cfg.apiUrl, cfg.apiKey, anchor, cwd);
1558
+ // The Skill Learning Loop no longer lands gains into skill folders: git owns
1559
+ // skills and GrepRAG never edits their content (adr/skill-ownership-git.md).
1560
+ const skillGainsPromise = Promise.resolve(undefined);
1609
1561
  // Tier-3 skill activation manifest (docs/load-system.md): fetch canonical
1610
1562
  // full descriptions, then omit skills this harness already advertises
1611
1563
  // natively. Best-effort; the renderer remains pure. The same roster is what
@@ -1613,6 +1565,20 @@ async function recap(input, mode = 'plain', opts = {}) {
1613
1565
  // extra round trip unless one is stale. adr: adr/skill-bundle-shape.md
1614
1566
  const mirroredSkillsPromise = (async () => {
1615
1567
  try {
1568
+ // Git owns skills: ingest what is committed before reading the roster,
1569
+ // so launchers and the manifest see the committed content. Cheap when
1570
+ // nothing changed (one git ls-tree per source).
1571
+ // adr: adr/skill-ownership-git.md
1572
+ try {
1573
+ const sources = (0, skill_git_ingest_1.gitSkillSources)(cwd);
1574
+ const projectId = sources.find(source => source.scope === 'project')?.projectId;
1575
+ const mirrorHashes = await (0, skill_git_ingest_1.fetchMirrorHashes)(cfg.apiUrl, cfg.apiKey, projectId);
1576
+ await (0, skill_git_ingest_1.ingestCommittedSkills)({
1577
+ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey, sources, mirrorHashes,
1578
+ harness: platform, sessionId: input.session_id,
1579
+ });
1580
+ }
1581
+ catch { /* best effort: the next session start retries */ }
1616
1582
  const params = new URLSearchParams({ scope: 'effective', projectId: anchor.projectId });
1617
1583
  const res = await fetch(`${cfg.apiUrl}/v1/skillmirror?${params.toString()}`, {
1618
1584
  headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
@@ -1635,6 +1601,16 @@ async function recap(input, mode = 'plain', opts = {}) {
1635
1601
  return undefined;
1636
1602
  }
1637
1603
  })();
1604
+ // The Codex/OpenCode AGENTS.md files follow ~/.claude/CLAUDE.md, the git
1605
+ // source, and GrepRAG's read-only copy follows its last commit.
1606
+ // adr: adr/skill-ownership-git.md
1607
+ const instructionsPromise = (async () => {
1608
+ try {
1609
+ (0, agents_1.renderGlobalInstructions)();
1610
+ await (0, agents_1.mirrorCommittedInstructions)({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
1611
+ }
1612
+ catch { /* best effort: the next session start retries */ }
1613
+ })();
1638
1614
  // Equipped-loadouts signal (docs/loadout.md §activation model): the recipient's
1639
1615
  // self-inflating skill bundles. SessionStart fetch → the ONE advisor announce
1640
1616
  // line each; ALSO cached locally so the per-turn Match sensor reads it without
@@ -1726,6 +1702,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1726
1702
  updateAvailablePromise,
1727
1703
  bodyPromise,
1728
1704
  ]);
1705
+ await instructionsPromise;
1729
1706
  const equippedLoadouts = loadoutState.decls;
1730
1707
  // Loadout version-gate deficiency (docs/loadout.md §Version gating step 3): a
1731
1708
  // pending loadout whose equip floor is ABOVE the installed CLI version can't be
package/dist/index.js CHANGED
@@ -75,7 +75,6 @@ const agents_1 = require("./commands/agents");
75
75
  const loadout_1 = require("./commands/loadout");
76
76
  const persona_1 = require("./commands/persona");
77
77
  const email_1 = require("./commands/email");
78
- const sms_1 = require("./commands/sms");
79
78
  const codex_1 = require("./commands/codex");
80
79
  const opencode_relay_1 = require("./commands/opencode-relay");
81
80
  const opencode_watch_1 = require("./commands/opencode-watch");
@@ -395,7 +394,7 @@ async function inbox(args) {
395
394
  // --local: the ground-truth view. Skip the cloud entirely and report the
396
395
  // sessions whose watcher process is alive on THIS machine right now (pidfile
397
396
  // ∩ OS liveness). This is what the desk-line returns when the cloud asks the
398
- // machine "what's truly active?" — and the answer the Front Desk should show.
397
+ // machine "what's truly active?".
399
398
  // The cloud `/attached` registry below can list dead sessions for minutes;
400
399
  // this never can. adr: adr/monitor-resilience.md
401
400
  if (subArgs.includes('--local')) {
@@ -635,11 +634,6 @@ async function inbox(args) {
635
634
  // Indent each body line so multi-line markdown stays visually grouped
636
635
  for (const line of msg.body.split('\n'))
637
636
  console.log(` ${line}`);
638
- // Front Desk relay: the operator reached this session through the
639
- // receptionist — answer on the rail they came in on, NOT the bare handle.
640
- if (msg.references?.reply_to) {
641
- console.log(` ↩ reply on the operator's rail: greprag send --to ${msg.references.reply_to} "your reply"`);
642
- }
643
637
  // References block — only printed when present, formatted as a compact section.
644
638
  const refs = msg.references;
645
639
  if (refs && (refs.memory_ids?.length || refs.artifacts?.length || refs.files?.length)) {
@@ -725,16 +719,6 @@ function parseSendAddress(addr) {
725
719
  }
726
720
  return { ok: true, targetKind: 'discord', target: snowflake };
727
721
  }
728
- // sms: prefix — the Front Desk return leg (a connected session replies to the
729
- // operator's phone). Own addressing grammar like discord:; server-side
730
- // /v1/inbox/send resolves the phone against sms_pairings. adr: docs/front-desk-switchboard.md §4
731
- if (addr.startsWith('sms:')) {
732
- const phone = addr.slice('sms:'.length);
733
- if (!/^\+?[0-9]{8,15}$/.test(phone)) {
734
- return { ok: false, error: `sms address "${addr}" must be sms:<E.164 phone, e.g. sms:+16045551234>.` };
735
- }
736
- return { ok: true, targetKind: 'sms', target: phone };
737
- }
738
722
  const segments = addr.split('/');
739
723
  if (segments.length > 2) {
740
724
  return { ok: false, error: `address "${addr}" has too many segments. The v0.12 grammar is\n` +
@@ -843,12 +827,6 @@ function buildReferences(args) {
843
827
  if (inReplyTo) {
844
828
  references = { ...(references ?? {}), in_reply_to: inReplyTo };
845
829
  }
846
- // --approval: flag this front-desk ping as an approval request so the Front
847
- // Desk offers a one-tap APPROVE (relay "yes") + CONNECT. Applies on top of any
848
- // references shape (incl. --ref-json). adr: adr/address-grammar.md
849
- if (args.includes('--approval')) {
850
- references = { ...(references ?? {}), approval: true };
851
- }
852
830
  return references;
853
831
  }
854
832
  /** greprag send "<text>" --to-desk --type <internal-type>
@@ -949,7 +927,7 @@ async function send(args) {
949
927
  // Project broadcasts were removed (2026-06-07). A project-name target has no
950
928
  // scope to send to — reject it. Session/chip traffic belongs on a session id;
951
929
  // first contact goes to the bare-handle front desk.
952
- // adr: adr/address-grammar.md, docs/front-desk-switchboard.md §1
930
+ // adr: adr/address-grammar.md
953
931
  if (parsed.ok && parsed.targetKind === 'project') {
954
932
  console.error(`address "${to}" targets a whole project — project broadcasts were removed.\n` +
955
933
  ` Send to one session: --to <handle>@greprag.com/<session-uuid>\n` +
@@ -978,11 +956,11 @@ async function send(args) {
978
956
  process.exit(1);
979
957
  }
980
958
  // A positional body carrying a LITERAL \n (backslash-n) is sent verbatim and
981
- // renders as raw "\n" on SMS, not a line break (fix e8aa3f83). Warn at the
959
+ // renders as raw "\n", not a line break (fix e8aa3f83). Warn at the
982
960
  // moment so the author switches to --body-file/--stdin (real newlines) instead
983
961
  // of silently shipping a mangled message.
984
962
  if (/\\n/.test(body) && !args.includes('--stdin') && getFlag(args, '--body-file') === undefined) {
985
- console.error('Warning: body contains a literal "\\n" — sent as-is (raw on SMS). For real newlines use --body-file or --stdin.');
963
+ console.error('Warning: body contains a literal "\\n" — sent as-is, not as a line break. For real newlines use --body-file or --stdin.');
986
964
  }
987
965
  const references = buildReferences(args);
988
966
  const payload = { to, body };
@@ -1002,12 +980,6 @@ async function send(args) {
1002
980
  console.log(`Sent to discord:${delivered.snowflake}`);
1003
981
  return;
1004
982
  }
1005
- // SMS destinations (Front Desk return leg) also short-circuit the inbox table
1006
- // — delivered to the operator's phone via Twilio, no row/id/retract.
1007
- if (delivered?.kind === 'sms' && delivered.phone) {
1008
- console.log(`Sent to sms:${delivered.phone}`);
1009
- return;
1010
- }
1011
983
  // Render the delivered-to in the v0.11 single-segment grammar.
1012
984
  // adr: adr/numeric-handles.md, adr/address-grammar.md
1013
985
  const base = delivered.handle || `${delivered.tenant}@greprag.com`;
@@ -1050,7 +1022,7 @@ async function send(args) {
1050
1022
  if (retractCode)
1051
1023
  console.log(`Retract: greprag retract ${retractCode}`);
1052
1024
  }
1053
- /** greprag discord <pair|unpair|me> — Discord DM bridge to inbox. */
1025
+ /** greprag discord <pair|unpair|me|typing> — pair Discord so sessions can DM you. */
1054
1026
  async function discord(args) {
1055
1027
  const sub = args[0];
1056
1028
  if (!sub || sub === 'help' || sub === '--help') {
@@ -1102,112 +1074,6 @@ async function discord(args) {
1102
1074
  console.log(`Project: ${result.project_name}`);
1103
1075
  console.log(`Paired at: ${result.paired_at}`);
1104
1076
  console.log(`Last inbound DM: ${result.last_seen_inbound_at ?? '(never)'}`);
1105
- if (result.handoff) {
1106
- console.log(`Handoff (active):`);
1107
- console.log(` Project: ${result.handoff.project_name}`);
1108
- console.log(` Session: ${result.handoff.session_id}`);
1109
- console.log(` Expires: ${result.handoff.expires_at}`);
1110
- }
1111
- else {
1112
- console.log(`Handoff: (none — DMs route to default project)`);
1113
- }
1114
- return;
1115
- }
1116
- if (sub === 'handoff') {
1117
- // Resolve project: --project <name> overrides anchor's project_id.
1118
- const projectFlag = getFlag(args, '--project');
1119
- let projectId;
1120
- let projectNameForWatch;
1121
- if (projectFlag) {
1122
- projectNameForWatch = projectFlag;
1123
- const res = await fetch(`${cfg.apiUrl}/v1/inbox/projects`, {
1124
- headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
1125
- });
1126
- const json = (await res.json());
1127
- const hit = json.projects?.find(p => p.project_name.toLowerCase() === projectFlag.toLowerCase());
1128
- if (!hit) {
1129
- console.error(`Error: project "${projectFlag}" not found under this tenant`);
1130
- process.exit(1);
1131
- }
1132
- projectId = hit.project_id;
1133
- }
1134
- else {
1135
- const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
1136
- if (!anchor.projectId) {
1137
- console.error('No project anchor in current directory. Run: greprag init, or pass --project <name>.');
1138
- process.exit(1);
1139
- }
1140
- projectId = anchor.projectId;
1141
- projectNameForWatch = anchor.projectName;
1142
- }
1143
- // Resolve session id: --session <id> overrides CLAUDE_SESSION_ID env.
1144
- const sessionFlag = getFlag(args, '--session');
1145
- const rawSession = sessionFlag || (0, session_id_1.readSessionEnv)() || '';
1146
- const sessionId = (0, session_id_1.truncateSessionId)(rawSession);
1147
- if (!sessionId) {
1148
- console.error('No session id available. Pass --session <8-hex>, or run inside a Claude Code session\n' +
1149
- '(Claude Code sets CLAUDE_CODE_SESSION_ID).');
1150
- process.exit(1);
1151
- }
1152
- // Optional TTL.
1153
- const ttlFlag = getFlag(args, '--ttl');
1154
- const ttlMinutes = ttlFlag ? parseInt(ttlFlag, 10) : undefined;
1155
- if (ttlFlag && (!Number.isFinite(ttlMinutes) || ttlMinutes <= 0)) {
1156
- console.error(`Error: --ttl must be a positive integer (got "${ttlFlag}")`);
1157
- process.exit(1);
1158
- }
1159
- const result = await (0, discord_1.callDiscordHandoff)({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey }, { projectId: projectId, sessionId, ttlMinutes });
1160
- if (!result.ok) {
1161
- console.error(`Error: ${result.error}`);
1162
- process.exit(1);
1163
- }
1164
- // --no-watch skips the inline watcher (for scripted "just pin" usage).
1165
- if (args.includes('--no-watch')) {
1166
- console.log(`Pinned ${result.project_name} / ${result.session_id} for ${result.ttl_minutes} min.`);
1167
- console.log(`Expires: ${result.expires_at}`);
1168
- console.log(`Bot DM'd you a confirmation. Reply on Discord to route to this session.`);
1169
- console.log(`(skipped inbox watcher — pass without --no-watch to stream DMs to stdout.)`);
1170
- return;
1171
- }
1172
- // --orchestrator: tenant-wide watcher (sees inbox traffic to/from any
1173
- // project under this tenant). The pin still routes the user's DMs to
1174
- // (this project, this session) — only the watcher's scope changes.
1175
- // Used by an orchestrator session that wants portfolio-wide visibility
1176
- // while keeping its own DM conversation anchored to itself.
1177
- const orchestrator = args.includes('--orchestrator');
1178
- // Inline watcher — the whole point of handoff. Pin the route AND listen
1179
- // for inbox events on the same SSE stream. When invoked under Monitor,
1180
- // each printed line becomes one notification, so the agent sees every
1181
- // event the moment it lands. Process blocks until killed.
1182
- console.error(`[handoff] Pinned ${result.project_name} / ${result.session_id} for ${result.ttl_minutes} min.`);
1183
- console.error(`[handoff] Bot DM'd you a confirmation. Reply on Discord — replies stream here.`);
1184
- if (orchestrator) {
1185
- console.error(`[handoff] Orchestrator mode — watcher is tenant-wide. You will see inbox events from any project under this tenant.`);
1186
- }
1187
- else {
1188
- console.error(`[handoff] Listening for DMs to session ${result.session_id} on project ${result.project_name}...`);
1189
- }
1190
- await (0, inbox_watch_1.runInboxWatch)({
1191
- // Orchestrator mode: tenant-wide watcher (no project/session filter).
1192
- // Otherwise: filter to this project + session.
1193
- project: orchestrator ? undefined : projectNameForWatch,
1194
- session: orchestrator ? undefined : sessionId,
1195
- json: args.includes('--json'),
1196
- // Direct mode (no supervisor): handoff PINS the route before watching,
1197
- // so a supervised respawn would re-invoke `handoff` and re-pin on every
1198
- // child restart. Supervision is scoped to `inbox watch` only; keep
1199
- // handoff's prior in-process behavior. adr: adr/monitor-resilience.md
1200
- noSupervise: true,
1201
- });
1202
- return;
1203
- }
1204
- if (sub === 'unhandoff') {
1205
- const result = await (0, discord_1.callDiscordUnhandoff)({ apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
1206
- if (!result.ok) {
1207
- console.error(`Error: ${result.error}`);
1208
- process.exit(1);
1209
- }
1210
- console.log('Handoff cleared. DMs revert to default project routing.');
1211
1077
  return;
1212
1078
  }
1213
1079
  if (sub === 'typing') {
@@ -1328,8 +1194,8 @@ Commands:
1328
1194
  agents <adopt|optimize|sync|status>
1329
1195
  Canonical global/repo CLAUDE.md + AGENTS.md doctrine.
1330
1196
  doctor [--inspect] [--yes] Diagnose + repair orphan project_ids and identity drift
1331
- desk [--json] Inspect the Agent Front Desk and live routing state.
1332
- sms <command> Configure or inspect SMS front-desk routing.
1197
+ desk <run|ensure|status> The desk line: this machine answers which sessions
1198
+ are live when the cloud asks.
1333
1199
  smell "<text>" Shortcut for \`greprag fix log "<text>"\`.
1334
1200
  context status [--session <id>] Context-size governor readout: live tokens, threshold,
1335
1201
  % consumed, turns since the crossover advisory last fired.
@@ -1414,10 +1280,8 @@ Inbox (email-style messaging across tenants):
1414
1280
  retract <code> Pull back a previously-sent message (code is printed on send).
1415
1281
  discord pair Pair Discord DMs to this project — generates a code to DM the bot.
1416
1282
  discord unpair Remove the Discord pairing.
1417
- discord me Show current Discord pairing + handoff state.
1418
- discord handoff [--ttl N] Pin DMs to this session for N min (default 60); bot DMs a confirmation.
1419
- discord unhandoff Clear the handoff pin (DMs revert to default project).
1420
- discord typing Refresh the "typing…" indicator on the paired DM thread.
1283
+ discord me Show current Discord pairing state.
1284
+ discord typing Refresh the "typing…" indicator on the paired DM thread.
1421
1285
  send "body" --to discord:<snowflake>
1422
1286
  Outbound DM to a paired Discord user from any session.
1423
1287
  Unread → hard delete. Read → body replaced with a retracted notice.
@@ -1576,6 +1440,11 @@ Skills (opt-in advisor skills bundled with the CLI):
1576
1440
  skill uninstall <name> Remove a previously-installed advisor
1577
1441
  skill source stamp <name> --project <p> --path <rel> [--path <rel>...] [--baseline <ref>]
1578
1442
  Stamp a skill-staleness baseline against local Git
1443
+ skill history <name> Every revision of a mirrored skill: date, author, reason, changes
1444
+ skill diff <name> <revA> [<revB>]
1445
+ Unified diff between two revisions (revB defaults to current)
1446
+ skill revert <name> --to <rev> --reason "<why>" --expected-hash <current-hash>
1447
+ Make an old revision current again, as a new revision
1579
1448
 
1580
1449
  Load (on-demand doctrine — the CLI is the payload, no .md files):
1581
1450
  load Browse the catalog (chip-spawn, codex chips, mechanic, …)
@@ -1603,11 +1472,11 @@ Persona (tenant-scoped speaking instructions):
1603
1472
  persona show Show the active Persona.
1604
1473
  persona clear Remove the active Persona.
1605
1474
 
1606
- GrepRAG Instructions (canonical global/repo CLAUDE.md + AGENTS.md):
1607
- instructions pull <file> [--scope global|repo] [--push]
1608
- Pull disk instructions into canon.
1609
- instructions push [--scope global|repo|all] [--check] [--force]
1610
- Render canon back to managed instruction files.
1475
+ Instructions (~/.claude/CLAUDE.md in the dotclaude git repo is the source):
1476
+ instructions render [--check] Write ~/.codex/AGENTS.md and
1477
+ ~/.config/opencode/AGENTS.md from the git CLAUDE.md
1478
+ plus ~/.claude/instructions/<harness>.md.
1479
+ instructions status Show whether each rendered file is current.
1611
1480
  agents ... Deprecated alias for instructions.
1612
1481
 
1613
1482
  Project metadata (rename / merge / list):
@@ -1663,7 +1532,6 @@ const HELP_GROUPS = [
1663
1532
  ['fix', fix_1.runFix],
1664
1533
  ['mechanic', mechanic_1.runMechanic],
1665
1534
  ['discord', discord],
1666
- ['sms', sms_1.runSms],
1667
1535
  ['codex', codex_1.runCodex],
1668
1536
  ['identity', identity_1.runIdentity],
1669
1537
  ['project', project_1.runProject],
@@ -1974,7 +1842,6 @@ async function main() {
1974
1842
  case 'send': return send(subArgs);
1975
1843
  case 'retract': return retract(subArgs);
1976
1844
  case 'discord': return discord(subArgs);
1977
- case 'sms': return (0, sms_1.runSms)(subArgs);
1978
1845
  case 'codex': return (0, codex_1.runCodex)(subArgs);
1979
1846
  case 'expand': return expand(subArgs);
1980
1847
  case 'search': return (0, search_1.runSearch)(subArgs); // one lexical search across everything (adr/searchable-store-kinds.md)
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
- /** Keep installed native skill adapters at canon.
2
+ /** Keep installed Codex/OpenCode launchers at canon.
3
3
  *
4
- * Adapters were written only by explicit syncs (init, `skill mirror sync`,
5
- * `--only`), so a canon change made anywhere — `skill mirror apply`, another
6
- * session, another machine — left them behind until someone re-synced by hand.
4
+ * Launchers were written only by explicit syncs (init, `skill mirror sync`),
5
+ * so a canon change ingested anywhere — another session, another machine —
6
+ * left them behind until someone re-synced by hand.
7
7
  *
8
8
  * SessionStart calls this with the mirror roster it already fetched. Detection
9
9
  * is local: each managed adapter's stamps against the roster. Only a stale
@@ -16,7 +16,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.readAdapterStamps = readAdapterStamps;
17
17
  exports.staleNativeAdapters = staleNativeAdapters;
18
18
  exports.refreshNativeAdapters = refreshNativeAdapters;
19
- const claude_lifecycle_cutover_1 = require("./claude-lifecycle-cutover");
20
19
  const native_skill_mirror_1 = require("./native-skill-mirror");
21
20
  const skill_mirror_files_1 = require("./skill-mirror-files");
22
21
  /** The identity stamps a generated adapter carries in its frontmatter. */
@@ -37,8 +36,7 @@ function readAdapterStamps(content) {
37
36
  function staleNativeAdapters(params) {
38
37
  const globalRoot = (0, native_skill_mirror_1.nativeSkillRoot)(params.platform, params.homeDir);
39
38
  const roots = [globalRoot];
40
- // Claude adapters live only in the home root (claude-lifecycle-cutover.ts).
41
- const projectRoot = params.projectDir && params.platform !== 'claude-code'
39
+ const projectRoot = params.projectDir
42
40
  ? (0, native_skill_mirror_1.writerOwnedProjectSkillRoot)(params.platform, params.projectDir, params.homeDir)
43
41
  : null;
44
42
  if (projectRoot)
@@ -80,10 +78,11 @@ function staleNativeAdapters(params) {
80
78
  }
81
79
  /** Rewrite each stale managed adapter of this harness from canon, through the
82
80
  * writer the sync commands use. One adapter per write, so a diverged one holds
83
- * back no other. A harness without native adapters is a no-op. */
81
+ * back no other. A harness without native adapters is a no-op, Claude Code
82
+ * included: it reads skills from their git folders (adr/skill-ownership-git.md). */
84
83
  async function refreshNativeAdapters(params) {
85
84
  const platform = params.platform;
86
- if (platform !== 'claude-code' && platform !== 'codex' && platform !== 'opencode') {
85
+ if (platform !== 'codex' && platform !== 'opencode') {
87
86
  return { refreshed: 0, blocked: 0 };
88
87
  }
89
88
  const stale = staleNativeAdapters({ ...params, platform });
@@ -94,15 +93,13 @@ async function refreshNativeAdapters(params) {
94
93
  if (!skill)
95
94
  continue; // canon unreachable: the next session start retries
96
95
  try {
97
- const result = platform === 'claude-code'
98
- ? (0, claude_lifecycle_cutover_1.applyClaudeLifecycleCutover)({ skills: [skill], names: [skill.skillName], homeDir: params.homeDir })
99
- : (0, native_skill_mirror_1.applyNativeSkillMirror)({
100
- platform,
101
- skills: [skill],
102
- homeDir: params.homeDir,
103
- projectDir: params.projectDir,
104
- prune: false,
105
- });
96
+ const result = (0, native_skill_mirror_1.applyNativeSkillMirror)({
97
+ platform,
98
+ skills: [skill],
99
+ homeDir: params.homeDir,
100
+ projectDir: params.projectDir,
101
+ prune: false,
102
+ });
106
103
  refreshed += result.updated;
107
104
  blocked += result.blocked;
108
105
  }
@@ -321,127 +321,49 @@ function canonicalContentHash(skill) {
321
321
  function canonicalLookupArgs(skill) {
322
322
  return `--skill-id ${canonicalSkillId(skill)}`;
323
323
  }
324
- function canonicalWriteArgs(skill) {
325
- if (skill.scope !== 'project' || !skill.projectId)
326
- return '';
327
- const replaces = (skill.replaces || []).map(id => ` --replaces ${id}`).join('');
328
- return `--scope project --project-id ${skill.projectId}${replaces}`;
329
- }
330
- function canonicalEditArgs(skill) {
331
- return [canonicalLookupArgs(skill), canonicalWriteArgs(skill)].filter(Boolean).join(' ');
332
- }
333
- function nativeCreationLocations(platform) {
334
- if (platform === 'codex') {
335
- return '`~/.codex/skills/<new-name>/SKILL.md` (global) or `<repo>/.agents/skills/<new-name>/SKILL.md` (project)';
336
- }
337
- if (platform === 'opencode') {
338
- return '`~/.config/opencode/skills/<new-name>/SKILL.md` (global) or `<repo>/.opencode/skills/<new-name>/SKILL.md` (project)';
339
- }
340
- return '`~/.claude/skills/<new-name>/SKILL.md` (global) or `<repo>/.claude/skills/<new-name>/SKILL.md` (project)';
341
- }
342
- function renderChangeSkillLine(params) {
343
- const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
344
- const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
345
- if (params.platform === 'codex') {
346
- return `- **Change this skill:** run \`greprag load skill-change\`, then \`${exportCommand}\`; patch the exported Markdown files non-interactively; run \`${applyCommand}\`; then \`${params.syncCommand}\`; then verify with \`${params.loadCommand}\`.`;
347
- }
348
- return `- **Change this skill:** run \`greprag load skill-change\`, then \`greprag skill edit ${params.skillName} ${params.editArgs}\`, then \`${params.syncCommand}\`, then verify with \`${params.loadCommand}\`.`;
349
- }
350
- function renderEditingSection(platform, params) {
351
- const exportCommand = `greprag skill mirror export ${params.skillName} ${params.identityArg} --to <dir>`;
352
- const applyCommand = `greprag skill mirror apply <dir> --skill ${params.skillName} ${params.editArgs} --expected-hash <exported-hash>`;
353
- if (platform === 'codex') {
354
- return `## Editing
355
-
356
- Codex agents must use the non-interactive mirror path:
357
-
358
- \`\`\`bash
359
- ${exportCommand}
360
- # patch the exported Markdown files with normal file edits
361
- ${applyCommand}
362
- \`\`\`
363
-
364
- Do not launch an interactive editor from Codex. If using \`greprag skill edit\` as an export helper, include \`--no-open\` and run the printed apply command after patching.
365
- `;
366
- }
367
- return `## Editing
368
-
369
- To edit this skill, run the guarded editor flow:
370
-
371
- \`\`\`bash
372
- greprag skill edit ${params.skillName} ${params.editArgs}
373
- \`\`\`
374
-
375
- Manual path:
376
-
377
- \`\`\`bash
378
- ${exportCommand}
379
- ${applyCommand}
380
- \`\`\`
381
- `;
324
+ /** Where the skill's one true copy lives: git. adr: adr/skill-ownership-git.md */
325
+ function gitSkillFolder(skill) {
326
+ return skill.scope === 'project'
327
+ ? `\`<repo>/.claude/skills/${skill.skillName}/\`${skill.projectName ? ` in the ${skill.projectName} repo` : ''}`
328
+ : `\`~/.claude/skills/${skill.skillName}/\` in the dotclaude repo`;
382
329
  }
383
330
  function renderAdapterBody(platform, skill) {
384
331
  const skillName = skill.skillName;
385
332
  const skillId = canonicalSkillId(skill);
386
333
  const identityArg = canonicalLookupArgs(skill);
387
- const editArgs = canonicalEditArgs(skill);
388
- const creationLocations = nativeCreationLocations(platform);
389
334
  const syncCommand = (0, skill_activation_manifest_1.nativeAdapterInstallCommand)(platform, [skillName]);
390
- const creationLine = platform === 'claude-code'
391
- ? '- **Create another skill:** use the normal native creation path and leave it unmanaged until a separate Claude cutover is approved.'
392
- : `- **Create another skill:** create a normal native skill at ${creationLocations}; run \`${syncCommand} --dry-run\` and confirm it says \`adopt\`; then run \`${syncCommand}\`. GrepRAG adopts it as canon and replaces the native package with an adapter.`;
393
335
  const companions = skill.files.map(file => file.path).filter(relative => relative !== 'SKILL.md').sort();
394
336
  const harnessArg = `--harness ${platform}`;
395
337
  const loadCommand = `greprag load ${skillName} ${identityArg} ${harnessArg}`;
396
- const changeSkillLine = renderChangeSkillLine({
397
- platform,
398
- skillName,
399
- editArgs,
400
- identityArg,
401
- syncCommand,
402
- loadCommand,
403
- });
404
- const editingSection = renderEditingSection(platform, { skillName, editArgs, identityArg });
405
338
  const companionLines = companions.length
406
339
  ? companions.map(relative => `- \`greprag load ${skillName} ${relative} ${identityArg} ${harnessArg}\``).join('\n')
407
- : '- No companion docs are mirrored into this native adapter.';
340
+ : '- No companion docs are mirrored into this launcher.';
408
341
  return `# ${skillName}
409
342
 
410
343
  ## GENERATED ADAPTER — NEVER EDIT
411
344
 
412
- This is a disposable GrepRAG launcher for ${platform}, not canonical skill content. Never edit or copy this adapter, and never use \`greprag skill install\` to create or change a mirrored skill.
413
-
414
- Do not add or maintain project-state/doc pointers here. Current project state belongs in repo docs and the GrepRAG doc mirror; \`greprag load ${skillName} ${identityArg} ${harnessArg}\` discovers relevant state docs dynamically.
345
+ This is a disposable GrepRAG launcher for ${platform}, not skill content. The skill lives in git at ${gitSkillFolder(skill)}; GrepRAG mirrors the committed files read-only and this launcher loads them.
415
346
 
416
347
  Required action chains:
417
348
 
418
349
  - **Use this skill:** run \`${loadCommand}\` before acting.
419
- ${changeSkillLine}
420
- ${creationLine}
350
+ - **Change this skill:** edit ${gitSkillFolder(skill)} with normal file edits, commit, then run \`${syncCommand}\` (session start does it too) and verify with \`${loadCommand}\`. Uncommitted edits are never served. History and rollback are git log / git revert on that folder.
351
+ - **Create another skill:** create \`~/.claude/skills/<new-name>/SKILL.md\` (dotclaude repo, global) or \`<repo>/.claude/skills/<new-name>/SKILL.md\` (project), commit, then run \`${syncCommand}\`.
421
352
 
422
353
  Authority: ${skillId}
423
354
  Source binding: ${sourceBindingState(skill)}
424
355
  Canonical content hash: ${canonicalContentHash(skill)}
425
356
  ${typeof skill.revision === 'number' ? `Mirror revision: ${skill.revision}` : 'Mirror revision: unknown'}
426
357
 
427
- ${editingSection}
428
-
429
- Snapshot rollback:
430
-
431
- \`\`\`bash
432
- greprag skill mirror snapshot list ${skillName} ${identityArg}
433
- greprag skill mirror snapshot promote ${skillName} --name <snapshot> ${editArgs} --expected-hash <current-hash>
434
- \`\`\`
435
-
436
358
  ## Mandatory Invocation
437
359
 
438
360
  When this skill activates, run:
439
361
 
440
362
  \`\`\`bash
441
- greprag load ${skillName} ${identityArg} ${harnessArg}
363
+ ${loadCommand}
442
364
  \`\`\`
443
365
 
444
- Read and follow that canonical payload before responding or taking action.
366
+ Read and follow that payload before responding or taking action.
445
367
 
446
368
  ## Companion Docs
447
369