greprag 5.82.1 → 5.83.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/dist/claude-lifecycle-cutover.js +5 -16
- package/dist/commands/codex.js +4 -0
- package/dist/commands/collision-check.js +9 -6
- package/dist/commands/corpus/client.js +16 -0
- package/dist/commands/corpus/index.js +5 -2
- package/dist/commands/corpus/section.js +3 -3
- package/dist/commands/corpus/walk.js +3 -3
- package/dist/commands/delivery-reminder.js +13 -9
- package/dist/commands/delivery.js +64 -0
- package/dist/commands/docptr.js +10 -24
- package/dist/commands/init.js +4 -5
- package/dist/commands/load-primer-reminder.js +2 -2
- package/dist/commands/load.js +45 -12
- package/dist/commands/merge-guard.js +94 -9
- package/dist/commands/parity.js +0 -2
- package/dist/commands/pipe-wrap.js +4 -2
- package/dist/commands/skill-mirror-reminder.js +24 -6
- package/dist/commands/skill.js +24 -8
- package/dist/commands/skillgain.js +5 -1
- package/dist/commands/state-trigger.js +67 -31
- package/dist/delivery-config.js +69 -0
- package/dist/deploy-locks.js +18 -3
- package/dist/doc-mirror-client.js +39 -11
- package/dist/hook.js +35 -39
- package/dist/index.js +62 -27
- package/dist/native-adapter-refresh.js +114 -0
- package/dist/native-skill-adoption.js +20 -53
- package/dist/native-skill-mirror.js +130 -52
- package/dist/native-skill-pending.js +5 -2
- package/dist/opencode-plugin.bundle.js +1083 -160
- package/dist/opencode-plugin.js +6 -0
- package/dist/skill-activation-manifest.js +98 -22
- package/dist/skill-landing.js +6 -0
- package/dist/skill-mirror-files.js +59 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +4 -19
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/corpus.md +2 -2
- package/skill/greprag/docs/setup.md +1 -1
- package/skill/mechanic/SKILL.md +15 -40
- package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
- package/skill/templates/chip-spawn.md +36 -20
- package/skill/templates/delivery.md +19 -5
|
@@ -42,20 +42,6 @@ const path = __importStar(require("path"));
|
|
|
42
42
|
const native_skill_mirror_1 = require("./native-skill-mirror");
|
|
43
43
|
const skill_mirror_files_1 = require("./skill-mirror-files");
|
|
44
44
|
exports.CLAUDE_LIFECYCLE_SKILLS = ['commit', 'deploy', 'release'];
|
|
45
|
-
const STATE_FILE = '.greprag-mirror.json';
|
|
46
|
-
function readState(root) {
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(root, STATE_FILE), 'utf8'));
|
|
49
|
-
if (parsed?.version === 1 && parsed.skills && typeof parsed.skills === 'object')
|
|
50
|
-
return parsed;
|
|
51
|
-
}
|
|
52
|
-
catch { /* first targeted cutover */ }
|
|
53
|
-
return { version: 1, skills: {} };
|
|
54
|
-
}
|
|
55
|
-
function writeState(root, state) {
|
|
56
|
-
fs.mkdirSync(root, { recursive: true });
|
|
57
|
-
fs.writeFileSync(path.join(root, STATE_FILE), JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
58
|
-
}
|
|
59
45
|
function adapterFor(skill) {
|
|
60
46
|
const files = (0, native_skill_mirror_1.buildNativeSkillAdapter)('claude-code', skill, { allowClaudeCode: true });
|
|
61
47
|
if (!files || files.length !== 1 || files[0].path !== 'SKILL.md') {
|
|
@@ -88,10 +74,13 @@ function requiredSkills(skills, names) {
|
|
|
88
74
|
function applyClaudeLifecycleCutover(params) {
|
|
89
75
|
const homeDir = params.homeDir || os.homedir();
|
|
90
76
|
const root = (0, native_skill_mirror_1.nativeSkillRoot)('claude-code', homeDir);
|
|
91
|
-
const state = readState(root);
|
|
77
|
+
const state = (0, native_skill_mirror_1.readState)(root);
|
|
92
78
|
const names = params.names?.length ? params.names : exports.CLAUDE_LIFECYCLE_SKILLS;
|
|
93
79
|
const prepared = requiredSkills(params.skills, names).map(skill => {
|
|
94
80
|
const target = path.join(root, skill.skillName);
|
|
81
|
+
if ((0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(target)) {
|
|
82
|
+
throw new Error(`Claude skill folder is owned elsewhere (a link or another installer's package): ${skill.skillName}; not written.`);
|
|
83
|
+
}
|
|
95
84
|
const skillMd = path.join(target, 'SKILL.md');
|
|
96
85
|
const adapterFiles = adapterFor(skill);
|
|
97
86
|
const nextHash = (0, skill_mirror_files_1.computeSkillFileHash)(adapterFiles);
|
|
@@ -138,7 +127,7 @@ function applyClaudeLifecycleCutover(params) {
|
|
|
138
127
|
else
|
|
139
128
|
result.installed++;
|
|
140
129
|
}
|
|
141
|
-
writeState(root, state);
|
|
130
|
+
(0, native_skill_mirror_1.writeState)(root, state);
|
|
142
131
|
return result;
|
|
143
132
|
}
|
|
144
133
|
async function syncClaudeLifecycleCutover(params) {
|
package/dist/commands/codex.js
CHANGED
|
@@ -472,6 +472,10 @@ function printCodexWatcherRetired() {
|
|
|
472
472
|
async function runCodex(args) {
|
|
473
473
|
const sub = args[0];
|
|
474
474
|
if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
|
|
475
|
+
if (args[1] === 'chip')
|
|
476
|
+
return (0, command_1.runCodexChip)(['--help', ...args.slice(2)]);
|
|
477
|
+
if (args[1] === 'app-server')
|
|
478
|
+
return (0, codex_app_server_1.runCodexAppServer)(['--help', ...args.slice(2)]);
|
|
475
479
|
console.log(HELP);
|
|
476
480
|
return;
|
|
477
481
|
}
|
|
@@ -114,22 +114,25 @@ function dedupePeers(peers, announced) {
|
|
|
114
114
|
const union = Array.from(new Set([...announced, ...peers.map(p => p.short)]));
|
|
115
115
|
return { fresh, union };
|
|
116
116
|
}
|
|
117
|
+
// adr: adr/delivery-announce-pilot.md
|
|
117
118
|
/** The LOUD directive — same `additionalContext` shape as the arm directive,
|
|
118
|
-
* naming each peer + instructing message-then-worktree. PURE.
|
|
119
|
+
* naming each peer + instructing message-then-worktree. PURE.
|
|
120
|
+
* It asks no heads-up of Claude Code peers: Claude Code holds a message to a
|
|
121
|
+
* session in another permission mode for that session's user and may let it
|
|
122
|
+
* expire, and the sender cannot see modes, so silence there is not approval.
|
|
123
|
+
* Delivery is the per-harness Delivery announce's alone, so step 3 defers. */
|
|
119
124
|
function buildCollisionDirective(peers, myShort, alias) {
|
|
120
125
|
const handle = alias || '<handle>';
|
|
121
126
|
const lines = peers.map(p => ` · ${p.short}${p.title ? ` — "${p.title}"` : ''}`);
|
|
122
127
|
const plural = peers.length === 1 ? '' : 's';
|
|
123
128
|
return (`COLLISION DETECTED — ${peers.length} other live session${plural} working in this same repo right now:\n`
|
|
124
129
|
+ lines.join('\n') + '\n'
|
|
125
|
-
+ `Coordinate BEFORE touching shared files: (1) message each peer now with a contextual heads-up — `
|
|
130
|
+
+ `Coordinate BEFORE touching shared files: (1) message each peer above now with a contextual heads-up — `
|
|
126
131
|
+ `Codex peers: use \`codex_app.list_threads\` to find the task and its repo/workspace, then \`codex_app.send_message_to_thread\` to send the heads-up; `
|
|
127
|
-
+ `Claude Code peers: use your harness's own session list + send tools — the 8-hex ids above are GREPRAG addresses and are not accepted by them; `
|
|
128
132
|
+ `peers OUTSIDE this harness (opencode / Grok / another tenant): \`greprag send "…what you're about to touch…" --to ${handle}@greprag.com/<their-8hex> --from-session ${myShort}\` `
|
|
129
133
|
+ `(YOU compose and send it — this is a notice, not an auto-send; recipients reply only for a concrete conflict, silence is approval, and you never poll or wait; watcher rows are not the Codex peer source of truth); `
|
|
130
|
-
+ `(2) do your code work in a worktree on its own branch (\`git worktree add .claude/worktrees/<slug> -b <branch>\`); `
|
|
131
|
-
+ `(3) at
|
|
132
|
-
+ `sweep every ready same-repo commit, ignore all uncommitted and untracked work, resolve conflicts directly, and finish the declared push/deploy/release path. Never edit the shared default checkout concurrently.`);
|
|
134
|
+
+ `(2) do your code work in a worktree on its own branch (\`git worktree add .claude/worktrees/<slug> -b <branch>\`), never in the shared default checkout; `
|
|
135
|
+
+ `(3) at delivery, notify peers only as this harness's Delivery announce says.`);
|
|
133
136
|
}
|
|
134
137
|
/** Reduce the watcher list to OTHER live sessions that are NOT in my repo
|
|
135
138
|
* (cross-repo). Excludes self, same-repo collisions (those get the LOUD
|
|
@@ -47,6 +47,8 @@ exports.abortNotConfigured = abortNotConfigured;
|
|
|
47
47
|
exports.apiCall = apiCall;
|
|
48
48
|
exports.apiGet = apiGet;
|
|
49
49
|
exports.apiDelete = apiDelete;
|
|
50
|
+
exports.formatPassage = formatPassage;
|
|
51
|
+
exports.getMaxChars = getMaxChars;
|
|
50
52
|
exports.getFlag = getFlag;
|
|
51
53
|
exports.getFlags = getFlags;
|
|
52
54
|
exports.extractPositionals = extractPositionals;
|
|
@@ -143,6 +145,20 @@ async function apiDelete(url, apiKey, body) {
|
|
|
143
145
|
* Listing and search now answer from the same classification.
|
|
144
146
|
* adr: adr/searchable-store-kinds.md, adr: adr/corpus-tags.md */
|
|
145
147
|
exports.INTERNAL_KINDS = ['inbox', 'fix', 'docptr', 'skillgain', 'parity', 'doctrine'];
|
|
148
|
+
/** Body of one node for `walk` / `section`, indented under its header line.
|
|
149
|
+
* Full text by default — these commands exist to read the whole passage around
|
|
150
|
+
* a hit (search snippets stay short). `maxChars` is the explicit opt-in cut. */
|
|
151
|
+
function formatPassage(content, maxChars) {
|
|
152
|
+
const text = maxChars && maxChars > 0 && content.length > maxChars
|
|
153
|
+
? content.slice(0, maxChars) + `... [+${content.length - maxChars} chars]`
|
|
154
|
+
: content;
|
|
155
|
+
return text.split('\n').map((line) => ` ${line}`).join('\n');
|
|
156
|
+
}
|
|
157
|
+
/** `--max-chars N` → N, or undefined (full text). */
|
|
158
|
+
function getMaxChars(args) {
|
|
159
|
+
const raw = getFlag(args, '--max-chars');
|
|
160
|
+
return raw ? parseInt(raw, 10) : undefined;
|
|
161
|
+
}
|
|
146
162
|
// -- Arg helpers -----------------------------------------------------------
|
|
147
163
|
function getFlag(args, flag) {
|
|
148
164
|
const idx = args.indexOf(flag);
|
|
@@ -57,10 +57,13 @@ Subcommands:
|
|
|
57
57
|
--all = every store under the tenant;
|
|
58
58
|
--store repeatable; --kind filters to
|
|
59
59
|
one kind. Hits are labeled by store.
|
|
60
|
-
section <store> "<heading>"
|
|
60
|
+
section <store> "<heading>" [--max-chars N]
|
|
61
|
+
Every node under a heading (forgiving
|
|
61
62
|
match: any depth, dash/case-insensitive).
|
|
62
|
-
walk <store> <nodeId> [--before 2 --after 2]
|
|
63
|
+
walk <store> <nodeId> [--before 2 --after 2] [--max-chars N]
|
|
63
64
|
Show a node and its neighbors.
|
|
65
|
+
section/walk print full node text;
|
|
66
|
+
--max-chars N cuts each node.
|
|
64
67
|
status <store> Show write-side enrichment progress
|
|
65
68
|
(per-node vocabulary bridge).
|
|
66
69
|
refresh <store> Manually re-check a tracked source —
|
|
@@ -13,9 +13,10 @@ async function sectionCmd(args) {
|
|
|
13
13
|
const storeRef = args[0];
|
|
14
14
|
const headingPath = args[1];
|
|
15
15
|
if (!storeRef || !headingPath) {
|
|
16
|
-
console.error('Usage: greprag corpus section <storeIdOrName> "<heading>"\n e.g. greprag corpus section "Scientific Advertising" "Chapter 7"\n Forgiving match: any depth, case-/dash-insensitive, leaf prefix.');
|
|
16
|
+
console.error('Usage: greprag corpus section <storeIdOrName> "<heading>" [--max-chars N]\n e.g. greprag corpus section "Scientific Advertising" "Chapter 7"\n Forgiving match: any depth, case-/dash-insensitive, leaf prefix.');
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
|
+
const maxChars = (0, client_1.getMaxChars)(args);
|
|
19
20
|
const store = await (0, client_1.resolveStore)(storeRef, cfg.apiUrl, cfg.apiKey);
|
|
20
21
|
const url = `${cfg.apiUrl}/v1/stores/${store.id}/section?path=${encodeURIComponent(headingPath)}`;
|
|
21
22
|
const res = await (0, client_1.apiGet)(url, cfg.apiKey);
|
|
@@ -24,9 +25,8 @@ async function sectionCmd(args) {
|
|
|
24
25
|
console.log(`\n${store.name} · section "${headingPath}" — ${res.nodes.length} node${res.nodes.length === 1 ? '' : 's'}\n`);
|
|
25
26
|
for (const n of res.nodes) {
|
|
26
27
|
const heading = n.headingPath?.length ? ` · ${n.headingPath.join(' / ')}` : '';
|
|
27
|
-
const snippet = n.content.length > 200 ? n.content.slice(0, 197) + '...' : n.content;
|
|
28
28
|
console.log(` [${n.nodeId}] pos=${n.position}${heading}`);
|
|
29
|
-
console.log(
|
|
29
|
+
console.log(`${(0, client_1.formatPassage)(n.content, maxChars)}\n`);
|
|
30
30
|
}
|
|
31
31
|
if (res.nodes.length === 0) {
|
|
32
32
|
console.log('No nodes under that heading. Check a hit\'s headingPath from `corpus search`, or broaden the heading.');
|
|
@@ -13,11 +13,12 @@ async function walkCmd(args) {
|
|
|
13
13
|
const storeRef = args[0];
|
|
14
14
|
const nodeId = args[1];
|
|
15
15
|
if (!storeRef || !nodeId) {
|
|
16
|
-
console.error('Usage: greprag corpus walk <storeIdOrName> <nodeId> [--before 2 --after 2]');
|
|
16
|
+
console.error('Usage: greprag corpus walk <storeIdOrName> <nodeId> [--before 2 --after 2] [--max-chars N]');
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
const before = parseInt((0, client_1.getFlag)(args, '--before') || '2', 10);
|
|
20
20
|
const after = parseInt((0, client_1.getFlag)(args, '--after') || '2', 10);
|
|
21
|
+
const maxChars = (0, client_1.getMaxChars)(args);
|
|
21
22
|
const store = await (0, client_1.resolveStore)(storeRef, cfg.apiUrl, cfg.apiKey);
|
|
22
23
|
// Step 1: fetch the anchor node to learn its position.
|
|
23
24
|
const anchorRes = await (0, client_1.apiGet)(`${cfg.apiUrl}/v1/stores/${store.id}/by-node-id/${nodeId}`, cfg.apiKey);
|
|
@@ -33,8 +34,7 @@ async function walkCmd(args) {
|
|
|
33
34
|
const isAnchor = n.nodeId === nodeId;
|
|
34
35
|
const marker = isAnchor ? '★' : ' ';
|
|
35
36
|
const heading = n.headingPath?.length ? ` · ${n.headingPath.join(' / ')}` : '';
|
|
36
|
-
const snippet = n.content.length > 320 ? n.content.slice(0, 317) + '...' : n.content;
|
|
37
37
|
console.log(` ${marker} [${n.nodeId}] pos=${n.position}${heading}`);
|
|
38
|
-
console.log(
|
|
38
|
+
console.log(`${(0, client_1.formatPassage)(n.content, maxChars)}\n`);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -18,18 +18,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.deliveryControlModule = void 0;
|
|
19
19
|
exports.buildDeliveryAnnounce = buildDeliveryAnnounce;
|
|
20
20
|
// adr: adr/delivery-announce-pilot.md
|
|
21
|
-
/** How THIS harness
|
|
22
|
-
* differs per platform. Everything else about notifying (once,
|
|
23
|
-
* chips excluded, why
|
|
24
|
-
* belongs in `greprag load delivery`, not
|
|
21
|
+
/** How THIS harness treats same-repo peers at delivery — the one line that
|
|
22
|
+
* legitimately differs per platform. Everything else about notifying (once,
|
|
23
|
+
* fire-and-forget, chips excluded, why Claude Code sends nothing) is shared
|
|
24
|
+
* prose that belongs in `greprag load delivery`, not duplicated here. */
|
|
25
25
|
function coordinationLine(platform) {
|
|
26
|
+
// Claude Code holds a message to a session in another permission mode for that
|
|
27
|
+
// session's user and may let it expire, and the sender cannot see the mode. A
|
|
28
|
+
// notice there can silently not arrive, so its silence was never approval.
|
|
29
|
+
if (platform === 'claude-code') {
|
|
30
|
+
return 'Send same-repo peers no delivery notice: Git is the coordination. Never poll, wait on, or ask peer sessions for status.';
|
|
31
|
+
}
|
|
26
32
|
const how = platform === 'codex'
|
|
27
33
|
? 'codex_app.list_threads + codex_app.send_message_to_thread'
|
|
28
|
-
: platform === '
|
|
29
|
-
?
|
|
30
|
-
:
|
|
31
|
-
? 'the GrepRAG registry'
|
|
32
|
-
: 'your harness\'s own peer tools';
|
|
34
|
+
: platform === 'opencode'
|
|
35
|
+
? 'the GrepRAG registry'
|
|
36
|
+
: 'your harness\'s own peer tools';
|
|
33
37
|
// Codex keeps its one extra word: `wait_threads` is the specific tool a Codex
|
|
34
38
|
// session reaches for when it decides to wait for a reply it was told not to
|
|
35
39
|
// wait for. A named tool is a rule; "do not wait" alone was not enough.
|
|
@@ -1,12 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.runDelivery = runDelivery;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
4
39
|
const delivery_config_1 = require("../delivery-config");
|
|
5
40
|
const HELP = `greprag delivery — resolve the current repo's delivery authority
|
|
6
41
|
|
|
7
42
|
Usage:
|
|
8
43
|
greprag delivery resolve [--json]
|
|
9
44
|
greprag delivery status [--json]
|
|
45
|
+
greprag delivery init [--write] draft this repo's profile (report only unless --write;
|
|
46
|
+
never overwrites an existing profile)
|
|
10
47
|
|
|
11
48
|
Reads .greprag/delivery.json when present. During migration only, falls back to
|
|
12
49
|
docs/deploy.md, docs/releases.md, then CLAUDE.md. Provider commands remain in
|
|
@@ -17,6 +54,8 @@ function runDelivery(args) {
|
|
|
17
54
|
return;
|
|
18
55
|
}
|
|
19
56
|
const sub = args[0] || 'resolve';
|
|
57
|
+
if (sub === 'init')
|
|
58
|
+
return runInit(args.includes('--write'));
|
|
20
59
|
if (sub !== 'resolve' && sub !== 'status') {
|
|
21
60
|
console.error(`Unknown delivery command: ${sub}\n\n${HELP}`);
|
|
22
61
|
process.exitCode = 1;
|
|
@@ -56,3 +95,28 @@ function runDelivery(args) {
|
|
|
56
95
|
if (result.mode === 'missing' || result.mode === 'invalid')
|
|
57
96
|
process.exitCode = 1;
|
|
58
97
|
}
|
|
98
|
+
function runInit(write) {
|
|
99
|
+
const draft = (0, delivery_config_1.draftDeliveryProfile)(process.cwd());
|
|
100
|
+
if (!draft) {
|
|
101
|
+
console.error('Not inside a Git repository.');
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const target = (0, delivery_config_1.deliveryProfilePath)(draft.root);
|
|
106
|
+
if (fs.existsSync(target)) {
|
|
107
|
+
console.log(`Profile already exists: ${target}\nEdit it by hand; init never overwrites.`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const body = JSON.stringify(draft.profile, null, 2) + '\n';
|
|
111
|
+
console.log(`Draft profile for ${draft.root}:\n${body}`);
|
|
112
|
+
console.log(`Deploy signals: ${draft.providerSignals.join(', ') || 'none (commit-and-merge only)'}`);
|
|
113
|
+
for (const item of draft.todo)
|
|
114
|
+
console.log(` todo: ${item}`);
|
|
115
|
+
if (!write) {
|
|
116
|
+
console.log('\nReport only. Rerun with --write to save it.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
120
|
+
fs.writeFileSync(target, body);
|
|
121
|
+
console.log(`\nWrote ${target}. Commit it.`);
|
|
122
|
+
}
|
package/dist/commands/docptr.js
CHANGED
|
@@ -51,9 +51,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.runDocPtr = runDocPtr;
|
|
53
53
|
const fs = __importStar(require("node:fs"));
|
|
54
|
+
const path = __importStar(require("node:path"));
|
|
54
55
|
const proc_1 = require("../proc");
|
|
55
56
|
const project_anchor_1 = require("../project-anchor");
|
|
56
57
|
const docptr_refs_1 = require("../docptr-refs");
|
|
58
|
+
const doc_mirror_client_1 = require("../doc-mirror-client");
|
|
57
59
|
function getConfig() {
|
|
58
60
|
return {
|
|
59
61
|
apiUrl: process.env.GREPRAG_API_URL || 'https://api.greprag.com',
|
|
@@ -67,25 +69,6 @@ USAGE
|
|
|
67
69
|
Judge every tracked, eligible .md and register the core ones — the
|
|
68
70
|
one-time catch-up for docs that predate the system or are never edited.
|
|
69
71
|
Idempotent. --missing-only skips already-registered docs.`;
|
|
70
|
-
/** Mirror of hook.ts isDocPathEligible — the deterministic pre-filter (non-.md,
|
|
71
|
-
* dot-dirs, build trees, harness-owned CLAUDE.md/MEMORY.md). Duplicated because
|
|
72
|
-
* the CLI command files are self-contained (zero cross-file coupling). */
|
|
73
|
-
function isDocPathEligible(relPath) {
|
|
74
|
-
const p = relPath.replace(/\\/g, '/');
|
|
75
|
-
if (!p.toLowerCase().endsWith('.md'))
|
|
76
|
-
return false;
|
|
77
|
-
const segments = p.split('/');
|
|
78
|
-
const base = segments[segments.length - 1];
|
|
79
|
-
if (base === 'CLAUDE.md' || base === 'MEMORY.md')
|
|
80
|
-
return false;
|
|
81
|
-
for (const seg of segments.slice(0, -1)) {
|
|
82
|
-
if (seg.startsWith('.'))
|
|
83
|
-
return false;
|
|
84
|
-
if (seg === 'node_modules' || seg === 'dist' || seg === 'build' || seg === 'vendor')
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
72
|
const SNIPPET_CHARS = 2_000;
|
|
90
73
|
const EVENTS_PER_POST = 8;
|
|
91
74
|
/** Judging runs server-side in waitUntil (async, after the POST returns), so a
|
|
@@ -109,13 +92,16 @@ async function runDocPtr(args) {
|
|
|
109
92
|
const missingOnly = args.includes('--missing-only');
|
|
110
93
|
const cwd = process.cwd();
|
|
111
94
|
const anchor = (0, project_anchor_1.readAnchor)(cwd);
|
|
95
|
+
// Paths are relative to the checkout root, so a backfill run from a subfolder
|
|
96
|
+
// registers the same paths the hook and the doc mirror use.
|
|
97
|
+
const root = (0, doc_mirror_client_1.docPathRoot)(cwd);
|
|
112
98
|
// Enumerate tracked, eligible docs.
|
|
113
99
|
let tracked;
|
|
114
100
|
try {
|
|
115
101
|
const out = (0, proc_1.safeExecFileSync)('git', ['ls-files', '--', '*.md'], {
|
|
116
|
-
cwd, encoding: 'utf-8',
|
|
102
|
+
cwd: root, encoding: 'utf-8',
|
|
117
103
|
});
|
|
118
|
-
tracked = out.split('\n').map(s => s.trim()).filter(s => s &&
|
|
104
|
+
tracked = out.split('\n').map(s => s.trim()).filter(s => s && (0, doc_mirror_client_1.isDocMirrorEligible)(s));
|
|
119
105
|
}
|
|
120
106
|
catch {
|
|
121
107
|
console.error('Not a git repo (or git unavailable) — backfill needs `git ls-files`.');
|
|
@@ -158,7 +144,7 @@ async function runDocPtr(args) {
|
|
|
158
144
|
// manifest. Best-effort: an empty set yields empty manifests (docs read fresh).
|
|
159
145
|
let present = new Set();
|
|
160
146
|
try {
|
|
161
|
-
const all = (0, proc_1.safeExecFileSync)('git', ['ls-files'], { cwd, encoding: 'utf-8' });
|
|
147
|
+
const all = (0, proc_1.safeExecFileSync)('git', ['ls-files'], { cwd: root, encoding: 'utf-8' });
|
|
162
148
|
present = new Set(all.split('\n').map(s => s.trim().replace(/\\/g, '/')).filter(Boolean));
|
|
163
149
|
}
|
|
164
150
|
catch { /* not a git repo — manifests stay empty */ }
|
|
@@ -166,11 +152,11 @@ async function runDocPtr(args) {
|
|
|
166
152
|
const events = [];
|
|
167
153
|
for (const rel of targets) {
|
|
168
154
|
try {
|
|
169
|
-
const snippet = fs.readFileSync(rel, 'utf-8').slice(0, SNIPPET_CHARS);
|
|
155
|
+
const snippet = fs.readFileSync(path.join(root, rel), 'utf-8').slice(0, SNIPPET_CHARS);
|
|
170
156
|
if (!snippet.trim())
|
|
171
157
|
continue;
|
|
172
158
|
const p = rel.replace(/\\/g, '/');
|
|
173
|
-
events.push({ path: p, snippet, refs: present.size ? (0, docptr_refs_1.extractDocRefs)(snippet, p,
|
|
159
|
+
events.push({ path: p, snippet, refs: present.size ? (0, docptr_refs_1.extractDocRefs)(snippet, p, root, present) : [] });
|
|
174
160
|
}
|
|
175
161
|
catch { /* skip */ }
|
|
176
162
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1629,11 +1629,10 @@ function applySettings(settings, apiKey) {
|
|
|
1629
1629
|
}
|
|
1630
1630
|
}
|
|
1631
1631
|
// PreToolUse (pre-spawn-check) — fires BEFORE every `mcp__ccd_session__spawn_task`
|
|
1632
|
-
// call. Validates the call against
|
|
1633
|
-
// chip-
|
|
1634
|
-
//
|
|
1635
|
-
//
|
|
1636
|
-
// fails; the agent re-composes the call.
|
|
1632
|
+
// call. Validates the call against two machine-checkable rules from
|
|
1633
|
+
// `greprag load chip-spawn`: title prefix `Chip: ` and Block 1 (`git branch -m`
|
|
1634
|
+
// and `git worktree add` in prompt). Returns permissionDecision: "deny" with remediation when
|
|
1635
|
+
// either rule fails; the agent re-composes the call.
|
|
1637
1636
|
const preSpawnCheckHook = {
|
|
1638
1637
|
matcher: 'mcp__ccd_session__spawn_task',
|
|
1639
1638
|
hooks: [{ type: 'command', command: 'greprag-hook pre-spawn-check', timeout: 3000 }],
|
|
@@ -29,10 +29,10 @@ exports.LOAD_PRIMER = [
|
|
|
29
29
|
'• Pointers (below) name a trigger + the entry to load at that moment. Or browse the catalog yourself any time you suspect a method exists.',
|
|
30
30
|
].join('\n');
|
|
31
31
|
/** POINTER — the one standing chip pointer. Thin: trigger + load command. The
|
|
32
|
-
* method (Block 1
|
|
32
|
+
* method (Block 1 + report-back) and the multi-chip upgrade both live in the payload. */
|
|
33
33
|
exports.CHIP_SPAWN_POINTER = [
|
|
34
34
|
'[greprag chips — agent coordination is built in (messaging plumbing in the inbox primer).]',
|
|
35
|
-
'• About to spawn a chip (`spawn_task`)? → run `greprag load chip-spawn` FIRST for the method (Block 1 worktree +
|
|
35
|
+
'• About to spawn a chip (`spawn_task`)? → run `greprag load chip-spawn` FIRST for the method (Block 1 worktree + report-back); it carries the multi-chip upgrade path inline. Non-negotiable — the spawn validator rejects an unstructured chip.',
|
|
36
36
|
].join('\n');
|
|
37
37
|
/** POINTER — OpenCode's native chip path (the fix-chip era vehicle: `greprag
|
|
38
38
|
* opencode chip` HTTP spawn, goals, native child sessions). Supersedes the
|
package/dist/commands/load.js
CHANGED
|
@@ -51,6 +51,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
51
51
|
};
|
|
52
52
|
})();
|
|
53
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.bundleOwnsSkill = bundleOwnsSkill;
|
|
54
55
|
exports.runLoad = runLoad;
|
|
55
56
|
exports.normalizeLoadName = normalizeLoadName;
|
|
56
57
|
const fs = __importStar(require("fs"));
|
|
@@ -72,7 +73,7 @@ const LIBRARY = {
|
|
|
72
73
|
},
|
|
73
74
|
'chip-spawn': {
|
|
74
75
|
files: ['skill/templates/chip-spawn.md'],
|
|
75
|
-
purpose: 'How to spawn one chip correctly (Block 1 worktree +
|
|
76
|
+
purpose: 'How to spawn one chip correctly (Block 1 worktree + report-back). Carries the multi-chip upgrade path inline.',
|
|
76
77
|
},
|
|
77
78
|
'codex-chip-spawn': {
|
|
78
79
|
files: ['skill/templates/codex-chip-spawn.md'],
|
|
@@ -118,6 +119,13 @@ const LIBRARY = {
|
|
|
118
119
|
function payloadPath(file) {
|
|
119
120
|
return path.join(__dirname, '..', '..', file);
|
|
120
121
|
}
|
|
122
|
+
/** True when the CLI ships `name` as its own skill bundle (`skill/<name>/`) and
|
|
123
|
+
* serves it from this library. The bundle is that skill's canon, so its global
|
|
124
|
+
* id means the bundle; a mirror row under the same name can only be a copy of
|
|
125
|
+
* an installed bundle, frozen when it was captured. adr: adr/skill-adapter-never-canon.md */
|
|
126
|
+
function bundleOwnsSkill(name) {
|
|
127
|
+
return LIBRARY[name]?.files[0] === `skill/${name}/SKILL.md`;
|
|
128
|
+
}
|
|
121
129
|
// ---------- Tier 3 — the skill mirror (docs/load-system.md) -------------------
|
|
122
130
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
123
131
|
const MIRROR_TIMEOUT_MS = 4_000;
|
|
@@ -185,25 +193,28 @@ async function printCatalog() {
|
|
|
185
193
|
console.log(` ${'agents'.padEnd(width)} Deprecated alias for instructions.`);
|
|
186
194
|
// Tier-3 section: the operator's own skills, auto-mirrored from use.
|
|
187
195
|
const data = await mirrorGet('/v1/skillmirror');
|
|
188
|
-
|
|
196
|
+
// A mirror row under a bundled skill's name is a stale copy; the bundled
|
|
197
|
+
// entry above is that skill.
|
|
198
|
+
const skills = (data?.skills || [])
|
|
199
|
+
.filter((s) => !bundleOwnsSkill(s.skillName || ''));
|
|
189
200
|
if (skills.length > 0) {
|
|
190
201
|
// A mirrored skill with no native adapter on this harness never reaches the
|
|
191
202
|
// harness's own skill list — the agent can only reach it through this
|
|
192
203
|
// catalog. Mark the gap here and name the command that closes it.
|
|
204
|
+
// A hand-kept copy does reach that list, but the harness runs the copy and
|
|
205
|
+
// never sees canon move on, so it is marked too (with `~`).
|
|
193
206
|
const platform = (0, harness_1.inferCurrentHarness)() || '';
|
|
194
|
-
const
|
|
207
|
+
const native = platform
|
|
195
208
|
? (0, skill_activation_manifest_1.readNativeSkillNames)({ platform, cwd: process.cwd(), homeDir: os.homedir() })
|
|
196
|
-
: new
|
|
197
|
-
const missing = platform
|
|
198
|
-
? skills.map((s) => s.skillName || '').filter((n) => n && !installed.has(n.toLowerCase()))
|
|
199
|
-
: [];
|
|
209
|
+
: new Map();
|
|
210
|
+
const { missing, localCopies } = (0, skill_activation_manifest_1.nativeAdapterGap)(platform, skills, native);
|
|
200
211
|
const w = Math.max(...skills.map((s) => (s.skillName || '').length), 8);
|
|
201
212
|
console.log('\nYour mirrored skills (auto-fresh from use — work on any machine/harness):\n');
|
|
202
213
|
for (const s of skills) {
|
|
203
214
|
if (!s.skillName)
|
|
204
215
|
continue;
|
|
205
216
|
const scope = s.scope === 'project' && s.projectId ? ` [project:${s.projectId.slice(0, 8)}]` : '';
|
|
206
|
-
const gap = missing.includes(s.skillName) ? ' *' : '';
|
|
217
|
+
const gap = missing.includes(s.skillName) ? ' *' : localCopies.includes(s.skillName) ? ' ~' : '';
|
|
207
218
|
console.log(` ${(s.skillName + scope + gap).padEnd(w + 20)} ${(s.description || '').slice(0, 110)}`);
|
|
208
219
|
}
|
|
209
220
|
const installCommand = (0, skill_activation_manifest_1.nativeAdapterInstallCommand)(platform, missing);
|
|
@@ -212,6 +223,13 @@ async function printCatalog() {
|
|
|
212
223
|
console.log(' in this harness\'s own skill list. Install the adapters with:\n');
|
|
213
224
|
console.log(` ${installCommand}\n`);
|
|
214
225
|
}
|
|
226
|
+
const cutoverCommand = (0, skill_activation_manifest_1.localCopyCutoverCommand)(platform, localCopies);
|
|
227
|
+
if (cutoverCommand) {
|
|
228
|
+
console.log(`\n ~ ${localCopies.length} skill(s) run from a hand-kept copy in ${platform}, not an adapter — the harness`);
|
|
229
|
+
console.log(' keeps running that copy when the mirrored skill changes. Replacing a copy is a per-skill');
|
|
230
|
+
console.log(' call; its SKILL.md is backed up first:\n');
|
|
231
|
+
console.log(` ${cutoverCommand}\n`);
|
|
232
|
+
}
|
|
215
233
|
}
|
|
216
234
|
console.log('\n greprag load <name> print the full entry / mirrored skill');
|
|
217
235
|
console.log(' greprag load /<name> print a user-named slash skill');
|
|
@@ -375,8 +393,13 @@ async function printStateDocHints(name, skillBody) {
|
|
|
375
393
|
const data = await mirrorPost(`/v1/doc/${projectId}/search`, { query, limit: 5 });
|
|
376
394
|
if (!data)
|
|
377
395
|
return;
|
|
396
|
+
// This block calls what it lists authoritative, so it lists only what the
|
|
397
|
+
// mirror would accept today. Skill files mirrored before skill trees were
|
|
398
|
+
// excluded, or by an older CLI, stay until a full `greprag doc update` prunes
|
|
399
|
+
// them.
|
|
400
|
+
// adr: adr/doc-mirror-state-only.md
|
|
378
401
|
const docs = (data.docs || [])
|
|
379
|
-
.filter((d) => typeof d.path === 'string' && d.path
|
|
402
|
+
.filter((d) => typeof d.path === 'string' && (0, doc_mirror_client_1.isDocMirrorEligible)(d.path))
|
|
380
403
|
.slice(0, 5);
|
|
381
404
|
if (docs.length > 0) {
|
|
382
405
|
process.stdout.write('\n[STATE DOCS — relevant project state from the current repo mirror]\n');
|
|
@@ -484,12 +507,22 @@ async function runLoad(args) {
|
|
|
484
507
|
console.error('No canonical GrepRAG Instructions found. Seed them with `greprag instructions adopt <CLAUDE.md|AGENTS.md>`.');
|
|
485
508
|
process.exit(1);
|
|
486
509
|
}
|
|
487
|
-
|
|
510
|
+
// An explicit --skill-id names one mirrored skill, so it outranks a bundled
|
|
511
|
+
// entry that merely shares the name, and a miss is an error rather than a
|
|
512
|
+
// quiet fallback to the bundled payload. A skill the CLI ships as its own
|
|
513
|
+
// bundle is the exception: its global id is the bundle, so every harness's
|
|
514
|
+
// launcher reaches the same canon. adr: adr/skill-adapter-never-canon.md
|
|
515
|
+
const mirroredArgs = parseMirroredLoadArgs(args.slice(1));
|
|
516
|
+
const bundleCanon = mirroredArgs.skillId === `global:${name}` && bundleOwnsSkill(name);
|
|
517
|
+
const entry = mirroredArgs.skillId && !bundleCanon ? undefined : LIBRARY[name];
|
|
488
518
|
if (!entry) {
|
|
489
|
-
// Not bundled → try the Tier-3 skill mirror before giving up.
|
|
490
|
-
const mirroredArgs = parseMirroredLoadArgs(args.slice(1));
|
|
491
519
|
if (await printMirroredSkill(name, mirroredArgs))
|
|
492
520
|
return;
|
|
521
|
+
if (mirroredArgs.skillId) {
|
|
522
|
+
console.error(`No mirrored skill "${name}" with id ${mirroredArgs.skillId}.`);
|
|
523
|
+
process.exitCode = 1;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
493
526
|
console.error(`Unknown load entry: ${name}\n`);
|
|
494
527
|
await printCatalog();
|
|
495
528
|
process.exitCode = 1;
|