greprag 5.49.15 → 5.50.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 (57) hide show
  1. package/README.md +4 -1
  2. package/dist/commands/codex-delivery.d.ts +2 -0
  3. package/dist/commands/codex-delivery.js +9 -16
  4. package/dist/commands/codex-delivery.js.map +1 -1
  5. package/dist/commands/codex-doctor.js +2 -1
  6. package/dist/commands/codex-doctor.js.map +1 -1
  7. package/dist/commands/codex-wake-test.js +2 -1
  8. package/dist/commands/codex-wake-test.js.map +1 -1
  9. package/dist/commands/codex.js +6 -3
  10. package/dist/commands/codex.js.map +1 -1
  11. package/dist/commands/doc-pointer-reminder.d.ts +18 -0
  12. package/dist/commands/doc-pointer-reminder.js +41 -0
  13. package/dist/commands/doc-pointer-reminder.js.map +1 -0
  14. package/dist/commands/enrichment-health-reminder.d.ts +23 -0
  15. package/dist/commands/enrichment-health-reminder.js +39 -0
  16. package/dist/commands/enrichment-health-reminder.js.map +1 -0
  17. package/dist/commands/inbox-primer-reminder.js +2 -2
  18. package/dist/commands/inbox-primer-reminder.js.map +1 -1
  19. package/dist/commands/init.js +20 -0
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/load.js +8 -0
  22. package/dist/commands/load.js.map +1 -1
  23. package/dist/commands/memory.js +40 -12
  24. package/dist/commands/memory.js.map +1 -1
  25. package/dist/commands/opencode-interrupt.d.ts +5 -0
  26. package/dist/commands/opencode-interrupt.js +8 -2
  27. package/dist/commands/opencode-interrupt.js.map +1 -1
  28. package/dist/commands/opencode-relay.js +1 -1
  29. package/dist/commands/opencode-relay.js.map +1 -1
  30. package/dist/commands/procedure.d.ts +15 -0
  31. package/dist/commands/procedure.js +167 -0
  32. package/dist/commands/procedure.js.map +1 -0
  33. package/dist/commands/reminder-registry.js +6 -0
  34. package/dist/commands/reminder-registry.js.map +1 -1
  35. package/dist/commands/reminder-types.d.ts +27 -0
  36. package/dist/commands/skill-gain-reminder.d.ts +17 -0
  37. package/dist/commands/skill-gain-reminder.js +32 -0
  38. package/dist/commands/skill-gain-reminder.js.map +1 -0
  39. package/dist/commands/skillgain.d.ts +12 -0
  40. package/dist/commands/skillgain.js +109 -0
  41. package/dist/commands/skillgain.js.map +1 -0
  42. package/dist/hook.js +204 -2
  43. package/dist/hook.js.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/opencode-plugin.bundle.js +70 -3
  47. package/dist/procedure.d.ts +88 -0
  48. package/dist/procedure.js +269 -0
  49. package/dist/procedure.js.map +1 -0
  50. package/dist/skill-landing.d.ts +88 -0
  51. package/dist/skill-landing.js +220 -0
  52. package/dist/skill-landing.js.map +1 -0
  53. package/package.json +1 -1
  54. package/skill/commander/SKILL.md +2 -2
  55. package/skill/greprag/SKILL.md +2 -2
  56. package/skill/greprag/docs/inbox-watch.md +5 -5
  57. package/skill/templates/chip-leader-opencode.md +98 -0
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /** Procedure System — pure core + local store (docs/procedure-system.md).
3
+ *
4
+ * A *procedure* is the repo-specific recipe behind an operational intent
5
+ * (deploy / push / release / commit / merge). The system's job: when the user
6
+ * asks for one, inject the known recipe + its gate BEFORE the agent moves, so
7
+ * the operational verb runs cleanly instead of by trial-and-error.
8
+ *
9
+ * This module is the PROOF slice — Tier 1 (deterministic keyword trigger) +
10
+ * REPLAY (inject a seeded/learned recipe). It is intentionally local-file
11
+ * backed, mirroring guard.ts's matchset cache: the UserPromptSubmit hook reads
12
+ * it on the hot path, so there is NO network here (server persistence + the
13
+ * LEARN/Stop-hook capture leg land later — docs/procedure-system.md phases).
14
+ *
15
+ * Pure (matchProcedure / buildProcedureInjection / GIT_SEED_SET) is split from
16
+ * the I/O (read/writeProcedureStore) so the matcher + injection text are
17
+ * testable without touching disk — the coordinate-gate.ts shape.
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.GIT_SEED_SET = exports.PROCEDURE_STORE_VERSION = void 0;
54
+ exports.procedureStorePath = procedureStorePath;
55
+ exports.readProcedureStore = readProcedureStore;
56
+ exports.writeProcedureStore = writeProcedureStore;
57
+ exports.upsertProcedure = upsertProcedure;
58
+ exports.removeProcedure = removeProcedure;
59
+ exports.intentSuppressed = intentSuppressed;
60
+ exports.matchProcedure = matchProcedure;
61
+ exports.buildProcedureInjection = buildProcedureInjection;
62
+ const path = __importStar(require("path"));
63
+ const fs = __importStar(require("fs"));
64
+ exports.PROCEDURE_STORE_VERSION = '1';
65
+ // ---------- Store I/O (local, hot-path) --------------------------------------
66
+ function stateDir() {
67
+ const home = process.env.HOME || process.env.USERPROFILE || '';
68
+ return path.join(home, '.greprag', 'state');
69
+ }
70
+ /** Beside the matchset cache (guard.ts), keyed by project_id so it survives
71
+ * renames/worktrees and stays per-project. */
72
+ function procedureStorePath(projectId) {
73
+ return path.join(stateDir(), `procedures-${projectId}.json`);
74
+ }
75
+ /** Read the project's procedure store. Returns an empty store on any miss —
76
+ * fail-open, so a read error never blocks the hot path. */
77
+ function readProcedureStore(projectId) {
78
+ try {
79
+ const raw = fs.readFileSync(procedureStorePath(projectId), 'utf-8');
80
+ const parsed = JSON.parse(raw);
81
+ if (parsed && Array.isArray(parsed.procedures)) {
82
+ return { version: parsed.version || exports.PROCEDURE_STORE_VERSION, procedures: parsed.procedures };
83
+ }
84
+ }
85
+ catch { /* missing / unreadable → empty */ }
86
+ return { version: exports.PROCEDURE_STORE_VERSION, procedures: [] };
87
+ }
88
+ function writeProcedureStore(projectId, store) {
89
+ const file = procedureStorePath(projectId);
90
+ const dir = path.dirname(file);
91
+ if (!fs.existsSync(dir))
92
+ fs.mkdirSync(dir, { recursive: true });
93
+ fs.writeFileSync(file, JSON.stringify(store, null, 2) + '\n');
94
+ }
95
+ /** Insert-or-replace by verb. Destructive existing procedures are NOT silently
96
+ * downgraded: a learned/auto write can't overwrite a destructive=true seed
97
+ * (callers pass `force` for an explicit operator re-seed). Returns the store. */
98
+ function upsertProcedure(store, proc, force = false) {
99
+ const existing = store.procedures.find(p => p.verb === proc.verb);
100
+ if (existing && existing.destructive && !force && proc.status !== 'seeded') {
101
+ return store; // protect the gate — never auto-overwrite a destructive seed
102
+ }
103
+ const next = store.procedures.filter(p => p.verb !== proc.verb);
104
+ next.push({ ...proc, updatedAt: proc.updatedAt || new Date().toISOString() });
105
+ return { version: store.version || exports.PROCEDURE_STORE_VERSION, procedures: next };
106
+ }
107
+ function removeProcedure(store, verb) {
108
+ return {
109
+ version: store.version || exports.PROCEDURE_STORE_VERSION,
110
+ procedures: store.procedures.filter(p => p.verb !== verb),
111
+ };
112
+ }
113
+ // ---------- Matching (PURE) --------------------------------------------------
114
+ /** Does a trigger phrase occur in the (lowercased) prompt? Single tokens match
115
+ * whole-word (`deploy` ≠ `redeployment`); multi-word phrases match as a
116
+ * substring (`ship it`). */
117
+ function triggerHits(promptLower, triggerLower) {
118
+ if (!triggerLower)
119
+ return false;
120
+ if (/\s/.test(triggerLower))
121
+ return promptLower.includes(triggerLower);
122
+ const re = new RegExp(`(^|[^a-z0-9])${escapeRegex(triggerLower)}([^a-z0-9]|$)`);
123
+ return re.test(promptLower);
124
+ }
125
+ function escapeRegex(s) {
126
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
127
+ }
128
+ // ---------- Intent guard (PURE) ----------------------------------------------
129
+ /** Tier-1 intent guard — cheap, deterministic suppression of keyword
130
+ * false-fires. A substring match injects on a *mention*, not a *request*
131
+ * (docs/procedure-system.md): bare "deploy" fires on "don't deploy yet" and
132
+ * "push" on "push back the deadline". For safe verbs that's noise; for a
133
+ * destructive verb a false ⛔ gate is actively misleading. Suppresses two
134
+ * classes — this is the deterministic stopgap; Tier-2's prompt-judge replaces
135
+ * it with real intent recognition. */
136
+ const INTENT_NEGATIONS = [
137
+ "don't", 'dont', 'not', 'no', 'never', 'without', 'avoid', "won't", 'wont',
138
+ "didn't", 'didnt', 'cannot', "can't", 'cant', 'stop',
139
+ ];
140
+ /** The trigger token living inside a common NON-command phrase. */
141
+ const INTENT_COLLISION_PHRASES = [
142
+ 'push back', 'push for', 'push notification', 'push notifications', 'pushover',
143
+ 'merge conflict', 'merge conflicts', 'merge request', 'merge sort',
144
+ 'press release', 'release notes', 'release candidate', 'release date',
145
+ 'commit message', 'commit hash', 'commit history', 'commit sha',
146
+ ];
147
+ /** True if a raw trigger hit should be SUPPRESSED — it reads as a mention, not
148
+ * a request. PURE. (1) the trigger only appears inside a collision phrase;
149
+ * (2) a negation sits within 3 tokens before the trigger. */
150
+ function intentSuppressed(promptLower, triggerLower) {
151
+ // (1) collision phrase — suppress only if the trigger has NO other occurrence
152
+ // outside the collision phrase (a real command alongside the mention wins).
153
+ for (const phrase of INTENT_COLLISION_PHRASES) {
154
+ if (phrase.includes(triggerLower) && promptLower.includes(phrase)) {
155
+ const stripped = promptLower.split(phrase).join(' · ');
156
+ if (!triggerHits(stripped, triggerLower))
157
+ return true;
158
+ }
159
+ }
160
+ // (2) negation within 3 tokens before the trigger ("don't deploy", "no merge").
161
+ const idx = promptLower.search(new RegExp(`(^|[^a-z0-9])${escapeRegex(triggerLower)}([^a-z0-9]|$)`));
162
+ if (idx >= 0) {
163
+ const before = promptLower.slice(0, idx).split(/[^a-z']+/).filter(Boolean);
164
+ const window = ' ' + before.slice(-3).join(' ') + ' ';
165
+ if (INTENT_NEGATIONS.some(neg => window.includes(' ' + neg + ' ')))
166
+ return true;
167
+ }
168
+ return false;
169
+ }
170
+ /** Match a user prompt against the store. Returns the procedure whose
171
+ * LONGEST trigger phrase hits (most-specific wins — `git push` beats `push`,
172
+ * `ship a release` beats `ship`), or null. PURE — no I/O. The Tier-1 intent
173
+ * guard filters mention-not-request hits unless `ignoreIntentGuard` is set
174
+ * (the `match` dry-run uses it to explain a suppression). */
175
+ function matchProcedure(prompt, store, opts = {}) {
176
+ if (!prompt)
177
+ return null;
178
+ const promptLower = prompt.toLowerCase();
179
+ let best = null;
180
+ let bestLen = -1;
181
+ for (const procedure of store.procedures) {
182
+ for (const trigger of procedure.triggers) {
183
+ const t = trigger.toLowerCase();
184
+ if (!triggerHits(promptLower, t))
185
+ continue;
186
+ if (!opts.ignoreIntentGuard && intentSuppressed(promptLower, t))
187
+ continue;
188
+ if (t.length > bestLen) {
189
+ best = { procedure, trigger };
190
+ bestLen = t.length;
191
+ }
192
+ }
193
+ }
194
+ return best;
195
+ }
196
+ // ---------- Injection text (PURE) --------------------------------------------
197
+ /** The additionalContext block injected at UserPromptSubmit when a procedure
198
+ * matches. Leads with the gate for destructive verbs (the guardrail is the
199
+ * point); otherwise leads with the steps. Bounded + labeled so the agent reads
200
+ * it as a known recipe, not a new instruction to reason about. */
201
+ function buildProcedureInjection(proc) {
202
+ const lines = [];
203
+ const tag = proc.destructive ? ' ⚠ DESTRUCTIVE' : '';
204
+ lines.push(`[Procedure: ${proc.verb} — ${proc.status}${tag}] This repo has a known recipe for "${proc.verb}".`);
205
+ if (proc.destructive && proc.caveats) {
206
+ lines.push(` ⛔ GATE: ${proc.caveats}`);
207
+ lines.push(` STEPS (only after approval): ${proc.steps}`);
208
+ }
209
+ else {
210
+ lines.push(` STEPS: ${proc.steps}`);
211
+ if (proc.caveats)
212
+ lines.push(` GATE: ${proc.caveats}`);
213
+ }
214
+ if (proc.endpoint)
215
+ lines.push(` ENDPOINT (done when): ${proc.endpoint}`);
216
+ lines.push(proc.destructive
217
+ ? ` Follow the gate exactly. Never run the steps without a fresh approval this turn.`
218
+ : ` Use this recipe. If it no longer works, say so — that's a stale procedure to re-learn.`);
219
+ return lines.join('\n');
220
+ }
221
+ // ---------- The git-ecosystem seed set ---------------------------------------
222
+ /** The five operational verbs Travis conflates, encoded with their real recipes
223
+ * + gates (docs/procedure-system.md). The deliberate contradiction —
224
+ * deploy = don't-ask, push = always-ask — is exactly what trips a learning
225
+ * dev; both gates ship on day one. `push` is destructive → seed-only. */
226
+ exports.GIT_SEED_SET = [
227
+ {
228
+ verb: 'commit',
229
+ triggers: ['commit', 'check in', 'check it in', 'checkpoint this', 'land it'],
230
+ steps: 'git add -A && git commit -m "wip: <msg>"; then merge the feature branch into the default branch, prune the worktree, delete the merged branch (the /commit flow).',
231
+ caveats: 'Scope = only MY work — never sweep a parallel workstream\'s pending changes. STOPS before push.',
232
+ endpoint: 'changes committed and the feature branch merged into default',
233
+ status: 'seeded',
234
+ },
235
+ {
236
+ verb: 'merge',
237
+ triggers: ['merge', 'merge the branch', 'merge to main', 'merge into main'],
238
+ steps: 'git checkout <default> && git merge <branch> (folded into the commit flow).',
239
+ caveats: 'Another session live in this repo? Coordinate with the peer before merging shared state.',
240
+ endpoint: 'branch merged into the default branch with no conflicts',
241
+ status: 'seeded',
242
+ },
243
+ {
244
+ verb: 'push',
245
+ triggers: ['push', 'git push', 'push to remote', 'push it up', 'push upstream'],
246
+ steps: 'git push',
247
+ caveats: 'git push triggers a PRODUCTION deploy. STOP and get explicit fresh approval before pushing — never push without a fresh "yes" this turn.',
248
+ endpoint: 'operator approved the push AND it completed',
249
+ status: 'seeded',
250
+ destructive: true,
251
+ },
252
+ {
253
+ verb: 'deploy',
254
+ triggers: ['deploy', 'deploy the api', 'deploy the worker', 'redeploy'],
255
+ steps: 'cd packages/api && npm run deploy',
256
+ caveats: 'Routine — after a core fix + commit, run this WITHOUT asking. This is the wrangler deploy of the API worker; it is NOT a git push.',
257
+ endpoint: 'wrangler prints the published worker version (deploy success output)',
258
+ status: 'seeded',
259
+ },
260
+ {
261
+ verb: 'release',
262
+ triggers: ['release', 'ship it', 'ship a release', 'cut a release', 'publish the release', 'do a release'],
263
+ steps: 'Run the /release flow — greprag\'s actual ship path (version bump + publish).',
264
+ caveats: 'greprag ships via /release — NOT /commit and NOT /deploy. Do not conflate the three: commit lands code, deploy pushes the worker, release publishes a version.',
265
+ endpoint: 'the release is published (new version live)',
266
+ status: 'seeded',
267
+ },
268
+ ];
269
+ //# sourceMappingURL=procedure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"procedure.js","sourceRoot":"","sources":["../src/procedure.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /** Skill-gain landing — the Skill Learning Loop's write-back half
2
+ * (docs/skill-learning-loop.md §fuse shape, operator-ratified 2026-07-01).
3
+ *
4
+ * THE SHAPE (Tony Stark's briefcase suit): the server-side distiller packaged
5
+ * each gain as a focused doc + a blind one-liner. This leg — pure code, never
6
+ * agent voluntarism (the skill-tuning autopsy's law) — lands both files:
7
+ *
8
+ * docs/learned/<slug>.md the laser-focused learning doc
9
+ * SKILL.md (EOF) one FUSE line: the one-liner + the schematic
10
+ * telling the NEXT invocation exactly what to do —
11
+ * expand the doc, fold it in where it belongs,
12
+ * delete the fuse and the doc.
13
+ *
14
+ * The fuse is self-applying because it loads WITH the skill: the next agent
15
+ * to invoke the skill reads it as part of the skill's own instructions, does
16
+ * the short tune with the whole skill in context (the only actor that ever
17
+ * legitimately has it), and the skill converges clean. If a run skips the
18
+ * tune, the fuse persists as ordinary progressive disclosure — the learning
19
+ * stays live either way (graceful degradation, no failure mode).
20
+ *
21
+ * Guards: CAP 3 undigested fuses per skill (overflow waits server-side —
22
+ * a skill drowning in briefcases isn't being used anyway); every artifact
23
+ * stamped `skillgain:<id>`; `greprag skillgain reject <id>` is the undo
24
+ * (removes fuse + doc). Landing routes through resolveSkillDir — the ONE
25
+ * seam that swaps to the greprag-load skill store when skills move into the
26
+ * CLI (the no-SKILL.md endgame).
27
+ *
28
+ * Write-target routing (the trap): greprag's bundled skills publish from the
29
+ * repo source (packages/cli/skill/<name>/) — ~/.claude/skills/<name>/ is an
30
+ * init-overwritten artifact. Bundled + repo present → repo source; otherwise
31
+ * the global skills dir; neither → the gain stays pending. */
32
+ export interface PendingSkillGain {
33
+ nodeId: string;
34
+ skill: string;
35
+ gainType: 'rule' | 'scope' | 'reference';
36
+ text: string;
37
+ oneLiner?: string;
38
+ docSlug?: string;
39
+ docBody?: string;
40
+ }
41
+ /** Skills that ship FROM the greprag repo — their canonical source is
42
+ * packages/cli/skill/<name>/; the installed copy is overwritten at init. */
43
+ export declare const BUNDLED_SKILLS: string[];
44
+ export declare const LEARNED_DIR_RELPATH: string;
45
+ /** v1 legacy zone — reject still scans it for stamps landed before the recut. */
46
+ export declare const LEARNINGS_RELPATH: string;
47
+ /** Max undigested fuse lines a SKILL.md may carry; past this, new gains wait
48
+ * server-side. Operator-ratified (2026-07-01). */
49
+ export declare const MAX_FUSES_PER_SKILL = 3;
50
+ /** Resolve the canonical directory for a skill. `existsFn` injectable for
51
+ * tests. Order: bundled repo source (when cwd IS the greprag repo) → global
52
+ * skills dir → null (defer). THE storage seam — the greprag-load skill-store
53
+ * backend replaces this resolution, nothing above it. */
54
+ export declare function resolveSkillDir(skill: string, cwd: string, homeDir: string, existsFn?: (p: string) => boolean): string | null;
55
+ export declare function hasGainStamp(content: string, nodeId: string): boolean;
56
+ /** Count undigested fuse lines in a SKILL.md body (digested ones were deleted
57
+ * by the tune, so the count self-heals as briefcases get absorbed). PURE. */
58
+ export declare function countFuses(skillMdContent: string): number;
59
+ /** The fuse line — the briefcase's button. One line, blind (the one-liner
60
+ * names the learning; the mass lives in the doc), carrying the exact
61
+ * schematic for the next invocation. PURE — unit-tested. */
62
+ export declare function buildFuseLine(gain: PendingSkillGain, docRelPath: string, projectName: string, dateISO: string): string;
63
+ export type LandOutcome = 'landed' | 'already-landed' | 'capped' | 'deferred';
64
+ /** Land ONE briefcase: write the learned doc + append the fuse to SKILL.md.
65
+ * Deterministic file work only — never throws (failure defers the gain). */
66
+ export declare function landBriefcase(gain: PendingSkillGain, cwd: string, homeDir: string, projectName: string): LandOutcome;
67
+ /** Remove a gain's artifacts wherever they landed — the `skillgain reject`
68
+ * undo: the fuse line (SKILL.md), the learned doc, and any v1-legacy
69
+ * learnings.md line. Returns true if anything was removed. Never throws. */
70
+ export declare function removeLandedGain(skill: string, nodeId: string, cwd: string, homeDir: string): boolean;
71
+ export interface SkillGainLandingReport {
72
+ /** Briefcases landed this session start (for the informational announce). */
73
+ landed: Array<{
74
+ nodeId: string;
75
+ skill: string;
76
+ oneLiner: string;
77
+ }>;
78
+ }
79
+ /** The full landing pass. Every gain with a payload lands as a briefcase
80
+ * (capped per skill + per start); payload-less/unroutable gains stay pending.
81
+ * Returns the report (announce) + the status updates to POST back. */
82
+ export declare function landPendingGains(pending: PendingSkillGain[], cwd: string, homeDir: string, projectName: string): {
83
+ report: SkillGainLandingReport;
84
+ statusUpdates: Array<{
85
+ nodeId: string;
86
+ status: 'landed';
87
+ }>;
88
+ };
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ /** Skill-gain landing — the Skill Learning Loop's write-back half
3
+ * (docs/skill-learning-loop.md §fuse shape, operator-ratified 2026-07-01).
4
+ *
5
+ * THE SHAPE (Tony Stark's briefcase suit): the server-side distiller packaged
6
+ * each gain as a focused doc + a blind one-liner. This leg — pure code, never
7
+ * agent voluntarism (the skill-tuning autopsy's law) — lands both files:
8
+ *
9
+ * docs/learned/<slug>.md the laser-focused learning doc
10
+ * SKILL.md (EOF) one FUSE line: the one-liner + the schematic
11
+ * telling the NEXT invocation exactly what to do —
12
+ * expand the doc, fold it in where it belongs,
13
+ * delete the fuse and the doc.
14
+ *
15
+ * The fuse is self-applying because it loads WITH the skill: the next agent
16
+ * to invoke the skill reads it as part of the skill's own instructions, does
17
+ * the short tune with the whole skill in context (the only actor that ever
18
+ * legitimately has it), and the skill converges clean. If a run skips the
19
+ * tune, the fuse persists as ordinary progressive disclosure — the learning
20
+ * stays live either way (graceful degradation, no failure mode).
21
+ *
22
+ * Guards: CAP 3 undigested fuses per skill (overflow waits server-side —
23
+ * a skill drowning in briefcases isn't being used anyway); every artifact
24
+ * stamped `skillgain:<id>`; `greprag skillgain reject <id>` is the undo
25
+ * (removes fuse + doc). Landing routes through resolveSkillDir — the ONE
26
+ * seam that swaps to the greprag-load skill store when skills move into the
27
+ * CLI (the no-SKILL.md endgame).
28
+ *
29
+ * Write-target routing (the trap): greprag's bundled skills publish from the
30
+ * repo source (packages/cli/skill/<name>/) — ~/.claude/skills/<name>/ is an
31
+ * init-overwritten artifact. Bundled + repo present → repo source; otherwise
32
+ * the global skills dir; neither → the gain stays pending. */
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
66
+ Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.MAX_FUSES_PER_SKILL = exports.LEARNINGS_RELPATH = exports.LEARNED_DIR_RELPATH = exports.BUNDLED_SKILLS = void 0;
68
+ exports.resolveSkillDir = resolveSkillDir;
69
+ exports.hasGainStamp = hasGainStamp;
70
+ exports.countFuses = countFuses;
71
+ exports.buildFuseLine = buildFuseLine;
72
+ exports.landBriefcase = landBriefcase;
73
+ exports.removeLandedGain = removeLandedGain;
74
+ exports.landPendingGains = landPendingGains;
75
+ const path = __importStar(require("path"));
76
+ const fs = __importStar(require("fs"));
77
+ /** Skills that ship FROM the greprag repo — their canonical source is
78
+ * packages/cli/skill/<name>/; the installed copy is overwritten at init. */
79
+ exports.BUNDLED_SKILLS = ['greprag', 'commander', 'content-advisor', 'mechanic'];
80
+ exports.LEARNED_DIR_RELPATH = path.join('docs', 'learned');
81
+ /** v1 legacy zone — reject still scans it for stamps landed before the recut. */
82
+ exports.LEARNINGS_RELPATH = path.join('docs', 'learnings.md');
83
+ /** Max undigested fuse lines a SKILL.md may carry; past this, new gains wait
84
+ * server-side. Operator-ratified (2026-07-01). */
85
+ exports.MAX_FUSES_PER_SKILL = 3;
86
+ /** Resolve the canonical directory for a skill. `existsFn` injectable for
87
+ * tests. Order: bundled repo source (when cwd IS the greprag repo) → global
88
+ * skills dir → null (defer). THE storage seam — the greprag-load skill-store
89
+ * backend replaces this resolution, nothing above it. */
90
+ function resolveSkillDir(skill, cwd, homeDir, existsFn = fs.existsSync) {
91
+ if (!/^[A-Za-z0-9._-]+$/.test(skill))
92
+ return null; // path-safe names only
93
+ if (exports.BUNDLED_SKILLS.includes(skill)) {
94
+ const repoSrc = path.join(cwd, 'packages', 'cli', 'skill', skill);
95
+ if (existsFn(repoSrc))
96
+ return repoSrc;
97
+ }
98
+ const global = path.join(homeDir, '.claude', 'skills', skill);
99
+ if (existsFn(global))
100
+ return global;
101
+ return null;
102
+ }
103
+ function hasGainStamp(content, nodeId) {
104
+ return content.includes(`skillgain:${nodeId}`);
105
+ }
106
+ /** Count undigested fuse lines in a SKILL.md body (digested ones were deleted
107
+ * by the tune, so the count self-heals as briefcases get absorbed). PURE. */
108
+ function countFuses(skillMdContent) {
109
+ return (skillMdContent.match(/<!-- skillgain:[0-9a-f]{8}/g) || []).length;
110
+ }
111
+ /** The fuse line — the briefcase's button. One line, blind (the one-liner
112
+ * names the learning; the mass lives in the doc), carrying the exact
113
+ * schematic for the next invocation. PURE — unit-tested. */
114
+ function buildFuseLine(gain, docRelPath, projectName, dateISO) {
115
+ return (`⚙ SKILL UPDATE PENDING (${gain.gainType}) — ${gain.oneLiner} `
116
+ + `→ BEFORE working: read ${docRelPath.replace(/\\/g, '/')}, fold the learning into this `
117
+ + `skill where it belongs (one small edit, keep it lean), then DELETE this line and the doc. `
118
+ + `Bogus? \`greprag skillgain reject ${gain.nodeId}\` instead. `
119
+ + `<!-- skillgain:${gain.nodeId} ${dateISO.slice(0, 10)} project:${projectName} -->`);
120
+ }
121
+ /** Land ONE briefcase: write the learned doc + append the fuse to SKILL.md.
122
+ * Deterministic file work only — never throws (failure defers the gain). */
123
+ function landBriefcase(gain, cwd, homeDir, projectName) {
124
+ try {
125
+ if (!gain.oneLiner || !gain.docSlug || !gain.docBody)
126
+ return 'deferred'; // payload-less legacy row
127
+ const dir = resolveSkillDir(gain.skill, cwd, homeDir);
128
+ if (!dir)
129
+ return 'deferred';
130
+ const skillMd = path.join(dir, 'SKILL.md');
131
+ let body;
132
+ try {
133
+ body = fs.readFileSync(skillMd, 'utf-8');
134
+ }
135
+ catch {
136
+ return 'deferred';
137
+ }
138
+ if (hasGainStamp(body, gain.nodeId))
139
+ return 'already-landed';
140
+ if (countFuses(body) >= exports.MAX_FUSES_PER_SKILL)
141
+ return 'capped';
142
+ // Doc first (a fuse pointing at a missing doc is the one broken state).
143
+ const docRel = path.join(exports.LEARNED_DIR_RELPATH, `${gain.docSlug}.md`);
144
+ const docAbs = path.join(dir, docRel);
145
+ fs.mkdirSync(path.dirname(docAbs), { recursive: true });
146
+ fs.writeFileSync(docAbs, gain.docBody.trimEnd()
147
+ + `\n\n<!-- skillgain:${gain.nodeId} — auto-landed by the Skill Learning Loop; deleted when digested -->\n`);
148
+ const fuse = buildFuseLine(gain, docRel, projectName, new Date().toISOString());
149
+ fs.writeFileSync(skillMd, body + (body.endsWith('\n') ? '' : '\n') + fuse + '\n');
150
+ return 'landed';
151
+ }
152
+ catch {
153
+ return 'deferred';
154
+ }
155
+ }
156
+ /** Remove a gain's artifacts wherever they landed — the `skillgain reject`
157
+ * undo: the fuse line (SKILL.md), the learned doc, and any v1-legacy
158
+ * learnings.md line. Returns true if anything was removed. Never throws. */
159
+ function removeLandedGain(skill, nodeId, cwd, homeDir) {
160
+ try {
161
+ const dir = resolveSkillDir(skill, cwd, homeDir);
162
+ if (!dir)
163
+ return false;
164
+ let removed = false;
165
+ for (const file of [path.join(dir, 'SKILL.md'), path.join(dir, exports.LEARNINGS_RELPATH)]) {
166
+ try {
167
+ const content = fs.readFileSync(file, 'utf-8');
168
+ if (!hasGainStamp(content, nodeId))
169
+ continue;
170
+ fs.writeFileSync(file, content.split('\n')
171
+ .filter(line => !line.includes(`skillgain:${nodeId}`)).join('\n'));
172
+ removed = true;
173
+ }
174
+ catch { /* file absent — fine */ }
175
+ }
176
+ const learnedDir = path.join(dir, exports.LEARNED_DIR_RELPATH);
177
+ try {
178
+ for (const f of fs.readdirSync(learnedDir)) {
179
+ const p = path.join(learnedDir, f);
180
+ try {
181
+ if (hasGainStamp(fs.readFileSync(p, 'utf-8'), nodeId)) {
182
+ fs.unlinkSync(p);
183
+ removed = true;
184
+ }
185
+ }
186
+ catch { /* unreadable — skip */ }
187
+ }
188
+ }
189
+ catch { /* no learned dir — fine */ }
190
+ return removed;
191
+ }
192
+ catch {
193
+ return false;
194
+ }
195
+ }
196
+ /** Bound the file work per session start; overflow waits (still pending). */
197
+ const MAX_LANDS_PER_START = 5;
198
+ /** The full landing pass. Every gain with a payload lands as a briefcase
199
+ * (capped per skill + per start); payload-less/unroutable gains stay pending.
200
+ * Returns the report (announce) + the status updates to POST back. */
201
+ function landPendingGains(pending, cwd, homeDir, projectName) {
202
+ const report = { landed: [] };
203
+ const statusUpdates = [];
204
+ for (const gain of pending) {
205
+ if (report.landed.length >= MAX_LANDS_PER_START)
206
+ break;
207
+ const outcome = landBriefcase(gain, cwd, homeDir, projectName);
208
+ if (outcome === 'landed' || outcome === 'already-landed') {
209
+ statusUpdates.push({ nodeId: gain.nodeId, status: 'landed' });
210
+ if (outcome === 'landed') {
211
+ report.landed.push({
212
+ nodeId: gain.nodeId, skill: gain.skill, oneLiner: gain.oneLiner || gain.text,
213
+ });
214
+ }
215
+ }
216
+ // 'capped' / 'deferred' → stays pending, retried next session start
217
+ }
218
+ return { report, statusUpdates };
219
+ }
220
+ //# sourceMappingURL=skill-landing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-landing.js","sourceRoot":"","sources":["../src/skill-landing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "greprag",
3
- "version": "5.49.15",
3
+ "version": "5.50.0",
4
4
  "description": "GrepRAG — agent memory for Claude Code, Codex, and OpenCode.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -83,7 +83,7 @@ greprag discord handoff --session <8-hex> --project <name> --ttl 60 --no-watch
83
83
 
84
84
  Tell the user: *"Handoff pinned for 60 min. Reply on Discord — your messages route to this session."*
85
85
 
86
- > Safety net: if `greprag inbox watchers` shows no watcher for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)**: `while true; do greprag inbox watch --session <8-hex> --json; echo "[restart]" >&2; sleep 1; done`.
86
+ > Safety net: if `greprag inbox watchers` shows no watcher for your 8-hex (auto-arm disabled, or it died and the next turn hasn't re-armed yet), arm one under **Monitor (persistent:true)** with the exit-aware relauncher: `while true; do greprag inbox watch --session <8-hex> --json --owner-pid <pid>; case $? in 0|64) break;; esac; sleep 1; done`. (The `case $? in 0|64) break` is required — it breaks on clean/consumer-gone (0) and fatal/bad-key (64) so the loop never busy-respawns an orphan; only a crash relaunches. A bare `while true; …; sleep 1; done` without it is the 2026-06-04 OOM orphan — don't use that form.)
87
87
 
88
88
  ### Orchestrator handoff (`--orchestrator`) — the only mode that arms
89
89
 
@@ -95,7 +95,7 @@ Run under **Monitor (persistent:true)**, wrapped so a process death auto-recover
95
95
  greprag discord handoff --session <8-hex> --project <name> --ttl 60 --orchestrator --no-watch && \
96
96
  while true; do
97
97
  greprag inbox watch --json
98
- echo "[wrapper] watcher exited, restarting in 1s" >&2
98
+ case $? in 0|64) break;; esac # 0 = clean/consumer-gone, 64 = fatal/bad-key — don't respawn an orphan
99
99
  sleep 1
100
100
  done
101
101
  ```