greprag 5.21.0 → 5.22.1
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/README.md +56 -13
- package/dist/codex-steering.d.ts +29 -0
- package/dist/codex-steering.js +168 -0
- package/dist/codex-steering.js.map +1 -0
- package/dist/commands/codex.d.ts +34 -0
- package/dist/commands/codex.js +490 -0
- package/dist/commands/codex.js.map +1 -0
- package/dist/commands/discover.d.ts +1 -1
- package/dist/commands/discover.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/init.js +510 -79
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lore.d.ts +2 -0
- package/dist/commands/lore.js +83 -6
- package/dist/commands/lore.js.map +1 -1
- package/dist/commands/status.d.ts +22 -10
- package/dist/commands/status.js +159 -69
- package/dist/commands/status.js.map +1 -1
- package/dist/hook.js +120 -13
- package/dist/hook.js.map +1 -1
- package/dist/index.js +109 -25
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin.d.ts +2 -2
- package/dist/opencode-plugin.js +47 -14
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/project-anchor.d.ts +11 -9
- package/dist/project-anchor.js +58 -27
- package/dist/project-anchor.js.map +1 -1
- package/package.json +4 -2
- package/scripts/postinstall.js +61 -46
- package/skill/greprag/SKILL.md +22 -4
- package/skill/greprag/docs/doctor.md +1 -1
- package/skill/greprag/docs/setup.md +98 -6
- package/skill/lore-advisor/SKILL.md +101 -43
package/dist/commands/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/** greprag init — configure
|
|
2
|
+
/** greprag init — configure agent clients for GrepRAG memory.
|
|
3
3
|
*
|
|
4
4
|
* 1. Obtain API key (provided or provisioned)
|
|
5
5
|
* 2. Validate key against health endpoint
|
|
6
|
-
* 3. Update
|
|
6
|
+
* 3. Update the target platform's native hooks/plugin/config */
|
|
7
7
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
8
|
if (k2 === undefined) k2 = k;
|
|
9
9
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.runInit = runInit;
|
|
42
|
+
exports.applyCodexHooks = applyCodexHooks;
|
|
42
43
|
exports.applySettings = applySettings;
|
|
43
44
|
exports.patchClaudeMdContent = patchClaudeMdContent;
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
@@ -47,7 +48,143 @@ const os = __importStar(require("os"));
|
|
|
47
48
|
const readline = __importStar(require("readline"));
|
|
48
49
|
const child_process_1 = require("child_process");
|
|
49
50
|
const project_anchor_1 = require("../project-anchor");
|
|
51
|
+
const codex_1 = require("./codex");
|
|
50
52
|
const API_URL = 'https://api.greprag.com';
|
|
53
|
+
function hasEnvPrefix(prefix) {
|
|
54
|
+
return Object.keys(process.env).some(k => k.startsWith(prefix));
|
|
55
|
+
}
|
|
56
|
+
function findUp(start, fileName) {
|
|
57
|
+
let dir = path.resolve(start);
|
|
58
|
+
while (true) {
|
|
59
|
+
const candidate = path.join(dir, fileName);
|
|
60
|
+
if (fs.existsSync(candidate))
|
|
61
|
+
return candidate;
|
|
62
|
+
const parent = path.dirname(dir);
|
|
63
|
+
if (parent === dir)
|
|
64
|
+
return null;
|
|
65
|
+
dir = parent;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function detectAgentTargets(cwd = process.cwd()) {
|
|
69
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
70
|
+
const detections = [];
|
|
71
|
+
const claudeEvidence = [];
|
|
72
|
+
if (process.env.CLAUDE_CODE_SESSION_ID)
|
|
73
|
+
claudeEvidence.push('CLAUDE_CODE_SESSION_ID is set');
|
|
74
|
+
if (process.env.CLAUDE_PROJECT_DIR)
|
|
75
|
+
claudeEvidence.push('CLAUDE_PROJECT_DIR is set');
|
|
76
|
+
if (home && fs.existsSync(path.join(home, '.claude')))
|
|
77
|
+
claudeEvidence.push('~/.claude exists');
|
|
78
|
+
if (claudeEvidence.length > 0)
|
|
79
|
+
detections.push({ target: 'claude', evidence: claudeEvidence });
|
|
80
|
+
const codexEvidence = [];
|
|
81
|
+
if (hasEnvPrefix('CODEX_'))
|
|
82
|
+
codexEvidence.push('CODEX_* environment is set');
|
|
83
|
+
if (home && fs.existsSync(path.join(home, '.codex')))
|
|
84
|
+
codexEvidence.push('~/.codex exists');
|
|
85
|
+
const agentsPath = findUp(cwd, 'AGENTS.md');
|
|
86
|
+
if (agentsPath)
|
|
87
|
+
codexEvidence.push(`AGENTS.md found at ${agentsPath}`);
|
|
88
|
+
if (codexEvidence.length > 0)
|
|
89
|
+
detections.push({ target: 'codex', evidence: codexEvidence });
|
|
90
|
+
const opencodeEvidence = [];
|
|
91
|
+
if (hasEnvPrefix('OPENCODE'))
|
|
92
|
+
opencodeEvidence.push('OPENCODE* environment is set');
|
|
93
|
+
if (home && fs.existsSync(path.join(home, '.config', 'opencode')))
|
|
94
|
+
opencodeEvidence.push('~/.config/opencode exists');
|
|
95
|
+
if (opencodeEvidence.length > 0)
|
|
96
|
+
detections.push({ target: 'opencode', evidence: opencodeEvidence });
|
|
97
|
+
return detections;
|
|
98
|
+
}
|
|
99
|
+
function describeTarget(target) {
|
|
100
|
+
if (target === 'claude')
|
|
101
|
+
return 'Claude Code';
|
|
102
|
+
if (target === 'codex')
|
|
103
|
+
return 'Codex';
|
|
104
|
+
return 'OpenCode';
|
|
105
|
+
}
|
|
106
|
+
function explicitInitHint() {
|
|
107
|
+
return [
|
|
108
|
+
'Run one of:',
|
|
109
|
+
' greprag init --codex --tenant-id <your-handle>',
|
|
110
|
+
' greprag init --claude --tenant-id <your-handle>',
|
|
111
|
+
' greprag init --opencode --tenant-id <your-handle>',
|
|
112
|
+
].join('\n');
|
|
113
|
+
}
|
|
114
|
+
function normalizeHandle(raw) {
|
|
115
|
+
return raw.trim().toLowerCase().replace(/@greprag\.com$/i, '');
|
|
116
|
+
}
|
|
117
|
+
function validateHandle(handle) {
|
|
118
|
+
return /^[a-z0-9-]{3,32}$/.test(handle);
|
|
119
|
+
}
|
|
120
|
+
async function promptForHandle() {
|
|
121
|
+
const handle = normalizeHandle(await prompt(' Choose your public GrepRAG handle, e.g. tanya -> tanya@greprag.com: '));
|
|
122
|
+
if (!validateHandle(handle)) {
|
|
123
|
+
console.error(' Error: public handle must be 3-32 chars: lowercase letters, numbers, and hyphens.');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const answer = (await prompt(` This will create/use public handle ${handle}@greprag.com. Continue? (Y/n): `)).trim().toLowerCase();
|
|
127
|
+
if (answer && answer !== 'y' && answer !== 'yes') {
|
|
128
|
+
console.error(' Setup cancelled before provisioning a public handle.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
return handle;
|
|
132
|
+
}
|
|
133
|
+
async function resolveProvisionHandle(opts) {
|
|
134
|
+
if (opts.tenantId) {
|
|
135
|
+
const handle = normalizeHandle(opts.tenantId);
|
|
136
|
+
if (!validateHandle(handle)) {
|
|
137
|
+
console.error(' Error: --tenant-id must be 3-32 chars: lowercase letters, numbers, and hyphens.');
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
console.log(` Public handle: ${handle}@greprag.com`);
|
|
141
|
+
return handle;
|
|
142
|
+
}
|
|
143
|
+
return promptForHandle();
|
|
144
|
+
}
|
|
145
|
+
async function chooseInitTarget() {
|
|
146
|
+
const detections = detectAgentTargets();
|
|
147
|
+
if (!process.stdin.isTTY) {
|
|
148
|
+
if (detections.length === 1)
|
|
149
|
+
return detections[0].target;
|
|
150
|
+
console.error(' Error: greprag init could not safely choose an agent target in non-interactive mode.');
|
|
151
|
+
if (detections.length > 1) {
|
|
152
|
+
console.error(' Detected more than one possible agent:');
|
|
153
|
+
for (const d of detections) {
|
|
154
|
+
console.error(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.error(explicitInitHint());
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
if (detections.length === 1) {
|
|
161
|
+
const detected = detections[0];
|
|
162
|
+
console.log(`\n Detected ${describeTarget(detected.target)} (${detected.evidence.join('; ')}).`);
|
|
163
|
+
const answer = (await prompt(` Configure GrepRAG for ${describeTarget(detected.target)}? (Y/n): `)).trim().toLowerCase();
|
|
164
|
+
if (!answer || answer === 'y' || answer === 'yes')
|
|
165
|
+
return detected.target;
|
|
166
|
+
}
|
|
167
|
+
else if (detections.length > 1) {
|
|
168
|
+
console.log('\n Multiple agent environments look available:');
|
|
169
|
+
for (const d of detections) {
|
|
170
|
+
console.log(` - ${describeTarget(d.target)}: ${d.evidence.join('; ')}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
console.log('\n Which agent are you setting up GrepRAG for?');
|
|
174
|
+
console.log(' 1. Codex');
|
|
175
|
+
console.log(' 2. Claude Code');
|
|
176
|
+
console.log(' 3. OpenCode');
|
|
177
|
+
const answer = (await prompt(' Choose 1, 2, or 3: ')).trim().toLowerCase();
|
|
178
|
+
if (answer === '1' || answer === 'codex')
|
|
179
|
+
return 'codex';
|
|
180
|
+
if (answer === '2' || answer === 'claude' || answer === 'claude code')
|
|
181
|
+
return 'claude';
|
|
182
|
+
if (answer === '3' || answer === 'opencode' || answer === 'open code')
|
|
183
|
+
return 'opencode';
|
|
184
|
+
console.error(' Error: no agent target selected.');
|
|
185
|
+
console.error(explicitInitHint());
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
51
188
|
async function runInit(opts) {
|
|
52
189
|
// --all: run the FULL standard init for cwd first (API key, hooks, anchor,
|
|
53
190
|
// global anchor, skill, chip-spawn doc, CLAUDE.md patch), THEN bulk-register
|
|
@@ -56,35 +193,40 @@ async function runInit(opts) {
|
|
|
56
193
|
// Reuse the existing GREPRAG_API_KEY from settings if one is already there,
|
|
57
194
|
// so re-runs don't churn the tenant's key via the provision path.
|
|
58
195
|
if (opts.all) {
|
|
59
|
-
let apiKey = opts.apiKey;
|
|
60
|
-
if (!apiKey) {
|
|
61
|
-
apiKey = readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
|
|
62
|
-
}
|
|
196
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
63
197
|
await runInit({ ...opts, all: false, apiKey });
|
|
64
198
|
return runInitAll(opts.root);
|
|
65
199
|
}
|
|
66
|
-
// --global: create ~/.
|
|
200
|
+
// --global: create ~/.greprag/project.json only — no API key, no hooks.
|
|
67
201
|
if (opts.global) {
|
|
68
202
|
return runGlobalInit(opts.name);
|
|
69
203
|
}
|
|
204
|
+
const explicitTargets = [opts.claude, opts.codex, opts.opencode].filter(Boolean).length;
|
|
205
|
+
if (explicitTargets > 1) {
|
|
206
|
+
console.error(' Error: choose only one target: --claude, --codex, or --opencode.');
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
70
209
|
// --opencode: configure for OpenCode instead of Claude Code.
|
|
71
210
|
if (opts.opencode) {
|
|
72
211
|
return runOpenCodeInit(opts);
|
|
73
212
|
}
|
|
213
|
+
// --codex: configure Codex lifecycle hooks.
|
|
214
|
+
if (opts.codex) {
|
|
215
|
+
return runCodexInit(opts);
|
|
216
|
+
}
|
|
217
|
+
if (!opts.claude) {
|
|
218
|
+
const target = await chooseInitTarget();
|
|
219
|
+
if (target === 'codex')
|
|
220
|
+
return runCodexInit(opts);
|
|
221
|
+
if (target === 'opencode')
|
|
222
|
+
return runOpenCodeInit(opts);
|
|
223
|
+
}
|
|
74
224
|
console.log('\n greprag init — Setting up agent memory for Claude Code\n');
|
|
75
225
|
// Step 1: Get API key
|
|
76
|
-
let apiKey = opts.apiKey;
|
|
226
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
77
227
|
if (!apiKey) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
tenantId = await prompt(' Enter a tenant ID (alphanumeric + hyphens, 3-32 chars): ');
|
|
81
|
-
}
|
|
82
|
-
tenantId = tenantId.trim().toLowerCase();
|
|
83
|
-
if (!tenantId) {
|
|
84
|
-
console.error(' Error: tenant ID is required');
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
console.log(` Provisioning API key for tenant "${tenantId}"...`);
|
|
228
|
+
const tenantId = await resolveProvisionHandle(opts);
|
|
229
|
+
console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
|
|
88
230
|
const result = await provision(tenantId);
|
|
89
231
|
if (!result.ok) {
|
|
90
232
|
console.error(` Error: ${result.error}`);
|
|
@@ -101,21 +243,7 @@ async function runInit(opts) {
|
|
|
101
243
|
process.exit(1);
|
|
102
244
|
}
|
|
103
245
|
console.log(' Key validated against api.greprag.com');
|
|
104
|
-
|
|
105
|
-
// a usable routing address offline. adr: adr/numeric-handles.md
|
|
106
|
-
try {
|
|
107
|
-
const { fetchIdentity, writeCache } = await Promise.resolve().then(() => __importStar(require('./identity')));
|
|
108
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
109
|
-
const cachePath = `${home}/.greprag/identity.json`;
|
|
110
|
-
const ident = await fetchIdentity('https://api.greprag.com', apiKey);
|
|
111
|
-
writeCache(cachePath, ident);
|
|
112
|
-
if (ident.handle)
|
|
113
|
-
console.log(` Identity: ${ident.handle}`);
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
// Non-fatal — `greprag identity show` can repopulate later.
|
|
117
|
-
console.log(` Identity cache deferred (${e.message})`);
|
|
118
|
-
}
|
|
246
|
+
await cacheIdentity(apiKey);
|
|
119
247
|
// Step 3: Update settings.json
|
|
120
248
|
console.log(' Configuring Claude Code settings...');
|
|
121
249
|
const settingsPath = getSettingsPath();
|
|
@@ -133,7 +261,7 @@ async function runInit(opts) {
|
|
|
133
261
|
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
|
|
134
262
|
}
|
|
135
263
|
else {
|
|
136
|
-
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .
|
|
264
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
|
|
137
265
|
// Only patch gitignore when the file is load-bearing for identity.
|
|
138
266
|
const gitignoreResult = ensureAnchorTrackable(process.cwd());
|
|
139
267
|
if (gitignoreResult)
|
|
@@ -155,25 +283,19 @@ async function runInit(opts) {
|
|
|
155
283
|
// with capabilities the operator may not want. Postinstall mirrors this
|
|
156
284
|
// policy (sync greprag/ always, refresh-only for opted-in advisors).
|
|
157
285
|
// adr: adr/skill-bundle-shape.md
|
|
158
|
-
const skillRoot =
|
|
159
|
-
const skillsTargetRoot = path.join(os.homedir(), '.claude', 'skills');
|
|
160
|
-
const CORE_SKILL = 'greprag';
|
|
286
|
+
const skillRoot = getSkillRoot();
|
|
161
287
|
const optionalSkills = [];
|
|
162
288
|
if (fs.existsSync(skillRoot)) {
|
|
289
|
+
const installed = installCoreSkill('claude');
|
|
290
|
+
if (installed)
|
|
291
|
+
changes.push(`Skill: installed → ${installed}`);
|
|
163
292
|
for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
|
|
164
293
|
if (!entry.isDirectory())
|
|
165
294
|
continue;
|
|
166
295
|
if (entry.name === 'templates')
|
|
167
296
|
continue;
|
|
168
|
-
if (entry.name
|
|
169
|
-
const srcDir = path.join(skillRoot, entry.name);
|
|
170
|
-
const destDir = path.join(skillsTargetRoot, entry.name);
|
|
171
|
-
copySkillDir(srcDir, destDir);
|
|
172
|
-
changes.push(`Skill: installed → ${destDir}`);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
297
|
+
if (entry.name !== 'greprag')
|
|
175
298
|
optionalSkills.push(entry.name);
|
|
176
|
-
}
|
|
177
299
|
}
|
|
178
300
|
}
|
|
179
301
|
// Step 6b: Install the chip-spawn doc to ~/.claude/docs/chip-spawn.md. The
|
|
@@ -219,25 +341,17 @@ async function runInit(opts) {
|
|
|
219
341
|
* global plugins directory.
|
|
220
342
|
*
|
|
221
343
|
* Does NOT create a separate `.opencode/project.json` — the opencode plugin's
|
|
222
|
-
* `readAnchor` checks `.
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
344
|
+
* `readAnchor` checks `.greprag/project.json` first, so a single shared
|
|
345
|
+
* anchor is used by all clients and memory accumulates under one project_id.
|
|
346
|
+
* A user who initialized opencode first and Claude Code second on the same
|
|
347
|
+
* repo gets the same project_id from both inits (idempotent). */
|
|
226
348
|
async function runOpenCodeInit(opts) {
|
|
227
349
|
console.log('\n greprag init — Setting up agent memory for OpenCode\n');
|
|
228
350
|
// Step 1: Get API key (same provision path as Claude Code init)
|
|
229
|
-
let apiKey = opts.apiKey;
|
|
351
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
230
352
|
if (!apiKey) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
tenantId = await prompt(' Enter a tenant ID (alphanumeric + hyphens, 3-32 chars): ');
|
|
234
|
-
}
|
|
235
|
-
tenantId = tenantId.trim().toLowerCase();
|
|
236
|
-
if (!tenantId) {
|
|
237
|
-
console.error(' Error: tenant ID is required');
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
console.log(` Provisioning API key for tenant "${tenantId}"...`);
|
|
353
|
+
const tenantId = await resolveProvisionHandle(opts);
|
|
354
|
+
console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
|
|
241
355
|
const result = await provision(tenantId);
|
|
242
356
|
if (!result.ok) {
|
|
243
357
|
console.error(` Error: ${result.error}`);
|
|
@@ -254,14 +368,15 @@ async function runOpenCodeInit(opts) {
|
|
|
254
368
|
process.exit(1);
|
|
255
369
|
}
|
|
256
370
|
console.log(' Key validated against api.greprag.com');
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
//
|
|
371
|
+
await cacheIdentity(apiKey);
|
|
372
|
+
writeGrepragEnv(apiKey);
|
|
373
|
+
// Step 3: Ensure env vars are also set in ~/.claude/settings.json for
|
|
374
|
+
// backwards compatibility with older OpenCode plugin installs.
|
|
260
375
|
const settingsPath = getSettingsPath();
|
|
261
376
|
const settings = readSettings(settingsPath);
|
|
262
377
|
if (!settings.env)
|
|
263
378
|
settings.env = {};
|
|
264
|
-
const envChanges = [];
|
|
379
|
+
const envChanges = ['Wrote ~/.greprag/.env'];
|
|
265
380
|
if (!settings.env.GREPRAG_API_KEY) {
|
|
266
381
|
settings.env.GREPRAG_API_KEY = apiKey;
|
|
267
382
|
envChanges.push('Set GREPRAG_API_KEY');
|
|
@@ -274,7 +389,7 @@ async function runOpenCodeInit(opts) {
|
|
|
274
389
|
writeSettings(settingsPath, settings);
|
|
275
390
|
}
|
|
276
391
|
// Step 4: Resolve the canonical project anchor via the same cascade Claude
|
|
277
|
-
// Code uses. Idempotent — if `.
|
|
392
|
+
// Code uses. Idempotent — if `.greprag/project.json` already exists, this
|
|
278
393
|
// returns the existing anchor unchanged. In a git repo with commits, the
|
|
279
394
|
// identity is derived from the root commit SHA and the file holds only
|
|
280
395
|
// settings (no project_id). The opencode plugin's readAnchor reads this
|
|
@@ -334,11 +449,110 @@ async function runOpenCodeInit(opts) {
|
|
|
334
449
|
console.log(` - Project anchor: ${anchor.anchorPath} (source: ${anchor.source})`);
|
|
335
450
|
if (pluginInstalled)
|
|
336
451
|
console.log(` - Plugin: ${pluginDest}`);
|
|
452
|
+
console.log(` - Shared env: ${getGrepragEnvPath()}`);
|
|
337
453
|
console.log(' OpenCode will load the plugin automatically on next session start.');
|
|
338
454
|
console.log(' The /greprag skill is also available for on-demand briefings.\n');
|
|
339
455
|
}
|
|
456
|
+
/** greprag init --codex
|
|
457
|
+
* Configures GrepRAG for Codex lifecycle hooks. Codex does not read
|
|
458
|
+
* ~/.claude/settings.json env, so this path writes ~/.greprag/.env and a
|
|
459
|
+
* user-level ~/.codex/hooks.json. The hooks still call the same greprag-hook
|
|
460
|
+
* binary; codex-* subcommands only adapt Codex's prompt/Stop payload shape. */
|
|
461
|
+
async function runCodexInit(opts) {
|
|
462
|
+
console.log('\n greprag init — Setting up agent memory for Codex\n');
|
|
463
|
+
let apiKey = opts.apiKey || readSharedApiKey();
|
|
464
|
+
if (!apiKey) {
|
|
465
|
+
const tenantId = await resolveProvisionHandle(opts);
|
|
466
|
+
console.log(` Provisioning API key for public handle ${tenantId}@greprag.com...`);
|
|
467
|
+
const result = await provision(tenantId);
|
|
468
|
+
if (!result.ok) {
|
|
469
|
+
console.error(` Error: ${result.error}`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
apiKey = result.apiKey;
|
|
473
|
+
console.log(` API key created: ${apiKey.slice(0, 17)}...`);
|
|
474
|
+
}
|
|
475
|
+
console.log(' Validating API key...');
|
|
476
|
+
const valid = await validateKey(apiKey);
|
|
477
|
+
if (!valid) {
|
|
478
|
+
console.error(' Error: API key validation failed.');
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
console.log(' Key validated against api.greprag.com');
|
|
482
|
+
const changes = [];
|
|
483
|
+
writeGrepragEnv(apiKey);
|
|
484
|
+
changes.push('Wrote ~/.greprag/.env for Codex hooks');
|
|
485
|
+
await cacheIdentity(apiKey);
|
|
486
|
+
const anchor = (0, project_anchor_1.ensureAnchor)(process.cwd());
|
|
487
|
+
if (anchor.source === 'git') {
|
|
488
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — derived from git history`);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
changes.push(`Project anchor: ${anchor.projectName} (${anchor.projectId.slice(0, 8)}) — stored in .greprag/project.json`);
|
|
492
|
+
const gitignoreResult = ensureAnchorTrackable(process.cwd());
|
|
493
|
+
if (gitignoreResult)
|
|
494
|
+
changes.push(gitignoreResult);
|
|
495
|
+
}
|
|
496
|
+
const globalAnchor = (0, project_anchor_1.ensureGlobalAnchor)();
|
|
497
|
+
changes.push(`Global anchor: ${globalAnchor.projectName} (${globalAnchor.projectId.slice(0, 8)})`);
|
|
498
|
+
writeProjectPath(anchor.projectName, process.cwd());
|
|
499
|
+
changes.push(`Registered repo path for ${anchor.projectName} → ~/.greprag/projects.json`);
|
|
500
|
+
const codexSkill = installCoreSkill('codex');
|
|
501
|
+
if (codexSkill)
|
|
502
|
+
changes.push(`Skill: installed → ${codexSkill}`);
|
|
503
|
+
try {
|
|
504
|
+
const res = await fetch(`${API_URL}/v1/inbox/projects/register`, {
|
|
505
|
+
method: 'POST',
|
|
506
|
+
headers: {
|
|
507
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
508
|
+
'Content-Type': 'application/json',
|
|
509
|
+
},
|
|
510
|
+
body: JSON.stringify({ project_id: anchor.projectId, project_name: anchor.projectName }),
|
|
511
|
+
});
|
|
512
|
+
const reg = await res.json();
|
|
513
|
+
changes.push(reg.ok ? 'Project registered for inbox addressing' : `Inbox registration: ${reg.error || 'failed'}`);
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
changes.push('Inbox registration skipped (network)');
|
|
517
|
+
}
|
|
518
|
+
const hooksPath = getCodexHooksPath();
|
|
519
|
+
const hooks = readCodexHooks(hooksPath);
|
|
520
|
+
const hookChanges = applyCodexHooks(hooks);
|
|
521
|
+
writeCodexHooks(hooksPath, hooks);
|
|
522
|
+
changes.push(...hookChanges);
|
|
523
|
+
const startup = (0, codex_1.codexStartupInfo)();
|
|
524
|
+
if (startup.installed) {
|
|
525
|
+
changes.push(`Codex live inbox startup watcher already installed (${startup.path})`);
|
|
526
|
+
}
|
|
527
|
+
else if (opts.installWatcher) {
|
|
528
|
+
(0, codex_1.installCodexStartup)();
|
|
529
|
+
changes.push(`Codex live inbox startup watcher installed (${startup.path})`);
|
|
530
|
+
}
|
|
531
|
+
else if (process.stdin.isTTY) {
|
|
532
|
+
const answer = (await prompt(' Install Codex live inbox watcher at login? (Y/n): ')).trim().toLowerCase();
|
|
533
|
+
if (!answer || answer === 'y' || answer === 'yes') {
|
|
534
|
+
(0, codex_1.installCodexStartup)();
|
|
535
|
+
changes.push(`Codex live inbox startup watcher installed (${startup.path})`);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
changes.push('Codex live inbox startup watcher skipped (run `greprag codex startup install` later)');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
changes.push('Codex live inbox startup watcher not installed (run `greprag codex startup install`)');
|
|
543
|
+
}
|
|
544
|
+
console.log('\n Setup complete!\n');
|
|
545
|
+
for (const change of changes) {
|
|
546
|
+
console.log(` - ${change}`);
|
|
547
|
+
}
|
|
548
|
+
console.log(`\n Codex hooks file: ${hooksPath}`);
|
|
549
|
+
console.log(` Project anchor: ${anchor.anchorPath}`);
|
|
550
|
+
console.log(' In Codex Desktop, open Settings -> Settings -> Hooks and trust the 6 GrepRAG hook definitions.');
|
|
551
|
+
console.log(' For live inbox push, run: greprag codex startup install');
|
|
552
|
+
console.log(' Memory hooks will activate on your next Codex session.\n');
|
|
553
|
+
}
|
|
340
554
|
/** greprag init --global
|
|
341
|
-
* Creates ~/.
|
|
555
|
+
* Creates ~/.greprag/project.json with a stable UUID.
|
|
342
556
|
* Designed for Cowork / any agent session whose cwd has no repo-level anchor.
|
|
343
557
|
* Idempotent — safe to run again; returns the existing anchor if already present. */
|
|
344
558
|
async function runGlobalInit(name) {
|
|
@@ -356,7 +570,7 @@ async function runGlobalInit(name) {
|
|
|
356
570
|
}
|
|
357
571
|
/** greprag init --all
|
|
358
572
|
* Bulk-register every git repo at depth 1 under a scan root (default: parent
|
|
359
|
-
* of cwd). Per-repo work: ensureAnchor (writes .
|
|
573
|
+
* of cwd). Per-repo work: ensureAnchor (writes .greprag/project.json or relies
|
|
360
574
|
* on the git-derived cascade), writeProjectPath (~/.greprag/projects.json),
|
|
361
575
|
* inbox registration (so `send --to <name>@.../<project-name>` resolves), and
|
|
362
576
|
* gitignore patch when the anchor file is load-bearing for identity.
|
|
@@ -367,11 +581,9 @@ async function runGlobalInit(name) {
|
|
|
367
581
|
async function runInitAll(rootPath) {
|
|
368
582
|
console.log('\n greprag init --all — Register every git repo for inbox addressing\n');
|
|
369
583
|
// 1. Existing API key required.
|
|
370
|
-
const
|
|
371
|
-
const settings = readSettings(settingsPath);
|
|
372
|
-
const apiKey = settings.env?.GREPRAG_API_KEY;
|
|
584
|
+
const apiKey = readSharedApiKey();
|
|
373
585
|
if (!apiKey) {
|
|
374
|
-
console.error(' Error: no GREPRAG_API_KEY in ~/.claude/settings.json.');
|
|
586
|
+
console.error(' Error: no GREPRAG_API_KEY in ~/.greprag/.env or ~/.claude/settings.json.');
|
|
375
587
|
console.error(' Run `greprag init` once to set up the key + hooks, then re-run init --all.');
|
|
376
588
|
process.exit(1);
|
|
377
589
|
}
|
|
@@ -587,6 +799,10 @@ function getSettingsPath() {
|
|
|
587
799
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
588
800
|
return path.join(home, '.claude', 'settings.json');
|
|
589
801
|
}
|
|
802
|
+
function getGrepragEnvPath() {
|
|
803
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
804
|
+
return path.join(home, '.greprag', '.env');
|
|
805
|
+
}
|
|
590
806
|
function readSettings(settingsPath) {
|
|
591
807
|
try {
|
|
592
808
|
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
@@ -603,6 +819,221 @@ function writeSettings(settingsPath, settings) {
|
|
|
603
819
|
}
|
|
604
820
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
605
821
|
}
|
|
822
|
+
function writeGrepragEnv(apiKey) {
|
|
823
|
+
const file = getGrepragEnvPath();
|
|
824
|
+
const dir = path.dirname(file);
|
|
825
|
+
let existing = {};
|
|
826
|
+
try {
|
|
827
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
828
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
829
|
+
const trimmed = line.trim();
|
|
830
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
831
|
+
continue;
|
|
832
|
+
const idx = trimmed.indexOf('=');
|
|
833
|
+
if (idx < 1)
|
|
834
|
+
continue;
|
|
835
|
+
existing[trimmed.slice(0, idx)] = trimmed.slice(idx + 1);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
catch { /* missing — write fresh */ }
|
|
839
|
+
existing.GREPRAG_API_KEY = apiKey;
|
|
840
|
+
existing.MEMORY_HOOK_ENABLED = 'true';
|
|
841
|
+
if (!fs.existsSync(dir))
|
|
842
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
843
|
+
const body = Object.entries(existing)
|
|
844
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
845
|
+
.join('\n') + '\n';
|
|
846
|
+
fs.writeFileSync(file, body);
|
|
847
|
+
}
|
|
848
|
+
function readEnvFile(file) {
|
|
849
|
+
const out = {};
|
|
850
|
+
try {
|
|
851
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
852
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
853
|
+
const trimmed = line.trim();
|
|
854
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
855
|
+
continue;
|
|
856
|
+
const idx = trimmed.indexOf('=');
|
|
857
|
+
if (idx < 1)
|
|
858
|
+
continue;
|
|
859
|
+
let value = trimmed.slice(idx + 1).trim();
|
|
860
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
861
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
862
|
+
value = value.slice(1, -1);
|
|
863
|
+
}
|
|
864
|
+
out[trimmed.slice(0, idx).trim()] = value;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
catch { /* missing env file */ }
|
|
868
|
+
return out;
|
|
869
|
+
}
|
|
870
|
+
function readSharedApiKey() {
|
|
871
|
+
return process.env.GREPRAG_API_KEY
|
|
872
|
+
|| readEnvFile(getGrepragEnvPath()).GREPRAG_API_KEY
|
|
873
|
+
|| readSettings(getSettingsPath()).env?.GREPRAG_API_KEY;
|
|
874
|
+
}
|
|
875
|
+
async function cacheIdentity(apiKey) {
|
|
876
|
+
try {
|
|
877
|
+
const { fetchIdentity, writeCache } = await Promise.resolve().then(() => __importStar(require('./identity')));
|
|
878
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
879
|
+
const cachePath = path.join(home, '.greprag', 'identity.json');
|
|
880
|
+
const ident = await fetchIdentity('https://api.greprag.com', apiKey);
|
|
881
|
+
writeCache(cachePath, ident);
|
|
882
|
+
if (ident.handle)
|
|
883
|
+
console.log(` Identity: ${ident.handle}`);
|
|
884
|
+
}
|
|
885
|
+
catch (e) {
|
|
886
|
+
console.log(` Identity cache deferred (${e.message})`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
function getSkillRoot() {
|
|
890
|
+
return path.join(__dirname, '../../skill');
|
|
891
|
+
}
|
|
892
|
+
function skillTargetRoot(target) {
|
|
893
|
+
const dir = target === 'codex' ? '.codex' : '.claude';
|
|
894
|
+
return path.join(os.homedir(), dir, 'skills');
|
|
895
|
+
}
|
|
896
|
+
function installCoreSkill(target) {
|
|
897
|
+
const srcDir = path.join(getSkillRoot(), 'greprag');
|
|
898
|
+
if (!fs.existsSync(srcDir))
|
|
899
|
+
return null;
|
|
900
|
+
const destDir = path.join(skillTargetRoot(target), 'greprag');
|
|
901
|
+
copySkillDir(srcDir, destDir);
|
|
902
|
+
return destDir;
|
|
903
|
+
}
|
|
904
|
+
function getCodexHooksPath() {
|
|
905
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
906
|
+
return path.join(home, '.codex', 'hooks.json');
|
|
907
|
+
}
|
|
908
|
+
function readCodexHooks(hooksPath) {
|
|
909
|
+
try {
|
|
910
|
+
const raw = fs.readFileSync(hooksPath, 'utf-8');
|
|
911
|
+
return JSON.parse(raw);
|
|
912
|
+
}
|
|
913
|
+
catch {
|
|
914
|
+
return {};
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function writeCodexHooks(hooksPath, hooks) {
|
|
918
|
+
const dir = path.dirname(hooksPath);
|
|
919
|
+
if (!fs.existsSync(dir))
|
|
920
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
921
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooks, null, 2) + '\n');
|
|
922
|
+
}
|
|
923
|
+
function applyCodexHooks(config) {
|
|
924
|
+
const changes = [];
|
|
925
|
+
if (!config.hooks)
|
|
926
|
+
config.hooks = {};
|
|
927
|
+
const recapHook = {
|
|
928
|
+
matcher: 'startup|resume|clear|compact',
|
|
929
|
+
hooks: [{
|
|
930
|
+
type: 'command',
|
|
931
|
+
command: 'greprag-hook recap',
|
|
932
|
+
timeout: 3,
|
|
933
|
+
statusMessage: 'Loading GrepRAG memory',
|
|
934
|
+
}],
|
|
935
|
+
};
|
|
936
|
+
if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'recap', recapHook.matcher)) {
|
|
937
|
+
if (!config.hooks.SessionStart)
|
|
938
|
+
config.hooks.SessionStart = [];
|
|
939
|
+
config.hooks.SessionStart.push(recapHook);
|
|
940
|
+
changes.push('Added Codex SessionStart hook (memory recap)');
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
changes.push('Codex SessionStart recap hook already configured (skipped)');
|
|
944
|
+
}
|
|
945
|
+
const sessionHook = {
|
|
946
|
+
matcher: 'startup|resume|clear|compact',
|
|
947
|
+
hooks: [{
|
|
948
|
+
type: 'command',
|
|
949
|
+
command: 'greprag-hook session-id',
|
|
950
|
+
timeout: 3,
|
|
951
|
+
statusMessage: 'Loading GrepRAG session id',
|
|
952
|
+
}],
|
|
953
|
+
};
|
|
954
|
+
if (!hasGrepragHookWithMatcher(config.hooks.SessionStart, 'session-id', sessionHook.matcher)) {
|
|
955
|
+
if (!config.hooks.SessionStart)
|
|
956
|
+
config.hooks.SessionStart = [];
|
|
957
|
+
config.hooks.SessionStart.push(sessionHook);
|
|
958
|
+
changes.push('Added Codex SessionStart hook (session-id awareness)');
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
changes.push('Codex SessionStart session-id hook already configured (skipped)');
|
|
962
|
+
}
|
|
963
|
+
const notifyHook = {
|
|
964
|
+
matcher: '',
|
|
965
|
+
hooks: [{
|
|
966
|
+
type: 'command',
|
|
967
|
+
command: 'greprag-hook codex-notify',
|
|
968
|
+
timeout: 3,
|
|
969
|
+
statusMessage: 'Checking GrepRAG inbox',
|
|
970
|
+
}],
|
|
971
|
+
};
|
|
972
|
+
if (!hasGrepragHook(config.hooks.UserPromptSubmit, 'codex-notify')) {
|
|
973
|
+
if (!config.hooks.UserPromptSubmit)
|
|
974
|
+
config.hooks.UserPromptSubmit = [];
|
|
975
|
+
config.hooks.UserPromptSubmit.push(notifyHook);
|
|
976
|
+
changes.push('Added Codex UserPromptSubmit hook (prompt cache + inbox steering)');
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
changes.push('Codex UserPromptSubmit hook already configured (skipped)');
|
|
980
|
+
}
|
|
981
|
+
const inboxHook = {
|
|
982
|
+
matcher: '',
|
|
983
|
+
hooks: [{
|
|
984
|
+
type: 'command',
|
|
985
|
+
command: 'greprag-hook codex-inbox',
|
|
986
|
+
timeout: 3,
|
|
987
|
+
statusMessage: 'Checking GrepRAG inbox',
|
|
988
|
+
}],
|
|
989
|
+
};
|
|
990
|
+
if (!hasGrepragHook(config.hooks.PostToolUse, 'codex-inbox')) {
|
|
991
|
+
if (!config.hooks.PostToolUse)
|
|
992
|
+
config.hooks.PostToolUse = [];
|
|
993
|
+
config.hooks.PostToolUse.push(inboxHook);
|
|
994
|
+
changes.push('Added Codex PostToolUse hook (inbox steering)');
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
changes.push('Codex PostToolUse inbox hook already configured (skipped)');
|
|
998
|
+
}
|
|
999
|
+
const storeHook = {
|
|
1000
|
+
matcher: '',
|
|
1001
|
+
hooks: [{
|
|
1002
|
+
type: 'command',
|
|
1003
|
+
command: 'greprag-hook codex-store',
|
|
1004
|
+
timeout: 10,
|
|
1005
|
+
statusMessage: 'Storing GrepRAG turn',
|
|
1006
|
+
}],
|
|
1007
|
+
};
|
|
1008
|
+
if (!hasGrepragHook(config.hooks.Stop, 'codex-store')) {
|
|
1009
|
+
if (!config.hooks.Stop)
|
|
1010
|
+
config.hooks.Stop = [];
|
|
1011
|
+
config.hooks.Stop.push(storeHook);
|
|
1012
|
+
changes.push('Added Codex Stop hook (memory store)');
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
changes.push('Codex Stop hook already configured (skipped)');
|
|
1016
|
+
}
|
|
1017
|
+
const postCompactHook = {
|
|
1018
|
+
matcher: 'manual|auto',
|
|
1019
|
+
hooks: [{
|
|
1020
|
+
type: 'command',
|
|
1021
|
+
command: 'greprag-hook session-id',
|
|
1022
|
+
timeout: 3,
|
|
1023
|
+
statusMessage: 'Restoring GrepRAG session id',
|
|
1024
|
+
}],
|
|
1025
|
+
};
|
|
1026
|
+
if (!hasGrepragHookWithMatcher(config.hooks.PostCompact, 'session-id', postCompactHook.matcher)) {
|
|
1027
|
+
if (!config.hooks.PostCompact)
|
|
1028
|
+
config.hooks.PostCompact = [];
|
|
1029
|
+
config.hooks.PostCompact.push(postCompactHook);
|
|
1030
|
+
changes.push('Added Codex PostCompact hook (session-id awareness)');
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
changes.push('Codex PostCompact session-id hook already configured (skipped)');
|
|
1034
|
+
}
|
|
1035
|
+
return changes;
|
|
1036
|
+
}
|
|
606
1037
|
function applySettings(settings, apiKey) {
|
|
607
1038
|
const changes = [];
|
|
608
1039
|
// Env vars
|
|
@@ -812,14 +1243,14 @@ function patchGlobalClaudeMd() {
|
|
|
812
1243
|
return true;
|
|
813
1244
|
}
|
|
814
1245
|
// -- Anchor trackability ----------------------------------------------------
|
|
815
|
-
/** When a repo gitignores `.
|
|
1246
|
+
/** When a repo gitignores `.greprag/`, the anchor file we just wrote won't
|
|
816
1247
|
* survive a fresh clone or worktree switch — the deterministic-hash fallback
|
|
817
1248
|
* will silently take over, splitting the project_id and breaking inbox
|
|
818
1249
|
* addressing. Detect that case at init time and patch the gitignore so the
|
|
819
1250
|
* anchor is durably tracked.
|
|
820
1251
|
*
|
|
821
|
-
* Common patterns we handle: `.
|
|
822
|
-
* Each is rewritten to its `*`-contents form so a `!.
|
|
1252
|
+
* Common patterns we handle: `.greprag/`, `.greprag`, `/.greprag/`, `/.greprag`.
|
|
1253
|
+
* Each is rewritten to its `*`-contents form so a `!.greprag/project.json`
|
|
823
1254
|
* re-include works (git won't descend into an excluded directory, so the
|
|
824
1255
|
* trailing-slash directory pattern must become a contents-only pattern).
|
|
825
1256
|
*
|
|
@@ -838,7 +1269,7 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
838
1269
|
// 1 when not ignored. Either non-zero exit means "no work to do."
|
|
839
1270
|
let checkOutput;
|
|
840
1271
|
try {
|
|
841
|
-
checkOutput = (0, child_process_1.execSync)('git check-ignore -v .
|
|
1272
|
+
checkOutput = (0, child_process_1.execSync)('git check-ignore -v .greprag/project.json', {
|
|
842
1273
|
cwd: projectRoot,
|
|
843
1274
|
encoding: 'utf-8',
|
|
844
1275
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -862,7 +1293,7 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
862
1293
|
return null;
|
|
863
1294
|
const trimmed = pattern.trim();
|
|
864
1295
|
const dirPatterns = new Set([
|
|
865
|
-
'.
|
|
1296
|
+
'.greprag/', '.greprag', '/.greprag/', '/.greprag',
|
|
866
1297
|
]);
|
|
867
1298
|
if (dirPatterns.has(trimmed)) {
|
|
868
1299
|
// Preserve leading slash if present in the original pattern.
|
|
@@ -871,13 +1302,13 @@ function ensureAnchorTrackable(projectRoot) {
|
|
|
871
1302
|
// Replace the single directory-ignore line with the contents-ignore form
|
|
872
1303
|
// plus the re-include for project.json. Git evaluates patterns in order
|
|
873
1304
|
// and re-includes override later, so the order matters: `*` first, then `!`.
|
|
874
|
-
lines.splice(lineNum - 1, 1, `${prefix}.
|
|
1305
|
+
lines.splice(lineNum - 1, 1, `${prefix}.greprag/*`, `!${prefix}.greprag/project.json`);
|
|
875
1306
|
fs.writeFileSync(gitignorePath, lines.join('\n'));
|
|
876
|
-
return `Patched ${gitignoreRel}: \`${trimmed}\`
|
|
1307
|
+
return `Patched ${gitignoreRel}: \`${trimmed}\` -> \`${prefix}.greprag/*\` + \`!${prefix}.greprag/project.json\` (anchor now trackable)`;
|
|
877
1308
|
}
|
|
878
1309
|
// Unrecognized pattern — don't risk breaking the user's other intent. Tell
|
|
879
1310
|
// them what to do manually.
|
|
880
|
-
return `WARNING: .
|
|
1311
|
+
return `WARNING: .greprag/project.json is ignored by \`${pattern}\` in ${gitignoreRel}. The anchor won't survive a fresh clone. Add \`!.greprag/project.json\` after that line manually, or run \`git add -f .greprag/project.json\` to force-track it once.`;
|
|
881
1312
|
}
|
|
882
1313
|
// -- Input ------------------------------------------------------------------
|
|
883
1314
|
/** Recursively copy a skill directory from the package bundle into
|