greprag 5.82.0 → 5.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-lifecycle-cutover.js +5 -16
- package/dist/commands/codex.js +4 -0
- 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.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 +1079 -159
- 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 +27 -13
- package/skill/greprag/docs/corpus.md +2 -2
- package/skill/greprag/docs/setup.md +1 -1
- package/skill/mechanic/SKILL.md +14 -39
- package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
- package/skill/templates/chip-spawn.md +36 -20
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* plumbing that happens to share a prefix, and `merge-base` is used BY the
|
|
23
23
|
* provenance check — blocking it would break the gate with the guard.
|
|
24
24
|
* · only a merge nothing already serialises. A command that takes the lock
|
|
25
|
-
* itself
|
|
25
|
+
* itself and stops on acquisition failure is already serialised.
|
|
26
26
|
*
|
|
27
27
|
* Holding the lock yourself is a pass. A peer holding it is the refusal that
|
|
28
28
|
* matters: it is the exact collision, caught before the merge instead of after.
|
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
* trips. Only `&&` earns this: after `;` or `||` the merge still runs when the
|
|
52
52
|
* acquire failed, so those are refused, and the refusal now says so.
|
|
53
53
|
*
|
|
54
|
+
* A THIRD, 2026-10-06 in C:/sift: an acquire run from a linked worktree wrote the
|
|
55
|
+
* lock into that worktree's private git dir, so this guard, reading the canonical
|
|
56
|
+
* checkout, refused the holder's own merge whatever its shape. Locks now live in the
|
|
57
|
+
* repository's shared `.git`, and a chained acquire counts when it writes the same
|
|
58
|
+
* lock file this guard reads.
|
|
59
|
+
*
|
|
54
60
|
* # adr: adr/deploy-gate-cli.md
|
|
55
61
|
*/
|
|
56
62
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -126,11 +132,11 @@ function shellSegments(command) {
|
|
|
126
132
|
let start = 0;
|
|
127
133
|
let afterAnd = true;
|
|
128
134
|
for (let match = separator.exec(masked); match; match = separator.exec(masked)) {
|
|
129
|
-
segments.push({ text: source.slice(start, match.index), afterAnd });
|
|
135
|
+
segments.push({ text: source.slice(start, match.index), start, afterAnd });
|
|
130
136
|
afterAnd = match[0] === '&&';
|
|
131
137
|
start = match.index + match[0].length;
|
|
132
138
|
}
|
|
133
|
-
segments.push({ text: source.slice(start), afterAnd });
|
|
139
|
+
segments.push({ text: source.slice(start), start, afterAnd });
|
|
134
140
|
return segments;
|
|
135
141
|
}
|
|
136
142
|
/** Quote-aware tokens. A quoted run is one token with its quotes removed, so
|
|
@@ -297,9 +303,10 @@ function buildMergeGuardReason(held, ownership) {
|
|
|
297
303
|
' greprag merge-lock acquire --label "<what you are merging>"',
|
|
298
304
|
' git merge --ff-only <rebased branch>',
|
|
299
305
|
' greprag merge-lock release --landed <the merge sha> --label "<what it was>"',
|
|
300
|
-
'One call works too
|
|
301
|
-
'
|
|
302
|
-
'
|
|
306
|
+
'One call works too when the acquire comes first and failure stops the merge:',
|
|
307
|
+
'use an `&&` chain, or in PowerShell a try/finally block with an immediate',
|
|
308
|
+
'`if ($LASTEXITCODE -ne 0) { throw ... }` after acquire. Bare `;` and `||`',
|
|
309
|
+
'let the merge run after a failed acquire, so those are refused.',
|
|
303
310
|
'`--landed` is what lets the deploy prove YOUR work is in what ships, rather than only',
|
|
304
311
|
'that production serves HEAD. Merging in a worktree instead? That is never blocked.',
|
|
305
312
|
].join(' ');
|
|
@@ -357,7 +364,8 @@ function guardMergeAt(input, segments, mergeAt) {
|
|
|
357
364
|
// truncating it locally to 8 was a second copy of identity logic waiting to disagree
|
|
358
365
|
// with the one in session-id.ts.
|
|
359
366
|
const ownership = (0, deploy_locks_1.lockOwnership)(held, input.session_id);
|
|
360
|
-
const chainedLock = takesLockForTarget(segments, mergeAt, cwd, target)
|
|
367
|
+
const chainedLock = takesLockForTarget(segments, mergeAt, cwd, target)
|
|
368
|
+
|| powerShellTryTakesLock(input.tool_input?.command ?? input.tool_input?.cmd ?? '', segments[mergeAt].start, cwd, target);
|
|
361
369
|
if (ownership !== 'mine' && !chainedLock)
|
|
362
370
|
return denyMerge(input, buildMergeGuardReason(held, ownership));
|
|
363
371
|
// Git owns the final freshness check AT mutation time. Even if master changes
|
|
@@ -403,11 +411,88 @@ function takesLockForTarget(segments, mergeAt, cwd, target) {
|
|
|
403
411
|
return false;
|
|
404
412
|
if (!isMergeLockAcquire(segments[i - 1].text))
|
|
405
413
|
continue;
|
|
406
|
-
|
|
407
|
-
|
|
414
|
+
return acquiresTargetLock(mergeCwd(segments, i - 1, cwd), target);
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
/** An acquire run in `dir` serialises a merge into `target` exactly when it writes the
|
|
419
|
+
* lock the guard reads for `target`. The lock is repository-wide, so an acquire from a
|
|
420
|
+
* linked worktree counts; comparing worktree roots refused that holder on 2026-10-06. */
|
|
421
|
+
function acquiresTargetLock(dir, target) {
|
|
422
|
+
try {
|
|
423
|
+
return (0, deploy_gate_1.normalizePath)((0, deploy_locks_1.mergeLockPath)(dir)) === (0, deploy_gate_1.normalizePath)((0, deploy_locks_1.mergeLockPath)(target));
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/** PowerShell's `try { acquire; if ($LASTEXITCODE -ne 0) { throw ... }; merge }
|
|
430
|
+
* finally { release }` is a fail-closed lock sequence too. Inspect only direct
|
|
431
|
+
* statements in the try body; a bare semicolon or an acquire nested in a branch
|
|
432
|
+
* must not excuse a merge. */
|
|
433
|
+
function powerShellTryTakesLock(command, mergeStart, cwd, target) {
|
|
434
|
+
const masked = maskQuoted(command);
|
|
435
|
+
const tryStart = /(?:^|[;\n])\s*try\s*\{/gi;
|
|
436
|
+
for (let match = tryStart.exec(masked); match; match = tryStart.exec(masked)) {
|
|
437
|
+
const open = masked.indexOf('{', match.index);
|
|
438
|
+
const close = matchingBrace(masked, open);
|
|
439
|
+
if (close < 0 || mergeStart <= open || mergeStart >= close)
|
|
440
|
+
continue;
|
|
441
|
+
const suffix = masked.slice(close + 1);
|
|
442
|
+
const finallyMatch = /^\s*finally\s*\{/.exec(suffix);
|
|
443
|
+
if (!finallyMatch || matchingBrace(masked, close + 1 + finallyMatch[0].lastIndexOf('{')) < 0)
|
|
444
|
+
continue;
|
|
445
|
+
const statements = powerShellStatements(command, masked, open + 1, close);
|
|
446
|
+
const mergeIndex = statements.findIndex(s => mergeStart >= s.start && mergeStart < s.end);
|
|
447
|
+
if (mergeIndex < 2 || mergeGlobalOpts(statements[mergeIndex].text)?.length !== 0)
|
|
448
|
+
continue;
|
|
449
|
+
// A location change would make the lock's target ambiguous. The regular
|
|
450
|
+
// chained-command path already resolves simple cd/Set-Location sequences.
|
|
451
|
+
if (/(?:^|[;\n])\s*(?:cd|chdir|sl|Set-Location|Push-Location|Pop-Location|pushd|popd)\b/i.test(masked.slice(0, mergeStart)))
|
|
452
|
+
continue;
|
|
453
|
+
if (!acquiresTargetLock(cwd, target))
|
|
454
|
+
continue;
|
|
455
|
+
for (let i = mergeIndex - 2; i >= 0; i--) {
|
|
456
|
+
if (!isMergeLockAcquire(statements[i].text))
|
|
457
|
+
continue;
|
|
458
|
+
if (/[|&]/.test(maskQuoted(statements[i].text)))
|
|
459
|
+
continue;
|
|
460
|
+
if (!/^if\s*\(\s*\$LASTEXITCODE\s*-ne\s*0\s*\)\s*\{\s*throw\b[^{}]*\}\s*$/i.test(statements[i + 1].text.trim()))
|
|
461
|
+
continue;
|
|
462
|
+
if (statements.slice(i + 2, mergeIndex).some(s => /\bmerge-lock\s+release\b/i.test(s.text)))
|
|
463
|
+
continue;
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
408
466
|
}
|
|
409
467
|
return false;
|
|
410
468
|
}
|
|
469
|
+
function matchingBrace(masked, open) {
|
|
470
|
+
let depth = 0;
|
|
471
|
+
for (let i = open; i < masked.length; i++) {
|
|
472
|
+
if (masked[i] === '{')
|
|
473
|
+
depth++;
|
|
474
|
+
else if (masked[i] === '}' && --depth === 0)
|
|
475
|
+
return i;
|
|
476
|
+
}
|
|
477
|
+
return -1;
|
|
478
|
+
}
|
|
479
|
+
function powerShellStatements(command, masked, start, end) {
|
|
480
|
+
const statements = [];
|
|
481
|
+
let depth = 0;
|
|
482
|
+
let from = start;
|
|
483
|
+
for (let i = start; i < end; i++) {
|
|
484
|
+
if (masked[i] === '{')
|
|
485
|
+
depth++;
|
|
486
|
+
else if (masked[i] === '}')
|
|
487
|
+
depth--;
|
|
488
|
+
else if (depth === 0 && (masked[i] === ';' || masked[i] === '\n')) {
|
|
489
|
+
statements.push({ text: command.slice(from, i).trim(), start: from, end: i });
|
|
490
|
+
from = i + 1;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
statements.push({ text: command.slice(from, end).trim(), start: from, end });
|
|
494
|
+
return statements.filter(s => s.text);
|
|
495
|
+
}
|
|
411
496
|
function denyMerge(input, reason) {
|
|
412
497
|
return {
|
|
413
498
|
hookSpecificOutput: {
|
package/dist/commands/parity.js
CHANGED
|
@@ -80,10 +80,8 @@ const SEED_UNITS = [
|
|
|
80
80
|
{
|
|
81
81
|
name: 'chip-spawn',
|
|
82
82
|
followers: [
|
|
83
|
-
{ harness: 'claude-code', candidate: '~/.claude/docs/chip-spawn.md' },
|
|
84
83
|
{ harness: 'claude-code', candidate: 'packages/cli/skill/templates/chip-spawn.md' },
|
|
85
84
|
{ harness: 'codex', candidate: 'packages/cli/skill/templates/codex-chip-spawn.md' },
|
|
86
|
-
{ harness: 'codex', candidate: 'docs/codex-chip-spawn.md' },
|
|
87
85
|
{ harness: 'opencode', candidate: null, note: 'no dedicated opencode chip-spawn doc found — chips ride greprag opencode chip + templates/chip-leader-opencode.md covers the leader shape only' },
|
|
88
86
|
],
|
|
89
87
|
},
|