greprag 5.82.1 → 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.
Files changed (39) hide show
  1. package/dist/claude-lifecycle-cutover.js +5 -16
  2. package/dist/commands/codex.js +4 -0
  3. package/dist/commands/corpus/client.js +16 -0
  4. package/dist/commands/corpus/index.js +5 -2
  5. package/dist/commands/corpus/section.js +3 -3
  6. package/dist/commands/corpus/walk.js +3 -3
  7. package/dist/commands/delivery.js +64 -0
  8. package/dist/commands/docptr.js +10 -24
  9. package/dist/commands/init.js +4 -5
  10. package/dist/commands/load-primer-reminder.js +2 -2
  11. package/dist/commands/load.js +45 -12
  12. package/dist/commands/merge-guard.js +94 -9
  13. package/dist/commands/parity.js +0 -2
  14. package/dist/commands/pipe-wrap.js +4 -2
  15. package/dist/commands/skill-mirror-reminder.js +24 -6
  16. package/dist/commands/skill.js +24 -8
  17. package/dist/commands/skillgain.js +5 -1
  18. package/dist/commands/state-trigger.js +67 -31
  19. package/dist/delivery-config.js +69 -0
  20. package/dist/deploy-locks.js +18 -3
  21. package/dist/doc-mirror-client.js +39 -11
  22. package/dist/hook.js +35 -39
  23. package/dist/index.js +62 -27
  24. package/dist/native-adapter-refresh.js +114 -0
  25. package/dist/native-skill-adoption.js +20 -53
  26. package/dist/native-skill-mirror.js +130 -52
  27. package/dist/native-skill-pending.js +5 -2
  28. package/dist/opencode-plugin.bundle.js +1079 -159
  29. package/dist/opencode-plugin.js +6 -0
  30. package/dist/skill-activation-manifest.js +98 -22
  31. package/dist/skill-landing.js +6 -0
  32. package/dist/skill-mirror-files.js +59 -0
  33. package/package.json +1 -1
  34. package/scripts/postinstall.js +4 -19
  35. package/skill/greprag/docs/corpus.md +2 -2
  36. package/skill/greprag/docs/setup.md +1 -1
  37. package/skill/mechanic/SKILL.md +14 -39
  38. package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
  39. package/skill/templates/chip-spawn.md +36 -20
@@ -39,15 +39,25 @@ exports.NATIVE_SKILL_ADAPTER_PLATFORMS = void 0;
39
39
  exports.nativeSkillMirrorEnabled = nativeSkillMirrorEnabled;
40
40
  exports.nativeSkillRoot = nativeSkillRoot;
41
41
  exports.nativeProjectSkillRoot = nativeProjectSkillRoot;
42
+ exports.writerOwnedProjectSkillRoot = writerOwnedProjectSkillRoot;
42
43
  exports.assertNoNativeSkillCollision = assertNoNativeSkillCollision;
44
+ exports.readState = readState;
45
+ exports.writeState = writeState;
46
+ exports.targetPath = targetPath;
47
+ exports.filePath = filePath;
48
+ exports.readManagedFiles = readManagedFiles;
49
+ exports.nativeAdapterFormat = nativeAdapterFormat;
43
50
  exports.buildNativeSkillAdapter = buildNativeSkillAdapter;
51
+ exports.removeTrackedFiles = removeTrackedFiles;
44
52
  exports.applyNativeSkillMirror = applyNativeSkillMirror;
53
+ exports.fetchNativeMirroredSkill = fetchNativeMirroredSkill;
45
54
  exports.syncNativeSkillMirror = syncNativeSkillMirror;
46
55
  exports.nativeSkillMirrorSummary = nativeSkillMirrorSummary;
47
56
  const fs = __importStar(require("node:fs"));
48
57
  const os = __importStar(require("node:os"));
49
58
  const path = __importStar(require("node:path"));
50
59
  const skill_mirror_files_1 = require("./skill-mirror-files");
60
+ const skill_activation_manifest_1 = require("./skill-activation-manifest");
51
61
  const SKILL_NAME_RE = /^[A-Za-z0-9._-]{1,64}$/;
52
62
  const NATIVE_SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
53
63
  const PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -78,6 +88,18 @@ function nativeProjectSkillRoot(platform, projectDir) {
78
88
  return path.join(projectDir, '.agents', 'skills');
79
89
  return path.join(projectDir, '.opencode', 'skills');
80
90
  }
