@esoteric-logic/praxis-harness 2.14.0 → 2.15.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/base/skills/px-prompt/SKILL.md +373 -0
- package/bin/praxis.js +7 -0
- package/bin/prompt-blocks.js +145 -0
- package/bin/prompt-compile.js +313 -0
- package/lib/assemblers.js +249 -0
- package/lib/loader.js +148 -0
- package/package.json +10 -3
- package/prompts/blocks/behaviors/flag-confidence.md +13 -0
- package/prompts/blocks/behaviors/handle-uncertainty.md +13 -0
- package/prompts/blocks/behaviors/no-flattery.md +15 -0
- package/prompts/blocks/behaviors/recommend-with-reasons.md +13 -0
- package/prompts/blocks/behaviors/verify-before-reporting.md +13 -0
- package/prompts/blocks/context/mcp-servers.md +12 -0
- package/prompts/blocks/context/official-docs-first.md +16 -0
- package/prompts/blocks/context/praxis-workflow.md +20 -0
- package/prompts/blocks/context/vault-integration.md +13 -0
- package/prompts/blocks/domains/cloud-infrastructure.md +13 -0
- package/prompts/blocks/domains/govcon.md +13 -0
- package/prompts/blocks/domains/web-development.md +13 -0
- package/prompts/blocks/formats/concise-responses.md +13 -0
- package/prompts/blocks/formats/what-so-what-now-what.md +16 -0
- package/prompts/blocks/identity/research-partner.md +10 -0
- package/prompts/blocks/identity/senior-engineer.md +15 -0
- package/prompts/blocks/identity/solutions-architect.md +13 -0
- package/prompts/profiles/_base.yaml +15 -0
- package/prompts/profiles/federal-cloud.yaml +18 -0
- package/prompts/profiles/praxis.yaml +13 -0
- package/prompts/projects/_template/prompt-config.yaml +34 -0
- package/prompts/projects/maximus/prompt-config.yaml +13 -0
- package/prompts/projects/maximus/references/maturity-questions.md +634 -0
- package/prompts/projects/maximus/references/phase-maturity-matrix.md +188 -0
- package/prompts/projects/maximus/references/proposal-writing-standards.md +367 -0
- package/prompts/projects/maximus/space-instructions.md +67 -0
- package/prompts/projects/maximus/system-prompt.md +641 -0
- package/prompts/projects/praxis/CLAUDE.md +84 -0
- package/prompts/projects/praxis/project-instructions.md +24 -0
- package/prompts/projects/praxis/prompt-config.yaml +40 -0
- package/prompts/projects/praxis/space-instructions.md +28 -0
- package/scripts/lint-harness.sh +42 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const yaml = require('js-yaml');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
TARGETS,
|
|
10
|
+
PROMPTS_DIR,
|
|
11
|
+
loadPraxisConfig,
|
|
12
|
+
loadProfile,
|
|
13
|
+
mergeProfiles,
|
|
14
|
+
loadBlocks,
|
|
15
|
+
applyOverrides,
|
|
16
|
+
} = require('../lib/loader');
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
interpolate,
|
|
20
|
+
findUnresolved,
|
|
21
|
+
assembleClaudeCode,
|
|
22
|
+
assembleClaudeProject,
|
|
23
|
+
assemblePerplexitySpace,
|
|
24
|
+
} = require('../lib/assemblers');
|
|
25
|
+
|
|
26
|
+
const PROJECTS_DIR = path.join(PROMPTS_DIR, 'projects');
|
|
27
|
+
|
|
28
|
+
const CHAR_BUDGETS = {
|
|
29
|
+
'claude-code': Infinity,
|
|
30
|
+
'claude-project': 2500,
|
|
31
|
+
'perplexity-space': 4000,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Global flags set by CLI parser
|
|
35
|
+
let PREVIEW_MODE = false;
|
|
36
|
+
let DIFF_MODE = false;
|
|
37
|
+
let STRICT_MODE = false;
|
|
38
|
+
|
|
39
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function fail(msg) {
|
|
42
|
+
console.error(`\x1b[31mERROR:\x1b[0m ${msg}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function warn(msg) {
|
|
47
|
+
console.error(`\x1b[33mWARN:\x1b[0m ${msg}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ok(msg) {
|
|
51
|
+
console.log(`\x1b[32m✓\x1b[0m ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Main ─────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/** Validate a standalone project — check files exist, report char budgets. */
|
|
57
|
+
function validateStandalone(projectName, projectDir, projectConfig) {
|
|
58
|
+
console.log(`\nValidating standalone: ${projectName}`);
|
|
59
|
+
|
|
60
|
+
const inventory = [
|
|
61
|
+
{ file: 'system-prompt.md', budget: Infinity, required: true, label: 'System Prompt (Claude Projects)' },
|
|
62
|
+
{ file: 'CLAUDE.md', budget: Infinity, required: false, label: 'Claude Code' },
|
|
63
|
+
{ file: 'space-instructions.md', budget: CHAR_BUDGETS['perplexity-space'], required: false, label: 'Perplexity Space' },
|
|
64
|
+
{ file: 'project-instructions.md', budget: CHAR_BUDGETS['claude-project'], required: false, label: 'Claude Project' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
let missingGenerable = [];
|
|
68
|
+
|
|
69
|
+
for (const item of inventory) {
|
|
70
|
+
const filePath = path.join(projectDir, item.file);
|
|
71
|
+
if (fs.existsSync(filePath)) {
|
|
72
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
73
|
+
const charCount = content.length;
|
|
74
|
+
const lineCount = content.split('\n').length;
|
|
75
|
+
const sizeInfo = item.budget < Infinity
|
|
76
|
+
? `${charCount} chars (budget: ${item.budget})`
|
|
77
|
+
: `${charCount} chars, ${lineCount} lines`;
|
|
78
|
+
|
|
79
|
+
if (charCount > item.budget) {
|
|
80
|
+
warn(`${item.file} exceeds budget: ${charCount} chars (limit: ${item.budget})`);
|
|
81
|
+
} else {
|
|
82
|
+
ok(`${item.file} — ${sizeInfo}`);
|
|
83
|
+
}
|
|
84
|
+
} else if (item.required) {
|
|
85
|
+
warn(`${item.file} MISSING — standalone projects require a system prompt`);
|
|
86
|
+
} else {
|
|
87
|
+
missingGenerable.push(item.file);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (missingGenerable.length > 0) {
|
|
92
|
+
console.log(`\n Missing platform outputs: ${missingGenerable.join(', ')}`);
|
|
93
|
+
console.log(' Run /px-prompt ' + projectName + ' to auto-generate from system-prompt.md');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Version consistency check
|
|
97
|
+
const systemPromptPath = path.join(projectDir, 'system-prompt.md');
|
|
98
|
+
if (fs.existsSync(systemPromptPath) && projectConfig.version) {
|
|
99
|
+
const spContent = fs.readFileSync(systemPromptPath, 'utf8');
|
|
100
|
+
const versionMatch = spContent.match(/^version:\s*["']?([^"'\n]+)/m);
|
|
101
|
+
if (versionMatch && versionMatch[1].trim() !== String(projectConfig.version).trim()) {
|
|
102
|
+
warn(`Version mismatch: prompt-config.yaml says "${projectConfig.version}" but system-prompt.md says "${versionMatch[1].trim()}"`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for reference files
|
|
107
|
+
const refsDir = path.join(projectDir, 'references');
|
|
108
|
+
if (fs.existsSync(refsDir)) {
|
|
109
|
+
const refs = fs.readdirSync(refsDir).filter((f) => f.endsWith('.md'));
|
|
110
|
+
if (refs.length > 0) {
|
|
111
|
+
ok(`${refs.length} reference file(s): ${refs.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function compileProject(projectName, targets) {
|
|
117
|
+
const projectDir = path.join(PROJECTS_DIR, projectName);
|
|
118
|
+
const configPath = path.join(projectDir, 'prompt-config.yaml');
|
|
119
|
+
|
|
120
|
+
if (!fs.existsSync(configPath)) {
|
|
121
|
+
fail(`Project config not found: ${configPath}\nRun /px-prompt <project-name> to create one.`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const projectConfig = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
125
|
+
|
|
126
|
+
// Standalone mode: validate files, report budgets, skip compilation
|
|
127
|
+
if (projectConfig.mode === 'standalone') {
|
|
128
|
+
validateStandalone(projectName, projectDir, projectConfig);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const praxisConfig = loadPraxisConfig();
|
|
133
|
+
|
|
134
|
+
// Build vars map: project vars + praxis config + project name
|
|
135
|
+
const vars = {
|
|
136
|
+
...praxisConfig,
|
|
137
|
+
...(projectConfig.vars || {}),
|
|
138
|
+
project: projectConfig.project || projectName,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Load profile: from named profile, project-local blocks, or _base fallback
|
|
142
|
+
let profile;
|
|
143
|
+
if (projectConfig.profile) {
|
|
144
|
+
profile = loadProfile(projectConfig.profile, fail);
|
|
145
|
+
} else if (projectConfig.blocks) {
|
|
146
|
+
const base = loadProfile('_base', fail);
|
|
147
|
+
profile = mergeProfiles(base, { blocks: projectConfig.blocks });
|
|
148
|
+
} else {
|
|
149
|
+
profile = loadProfile('_base', fail);
|
|
150
|
+
}
|
|
151
|
+
profile = applyOverrides(profile, projectConfig.overrides);
|
|
152
|
+
|
|
153
|
+
const profileName = projectConfig.profile || 'project-local';
|
|
154
|
+
console.log(`\nCompiling: ${projectName} (profile: ${profileName})`);
|
|
155
|
+
|
|
156
|
+
const assemblers = {
|
|
157
|
+
'claude-code': assembleClaudeCode,
|
|
158
|
+
'claude-project': assembleClaudeProject,
|
|
159
|
+
'perplexity-space': assemblePerplexitySpace,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const outputNames = {
|
|
163
|
+
'claude-code': 'CLAUDE.md',
|
|
164
|
+
'claude-project': 'project-instructions.md',
|
|
165
|
+
'perplexity-space': 'space-instructions.md',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
for (const target of targets) {
|
|
169
|
+
const blocks = loadBlocks(profile, target, warn);
|
|
170
|
+
let output = assemblers[target](blocks, projectConfig, vars);
|
|
171
|
+
|
|
172
|
+
// Interpolate variables
|
|
173
|
+
output = interpolate(output, vars);
|
|
174
|
+
|
|
175
|
+
// Validate no unresolved placeholders
|
|
176
|
+
const unresolved = findUnresolved(output);
|
|
177
|
+
if (unresolved.length > 0) {
|
|
178
|
+
if (STRICT_MODE) {
|
|
179
|
+
fail(`[strict] Unresolved placeholders in ${target}: ${unresolved.join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
warn(`Unresolved placeholders in ${target}: ${unresolved.join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check character budget
|
|
185
|
+
const budget = CHAR_BUDGETS[target];
|
|
186
|
+
if (output.length > budget) {
|
|
187
|
+
if (STRICT_MODE) {
|
|
188
|
+
fail(`[strict] ${target} exceeds budget: ${output.length} chars (limit: ${budget})`);
|
|
189
|
+
}
|
|
190
|
+
warn(`${target} output exceeds budget: ${output.length} chars (limit: ${budget})`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const outputPath = path.join(projectDir, outputNames[target]);
|
|
194
|
+
|
|
195
|
+
// Preview mode: print to stdout instead of writing
|
|
196
|
+
if (PREVIEW_MODE) {
|
|
197
|
+
console.log(`\n--- ${outputNames[target]} (${output.length} chars) ---`);
|
|
198
|
+
console.log(output);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Diff mode: show diff against existing file before writing
|
|
203
|
+
if (DIFF_MODE && fs.existsSync(outputPath)) {
|
|
204
|
+
const existing = fs.readFileSync(outputPath, 'utf8');
|
|
205
|
+
if (existing === output) {
|
|
206
|
+
ok(`${outputNames[target]} — unchanged (${output.length} chars)`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
console.log(`\n--- ${outputNames[target]} changed ---`);
|
|
210
|
+
const existingLines = existing.split('\n');
|
|
211
|
+
const outputLines = output.split('\n');
|
|
212
|
+
const addedCount = outputLines.filter((l) => !existingLines.includes(l)).length;
|
|
213
|
+
const removedCount = existingLines.filter((l) => !outputLines.includes(l)).length;
|
|
214
|
+
console.log(` +${addedCount} lines added, -${removedCount} lines removed`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(outputPath, output, 'utf8');
|
|
218
|
+
ok(`${outputNames[target]} — ${output.length} chars → ${outputPath}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── CLI ──────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
function main() {
|
|
225
|
+
const args = process.argv.slice(2);
|
|
226
|
+
|
|
227
|
+
if (args.length === 0 || args.includes('--help')) {
|
|
228
|
+
console.log('Usage: prompt-compile <project-name|--all> [options]');
|
|
229
|
+
console.log('Options:');
|
|
230
|
+
console.log(' --target <target> claude-code|claude-project|perplexity-space|all');
|
|
231
|
+
console.log(' --preview Print output to stdout without writing files');
|
|
232
|
+
console.log(' --diff Show what changed before writing');
|
|
233
|
+
console.log(' --strict Exit with error on budget overruns or unresolved vars');
|
|
234
|
+
console.log(' --list List all projects with mode and file status');
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// --list mode: show all projects
|
|
239
|
+
if (args.includes('--list')) {
|
|
240
|
+
const projectDirs = fs.readdirSync(PROJECTS_DIR)
|
|
241
|
+
.filter((d) => d !== '_template' && fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory());
|
|
242
|
+
if (projectDirs.length === 0) {
|
|
243
|
+
console.log('No projects found.');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
console.log(`${'Project'.padEnd(20)} ${'Mode'.padEnd(12)} ${'System Prompt'.padEnd(15)} ${'Claude Proj'.padEnd(15)} ${'Perplexity'.padEnd(15)} ${'CLAUDE.md'.padEnd(12)} Refs`);
|
|
247
|
+
console.log('-'.repeat(95));
|
|
248
|
+
for (const name of projectDirs) {
|
|
249
|
+
const dir = path.join(PROJECTS_DIR, name);
|
|
250
|
+
const cfgPath = path.join(dir, 'prompt-config.yaml');
|
|
251
|
+
const cfg = fs.existsSync(cfgPath) ? yaml.load(fs.readFileSync(cfgPath, 'utf8')) : {};
|
|
252
|
+
const mode = cfg.mode || 'compiled';
|
|
253
|
+
const fileStatus = (f) => {
|
|
254
|
+
const p = path.join(dir, f);
|
|
255
|
+
if (!fs.existsSync(p)) return '—';
|
|
256
|
+
return `${fs.readFileSync(p, 'utf8').length} chars`;
|
|
257
|
+
};
|
|
258
|
+
const refsDir = path.join(dir, 'references');
|
|
259
|
+
const refCount = fs.existsSync(refsDir)
|
|
260
|
+
? fs.readdirSync(refsDir).filter((f) => f.endsWith('.md')).length
|
|
261
|
+
: 0;
|
|
262
|
+
console.log(
|
|
263
|
+
`${name.padEnd(20)} ${mode.padEnd(12)} ${fileStatus('system-prompt.md').padEnd(15)} ${fileStatus('project-instructions.md').padEnd(15)} ${fileStatus('space-instructions.md').padEnd(15)} ${fileStatus('CLAUDE.md').padEnd(12)} ${refCount}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Parse global flags
|
|
270
|
+
PREVIEW_MODE = args.includes('--preview');
|
|
271
|
+
DIFF_MODE = args.includes('--diff');
|
|
272
|
+
STRICT_MODE = args.includes('--strict');
|
|
273
|
+
|
|
274
|
+
// Parse --target flag
|
|
275
|
+
const targetIdx = args.indexOf('--target');
|
|
276
|
+
let targets = TARGETS;
|
|
277
|
+
if (targetIdx !== -1 && args[targetIdx + 1]) {
|
|
278
|
+
const targetArg = args[targetIdx + 1];
|
|
279
|
+
if (TARGETS.includes(targetArg)) {
|
|
280
|
+
targets = [targetArg];
|
|
281
|
+
} else if (targetArg !== 'all') {
|
|
282
|
+
fail(`Unknown target: ${targetArg}. Use: ${TARGETS.join(', ')}, all`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Determine project(s) to compile
|
|
287
|
+
const flagValues = new Set();
|
|
288
|
+
if (targetIdx !== -1 && args[targetIdx + 1]) {
|
|
289
|
+
flagValues.add(args[targetIdx + 1]);
|
|
290
|
+
}
|
|
291
|
+
const projectArg = args.find((a) => !a.startsWith('--') && !flagValues.has(a));
|
|
292
|
+
|
|
293
|
+
if (args.includes('--all')) {
|
|
294
|
+
const projectDirs = fs.readdirSync(PROJECTS_DIR)
|
|
295
|
+
.filter((d) => d !== '_template' && fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory());
|
|
296
|
+
|
|
297
|
+
if (projectDirs.length === 0) {
|
|
298
|
+
fail('No projects found in prompts/projects/');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const projectName of projectDirs) {
|
|
302
|
+
compileProject(projectName, targets);
|
|
303
|
+
}
|
|
304
|
+
} else if (projectArg) {
|
|
305
|
+
compileProject(projectArg, targets);
|
|
306
|
+
} else {
|
|
307
|
+
fail('Specify a project name or use --all');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log('\nDone.');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main();
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Replace {{var}} placeholders with values from vars map. */
|
|
4
|
+
function interpolate(text, vars) {
|
|
5
|
+
return text.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
6
|
+
if (key in vars) return vars[key];
|
|
7
|
+
return `{{${key}}}`;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Check for unresolved {{...}} placeholders. */
|
|
12
|
+
function findUnresolved(text) {
|
|
13
|
+
const matches = text.match(/\{\{(\w+)\}\}/g);
|
|
14
|
+
return matches ? [...new Set(matches)] : [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function assembleClaudeCode(blocks, projectConfig, vars) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
20
|
+
lines.push(`# ${vars.project || vars.repo_name || 'Project'}`);
|
|
21
|
+
lines.push(`<!-- Generated by Praxis prompt-compile | profile: ${projectConfig.profile} | ${today} -->`);
|
|
22
|
+
lines.push('');
|
|
23
|
+
|
|
24
|
+
// Identity
|
|
25
|
+
const identityBlocks = blocks.filter((b) => b.category === 'identity');
|
|
26
|
+
if (identityBlocks.length > 0) {
|
|
27
|
+
lines.push('## Identity');
|
|
28
|
+
for (const block of identityBlocks) lines.push(block.content, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Global Rules reference
|
|
32
|
+
lines.push('## Global Rules');
|
|
33
|
+
lines.push('Inherits execution engine from `~/.claude/CLAUDE.md`.');
|
|
34
|
+
lines.push('');
|
|
35
|
+
|
|
36
|
+
// Git Identity
|
|
37
|
+
if (vars.git_email || vars.git_identity) {
|
|
38
|
+
lines.push('## Git Identity');
|
|
39
|
+
if (vars.git_identity) lines.push(`- **Type**: ${vars.git_identity}`);
|
|
40
|
+
if (vars.git_email) lines.push(`- **Email**: ${vars.git_email}`);
|
|
41
|
+
lines.push('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Behaviors
|
|
45
|
+
const behaviorBlocks = blocks.filter((b) => b.category === 'behaviors');
|
|
46
|
+
if (behaviorBlocks.length > 0) {
|
|
47
|
+
lines.push('## Behaviors');
|
|
48
|
+
for (const block of behaviorBlocks) lines.push(block.content, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Domains
|
|
52
|
+
const domainBlocks = blocks.filter((b) => b.category === 'domains');
|
|
53
|
+
if (domainBlocks.length > 0) {
|
|
54
|
+
lines.push('## Domain Expertise');
|
|
55
|
+
for (const block of domainBlocks) lines.push(block.content, '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Formats
|
|
59
|
+
const formatBlocks = blocks.filter((b) => b.category === 'formats');
|
|
60
|
+
if (formatBlocks.length > 0) {
|
|
61
|
+
lines.push('## Output Format');
|
|
62
|
+
for (const block of formatBlocks) lines.push(block.content, '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Tech Stack + Commands (from claude_code_append)
|
|
66
|
+
const append = (projectConfig.overrides || {}).claude_code_append || {};
|
|
67
|
+
if (append.tech_stack) {
|
|
68
|
+
lines.push('## Tech Stack');
|
|
69
|
+
lines.push(append.tech_stack.trim(), '');
|
|
70
|
+
}
|
|
71
|
+
if (append.commands) {
|
|
72
|
+
lines.push('## Commands');
|
|
73
|
+
lines.push('```bash');
|
|
74
|
+
lines.push(append.commands.trim());
|
|
75
|
+
lines.push('```', '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Context
|
|
79
|
+
const contextBlocks = blocks.filter((b) => b.category === 'context');
|
|
80
|
+
if (contextBlocks.length > 0) {
|
|
81
|
+
for (const block of contextBlocks) lines.push(block.content, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Extra notes
|
|
85
|
+
if (append.extra_notes) {
|
|
86
|
+
lines.push('## Important Notes');
|
|
87
|
+
lines.push(append.extra_notes.trim(), '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Vault Project
|
|
91
|
+
if (vars.vault_project_path) {
|
|
92
|
+
lines.push('## Vault Project');
|
|
93
|
+
lines.push(`- **Vault path**: ${vars.vault_project_path}`);
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Standard footer
|
|
98
|
+
lines.push('## Verification');
|
|
99
|
+
lines.push('- Before marking any task complete, run the test suite');
|
|
100
|
+
lines.push('- Check logs before claiming a bug is fixed');
|
|
101
|
+
lines.push('');
|
|
102
|
+
lines.push('## Conventions');
|
|
103
|
+
lines.push('- **Commits**: conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)');
|
|
104
|
+
lines.push('- **Branches**: `feat/description` or `fix/description`');
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push('## Error Learning');
|
|
107
|
+
lines.push('<!-- Add project-specific learnings below -->');
|
|
108
|
+
lines.push('');
|
|
109
|
+
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assembleClaudeProject(blocks, projectConfig, vars) {
|
|
114
|
+
const lines = [];
|
|
115
|
+
|
|
116
|
+
// Layer 1: Role
|
|
117
|
+
const identityBlocks = blocks.filter((b) => b.category === 'identity');
|
|
118
|
+
lines.push('## Role');
|
|
119
|
+
if (projectConfig.description) lines.push(projectConfig.description);
|
|
120
|
+
for (const block of identityBlocks) lines.push(block.content);
|
|
121
|
+
lines.push('');
|
|
122
|
+
|
|
123
|
+
// Layer 2: Behavioral Constraints
|
|
124
|
+
const behaviorBlocks = blocks.filter((b) => b.category === 'behaviors');
|
|
125
|
+
if (behaviorBlocks.length > 0) {
|
|
126
|
+
lines.push('## Behavioral Constraints');
|
|
127
|
+
for (const block of behaviorBlocks) lines.push('- ' + block.content);
|
|
128
|
+
lines.push('');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Layer 3: Domain Expertise
|
|
132
|
+
const domainBlocks = blocks.filter((b) => b.category === 'domains');
|
|
133
|
+
if (domainBlocks.length > 0) {
|
|
134
|
+
lines.push('## Domain Expertise');
|
|
135
|
+
for (const block of domainBlocks) lines.push('- ' + block.content);
|
|
136
|
+
lines.push('');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Layer 3b: Output Format
|
|
140
|
+
const formatBlocks = blocks.filter((b) => b.category === 'formats');
|
|
141
|
+
if (formatBlocks.length > 0) {
|
|
142
|
+
lines.push('## Output Format');
|
|
143
|
+
for (const block of formatBlocks) lines.push('- ' + block.content);
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Additional context
|
|
148
|
+
const append = (projectConfig.overrides || {}).claude_project_append || {};
|
|
149
|
+
if (append.additional_context) {
|
|
150
|
+
lines.push(append.additional_context.trim(), '');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Layer 4: Quality Gates
|
|
154
|
+
if (append.quality_gates) {
|
|
155
|
+
lines.push('## Quality Gates');
|
|
156
|
+
lines.push(append.quality_gates.trim(), '');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Context blocks (workflow, etc.)
|
|
160
|
+
const contextBlocks = blocks.filter((b) => b.category === 'context');
|
|
161
|
+
if (contextBlocks.length > 0) {
|
|
162
|
+
for (const block of contextBlocks) lines.push(block.content);
|
|
163
|
+
lines.push('');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Layer 4b: Knowledge Files (if project has them)
|
|
167
|
+
const knowledgeFiles = projectConfig.knowledge_files || [];
|
|
168
|
+
if (knowledgeFiles.length > 0) {
|
|
169
|
+
lines.push('## Knowledge Files');
|
|
170
|
+
lines.push('Upload these alongside this prompt:');
|
|
171
|
+
for (const kf of knowledgeFiles) {
|
|
172
|
+
lines.push(`- **${kf.file}** — ${kf.description}`);
|
|
173
|
+
}
|
|
174
|
+
lines.push('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Layer 5: Failure Handling (always present)
|
|
178
|
+
lines.push('## When Uncertain');
|
|
179
|
+
lines.push('State uncertainty explicitly. Ask one clarifying question rather than guessing.');
|
|
180
|
+
lines.push('');
|
|
181
|
+
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function assemblePerplexitySpace(blocks, projectConfig, vars) {
|
|
186
|
+
const lines = [];
|
|
187
|
+
|
|
188
|
+
// Purpose
|
|
189
|
+
const identityBlocks = blocks.filter((b) => b.category === 'identity');
|
|
190
|
+
lines.push('## Purpose');
|
|
191
|
+
if (projectConfig.description) lines.push(projectConfig.description);
|
|
192
|
+
for (const block of identityBlocks) lines.push(block.content);
|
|
193
|
+
lines.push('');
|
|
194
|
+
|
|
195
|
+
// Source Priority
|
|
196
|
+
const sourceBlocks = blocks.filter(
|
|
197
|
+
(b) => b.category === 'context' && (b.meta.tags || []).includes('sources')
|
|
198
|
+
);
|
|
199
|
+
if (sourceBlocks.length > 0) {
|
|
200
|
+
lines.push('## Source Priority');
|
|
201
|
+
for (const block of sourceBlocks) lines.push(block.content);
|
|
202
|
+
lines.push('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Domain Expertise
|
|
206
|
+
const domainBlocks = blocks.filter((b) => b.category === 'domains');
|
|
207
|
+
if (domainBlocks.length > 0) {
|
|
208
|
+
lines.push('## Domain Expertise');
|
|
209
|
+
for (const block of domainBlocks) lines.push(block.content);
|
|
210
|
+
lines.push('');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Research Domains
|
|
214
|
+
const append = (projectConfig.overrides || {}).perplexity_space_append || {};
|
|
215
|
+
if (append.research_domains) {
|
|
216
|
+
lines.push('## Research Domains');
|
|
217
|
+
lines.push(append.research_domains.trim(), '');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// How to Answer
|
|
221
|
+
const behaviorBlocks = blocks.filter((b) => b.category === 'behaviors');
|
|
222
|
+
const formatBlocks = blocks.filter((b) => b.category === 'formats');
|
|
223
|
+
if (behaviorBlocks.length > 0 || formatBlocks.length > 0) {
|
|
224
|
+
lines.push('## How to Answer');
|
|
225
|
+
for (const block of behaviorBlocks) lines.push(block.content);
|
|
226
|
+
for (const block of formatBlocks) lines.push(block.content);
|
|
227
|
+
lines.push('');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Anti-hallucination layer (auto-injected for all Perplexity outputs)
|
|
231
|
+
lines.push('## Accuracy Standards');
|
|
232
|
+
lines.push('- Flag your confidence level when synthesizing across sources');
|
|
233
|
+
lines.push('- Distinguish verified facts from analytical inferences');
|
|
234
|
+
lines.push('- If sources disagree, cite both and explain the discrepancy');
|
|
235
|
+
lines.push('- Never fabricate version numbers, API signatures, URLs, or code examples');
|
|
236
|
+
lines.push('- When information may be outdated (>12 months), note the publication date');
|
|
237
|
+
lines.push('- If you cannot find reliable sources, state that clearly rather than speculating');
|
|
238
|
+
lines.push('');
|
|
239
|
+
|
|
240
|
+
return lines.join('\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
interpolate,
|
|
245
|
+
findUnresolved,
|
|
246
|
+
assembleClaudeCode,
|
|
247
|
+
assembleClaudeProject,
|
|
248
|
+
assemblePerplexitySpace,
|
|
249
|
+
};
|