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.
Files changed (43) hide show
  1. package/dist/claude-lifecycle-cutover.js +5 -16
  2. package/dist/commands/codex.js +4 -0
  3. package/dist/commands/collision-check.js +9 -6
  4. package/dist/commands/corpus/client.js +16 -0
  5. package/dist/commands/corpus/index.js +5 -2
  6. package/dist/commands/corpus/section.js +3 -3
  7. package/dist/commands/corpus/walk.js +3 -3
  8. package/dist/commands/delivery-reminder.js +13 -9
  9. package/dist/commands/delivery.js +64 -0
  10. package/dist/commands/docptr.js +10 -24
  11. package/dist/commands/init.js +4 -5
  12. package/dist/commands/load-primer-reminder.js +2 -2
  13. package/dist/commands/load.js +45 -12
  14. package/dist/commands/merge-guard.js +94 -9
  15. package/dist/commands/parity.js +0 -2
  16. package/dist/commands/pipe-wrap.js +4 -2
  17. package/dist/commands/skill-mirror-reminder.js +24 -6
  18. package/dist/commands/skill.js +24 -8
  19. package/dist/commands/skillgain.js +5 -1
  20. package/dist/commands/state-trigger.js +67 -31
  21. package/dist/delivery-config.js +69 -0
  22. package/dist/deploy-locks.js +18 -3
  23. package/dist/doc-mirror-client.js +39 -11
  24. package/dist/hook.js +35 -39
  25. package/dist/index.js +62 -27
  26. package/dist/native-adapter-refresh.js +114 -0
  27. package/dist/native-skill-adoption.js +20 -53
  28. package/dist/native-skill-mirror.js +130 -52
  29. package/dist/native-skill-pending.js +5 -2
  30. package/dist/opencode-plugin.bundle.js +1083 -160
  31. package/dist/opencode-plugin.js +6 -0
  32. package/dist/skill-activation-manifest.js +98 -22
  33. package/dist/skill-landing.js +6 -0
  34. package/dist/skill-mirror-files.js +59 -0
  35. package/package.json +1 -1
  36. package/scripts/postinstall.js +4 -19
  37. package/skill/greprag/SKILL.md +1 -1
  38. package/skill/greprag/docs/corpus.md +2 -2
  39. package/skill/greprag/docs/setup.md +1 -1
  40. package/skill/mechanic/SKILL.md +15 -40
  41. package/skill/mechanic/docs/skill-fix-conventions.md +2 -2
  42. package/skill/templates/chip-spawn.md +36 -20
  43. package/skill/templates/delivery.md +19 -5
@@ -41,49 +41,7 @@ const os = __importStar(require("node:os"));
41
41
  const path = __importStar(require("node:path"));
42
42
  const native_skill_mirror_1 = require("./native-skill-mirror");
43
43
  const skill_mirror_files_1 = require("./skill-mirror-files");
44
- const STATE_FILE = '.greprag-mirror.json';
45
44
  const NATIVE_SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