91
+ /** The project root a GrepRAG writer (sync, prune, adoption, pending capture)
92
+ * may own, or null. At the home directory the project paths are user-level
93
+ * roots owned by others: Codex reads `~/.agents/skills` as the user's skills,
94
+ * and `npx skills` installs there and links each package into
95
+ * `~/.claude/skills`. A sync run from home once adopted those packages, and
96
+ * its Codex launchers reached Claude through the links.
97
+ * adr: adr/skill-bundle-shape.md */
98
+ function writerOwnedProjectSkillRoot(platform, projectDir, homeDir = os.homedir()) {
99
+ if (path.resolve(projectDir).toLowerCase() === path.resolve(homeDir).toLowerCase())
100
+ return null;
101
+ return nativeProjectSkillRoot(platform, projectDir);
102
+ }
81
103
  function skillScope(skill) {
82
104
  return skill.scope === 'project' ? 'project' : 'global';
83
105
  }
@@ -122,7 +144,7 @@ function rootForSkill(platform, skill, homeDir, projectDir) {
122
144
  return nativeSkillRoot(platform, homeDir);
123
145
  if (!projectDir)
124
146
  return null;
125
- return nativeProjectSkillRoot(platform, projectDir);
147
+ return writerOwnedProjectSkillRoot(platform, projectDir, homeDir);
126
148
  }
