refactor(linkedin-studio): M0-8 — voice read via getDataRoot (SC7)
This commit is contained in:
parent
3ddf9c5ffa
commit
289eab498e
2 changed files with 71 additions and 9 deletions
65
hooks/scripts/__tests__/user-prompt-context.test.mjs
Normal file
65
hooks/scripts/__tests__/user-prompt-context.test.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { describe, test, afterEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// M0-8 / SC7: the UserPromptSubmit hook must point the voice-profile hint at the
|
||||
// REAL artefact in the EXTERNAL data root (getDataRoot('voice-samples')), not the
|
||||
// in-plugin placeholder. This hook is a procedural stdin->stdout CLI with no
|
||||
// exports, so we test it end-to-end as a subprocess: pipe the prompt JSON, stub
|
||||
// LINKEDIN_STUDIO_DATA + STATE_FILE, and inspect the injected systemMessage.
|
||||
const hookPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'user-prompt-context.mjs');
|
||||
|
||||
function runHook(prompt, env) {
|
||||
const out = execFileSync('node', [hookPath], {
|
||||
input: JSON.stringify({ prompt }),
|
||||
env: { ...process.env, ...env },
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return JSON.parse(out);
|
||||
}
|
||||
|
||||
describe('user-prompt-context — voice hint points at external data root (M0-8/SC7)', () => {
|
||||
let dataDir;
|
||||
|
||||
afterEach(() => {
|
||||
if (dataDir && existsSync(dataDir)) rmSync(dataDir, { recursive: true, force: true });
|
||||
dataDir = undefined;
|
||||
});
|
||||
|
||||
test('real external voice present → systemMessage references the external voice file', () => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'lis-upc-'));
|
||||
mkdirSync(join(dataDir, 'voice-samples'), { recursive: true });
|
||||
writeFileSync(join(dataDir, 'voice-samples', 'authentic-voice-samples.md'), '# My Voice\n\nreal content\n', 'utf-8');
|
||||
|
||||
const result = runHook('write a linkedin post', {
|
||||
LINKEDIN_STUDIO_DATA: dataDir,
|
||||
STATE_FILE: join(dataDir, 'no-state.local.md'),
|
||||
});
|
||||
|
||||
assert.ok(result.systemMessage, 'a LinkedIn prompt must produce an enrichment systemMessage');
|
||||
assert.ok(
|
||||
result.systemMessage.includes(join(dataDir, 'voice-samples', 'authentic-voice-samples.md')),
|
||||
'voice hint must point at the external voice file, not the in-plugin placeholder',
|
||||
);
|
||||
});
|
||||
|
||||
test('external voice absent → no voice-profile hint injected (graceful degradation)', () => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'lis-upc-'));
|
||||
// no voice-samples dir/file seeded under the external root
|
||||
|
||||
const result = runHook('write a linkedin post', {
|
||||
LINKEDIN_STUDIO_DATA: dataDir,
|
||||
STATE_FILE: join(dataDir, 'no-state.local.md'),
|
||||
});
|
||||
|
||||
assert.ok(result.systemMessage, 'still enriches (scorecard reminder), just without the voice hint');
|
||||
assert.ok(
|
||||
!result.systemMessage.includes('Voice Profile:'),
|
||||
'no voice hint when the external voice artefact is absent',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -13,13 +13,10 @@
|
|||
// 0 - Always allow (informational hook)
|
||||
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { join } from 'node:path';
|
||||
import { getDataRoot, getStateFile } from './data-root.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pluginRoot = join(__dirname, '..', '..');
|
||||
const home = process.env.HOME || process.env.USERPROFILE || '';
|
||||
const stateFile = join(home, '.claude', 'linkedin-studio.local.md');
|
||||
const stateFile = getStateFile();
|
||||
|
||||
// Read stdin JSON
|
||||
let input;
|
||||
|
|
@ -98,10 +95,10 @@ if (!isLinkedin) {
|
|||
// === Build context enrichment ===
|
||||
let context = '**LinkedIn Context Enrichment (auto-injected):**\n\n';
|
||||
|
||||
// 1. Voice profile reference
|
||||
const voiceFile = join(pluginRoot, 'assets', 'voice-samples', 'authentic-voice-samples.md');
|
||||
// 1. Voice profile reference — the REAL artefact in the external data root (SC7).
|
||||
const voiceFile = join(getDataRoot('voice-samples'), 'authentic-voice-samples.md');
|
||||
if (existsSync(voiceFile)) {
|
||||
context += '**Voice Profile:** Read `assets/voice-samples/authentic-voice-samples.md` for tone matching.\n\n';
|
||||
context += `**Voice Profile:** Read \`${voiceFile}\` for tone matching.\n\n`;
|
||||
}
|
||||
|
||||
// 2-4. State file data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue