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.
- package/dist/commands/agent-doctrine-file.js +5 -128
- package/dist/commands/agents.js +125 -846
- package/dist/commands/app-model.js +1 -1
- package/dist/commands/delivery-reminder.js +1 -1
- package/dist/commands/discord.js +12 -61
- package/dist/commands/inbox-watch.js +0 -13
- package/dist/commands/init.js +17 -29
- package/dist/commands/load.js +15 -35
- package/dist/commands/parity.js +1 -1
- package/dist/commands/reminder-registry.js +1 -8
- package/dist/commands/session.js +2 -2
- package/dist/commands/skill-mirror-reminder.js +1 -19
- package/dist/commands/skill.js +236 -448
- package/dist/hook.js +31 -54
- package/dist/index.js +19 -152
- package/dist/native-adapter-refresh.js +15 -18
- package/dist/native-skill-mirror.js +11 -89
- package/dist/opencode-plugin.bundle.js +82 -282
- package/dist/reminder-injector.js +1 -1
- package/dist/skill-activation-manifest.js +14 -37
- package/dist/skill-git-ingest.js +297 -0
- package/dist/skill-landing.js +7 -173
- package/dist/skill-mirror-client.js +4 -251
- package/dist/skill-staleness.js +3 -6
- package/package.json +1 -1
- package/skill/commander/SKILL.md +10 -12
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/discord-handoff.md +6 -30
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/chip-spawn.md +6 -2
- package/skill/templates/delivery.md +16 -0
- package/skill/templates/os.md +1 -1
- package/skill/templates/prompt-audit.md +26 -49
- package/skill/templates/skill-change.md +15 -11
- 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
|
|
145
|
-
const
|
|
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
|
|
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
|
|
1606
|
-
//
|
|
1607
|
-
|
|
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?"
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
1332
|
-
|
|
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
|
|
1418
|
-
discord
|
|
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
|
-
|
|
1607
|
-
instructions
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
|
2
|
+
/** Keep installed Codex/OpenCode launchers at canon.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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 !== '
|
|
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 =
|
|
98
|
-
|
|
99
|
-
:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
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
|
|
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
|
-
${
|
|
420
|
-
|
|
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
|
-
|
|
363
|
+
${loadCommand}
|
|
442
364
|
\`\`\`
|
|
443
365
|
|
|
444
|
-
Read and follow that
|
|
366
|
+
Read and follow that payload before responding or taking action.
|
|
445
367
|
|
|
446
368
|
## Companion Docs
|
|
447
369
|
|