127
149
  function emptyState() {
128
150
  return { version: 1, skills: {} };
@@ -364,11 +386,9 @@ function renderAdapterBody(platform, skill) {
364
386
  const identityArg = canonicalLookupArgs(skill);
365
387
  const editArgs = canonicalEditArgs(skill);
366
388
  const creationLocations = nativeCreationLocations(platform);
367
- const syncCommand = platform === 'claude-code'
368
- ? 'greprag skill mirror sync claude-code --cutover-lifecycle'
369
- : `greprag skill mirror sync ${platform}`;
389
+ const syncCommand = (0, skill_activation_manifest_1.nativeAdapterInstallCommand)(platform, [skillName]);
370
390
  const creationLine = platform === 'claude-code'
371
- ? '- **Create another skill:** the targeted lifecycle command does not adopt or manage other Claude skills; use the normal native creation path and leave it unmanaged until a separate Claude cutover is approved.'
391
+ ? '- **Create another skill:** use the normal native creation path and leave it unmanaged until a separate Claude cutover is approved.'
372
392
  : `- **Create another skill:** create a normal native skill at ${creationLocations}; run \`${syncCommand} --dry-run\` and confirm it says \`adopt\`; then run \`${syncCommand}\`. GrepRAG adopts it as canon and replaces the native package with an adapter.`;
373
393
  const companions = skill.files.map(file => file.path).filter(relative => relative !== 'SKILL.md').sort();
374
394
  const harnessArg = `--harness ${platform}`;
@@ -428,10 +448,7 @@ Read and follow that canonical payload before responding or taking action.
428
448
  ${companionLines}
429
449
  `;
430
450
  }
431
- function buildNativeSkillAdapter(platform, skill, options = {}) {
432
- if (!nativeSkillMirrorEnabled(platform)
433
- && !(platform === 'claude-code' && options.allowClaudeCode))
434
- return null;
451
+ function renderNativeSkillAdapter(platform, skill, stamps) {
435
452
  if (!NATIVE_SKILL_NAME_RE.test(skill.skillName))
436
453
  return null;
437
454
  if (!skill.files.some(file => file.path === 'SKILL.md')
@@ -450,8 +467,9 @@ function buildNativeSkillAdapter(platform, skill, options = {}) {
450
467
  ...(typeof skill.revision === 'number' ? [`greprag_revision: ${skill.revision}`] : []),
451
468
  ...(scope === 'project' && skill.projectId ? [`greprag_project_id: ${skill.projectId}`] : []),
452
469
  ...(skill.replaces?.length ? [`replaces: ${skill.replaces.join(', ')}`] : []),
470
+ ...stamps,
453
471
  ].join('\n');
454
- const content = `---
472
+ return `---
455
473
  name: ${skill.skillName}
456
474
  description: |-
457
475
  ${yamlLiteral(description)}
@@ -459,8 +477,57 @@ ${extraFrontmatter}
459
477
  ---
460
478
 
461
479
  ${renderAdapterBody(platform, skill)}`;
462
- return [{ path: 'SKILL.md', content }];
463
480
  }
481
+ // Any change to the adapter template changes how these probes render, so their
482
+ // fingerprint names the renderer that wrote an adapter. Without it a renderer fix
483
+ // never reaches adapters whose canon has not moved: on 2026-10-04 every Claude
484
+ // adapter kept naming a sync that could not refresh it until someone re-synced
485
+ // all 40 by hand.
486
+ const PROBE_PROJECT_ID = '00000000-0000-4000-8000-000000000000';
487
+ const ADAPTER_FORMAT_PROBES = [
488
+ {
489
+ skillName: 'format-probe',
490
+ skillId: 'global:format-probe',
491
+ contentHash: '0'.repeat(64),
492
+ revision: 1,
493
+ triggerDescription: 'Format probe.',
494
+ files: [{ path: 'SKILL.md', content: '# Format probe\n' }, { path: 'docs/probe.md', content: '' }],
495
+ },
496
+ {
497
+ skillName: 'format-probe',
498
+ skillId: `project:${PROBE_PROJECT_ID}:format-probe`,
499
+ scope: 'project',
500
+ projectId: PROBE_PROJECT_ID,
501
+ replaces: ['global:format-probe'],
502
+ triggerDescription: 'Format probe.',
503
+ files: [{ path: 'SKILL.md', content: '<!-- greprag-source-bindings -->\n' }],
504
+ },
505
+ ];
506
+ const adapterFormats = new Map();
507
+ /** Fingerprint of this CLI's adapter renderer for one harness. Every adapter
508
+ * carries it, so an adapter written by an older renderer reads as stale. */
509
+ function nativeAdapterFormat(platform) {
510
+ let format = adapterFormats.get(platform);
511
+ if (!format) {
512
+ format = (0, skill_mirror_files_1.computeSkillFileHash)(ADAPTER_FORMAT_PROBES.map((probe, index) => ({
513
+ path: String(index),
514
+ content: renderNativeSkillAdapter(platform, probe, []) || '',
515
+ }))).slice(0, 12);
516
+ adapterFormats.set(platform, format);
517
+ }
518
+ return format;
519
+ }
520
+ function buildNativeSkillAdapter(platform, skill, options = {}) {
521
+ if (!nativeSkillMirrorEnabled(platform)
522
+ && !(platform === 'claude-code' && options.allowClaudeCode))
523
+ return null;
524
+ const content = renderNativeSkillAdapter(platform, skill, [
525
+ `greprag_adapter_format: ${nativeAdapterFormat(platform)}`,
526
+ ]);
527
+ return content === null ? null : [{ path: 'SKILL.md', content }];
528
+ }
529
+ /** Remove the named files from a skill package, then any directory they leave
530
+ * empty. Every other file in the package stays. */
464
531
  function removeTrackedFiles(target, relativeFiles) {
465
532
  const directories = new Set();
466
533
  for (const relative of relativeFiles) {
@@ -539,6 +606,16 @@ function applyNativeSkillMirror(params) {
539
606
  }
540
607
  const target = targetPath(root, skill.skillName);
541
608
  const managed = rootEntry.state.skills[skill.skillName];
609
+ // A folder owned elsewhere (a link, another installer's package) is not
610
+ // ours; a managed name that became one is held for the operator, an
611
+ // unmanaged one is skipped like any package.
612
+ if ((0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(target)) {
613
+ if (managed)
614
+ result.blocked++;
615
+ else
616
+ result.skipped++;
617
+ continue;
618
+ }
542
619
  if (fs.existsSync(target) && !managed) {
543
620
  result.skipped++;
544
621
  continue;
@@ -580,8 +657,9 @@ function applyNativeSkillMirror(params) {
580
657
  const globalRoot = nativeSkillRoot(params.platform, homeDir);
581
658
  if (params.pruneGlobalRoot !== false)
582
659
  rootState(globalRoot);
583
- if (projectDir)
584
- rootState(nativeProjectSkillRoot(params.platform, projectDir));
660
+ const projectRoot = projectDir ? writerOwnedProjectSkillRoot(params.platform, projectDir, homeDir) : null;
661
+ if (projectRoot)
662
+ rootState(projectRoot);
585
663
  for (const [root, entry] of roots) {
586
664
  if (params.pruneGlobalRoot === false && root === globalRoot)
587
665
  continue;
@@ -589,7 +667,7 @@ function applyNativeSkillMirror(params) {
589
667
  if (entry.present.has(skillName))
590
668
  continue;
591
669
  const target = targetPath(root, skillName);
592
- const currentFiles = readManagedFiles(target, managed);
670
+ const currentFiles = (0, skill_mirror_files_1.isSkillFolderOwnedElsewhere)(target) ? null : readManagedFiles(target, managed);
593
671
  if (!currentFiles || (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) !== managed.hash) {
594
672
  result.blocked++;
595
673
  continue;
@@ -607,6 +685,43 @@ function applyNativeSkillMirror(params) {
607
685
  }
608
686
  return result;
609
687
  }
688
+ /** Fetch the canonical package behind one roster row. Null when the mirror
689
+ * cannot serve it. */
690
+ async function fetchNativeMirroredSkill(apiUrl, apiKey, row) {
691
+ const skillQs = new URLSearchParams();
692
+ if (row.skillId)
693
+ skillQs.set('skillId', row.skillId);
694
+ else if (row.scope) {
695
+ skillQs.set('scope', row.scope);
696
+ if (row.projectId)
697
+ skillQs.set('projectId', row.projectId);
698
+ }
699
+ const response = await fetch(`${apiUrl}/v1/skillmirror/${encodeURIComponent(row.skillName)}${skillQs.size ? `?${skillQs.toString()}` : ''}`, { headers: { Authorization: `Bearer ${apiKey}` } });
700
+ if (!response.ok)
701
+ return null;
702
+ const body = await response.json();
703
+ return Array.isArray(body.files) ? {
704
+ skillName: row.skillName,
705
+ skillId: typeof row.skillId === 'string' ? row.skillId : undefined,
706
+ scope: row.scope === 'project' ? 'project' : 'global',
707
+ projectId: typeof row.projectId === 'string' ? row.projectId : undefined,
708
+ projectName: typeof row.projectName === 'string' ? row.projectName : undefined,
709
+ replaces: Array.isArray(row.replaces) ? row.replaces : [],
710
+ contentHash: typeof body.contentHash === 'string'
711
+ ? body.contentHash
712
+ : typeof row.contentHash === 'string'
713
+ ? row.contentHash
714
+ : undefined,
715
+ revision: typeof body.revision === 'number'
716
+ ? body.revision
717
+ : typeof row.revision === 'number'
718
+ ? row.revision
719
+ : undefined,
720
+ files: body.files,
721
+ description: typeof row.description === 'string' ? row.description : undefined,
722
+ triggerDescription: typeof row.triggerDescription === 'string' ? row.triggerDescription : undefined,
723
+ } : null;
724
+ }
610
725
  async function syncNativeSkillMirror(params) {
611
726
  if (!nativeSkillMirrorEnabled(params.platform)) {
612
727
  return { installed: 0, updated: 0, current: 0, removed: 0, skipped: 0, blocked: 0 };
@@ -625,44 +740,7 @@ async function syncNativeSkillMirror(params) {
625
740
  const data = await list.json();
626
741
  const rows = (data.skills || [])
627
742
  .filter((row) => typeof row.skillName === 'string' && SKILL_NAME_RE.test(row.skillName));
628
- const fetched = await Promise.all(rows.map(async (row) => {
629
- const skillName = row.skillName;
630
- const skillQs = new URLSearchParams();
631
- if (row.skillId)
632
- skillQs.set('skillId', row.skillId);
633
- else if (row.scope) {
634
- skillQs.set('scope', row.scope);
635
- if (row.projectId)
636
- skillQs.set('projectId', row.projectId);
637
- }
638
- const response = await fetch(`${params.apiUrl}/v1/skillmirror/${encodeURIComponent(skillName)}${skillQs.size ? `?${skillQs.toString()}` : ''}`, {
639
- headers: { Authorization: `Bearer ${params.apiKey}` },
640
- });
641
- if (!response.ok)
642
- return null;
643
- const body = await response.json();
644
- return Array.isArray(body.files) ? {
645
- skillName,
646
- skillId: typeof row.skillId === 'string' ? row.skillId : undefined,
647
- scope: row.scope === 'project' ? 'project' : 'global',
648
- projectId: typeof row.projectId === 'string' ? row.projectId : undefined,
649
- projectName: typeof row.projectName === 'string' ? row.projectName : undefined,
650
- replaces: Array.isArray(row.replaces) ? row.replaces : [],
651
- contentHash: typeof body.contentHash === 'string'
652
- ? body.contentHash
653
- : typeof row.contentHash === 'string'
654
- ? row.contentHash
655
- : undefined,
656
- revision: typeof body.revision === 'number'
657
- ? body.revision
658
- : typeof row.revision === 'number'
659
- ? row.revision
660
- : undefined,
661
- files: body.files,
662
- description: typeof row.description === 'string' ? row.description : undefined,
663
- triggerDescription: typeof row.triggerDescription === 'string' ? row.triggerDescription : undefined,
664
- } : null;
665
- }));
743
+ const fetched = await Promise.all(rows.map(row => fetchNativeMirroredSkill(params.apiUrl, params.apiKey, row)));
666
744
  if (fetched.some(skill => skill === null)) {
667
745
  throw new Error('skill mirror snapshot incomplete; existing native adapters preserved');
668
746
  }
@@ -99,10 +99,13 @@ function ownedNativeRoots(params) {
99
99
  root: path.resolve((0, native_skill_mirror_1.nativeSkillRoot)(platform, params.homeDir)),
100
100
  scope: 'global',
101
101
  });
102
- if (params.projectId) {
102
+ const projectRoot = params.projectId
103
+ ? (0, native_skill_mirror_1.writerOwnedProjectSkillRoot)(platform, params.cwd, params.homeDir)
104
+ : null;
105
+ if (projectRoot) {
103
106
  roots.push({
104
107
  platform,
105
- root: path.resolve((0, native_skill_mirror_1.nativeProjectSkillRoot)(platform, params.cwd)),
108
+ root: path.resolve(projectRoot),
106
109
  scope: 'project',
107
110
  projectId: params.projectId,
108
111
  projectName: params.projectName,