@productbrain/cli 0.1.0-beta.7395 → 0.1.0-beta.7400
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/__tests__/handshake-dormancy.test.js +6 -64
- package/dist/__tests__/handshake-dormancy.test.js.map +1 -1
- package/dist/__tests__/handshake-preview.test.js +41 -651
- package/dist/__tests__/handshake-preview.test.js.map +1 -1
- package/dist/__tests__/handshake.e2e.test.js +3 -262
- package/dist/__tests__/handshake.e2e.test.js.map +1 -1
- package/dist/__tests__/perimeter.test.js +1 -1
- package/dist/__tests__/perimeter.test.js.map +1 -1
- package/dist/__tests__/route-latency-budget.test.js +3 -1
- package/dist/__tests__/route-latency-budget.test.js.map +1 -1
- package/dist/commands/admin/seed.test.js +4 -9
- package/dist/commands/admin/seed.test.js.map +1 -1
- package/dist/commands/admin/seedRegistryEntries.generated.d.ts.map +1 -1
- package/dist/commands/admin/seedRegistryEntries.generated.js +0 -18
- package/dist/commands/admin/seedRegistryEntries.generated.js.map +1 -1
- package/dist/commands/admin/seedRegistryEntries.test.js +10 -7
- package/dist/commands/admin/seedRegistryEntries.test.js.map +1 -1
- package/dist/commands/connect-context.d.ts +8 -10
- package/dist/commands/connect-context.d.ts.map +1 -1
- package/dist/commands/connect-context.js +6 -8
- package/dist/commands/connect-context.js.map +1 -1
- package/dist/commands/connect-context.test.js +15 -10
- package/dist/commands/connect-context.test.js.map +1 -1
- package/dist/commands/connect-screens.d.ts +5 -4
- package/dist/commands/connect-screens.d.ts.map +1 -1
- package/dist/commands/connect-screens.js +7 -7
- package/dist/commands/connect-screens.js.map +1 -1
- package/dist/commands/handshake.d.ts +0 -19
- package/dist/commands/handshake.d.ts.map +1 -1
- package/dist/commands/handshake.js +67 -746
- package/dist/commands/handshake.js.map +1 -1
- package/dist/generated/routeLatencyBudget.generated.d.ts.map +1 -1
- package/dist/generated/routeLatencyBudget.generated.js +0 -25
- package/dist/generated/routeLatencyBudget.generated.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/{setup/perimeter.d.ts → lib/writePerimeter.d.ts} +11 -11
- package/dist/lib/writePerimeter.d.ts.map +1 -0
- package/dist/{setup/perimeter.js → lib/writePerimeter.js} +11 -11
- package/dist/lib/writePerimeter.js.map +1 -0
- package/package.json +1 -1
- package/dist/setup/perimeter.d.ts.map +0 -1
- package/dist/setup/perimeter.js.map +0 -1
- package/dist/surfaces/__tests__/pb-setup-passthrough.test.d.ts +0 -2
- package/dist/surfaces/__tests__/pb-setup-passthrough.test.d.ts.map +0 -1
- package/dist/surfaces/__tests__/pb-setup-passthrough.test.js +0 -131
- package/dist/surfaces/__tests__/pb-setup-passthrough.test.js.map +0 -1
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* STD-197: preview is the new default (no --apply flag = no writes).
|
|
4
4
|
*/
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
-
import { createHash } from 'crypto';
|
|
7
6
|
const { vfs, MOCK_MARKER, MOCK_CWD, MOCK_HOME } = vi.hoisted(() => ({
|
|
8
7
|
vfs: {},
|
|
9
8
|
MOCK_MARKER: 'auto-generated by pb handshake',
|
|
@@ -126,28 +125,15 @@ vi.mock('../lib/method-registry.js', () => ({
|
|
|
126
125
|
}));
|
|
127
126
|
import { readdirSync, writeFileSync } from 'fs';
|
|
128
127
|
import { formatHandshakeReport } from '../formatters/handshake.js';
|
|
129
|
-
import { kernelCall
|
|
130
|
-
import { readSession } from '../lib/session.js';
|
|
128
|
+
import { kernelCall } from '../lib/client.js';
|
|
131
129
|
import { detectRepo } from '../lib/repo-detect.js';
|
|
132
130
|
import { readManifestStatus } from '../generators/manifest.js';
|
|
133
|
-
import { generateCursorSkill, shouldEmitToTarget } from '../generators/portable-knowledge.js';
|
|
131
|
+
import { generateCursorSkill, shouldEmitToTarget, readCanonicalSkills } from '../generators/portable-knowledge.js';
|
|
134
132
|
import { runHandshake } from '../commands/handshake.js';
|
|
135
133
|
function capturedReport() {
|
|
136
134
|
const calls = vi.mocked(formatHandshakeReport).mock.calls;
|
|
137
135
|
return calls.length > 0 ? calls[calls.length - 1][0] : null;
|
|
138
136
|
}
|
|
139
|
-
function setupAuthoringTestHash(asset) {
|
|
140
|
-
const canonical = {
|
|
141
|
-
entryId: asset.entryId,
|
|
142
|
-
name: asset.name,
|
|
143
|
-
description: asset.description,
|
|
144
|
-
assetKind: asset.assetKind,
|
|
145
|
-
triggers: asset.triggers ?? [],
|
|
146
|
-
semanticRefs: asset.semanticRefs ?? [],
|
|
147
|
-
body: asset.body.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd(),
|
|
148
|
-
};
|
|
149
|
-
return `sha256:${createHash('sha256').update(JSON.stringify(canonical), 'utf8').digest('hex')}`;
|
|
150
|
-
}
|
|
151
137
|
describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
152
138
|
let cwdSpy;
|
|
153
139
|
beforeEach(() => {
|
|
@@ -219,43 +205,17 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
219
205
|
const combined = chunks.join('').trim();
|
|
220
206
|
expect(() => JSON.parse(combined)).not.toThrow();
|
|
221
207
|
});
|
|
222
|
-
it('PR #912 round 12 (Codex P2): PB_JSON=1 does NOT suppress logErr —
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return null;
|
|
234
|
-
if (tool === 'chain.searchEntries')
|
|
235
|
-
return [];
|
|
236
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
237
|
-
return {
|
|
238
|
-
activeAssets: [{
|
|
239
|
-
entryId: 'SETUP-SKILL-STALE',
|
|
240
|
-
name: 'Stale Skill',
|
|
241
|
-
description: 'DB changed after local sync.',
|
|
242
|
-
body: '# Stale Skill\nUpdated DB body.',
|
|
243
|
-
triggers: [],
|
|
244
|
-
assetKind: 'skill',
|
|
245
|
-
semanticRefs: [],
|
|
246
|
-
disabledByOwner: false,
|
|
247
|
-
lastProjectedHash: 'sha256:new-baseline',
|
|
248
|
-
scope: 'personal',
|
|
249
|
-
}],
|
|
250
|
-
dormantAssets: [],
|
|
251
|
-
hasAnyReceipt: true,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
255
|
-
return { ok: true };
|
|
256
|
-
if (tool === 'setup.materializeSetup')
|
|
257
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
258
|
-
return [];
|
|
208
|
+
it('PR #912 round 12 (Codex P2): PB_JSON=1 does NOT suppress logErr — warnings still reach stderr', async () => {
|
|
209
|
+
// WP-776 S3d/S3f: the original fixture ("DB baseline changed" during authoring sync) is
|
|
210
|
+
// deleted along with the setup-assets system. An unregistered manifest surface is a
|
|
211
|
+
// still-live, simpler logErr() trigger (surfaces/registry.ts's real, unmocked
|
|
212
|
+
// validateSurfacesForMode) that proves the same PB_JSON invariant.
|
|
213
|
+
vi.mocked(readManifestStatus).mockReturnValue({
|
|
214
|
+
mode: 'project',
|
|
215
|
+
surfaces: ['.cursor', '.not-a-real-surface'],
|
|
216
|
+
lock: {},
|
|
217
|
+
parseStatus: 'ok',
|
|
218
|
+
manifest: null,
|
|
259
219
|
});
|
|
260
220
|
const priorPbJson = process.env.PB_JSON;
|
|
261
221
|
process.env.PB_JSON = '1';
|
|
@@ -274,7 +234,7 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
274
234
|
stdoutSpy.mockRestore();
|
|
275
235
|
stderrSpy.mockRestore();
|
|
276
236
|
}
|
|
277
|
-
expect(stderrChunks.some((c) => c.includes('
|
|
237
|
+
expect(stderrChunks.some((c) => c.includes('manifest surface ".not-a-real-surface" is not registered'))).toBe(true);
|
|
278
238
|
});
|
|
279
239
|
it('PR #912 round 13 (Codex P1, security): pb --json handshake never leaks an HTTPS git-remote credential or raw Convex fields', async () => {
|
|
280
240
|
vfs[`${MOCK_CWD}/.productbrain/briefing.md`] = 'Driving entry: WP-462.';
|
|
@@ -422,7 +382,11 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
422
382
|
expect(capturedReport()?.filesWritten).toEqual([]);
|
|
423
383
|
expect(capturedReport()?.filesSkipped?.some((f) => f.reason === 'preview-only (materialize: observe)')).toBe(true);
|
|
424
384
|
});
|
|
425
|
-
it('apply + govern authority writes files
|
|
385
|
+
it('apply + govern authority writes files (OBJ-24 complement to observe)', async () => {
|
|
386
|
+
// WP-776 S3d/S3f: the setup.materializeSetup assertion is dropped — that receipt write is
|
|
387
|
+
// deleted along with the rest of the setup-assets system (see handshake.ts's own comment
|
|
388
|
+
// at the old step 8b). The write-mode invariant itself (govern authority actually writes,
|
|
389
|
+
// unlike observe) still holds and is still worth guarding.
|
|
426
390
|
vi.mocked(readManifestStatus).mockReturnValue({
|
|
427
391
|
mode: 'govern',
|
|
428
392
|
surfaces: ['.cursor'],
|
|
@@ -432,7 +396,6 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
432
396
|
});
|
|
433
397
|
await runHandshake({ apply: true, surfaces: ['cursor'] });
|
|
434
398
|
expect(writeFileSync).toHaveBeenCalled();
|
|
435
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.materializeSetup')).toBe(true);
|
|
436
399
|
expect(capturedReport()?.filesSkipped?.some((f) => f.reason === 'preview-only (materialize: observe)')).toBeFalsy();
|
|
437
400
|
});
|
|
438
401
|
it('--surfaces is an intersection with manifest.surfaces, not a superset', async () => {
|
|
@@ -444,125 +407,18 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
444
407
|
manifest: null,
|
|
445
408
|
});
|
|
446
409
|
vi.mocked(shouldEmitToTarget).mockImplementation((_entry, target) => target === 'cursor');
|
|
447
|
-
vi.mocked(generateCursorSkill).mockImplementation((
|
|
448
|
-
vi.mocked(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (tool === 'chain.getOrientView')
|
|
452
|
-
return null;
|
|
453
|
-
if (tool === 'chain.searchEntries')
|
|
454
|
-
return [];
|
|
455
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
456
|
-
return [{
|
|
457
|
-
entryId: 'SETUP-SKILL-INTERSECTION',
|
|
458
|
-
name: 'Intersection',
|
|
459
|
-
description: 'Intersection',
|
|
460
|
-
body: '# Intersection',
|
|
461
|
-
triggers: [],
|
|
462
|
-
assetKind: 'skill',
|
|
463
|
-
semanticRefs: [],
|
|
464
|
-
disabledByOwner: false,
|
|
465
|
-
lastProjectedHash: null,
|
|
466
|
-
}];
|
|
467
|
-
}
|
|
468
|
-
if (tool === 'setup.getCurrentSetupState')
|
|
469
|
-
return { effectiveMode: 'observe' };
|
|
470
|
-
if (tool === 'setup.recordTransition')
|
|
471
|
-
return { ok: true, receiptId: 'r1', deduped: false };
|
|
472
|
-
return [];
|
|
473
|
-
});
|
|
410
|
+
vi.mocked(generateCursorSkill).mockImplementation(() => '# Intersection');
|
|
411
|
+
vi.mocked(readCanonicalSkills).mockReturnValueOnce([
|
|
412
|
+
{ name: 'Intersection', description: 'Intersection', triggers: [], body: '# Intersection', sourcePath: 'local' },
|
|
413
|
+
]);
|
|
474
414
|
await runHandshake({ apply: true, surfaces: ['claude'] });
|
|
475
415
|
expect(writeFileSync).not.toHaveBeenCalledWith(expect.stringContaining('/.cursor/skills/Intersection/SKILL.md'), expect.any(String));
|
|
476
416
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
parseStatus: 'ok',
|
|
483
|
-
manifest: null,
|
|
484
|
-
});
|
|
485
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
486
|
-
if (tool === 'chain.workspaceReadiness')
|
|
487
|
-
return null;
|
|
488
|
-
if (tool === 'chain.getOrientView')
|
|
489
|
-
return null;
|
|
490
|
-
if (tool === 'chain.searchEntries')
|
|
491
|
-
return [];
|
|
492
|
-
if (tool === 'setup.listAssetsForUser')
|
|
493
|
-
return [];
|
|
494
|
-
if (tool === 'setup.getCurrentSetupState')
|
|
495
|
-
return { effectiveMode: 'observe' };
|
|
496
|
-
if (tool === 'setup.recordTransition')
|
|
497
|
-
return { ok: true, receiptId: 'r1', deduped: false };
|
|
498
|
-
return [];
|
|
499
|
-
});
|
|
500
|
-
await runHandshake({ apply: true });
|
|
501
|
-
expect(kernelCall).toHaveBeenCalledWith('setup.recordTransition', {
|
|
502
|
-
fromMode: 'observe',
|
|
503
|
-
toMode: 'project',
|
|
504
|
-
parseStatus: 'ok',
|
|
505
|
-
surfaces: ['.cursor'],
|
|
506
|
-
lock: { 'SETUP-SKILL-TRANSITION': 'workspace' },
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
it('apply bulk-imports .productbrain authoring files with frontmatter id before DB projection', async () => {
|
|
510
|
-
vi.mocked(readManifestStatus).mockReturnValue({
|
|
511
|
-
mode: 'project',
|
|
512
|
-
surfaces: ['.cursor'],
|
|
513
|
-
lock: {},
|
|
514
|
-
parseStatus: 'ok',
|
|
515
|
-
manifest: null,
|
|
516
|
-
});
|
|
517
|
-
const skillPath = `${MOCK_CWD}/.productbrain/skills/local.md`;
|
|
518
|
-
vfs[`${MOCK_CWD}/.productbrain/skills`] = '';
|
|
519
|
-
vfs[skillPath] = [
|
|
520
|
-
'---',
|
|
521
|
-
'id: SETUP-SKILL-FRONTMATTER',
|
|
522
|
-
'name: Local Skill',
|
|
523
|
-
'description: Local description',
|
|
524
|
-
'triggers:',
|
|
525
|
-
' - local',
|
|
526
|
-
'semanticRefs:',
|
|
527
|
-
' - entry:WP-423',
|
|
528
|
-
'---',
|
|
529
|
-
'# Local Skill',
|
|
530
|
-
'',
|
|
531
|
-
'Body.',
|
|
532
|
-
].join('\n');
|
|
533
|
-
vi.mocked(readdirSync).mockImplementation((path) => {
|
|
534
|
-
if (String(path).endsWith('/.productbrain/skills'))
|
|
535
|
-
return ['local.md'];
|
|
536
|
-
return [];
|
|
537
|
-
});
|
|
538
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
539
|
-
if (tool === 'chain.workspaceReadiness')
|
|
540
|
-
return null;
|
|
541
|
-
if (tool === 'chain.getOrientView')
|
|
542
|
-
return null;
|
|
543
|
-
if (tool === 'chain.searchEntries')
|
|
544
|
-
return [];
|
|
545
|
-
// WP-428 S2 (Critical #1): CLI now calls ingestSetupAssetWithBody per-asset.
|
|
546
|
-
if (tool === 'setup.ingestSetupAssetWithBody')
|
|
547
|
-
return { action: 'created', entryId: 'SETUP-SKILL-FRONTMATTER' };
|
|
548
|
-
if (tool === 'setup.listAssetsForUser')
|
|
549
|
-
return [];
|
|
550
|
-
if (tool === 'setup.getCurrentSetupState')
|
|
551
|
-
return { effectiveMode: 'project' };
|
|
552
|
-
return [];
|
|
553
|
-
});
|
|
554
|
-
await runHandshake({ apply: true });
|
|
555
|
-
// WP-428 S2: CLI now calls ingestSetupAssetWithBody per-asset (not ingestSetupAssetsBatch).
|
|
556
|
-
expect(kernelCall).toHaveBeenCalledWith('setup.ingestSetupAssetWithBody', expect.objectContaining({
|
|
557
|
-
entryId: 'SETUP-SKILL-LOCAL',
|
|
558
|
-
frontmatterId: 'SETUP-SKILL-FRONTMATTER',
|
|
559
|
-
name: 'Local Skill',
|
|
560
|
-
description: 'Local description',
|
|
561
|
-
assetKind: 'skill',
|
|
562
|
-
triggers: ['local'],
|
|
563
|
-
semanticRefs: ['entry:WP-423'],
|
|
564
|
-
}));
|
|
565
|
-
});
|
|
417
|
+
// WP-776 S3d/S3f: "apply records a mode-change transition..." and "apply bulk-imports
|
|
418
|
+
// .productbrain authoring files..." are deleted — both tested the DB-first setup-asset
|
|
419
|
+
// source (setup.recordTransition, setup.ingestSetupAssetWithBody), which is gone along with
|
|
420
|
+
// the rest of the setup-assets system. `pb handshake` now always reads skills/rules from the
|
|
421
|
+
// filesystem; there is no DB projection or transition receipt left to test.
|
|
566
422
|
it('preview mode reports unchanged for files with identical content', async () => {
|
|
567
423
|
const agentsPath = `${MOCK_CWD}/AGENTS.md`;
|
|
568
424
|
vfs[agentsPath] = `<!-- ${MOCK_MARKER} -->\nagents content`;
|
|
@@ -609,484 +465,18 @@ describe('runHandshake — preview/apply/dryRun invariants (WP-332 S1)', () => {
|
|
|
609
465
|
expect(vfs[claudePath]).toContain('<!-- pb:region:start');
|
|
610
466
|
expect(vfs[agentsPath]).toContain('<!-- pb:region:start');
|
|
611
467
|
});
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// The session-bound path is genuinely exercised (materializeSetup runs through it)…
|
|
626
|
-
expect(kernelCallWithSession).toHaveBeenCalled();
|
|
627
|
-
// …but NO draft TEN is ever logged about the user's own files.
|
|
628
|
-
expect(kernelCallWithSession).not.toHaveBeenCalledWith('chain.createEntry', expect.anything());
|
|
629
|
-
}
|
|
630
|
-
finally {
|
|
631
|
-
// Restore defaults — beforeEach clears call history but not implementations.
|
|
632
|
-
vi.mocked(readSession).mockReturnValue(null);
|
|
633
|
-
vi.mocked(kernelCall).mockResolvedValue([]);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
it('PR #912 round 14 (Codex P2): the empty-active-assets progress line describes report-only leftover listing, never cleanup/removal', async () => {
|
|
637
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => tool === 'setup.listAssetsForUser'
|
|
638
|
-
? { activeAssets: [], dormantAssets: [], hasAnyReceipt: true }
|
|
639
|
-
: []);
|
|
640
|
-
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
641
|
-
let combined;
|
|
642
|
-
try {
|
|
643
|
-
await runHandshake({ apply: true });
|
|
644
|
-
combined = writeSpy.mock.calls.map(([chunk]) => String(chunk)).join('');
|
|
645
|
-
}
|
|
646
|
-
finally {
|
|
647
|
-
writeSpy.mockRestore();
|
|
648
|
-
vi.mocked(kernelCall).mockResolvedValue([]);
|
|
649
|
-
}
|
|
650
|
-
expect(combined).toContain('any existing projections on the selected surfaces will be listed as leftovers, not removed');
|
|
651
|
-
expect(combined).not.toMatch(/projections clear/i);
|
|
652
|
-
expect(combined).not.toMatch(/cleanup runs/i);
|
|
653
|
-
});
|
|
654
|
-
it('apply mode projects DB setup assets with body-derived refs, disabled filtering, and hash trailers', async () => {
|
|
655
|
-
vi.mocked(shouldEmitToTarget).mockImplementation((_entry, target) => target === 'cursor');
|
|
656
|
-
vi.mocked(generateCursorSkill).mockImplementation((skill) => skill.body);
|
|
657
|
-
vi.mocked(kernelCall).mockImplementation(async (tool, args) => {
|
|
658
|
-
if (tool === 'chain.workspaceReadiness')
|
|
659
|
-
return null;
|
|
660
|
-
if (tool === 'chain.getOrientView')
|
|
661
|
-
return null;
|
|
662
|
-
if (tool === 'chain.searchEntries')
|
|
663
|
-
return [];
|
|
664
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
665
|
-
return [
|
|
666
|
-
{
|
|
667
|
-
entryId: 'SETUP-SKILL-NO-REFS',
|
|
668
|
-
name: 'No Refs',
|
|
669
|
-
description: 'No ref skill',
|
|
670
|
-
body: '# No refs\nPlain body.',
|
|
671
|
-
triggers: [],
|
|
672
|
-
assetKind: 'skill',
|
|
673
|
-
semanticRefs: [],
|
|
674
|
-
disabledByOwner: false,
|
|
675
|
-
lastProjectedHash: null,
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
entryId: 'SETUP-SKILL-WITH-REF',
|
|
679
|
-
name: 'With Ref',
|
|
680
|
-
description: 'Ref skill',
|
|
681
|
-
body: '# With ref\nUse {{ref:entry:WP-345}}.',
|
|
682
|
-
triggers: [],
|
|
683
|
-
assetKind: 'skill',
|
|
684
|
-
semanticRefs: ['entry:WP-345'],
|
|
685
|
-
disabledByOwner: false,
|
|
686
|
-
lastProjectedHash: null,
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
entryId: 'SETUP-SKILL-DISABLED',
|
|
690
|
-
name: 'Disabled Skill',
|
|
691
|
-
description: 'Disabled',
|
|
692
|
-
body: '# Disabled',
|
|
693
|
-
triggers: [],
|
|
694
|
-
assetKind: 'skill',
|
|
695
|
-
semanticRefs: [],
|
|
696
|
-
disabledByOwner: true,
|
|
697
|
-
lastProjectedHash: null,
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
entryId: 'SETUP-BUNDLE-DEMO',
|
|
701
|
-
name: 'Bundle Demo',
|
|
702
|
-
description: 'Bundle',
|
|
703
|
-
body: '# Bundle',
|
|
704
|
-
triggers: [],
|
|
705
|
-
assetKind: 'bundle',
|
|
706
|
-
semanticRefs: [],
|
|
707
|
-
disabledByOwner: false,
|
|
708
|
-
lastProjectedHash: null,
|
|
709
|
-
},
|
|
710
|
-
];
|
|
711
|
-
}
|
|
712
|
-
if (tool === 'setup.resolveSemanticRefs') {
|
|
713
|
-
expect(args?.semanticRefs).toEqual(['entry:WP-345']);
|
|
714
|
-
return [{ ref: 'entry:WP-345', resolved: { entryId: 'WP-345', name: 'Dynamic Skills' } }];
|
|
715
|
-
}
|
|
716
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
717
|
-
return { ok: true };
|
|
718
|
-
if (tool === 'setup.materializeSetup')
|
|
719
|
-
return { ok: true, assetCount: 2, receiptWritten: true };
|
|
720
|
-
return [];
|
|
721
|
-
});
|
|
722
|
-
await runHandshake({ apply: true, surfaces: ['cursor'] });
|
|
723
|
-
const writes = vi.mocked(writeFileSync).mock.calls.map(([path, content]) => ({
|
|
724
|
-
path: String(path),
|
|
725
|
-
content: String(content),
|
|
726
|
-
}));
|
|
727
|
-
const noRefs = writes.find((w) => w.path.endsWith('/.cursor/skills/No Refs/SKILL.md'));
|
|
728
|
-
const withRef = writes.find((w) => w.path.endsWith('/.cursor/skills/With Ref/SKILL.md'));
|
|
729
|
-
expect(noRefs?.content).toContain('Plain body.');
|
|
730
|
-
expect(noRefs?.content).toContain('<!-- pb-hash: sha256:');
|
|
731
|
-
expect(withRef?.content).toContain('Use {{ref:entry:WP-345}}.');
|
|
732
|
-
expect(withRef?.content).toContain('<!-- pb-hash: sha256:');
|
|
733
|
-
expect(writes.some((w) => w.path.includes('Disabled Skill'))).toBe(false);
|
|
734
|
-
expect(writes.some((w) => w.path.includes('Bundle Demo'))).toBe(false);
|
|
735
|
-
const hashUpdates = vi.mocked(kernelCall).mock.calls.filter(([tool]) => tool === 'setup.updateLastProjectedHash');
|
|
736
|
-
expect(hashUpdates).toHaveLength(2);
|
|
737
|
-
expect(hashUpdates.map(([, args]) => args.entryId).sort()).toEqual([
|
|
738
|
-
'SETUP-SKILL-NO-REFS',
|
|
739
|
-
'SETUP-SKILL-WITH-REF',
|
|
740
|
-
]);
|
|
741
|
-
});
|
|
742
|
-
it('does not update lastProjectedHash when a DB asset projection is forked and skipped', async () => {
|
|
743
|
-
vi.mocked(shouldEmitToTarget).mockImplementation((_entry, target) => target === 'cursor');
|
|
744
|
-
vi.mocked(generateCursorSkill).mockImplementation((skill) => skill.body);
|
|
745
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
746
|
-
if (tool === 'chain.workspaceReadiness')
|
|
747
|
-
return null;
|
|
748
|
-
if (tool === 'chain.getOrientView')
|
|
749
|
-
return null;
|
|
750
|
-
if (tool === 'chain.searchEntries')
|
|
751
|
-
return [];
|
|
752
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
753
|
-
return [
|
|
754
|
-
{
|
|
755
|
-
entryId: 'SETUP-SKILL-FORKED',
|
|
756
|
-
name: 'Forked Skill',
|
|
757
|
-
description: 'Forked',
|
|
758
|
-
body: '# Forked\nBody.',
|
|
759
|
-
triggers: [],
|
|
760
|
-
assetKind: 'skill',
|
|
761
|
-
semanticRefs: [],
|
|
762
|
-
disabledByOwner: false,
|
|
763
|
-
lastProjectedHash: null,
|
|
764
|
-
},
|
|
765
|
-
];
|
|
766
|
-
}
|
|
767
|
-
if (tool === 'setup.materializeSetup')
|
|
768
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
769
|
-
return [];
|
|
770
|
-
});
|
|
771
|
-
vfs[`${MOCK_CWD}/.cursor/skills/Forked Skill/SKILL.md`] = '# User fork\nNo generated marker.';
|
|
772
|
-
await runHandshake({ apply: true, surfaces: ['cursor'] });
|
|
773
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.updateLastProjectedHash')).toBe(false);
|
|
774
|
-
});
|
|
775
|
-
it('writes missing caller-owned personal DB assets back to .productbrain authoring files', async () => {
|
|
776
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
777
|
-
if (tool === 'chain.workspaceReadiness')
|
|
778
|
-
return null;
|
|
779
|
-
if (tool === 'chain.getOrientView')
|
|
780
|
-
return null;
|
|
781
|
-
if (tool === 'chain.searchEntries')
|
|
782
|
-
return [];
|
|
783
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
784
|
-
return {
|
|
785
|
-
activeAssets: [
|
|
786
|
-
{
|
|
787
|
-
entryId: 'SETUP-SKILL-PERSONAL',
|
|
788
|
-
name: 'Personal Skill',
|
|
789
|
-
description: 'Personal authoring row.',
|
|
790
|
-
body: '# Personal Skill\nBody.',
|
|
791
|
-
triggers: ['personal'],
|
|
792
|
-
assetKind: 'skill',
|
|
793
|
-
semanticRefs: ['entry:WP-423'],
|
|
794
|
-
disabledByOwner: false,
|
|
795
|
-
lastProjectedHash: null,
|
|
796
|
-
scope: 'personal',
|
|
797
|
-
},
|
|
798
|
-
],
|
|
799
|
-
dormantAssets: [],
|
|
800
|
-
hasAnyReceipt: true,
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
804
|
-
return { ok: true };
|
|
805
|
-
if (tool === 'setup.materializeSetup')
|
|
806
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
807
|
-
return [];
|
|
808
|
-
});
|
|
809
|
-
await runHandshake({ apply: true });
|
|
810
|
-
const path = `${MOCK_CWD}/.productbrain/skills/Personal Skill.md`;
|
|
811
|
-
expect(vfs[path]).toContain('id: SETUP-SKILL-PERSONAL');
|
|
812
|
-
expect(vfs[path]).toContain('# Personal Skill\nBody.');
|
|
813
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.updateLastProjectedHash')).toBe(true);
|
|
814
|
-
});
|
|
815
|
-
it('writes newer DB edits over a tracked authoring file unchanged from baseline', async () => {
|
|
816
|
-
const baselineHash = setupAuthoringTestHash({
|
|
817
|
-
entryId: 'SETUP-SKILL-DB-WINS',
|
|
818
|
-
name: 'DB Wins',
|
|
819
|
-
description: 'DB should win.',
|
|
820
|
-
assetKind: 'skill',
|
|
821
|
-
body: '# Original',
|
|
822
|
-
});
|
|
823
|
-
vfs[`${MOCK_CWD}/.productbrain/skills`] = '';
|
|
824
|
-
vfs[`${MOCK_CWD}/.productbrain/skills/DB Wins.md`] = [
|
|
825
|
-
'---',
|
|
826
|
-
'id: SETUP-SKILL-DB-WINS',
|
|
827
|
-
'name: "DB Wins"',
|
|
828
|
-
'description: "DB should win."',
|
|
829
|
-
'assetKind: skill',
|
|
830
|
-
'---',
|
|
831
|
-
'# Original',
|
|
832
|
-
'',
|
|
833
|
-
].join('\n');
|
|
834
|
-
vfs[`${MOCK_CWD}/.productbrain/.authoring-sync.json`] = JSON.stringify({
|
|
835
|
-
version: 1,
|
|
836
|
-
assets: {
|
|
837
|
-
'SETUP-SKILL-DB-WINS': {
|
|
838
|
-
path: '.productbrain/skills/DB Wins.md',
|
|
839
|
-
hash: baselineHash,
|
|
840
|
-
},
|
|
841
|
-
},
|
|
842
|
-
});
|
|
843
|
-
vi.mocked(readdirSync).mockImplementation((path) => {
|
|
844
|
-
if (String(path).endsWith('/.productbrain/skills'))
|
|
845
|
-
return ['DB Wins.md'];
|
|
846
|
-
return [];
|
|
847
|
-
});
|
|
848
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
849
|
-
if (tool === 'chain.workspaceReadiness')
|
|
850
|
-
return null;
|
|
851
|
-
if (tool === 'chain.getOrientView')
|
|
852
|
-
return null;
|
|
853
|
-
if (tool === 'chain.searchEntries')
|
|
854
|
-
return [];
|
|
855
|
-
// WP-428 S2 (Critical #1): CLI now calls ingestSetupAssetWithBody per-asset.
|
|
856
|
-
if (tool === 'setup.ingestSetupAssetWithBody') {
|
|
857
|
-
return { action: 'unchanged', entryId: 'SETUP-SKILL-DB-WINS' };
|
|
858
|
-
}
|
|
859
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
860
|
-
return {
|
|
861
|
-
activeAssets: [
|
|
862
|
-
{
|
|
863
|
-
entryId: 'SETUP-SKILL-DB-WINS',
|
|
864
|
-
name: 'DB Wins',
|
|
865
|
-
description: 'DB should win.',
|
|
866
|
-
body: '# DB edit',
|
|
867
|
-
triggers: [],
|
|
868
|
-
assetKind: 'skill',
|
|
869
|
-
semanticRefs: [],
|
|
870
|
-
disabledByOwner: false,
|
|
871
|
-
lastProjectedHash: baselineHash,
|
|
872
|
-
scope: 'personal',
|
|
873
|
-
},
|
|
874
|
-
],
|
|
875
|
-
dormantAssets: [],
|
|
876
|
-
hasAnyReceipt: true,
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
880
|
-
return { ok: true };
|
|
881
|
-
if (tool === 'setup.materializeSetup')
|
|
882
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
883
|
-
return [];
|
|
884
|
-
});
|
|
885
|
-
await runHandshake({ apply: true });
|
|
886
|
-
expect(vfs[`${MOCK_CWD}/.productbrain/skills/DB Wins.md`]).toContain('# DB edit');
|
|
887
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.updateLastProjectedHash')).toBe(true);
|
|
888
|
-
});
|
|
889
|
-
it('marks personal DB assets dormant when their tracked authoring file is deleted', async () => {
|
|
890
|
-
const syncedHash = setupAuthoringTestHash({
|
|
891
|
-
entryId: 'SETUP-SKILL-DELETED',
|
|
892
|
-
name: 'Deleted Skill',
|
|
893
|
-
description: 'Deleted authoring row.',
|
|
894
|
-
assetKind: 'skill',
|
|
895
|
-
body: '# Deleted Skill\nBody.',
|
|
896
|
-
});
|
|
897
|
-
vfs[`${MOCK_CWD}/.productbrain/.authoring-sync.json`] = JSON.stringify({
|
|
898
|
-
version: 1,
|
|
899
|
-
assets: {
|
|
900
|
-
'SETUP-SKILL-DELETED': {
|
|
901
|
-
path: '.productbrain/skills/Deleted Skill.md',
|
|
902
|
-
hash: syncedHash,
|
|
903
|
-
},
|
|
904
|
-
},
|
|
905
|
-
});
|
|
906
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
907
|
-
if (tool === 'chain.workspaceReadiness')
|
|
908
|
-
return null;
|
|
909
|
-
if (tool === 'chain.getOrientView')
|
|
910
|
-
return null;
|
|
911
|
-
if (tool === 'chain.searchEntries')
|
|
912
|
-
return [];
|
|
913
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
914
|
-
return {
|
|
915
|
-
activeAssets: [
|
|
916
|
-
{
|
|
917
|
-
entryId: 'SETUP-SKILL-DELETED',
|
|
918
|
-
name: 'Deleted Skill',
|
|
919
|
-
description: 'Deleted authoring row.',
|
|
920
|
-
body: '# Deleted Skill\nBody.',
|
|
921
|
-
triggers: [],
|
|
922
|
-
assetKind: 'skill',
|
|
923
|
-
semanticRefs: [],
|
|
924
|
-
disabledByOwner: false,
|
|
925
|
-
lastProjectedHash: syncedHash,
|
|
926
|
-
scope: 'personal',
|
|
927
|
-
},
|
|
928
|
-
],
|
|
929
|
-
dormantAssets: [],
|
|
930
|
-
hasAnyReceipt: true,
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
if (tool === 'setup.markPersonalSetupAssetDormantFromSync')
|
|
934
|
-
return { action: 'dormant', entryId: 'SETUP-SKILL-DELETED' };
|
|
935
|
-
if (tool === 'setup.materializeSetup')
|
|
936
|
-
return { ok: true, assetCount: 0, receiptWritten: true };
|
|
937
|
-
return [];
|
|
938
|
-
});
|
|
939
|
-
await runHandshake({ apply: true });
|
|
940
|
-
expect(vi.mocked(kernelCall).mock.calls).toContainEqual([
|
|
941
|
-
'setup.markPersonalSetupAssetDormantFromSync',
|
|
942
|
-
{ entryId: 'SETUP-SKILL-DELETED', expectedLastProjectedHash: syncedHash },
|
|
943
|
-
]);
|
|
944
|
-
});
|
|
945
|
-
it('does not mark a stale tracked deletion dormant when the DB baseline changed', async () => {
|
|
946
|
-
vfs[`${MOCK_CWD}/.productbrain/.authoring-sync.json`] = JSON.stringify({
|
|
947
|
-
version: 1,
|
|
948
|
-
assets: {
|
|
949
|
-
'SETUP-SKILL-STALE': {
|
|
950
|
-
path: '.productbrain/skills/Stale Skill.md',
|
|
951
|
-
hash: 'sha256:old-baseline',
|
|
952
|
-
},
|
|
953
|
-
},
|
|
954
|
-
});
|
|
955
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
956
|
-
if (tool === 'chain.workspaceReadiness')
|
|
957
|
-
return null;
|
|
958
|
-
if (tool === 'chain.getOrientView')
|
|
959
|
-
return null;
|
|
960
|
-
if (tool === 'chain.searchEntries')
|
|
961
|
-
return [];
|
|
962
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
963
|
-
return {
|
|
964
|
-
activeAssets: [
|
|
965
|
-
{
|
|
966
|
-
entryId: 'SETUP-SKILL-STALE',
|
|
967
|
-
name: 'Stale Skill',
|
|
968
|
-
description: 'DB changed after local sync.',
|
|
969
|
-
body: '# Stale Skill\nUpdated DB body.',
|
|
970
|
-
triggers: [],
|
|
971
|
-
assetKind: 'skill',
|
|
972
|
-
semanticRefs: [],
|
|
973
|
-
disabledByOwner: false,
|
|
974
|
-
lastProjectedHash: 'sha256:new-baseline',
|
|
975
|
-
scope: 'personal',
|
|
976
|
-
},
|
|
977
|
-
],
|
|
978
|
-
dormantAssets: [],
|
|
979
|
-
hasAnyReceipt: true,
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
983
|
-
return { ok: true };
|
|
984
|
-
if (tool === 'setup.materializeSetup')
|
|
985
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
986
|
-
return [];
|
|
987
|
-
});
|
|
988
|
-
await runHandshake({ apply: true });
|
|
989
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.markPersonalSetupAssetDormantFromSync')).toBe(false);
|
|
990
|
-
expect(vfs[`${MOCK_CWD}/.productbrain/skills/Stale Skill.md`]).toContain('Updated DB body.');
|
|
991
|
-
});
|
|
992
|
-
it('does not write a stale snapshot back when dormant sync returns a server-side conflict', async () => {
|
|
993
|
-
const listedHash = setupAuthoringTestHash({
|
|
994
|
-
entryId: 'SETUP-SKILL-RACE',
|
|
995
|
-
name: 'Race Skill',
|
|
996
|
-
description: 'Race during sync.',
|
|
997
|
-
assetKind: 'skill',
|
|
998
|
-
body: '# Race Skill\nDB body.',
|
|
999
|
-
});
|
|
1000
|
-
vfs[`${MOCK_CWD}/.productbrain/.authoring-sync.json`] = JSON.stringify({
|
|
1001
|
-
version: 1,
|
|
1002
|
-
assets: {
|
|
1003
|
-
'SETUP-SKILL-RACE': {
|
|
1004
|
-
path: '.productbrain/skills/Race Skill.md',
|
|
1005
|
-
hash: listedHash,
|
|
1006
|
-
},
|
|
1007
|
-
},
|
|
1008
|
-
});
|
|
1009
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
1010
|
-
if (tool === 'chain.workspaceReadiness')
|
|
1011
|
-
return null;
|
|
1012
|
-
if (tool === 'chain.getOrientView')
|
|
1013
|
-
return null;
|
|
1014
|
-
if (tool === 'chain.searchEntries')
|
|
1015
|
-
return [];
|
|
1016
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
1017
|
-
return {
|
|
1018
|
-
activeAssets: [
|
|
1019
|
-
{
|
|
1020
|
-
entryId: 'SETUP-SKILL-RACE',
|
|
1021
|
-
name: 'Race Skill',
|
|
1022
|
-
description: 'Race during sync.',
|
|
1023
|
-
body: '# Race Skill\nDB body.',
|
|
1024
|
-
triggers: [],
|
|
1025
|
-
assetKind: 'skill',
|
|
1026
|
-
semanticRefs: [],
|
|
1027
|
-
disabledByOwner: false,
|
|
1028
|
-
lastProjectedHash: listedHash,
|
|
1029
|
-
scope: 'personal',
|
|
1030
|
-
},
|
|
1031
|
-
],
|
|
1032
|
-
dormantAssets: [],
|
|
1033
|
-
hasAnyReceipt: true,
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
if (tool === 'setup.markPersonalSetupAssetDormantFromSync')
|
|
1037
|
-
return { action: 'conflict', entryId: 'SETUP-SKILL-RACE' };
|
|
1038
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
1039
|
-
return { ok: true };
|
|
1040
|
-
if (tool === 'setup.materializeSetup')
|
|
1041
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
1042
|
-
return [];
|
|
1043
|
-
});
|
|
1044
|
-
await runHandshake({ apply: true });
|
|
1045
|
-
expect(vi.mocked(kernelCall).mock.calls).toContainEqual([
|
|
1046
|
-
'setup.markPersonalSetupAssetDormantFromSync',
|
|
1047
|
-
{ entryId: 'SETUP-SKILL-RACE', expectedLastProjectedHash: listedHash },
|
|
1048
|
-
]);
|
|
1049
|
-
expect(vfs[`${MOCK_CWD}/.productbrain/skills/Race Skill.md`]).toBeUndefined();
|
|
1050
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool, args]) => tool === 'setup.updateLastProjectedHash' &&
|
|
1051
|
-
args?.entryId === 'SETUP-SKILL-RACE')).toBe(false);
|
|
1052
|
-
});
|
|
1053
|
-
it('does not mark a missing personal authoring file dormant on a fresh clone without local tracking state', async () => {
|
|
1054
|
-
vi.mocked(kernelCall).mockImplementation(async (tool) => {
|
|
1055
|
-
if (tool === 'chain.workspaceReadiness')
|
|
1056
|
-
return null;
|
|
1057
|
-
if (tool === 'chain.getOrientView')
|
|
1058
|
-
return null;
|
|
1059
|
-
if (tool === 'chain.searchEntries')
|
|
1060
|
-
return [];
|
|
1061
|
-
if (tool === 'setup.listAssetsForUser') {
|
|
1062
|
-
return {
|
|
1063
|
-
activeAssets: [
|
|
1064
|
-
{
|
|
1065
|
-
entryId: 'SETUP-SKILL-FRESH',
|
|
1066
|
-
name: 'Fresh Skill',
|
|
1067
|
-
description: 'Fresh clone row.',
|
|
1068
|
-
body: '# Fresh Skill\nBody.',
|
|
1069
|
-
triggers: [],
|
|
1070
|
-
assetKind: 'skill',
|
|
1071
|
-
semanticRefs: [],
|
|
1072
|
-
disabledByOwner: false,
|
|
1073
|
-
lastProjectedHash: 'sha256:previous',
|
|
1074
|
-
scope: 'personal',
|
|
1075
|
-
},
|
|
1076
|
-
],
|
|
1077
|
-
dormantAssets: [],
|
|
1078
|
-
hasAnyReceipt: true,
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
if (tool === 'setup.updateLastProjectedHash')
|
|
1082
|
-
return { ok: true };
|
|
1083
|
-
if (tool === 'setup.materializeSetup')
|
|
1084
|
-
return { ok: true, assetCount: 1, receiptWritten: true };
|
|
1085
|
-
return [];
|
|
1086
|
-
});
|
|
1087
|
-
await runHandshake({ apply: true });
|
|
1088
|
-
expect(vi.mocked(kernelCall).mock.calls.some(([tool]) => tool === 'setup.markPersonalSetupAssetDormantFromSync')).toBe(false);
|
|
1089
|
-
expect(vfs[`${MOCK_CWD}/.productbrain/skills/Fresh Skill.md`]).toContain('id: SETUP-SKILL-FRESH');
|
|
1090
|
-
});
|
|
468
|
+
// WP-776 S3d/S3f: the remaining DB-first setup-asset projection/authoring-sync tests that
|
|
469
|
+
// used to live here (project DB setup assets with refs/hash trailers, skip updating
|
|
470
|
+
// lastProjectedHash on a forked write, write personal DB assets back to .productbrain
|
|
471
|
+
// authoring files, write newer DB edits over a tracked file, mark personal DB assets dormant
|
|
472
|
+
// on authoring-file deletion, refuse a stale dormant-mark when the DB baseline changed,
|
|
473
|
+
// refuse a stale snapshot write on a server-side sync conflict, refuse a dormant-mark with no
|
|
474
|
+
// local tracking state, and the 'user-owned files never trigger a drift TEN' regression guard)
|
|
475
|
+
// are all deleted — every one of them exercised setup.listAssetsForUser / setup.
|
|
476
|
+
// ingestSetupAssetWithBody / setup.markPersonalSetupAssetDormantFromSync / setup.
|
|
477
|
+
// updateLastProjectedHash / setup.materializeSetup, none of which exist any more.
|
|
478
|
+
// `pb handshake` reads skills/rules from the filesystem only now (see the '--surfaces is an
|
|
479
|
+
// intersection' test above); there is no DB-sourced asset, authoring sync, or dormant-marking
|
|
480
|
+
// behavior left to test.
|
|
1091
481
|
});
|
|
1092
482
|
//# sourceMappingURL=handshake-preview.test.js.map
|