46
- function emptyState() {
47
- return { version: 1, skills: {} };
48
- }
49
- function readState(root) {
50
- try {
51
- const parsed = JSON.parse(fs.readFileSync(path.join(root, STATE_FILE), 'utf8'));
52
- if (parsed?.version === 1 && parsed.skills && typeof parsed.skills === 'object')
53
- return parsed;
54
- }
55
- catch { /* first run or corrupt state: existing directories remain unmanaged */ }
56
- return emptyState();
57
- }
58
- function writeState(root, state) {
59
- fs.mkdirSync(root, { recursive: true });
60
- fs.writeFileSync(path.join(root, STATE_FILE), JSON.stringify(state, null, 2) + '\n', 'utf8');
61
- }
62
- function targetPath(root, skillName) {
63
- const target = path.resolve(root, skillName);
64
- const prefix = path.resolve(root) + path.sep;
65
- if (!target.startsWith(prefix))
66
- throw new Error('skill target escapes native root');
67
- return target;
68
- }
69
- function filePath(target, relative) {
70
- const resolved = path.resolve(target, ...relative.split('/'));
71
- const prefix = path.resolve(target) + path.sep;
72
- if (!resolved.startsWith(prefix))
73
- throw new Error('skill file escapes target directory');
74
- return resolved;
75
- }
76
- function readManagedFiles(target, managed) {
77
- try {
78
- return managed.files.map(relative => ({
79
- path: relative,
80
- content: fs.readFileSync(filePath(target, relative), 'utf8'),
81
- }));
82
- }
83
- catch {
84
- return null;
85
- }
86
- }
87
45
  function listSkillDirs(root) {
88
46
  try {
89
47
  return fs.readdirSync(root, { withFileTypes: true })
@@ -122,9 +80,12 @@ function countSkillFiles(root) {
122
80
  }
123
81
  function adoptionRoots(params) {
124
82
  const roots = [{ root: (0, native_skill_mirror_1.nativeSkillRoot)(params.platform, params.homeDir), scope: 'global' }];
125
- if (params.projectDir && params.projectId) {
83
+ const projectRoot = params.projectDir && params.projectId
84
+ ? (0, native_skill_mirror_1.writerOwnedProjectSkillRoot)(params.platform, params.projectDir, params.homeDir)
85
+ : null;
86
+ if (projectRoot) {
126
87
  roots.push({
127
- root: (0, native_skill_mirror_1.nativeProjectSkillRoot)(params.platform, params.projectDir),
88
+ root: projectRoot,
128
89
  scope: 'project',
129
90
  projectId: params.projectId,
130
91
  projectName: params.projectName,
@@ -169,12 +130,12 @@ function planNativeSkillAdoption(params) {
169
130
  projectId: params.projectId,
170
131
  projectName: params.projectName,
171
132
  })) {
172
- const state = readState(root.root);
133
+ const state = (0, native_skill_mirror_1.readState)(root.root);
173
134
  for (const skillName of listSkillDirs(root.root)) {
174
- const target = targetPath(root.root, skillName);
135
+ const target = (0, native_skill_mirror_1.targetPath)(root.root, skillName);
175
136
  const managed = state.skills[skillName];
176
137
  if (managed) {
177
- const currentFiles = readManagedFiles(target, managed);
138
+ const currentFiles = (0, native_skill_mirror_1.readManagedFiles)(target, managed);
178
139
  const currentHash = currentFiles ? (0, skill_mirror_files_1.computeSkillFileHash)(currentFiles) : '';
179
140
  const entry = { ...root, skillName, reason: currentHash === managed.hash ? 'managed/current' : 'managed/diverged' };
180
141
  if (currentHash === managed.hash)
@@ -224,8 +185,8 @@ function installAdoptedNativeSkill(params) {
224
185
  return 'skipped';
225
186
  if (!NATIVE_SKILL_NAME_RE.test(params.skill.skillName))
226
187
  return 'skipped';
227
- const target = targetPath(params.root, params.skill.skillName);
228
- const state = readState(params.root);
188
+ const target = (0, native_skill_mirror_1.targetPath)(params.root, params.skill.skillName);
189
+ const state = (0, native_skill_mirror_1.readState)(params.root);
229
190
  if (state.skills[params.skill.skillName])
230
191
  return 'blocked';
231
192
  const currentFiles = params.collectFiles(target);
@@ -234,17 +195,23 @@ function installAdoptedNativeSkill(params) {
234
195
  const adapterFiles = (0, native_skill_mirror_1.buildNativeSkillAdapter)(params.platform, params.skill);
235
196
  if (!adapterFiles)
236
197
  return 'skipped';
237
- fs.rmSync(target, { recursive: true, force: true });
238
- fs.mkdirSync(target, { recursive: true });
198
+ // Adoption replaces only the files it just moved into canon. The mirror
199
+ // carries SKILL.md and docs/**/*.md; everything else in the package
200
+ // (scripts/, references/, assets/, any non-markdown file) exists nowhere but
201
+ // this folder, and wiping the folder destroyed exactly those. Write the
202
+ // adapter, then remove the adopted files it did not overwrite, the same rule
203
+ // a managed adapter update follows. adr: adr/skill-bundle-shape.md
239
204
  for (const file of adapterFiles) {
240
- const destination = filePath(target, file.path);
205
+ const destination = (0, native_skill_mirror_1.filePath)(target, file.path);
241
206
  fs.mkdirSync(path.dirname(destination), { recursive: true });
242
207
  fs.writeFileSync(destination, file.content, 'utf8');
243
208
  }
209
+ const adapterPaths = new Set(adapterFiles.map(file => file.path));
210
+ (0, native_skill_mirror_1.removeTrackedFiles)(target, currentFiles.map(file => file.path).filter(relative => !adapterPaths.has(relative)));
244
211
  state.skills[params.skill.skillName] = {
245
212
  hash: (0, skill_mirror_files_1.computeSkillFileHash)(adapterFiles),
246
213
  files: adapterFiles.map(file => file.path).sort(),
247
214
  };
248
- writeState(params.root, state);
215
+ (0, native_skill_mirror_1.writeState)(params.root, state);
249
216
  return 'installed';
250
217
  }
@@ -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,