@esoteric-logic/praxis-harness 2.16.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +60 -0
  2. package/base/skills/px-prompt/SKILL.md +917 -107
  3. package/bin/praxis.js +73 -1
  4. package/bin/prompt-compile.js +129 -26
  5. package/bin/prompt-knowledge.js +152 -0
  6. package/lib/assemblers.js +25 -6
  7. package/lib/loader.js +172 -13
  8. package/package.json +3 -2
  9. package/prompts/blocks/behaviors/first-action-rule.md +21 -0
  10. package/prompts/blocks/behaviors/no-flattery.md +1 -2
  11. package/prompts/blocks/behaviors/phase-aware-reasoning.md +41 -0
  12. package/prompts/blocks/behaviors/radical-candor.md +23 -0
  13. package/prompts/blocks/context/mcp-servers.md +1 -1
  14. package/prompts/blocks/domains/federal-cost-analysis.md +33 -0
  15. package/prompts/blocks/domains/govcon-capture.md +89 -0
  16. package/prompts/blocks/domains/govcon-proposal.md +153 -0
  17. package/prompts/blocks/domains/pamasi-framework.md +58 -0
  18. package/prompts/blocks/domains/proposal-writing-rules.md +59 -0
  19. package/prompts/blocks/domains/red-team-review.md +45 -0
  20. package/prompts/blocks/formats/perplexity-generation.md +37 -0
  21. package/prompts/blocks/formats/scorecard-output.md +51 -0
  22. package/prompts/blocks/identity/federal-deal-sa.md +81 -0
  23. package/prompts/blocks/skills/mermaid-diagrams.md +39 -0
  24. package/prompts/{projects → personal}/praxis/CLAUDE.md +2 -3
  25. package/prompts/personal/praxis/project-instructions-claude-desktop.md +30 -0
  26. package/prompts/{projects/praxis/space-instructions.md → personal/praxis/space-instructions-perplexity.md} +2 -1
  27. package/prompts/profiles/_base.yaml +1 -0
  28. package/prompts/profiles/maximus-sa.yaml +27 -0
  29. package/prompts/projects/_template/prompt-config.yaml +4 -0
  30. package/prompts/templates/knowledge/architecture-constraints.md +19 -0
  31. package/prompts/templates/knowledge/corporate-reference.md +25 -0
  32. package/prompts/templates/knowledge/deal-context.md +27 -0
  33. package/prompts/work/elect/client-config.yaml +9 -0
  34. package/prompts/work/elect/deals/azure-architecture/CLAUDE.md +61 -0
  35. package/prompts/work/elect/deals/azure-architecture/prompt-config.yaml +16 -0
  36. package/prompts/work/elect/deals/azure-architecture/space-instructions-perplexity.md +39 -0
  37. package/prompts/work/elect/deals/azure-architecture/system-prompt.md +72 -0
  38. package/prompts/work/maximus/client-config.yaml +81 -0
  39. package/prompts/{projects/maximus/system-prompt.md → work/maximus/deals/dha-tricare/CLAUDE.md} +279 -314
  40. package/prompts/work/maximus/deals/dha-tricare/knowledge/deal-context.md +21 -0
  41. package/prompts/work/maximus/deals/dha-tricare/knowledge/maximus-corporate.md +30 -0
  42. package/prompts/work/maximus/deals/dha-tricare/project-instructions-claude-desktop.md +58 -0
  43. package/prompts/work/maximus/deals/dha-tricare/prompt-config.yaml +41 -0
  44. package/prompts/work/maximus/deals/dha-tricare/references/dha-tricare-intel.md +104 -0
  45. package/prompts/work/maximus/deals/dha-tricare/space-instructions-perplexity.md +42 -0
  46. package/prompts/work/maximus/references/maximus-corporate.md +39 -0
  47. package/prompts/projects/maximus/prompt-config.yaml +0 -13
  48. package/prompts/projects/maximus/space-instructions.md +0 -67
  49. package/prompts/projects/praxis/project-instructions.md +0 -24
  50. /package/prompts/{projects → personal}/praxis/prompt-config.yaml +0 -0
  51. /package/prompts/{projects → work}/maximus/references/maturity-questions.md +0 -0
  52. /package/prompts/{projects → work}/maximus/references/phase-maturity-matrix.md +0 -0
  53. /package/prompts/{projects → work}/maximus/references/proposal-writing-standards.md +0 -0
package/bin/praxis.js CHANGED
@@ -384,6 +384,77 @@ function uninstall() {
384
384
  console.log('');
385
385
  }
386
386
 
387
+ // ── Scaffold ────────────────────────────────────────────────
388
+
389
+ function scaffold() {
390
+ const subCmd = process.argv[3];
391
+ if (subCmd !== 'new') {
392
+ fail('Usage: praxis scaffold new <project-name> [--profile <name>] [--description "<text>"]');
393
+ }
394
+
395
+ const projectName = process.argv[4];
396
+ if (!projectName || projectName.startsWith('--')) {
397
+ fail('Specify a project name: praxis scaffold new <project-name>');
398
+ }
399
+
400
+ const PROJECTS_DIR = path.join(PKG_DIR, 'prompts', 'projects');
401
+ const TEMPLATE_DIR = path.join(PROJECTS_DIR, '_template');
402
+ const projectDir = path.join(PROJECTS_DIR, projectName);
403
+
404
+ if (fs.existsSync(projectDir)) {
405
+ fail(`Project already exists: ${projectDir}`);
406
+ }
407
+
408
+ // Parse flags
409
+ const args = process.argv.slice(5);
410
+ let profileName = '_base';
411
+ let description = '';
412
+ for (let i = 0; i < args.length; i++) {
413
+ if (args[i] === '--profile' && args[i + 1]) { profileName = args[++i]; }
414
+ if (args[i] === '--description' && args[i + 1]) { description = args[++i]; }
415
+ }
416
+
417
+ header(`Scaffolding project: ${projectName}`);
418
+
419
+ // Create project directory structure
420
+ fs.mkdirSync(path.join(projectDir, 'references'), { recursive: true });
421
+ fs.mkdirSync(path.join(projectDir, 'knowledge'), { recursive: true });
422
+
423
+ // Read template config
424
+ const templatePath = path.join(TEMPLATE_DIR, 'prompt-config.yaml');
425
+ if (!fs.existsSync(templatePath)) {
426
+ fail(`Template not found: ${templatePath}`);
427
+ }
428
+ let configContent = fs.readFileSync(templatePath, 'utf8');
429
+ configContent = configContent.replace('{{project_name}}', projectName);
430
+ configContent = configContent.replace('{{project_description}}', description);
431
+
432
+ configContent = configContent.replace('profile: null', `profile: ${profileName}`);
433
+
434
+ fs.writeFileSync(path.join(projectDir, 'prompt-config.yaml'), configContent, 'utf8');
435
+
436
+ ok(`Created ${projectDir}/prompt-config.yaml`);
437
+ ok(`Created ${projectDir}/references/`);
438
+ ok(`Created ${projectDir}/knowledge/`);
439
+ dim(`Profile: ${profileName}`);
440
+ if (description) dim(`Description: ${description}`);
441
+
442
+ // Preview compile
443
+ console.log('');
444
+ dim('Preview compiled output:');
445
+ spawnSync('node', [
446
+ path.join(PKG_DIR, 'bin', 'prompt-compile.js'),
447
+ projectName, '--preview',
448
+ ], { stdio: 'inherit', cwd: PKG_DIR });
449
+
450
+ console.log('');
451
+ header('Next steps');
452
+ dim(`1. Edit prompts/projects/${projectName}/prompt-config.yaml`);
453
+ dim(`2. Add reference files to prompts/projects/${projectName}/references/`);
454
+ dim(`3. Run: node bin/prompt-compile.js ${projectName}`);
455
+ console.log('');
456
+ }
457
+
387
458
  // ── Help ─────────────────────────────────────────────────────
388
459
 
389
460
  function printHelp() {
@@ -397,6 +468,7 @@ Commands:
397
468
  update Re-copy from latest npm package version
398
469
  health Verify install integrity
399
470
  uninstall Remove Praxis-owned files from ~/.claude/
471
+ scaffold Scaffold a new prompt project
400
472
 
401
473
  Flags:
402
474
  --help, -h Show this help
@@ -407,7 +479,7 @@ Flags:
407
479
  // ── Main ─────────────────────────────────────────────────────
408
480
 
409
481
  const arg = process.argv[2] || 'install';
410
- const commands = { install, update, health, uninstall };
482
+ const commands = { install, update, health, uninstall, scaffold };
411
483
 
412
484
  if (arg === '--help' || arg === '-h') { printHelp(); }
413
485
  else if (arg === '--version' || arg === '-v') { console.log(VERSION); }
@@ -8,11 +8,17 @@ const yaml = require('js-yaml');
8
8
  const {
9
9
  TARGETS,
10
10
  PROMPTS_DIR,
11
+ WORK_DIR,
12
+ PERSONAL_DIR,
11
13
  loadPraxisConfig,
12
14
  loadProfile,
13
15
  mergeProfiles,
14
16
  loadBlocks,
15
17
  applyOverrides,
18
+ loadClientConfig,
19
+ discoverAllDeals,
20
+ resolveProject,
21
+ mergeClientDealConfig,
16
22
  } = require('../lib/loader');
17
23
 
18
24
  const {
@@ -25,6 +31,29 @@ const {
25
31
 
26
32
  const PROJECTS_DIR = path.join(PROMPTS_DIR, 'projects');
27
33
 
34
+ /** Discover all compilable projects — clients hierarchy + legacy flat projects. */
35
+ function discoverAllProjects() {
36
+ const results = [];
37
+
38
+ // New hierarchy: clients/*/deals/*/
39
+ for (const entry of discoverAllDeals()) {
40
+ results.push({ name: entry.displayName, dir: entry.dealDir, client: entry.client, deal: entry.deal, clientDir: entry.clientDir });
41
+ }
42
+
43
+ // Legacy: projects/*/ (skip _template, skip projects that also exist in clients)
44
+ if (fs.existsSync(PROJECTS_DIR)) {
45
+ const dealNames = new Set(results.map((r) => r.deal));
46
+ const legacyDirs = fs.readdirSync(PROJECTS_DIR)
47
+ .filter((d) => d !== '_template' && fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory())
48
+ .filter((d) => !dealNames.has(d));
49
+
50
+ for (const name of legacyDirs) {
51
+ results.push({ name, dir: path.join(PROJECTS_DIR, name), client: null, deal: name, clientDir: null });
52
+ }
53
+ }
54
+ return results;
55
+ }
56
+
28
57
  const CHAR_BUDGETS = {
29
58
  'claude-code': Infinity,
30
59
  'claude-project': 2500,
@@ -58,10 +87,10 @@ function validateStandalone(projectName, projectDir, projectConfig) {
58
87
  console.log(`\nValidating standalone: ${projectName}`);
59
88
 
60
89
  const inventory = [
61
- { file: 'system-prompt.md', budget: Infinity, required: true, label: 'System Prompt (Claude Projects)' },
90
+ { file: 'system-prompt.md', budget: Infinity, required: true, label: 'System Prompt (Source)' },
62
91
  { 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' },
92
+ { file: 'space-instructions-perplexity.md', budget: CHAR_BUDGETS['perplexity-space'], required: false, label: 'Perplexity Space' },
93
+ { file: 'project-instructions-claude-desktop.md', budget: CHAR_BUDGETS['claude-project'], required: false, label: 'Claude Desktop' },
65
94
  ];
66
95
 
67
96
  const results = [];
@@ -121,15 +150,35 @@ function validateStandalone(projectName, projectDir, projectConfig) {
121
150
  }
122
151
 
123
152
  /** Compile a project. Returns { mode, results[] } for summary table. */
124
- function compileProject(projectName, targets) {
125
- const projectDir = path.join(PROJECTS_DIR, projectName);
126
- const configPath = path.join(projectDir, 'prompt-config.yaml');
153
+ function compileProject(projectName, targets, projectDirOverride, clientDirOverride) {
154
+ // Resolve project path — supports "client/deal", "deal", or direct dir override
155
+ let projectDir;
156
+ let clientDir = clientDirOverride || null;
157
+ if (projectDirOverride) {
158
+ projectDir = projectDirOverride;
159
+ } else {
160
+ const resolved = resolveProject(projectName);
161
+ if (!resolved.dealDir || !fs.existsSync(resolved.dealDir)) {
162
+ fail(`Project not found: ${projectName}\nRun /px-prompt <project-name> to create one.`);
163
+ }
164
+ projectDir = resolved.dealDir;
165
+ clientDir = resolved.clientDir;
166
+ }
127
167
 
168
+ const configPath = path.join(projectDir, 'prompt-config.yaml');
128
169
  if (!fs.existsSync(configPath)) {
129
170
  fail(`Project config not found: ${configPath}\nRun /px-prompt <project-name> to create one.`);
130
171
  }
131
172
 
132
- const projectConfig = yaml.load(fs.readFileSync(configPath, 'utf8'));
173
+ let projectConfig = yaml.load(fs.readFileSync(configPath, 'utf8'));
174
+
175
+ // Merge client config if in hierarchy
176
+ if (clientDir) {
177
+ const clientConfig = loadClientConfig(clientDir);
178
+ if (clientConfig) {
179
+ projectConfig = mergeClientDealConfig(clientConfig, projectConfig);
180
+ }
181
+ }
133
182
 
134
183
  if (projectConfig.mode === 'standalone') {
135
184
  return validateStandalone(projectName, projectDir, projectConfig);
@@ -165,8 +214,8 @@ function compileProject(projectName, targets) {
165
214
 
166
215
  const outputNames = {
167
216
  'claude-code': 'CLAUDE.md',
168
- 'claude-project': 'project-instructions.md',
169
- 'perplexity-space': 'space-instructions.md',
217
+ 'claude-project': 'project-instructions-claude-desktop.md',
218
+ 'perplexity-space': 'space-instructions-perplexity.md',
170
219
  };
171
220
 
172
221
  const results = [];
@@ -242,41 +291,96 @@ function main() {
242
291
  console.log(' --diff Show what changed before writing');
243
292
  console.log(' --strict Exit with error on budget overruns or unresolved vars');
244
293
  console.log(' --sync Compile all projects with diff, show summary table');
294
+ console.log(' --dashboard Rich project index with budgets and staleness');
245
295
  console.log(' --list List all projects with mode and file status');
246
296
  process.exit(0);
247
297
  }
248
298
 
249
299
  // --list mode: show all projects
250
300
  if (args.includes('--list')) {
251
- const projectDirs = fs.readdirSync(PROJECTS_DIR)
252
- .filter((d) => d !== '_template' && fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory());
253
- if (projectDirs.length === 0) {
301
+ const allProjects = discoverAllProjects();
302
+ if (allProjects.length === 0) {
254
303
  console.log('No projects found.');
255
304
  process.exit(0);
256
305
  }
257
- console.log(`${'Project'.padEnd(20)} ${'Mode'.padEnd(12)} ${'System Prompt'.padEnd(15)} ${'Claude Proj'.padEnd(15)} ${'Perplexity'.padEnd(15)} ${'CLAUDE.md'.padEnd(12)} Refs`);
258
- console.log('-'.repeat(95));
259
- for (const name of projectDirs) {
260
- const dir = path.join(PROJECTS_DIR, name);
261
- const cfgPath = path.join(dir, 'prompt-config.yaml');
306
+ console.log(`${'Project'.padEnd(28)} ${'Mode'.padEnd(12)} ${'System Prompt'.padEnd(15)} ${'Claude Desktop'.padEnd(15)} ${'Perplexity'.padEnd(15)} ${'CLAUDE.md'.padEnd(12)} Refs`);
307
+ console.log('-'.repeat(105));
308
+ for (const proj of allProjects) {
309
+ const cfgPath = path.join(proj.dir, 'prompt-config.yaml');
262
310
  const cfg = fs.existsSync(cfgPath) ? yaml.load(fs.readFileSync(cfgPath, 'utf8')) : {};
263
311
  const mode = cfg.mode || 'compiled';
264
312
  const fileStatus = (f) => {
265
- const p = path.join(dir, f);
313
+ const p = path.join(proj.dir, f);
266
314
  if (!fs.existsSync(p)) return '—';
267
315
  return `${fs.readFileSync(p, 'utf8').length} chars`;
268
316
  };
269
- const refsDir = path.join(dir, 'references');
317
+ const refsDir = path.join(proj.dir, 'references');
270
318
  const refCount = fs.existsSync(refsDir)
271
319
  ? fs.readdirSync(refsDir).filter((f) => f.endsWith('.md')).length
272
320
  : 0;
273
321
  console.log(
274
- `${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}`
322
+ `${proj.name.padEnd(28)} ${mode.padEnd(12)} ${fileStatus('system-prompt.md').padEnd(15)} ${fileStatus('project-instructions-claude-desktop.md').padEnd(15)} ${fileStatus('space-instructions-perplexity.md').padEnd(15)} ${fileStatus('CLAUDE.md').padEnd(12)} ${refCount}`
275
323
  );
276
324
  }
277
325
  process.exit(0);
278
326
  }
279
327
 
328
+ // --dashboard mode: rich project index with staleness and budgets
329
+ if (args.includes('--dashboard')) {
330
+ const allProjects = discoverAllProjects();
331
+ if (allProjects.length === 0) {
332
+ console.log('No projects found.');
333
+ process.exit(0);
334
+ }
335
+
336
+ const STALE_DAYS = 30;
337
+ const now = Date.now();
338
+
339
+ console.log('\n\x1b[1mPROMPT ENGINE DASHBOARD\x1b[0m');
340
+ console.log('\x1b[90m' + '━'.repeat(110) + '\x1b[0m');
341
+ console.log(
342
+ `${'Project'.padEnd(24)} ${'Mode'.padEnd(12)} ${'Perplexity'.padEnd(14)} ${'Claude Proj'.padEnd(14)} ${'CLAUDE.md'.padEnd(14)} ${'Refs'.padEnd(6)} ${'Updated'.padEnd(12)} Stale?`
343
+ );
344
+ console.log('\x1b[90m' + '─'.repeat(110) + '\x1b[0m');
345
+
346
+ for (const proj of allProjects) {
347
+ const cfgPath = path.join(proj.dir, 'prompt-config.yaml');
348
+ const cfg = fs.existsSync(cfgPath) ? yaml.load(fs.readFileSync(cfgPath, 'utf8')) : {};
349
+ const mode = cfg.mode || 'compiled';
350
+
351
+ const fileBudget = (f, budget) => {
352
+ const p = path.join(proj.dir, f);
353
+ if (!fs.existsSync(p)) return '\x1b[90m—\x1b[0m';
354
+ const chars = fs.readFileSync(p, 'utf8').length;
355
+ const icon = chars > budget ? '\x1b[33m⚠\x1b[0m' : '\x1b[32m✓\x1b[0m';
356
+ return `${chars} ${budget < Infinity ? icon : ''}`;
357
+ };
358
+
359
+ const refsDir = path.join(proj.dir, 'references');
360
+ const refCount = fs.existsSync(refsDir)
361
+ ? fs.readdirSync(refsDir).filter((f) => f.endsWith('.md')).length
362
+ : 0;
363
+
364
+ let latestMtime = 0;
365
+ const allFiles = fs.readdirSync(proj.dir).filter((f) => f.endsWith('.md') || f.endsWith('.yaml'));
366
+ for (const f of allFiles) {
367
+ const stat = fs.statSync(path.join(proj.dir, f));
368
+ if (stat.mtimeMs > latestMtime) latestMtime = stat.mtimeMs;
369
+ }
370
+ const updated = latestMtime > 0 ? new Date(latestMtime).toISOString().slice(0, 10) : '—';
371
+ const daysSince = latestMtime > 0 ? Math.floor((now - latestMtime) / 86400000) : 999;
372
+ const stale = daysSince > STALE_DAYS ? '\x1b[31mYes\x1b[0m' : '\x1b[32mNo\x1b[0m';
373
+
374
+ console.log(
375
+ `${proj.name.padEnd(24)} ${mode.padEnd(12)} ${fileBudget('space-instructions-perplexity.md', CHAR_BUDGETS['perplexity-space']).padEnd(23)} ${fileBudget('project-instructions-claude-desktop.md', CHAR_BUDGETS['claude-project']).padEnd(23)} ${fileBudget('CLAUDE.md', Infinity).padEnd(23)} ${String(refCount).padEnd(6)} ${updated.padEnd(12)} ${stale}`
376
+ );
377
+ }
378
+
379
+ console.log('\x1b[90m' + '━'.repeat(110) + '\x1b[0m');
380
+ console.log('\x1b[90mStaleness: >30 days since last file change. Budgets: ✓ under, ⚠ over.\x1b[0m\n');
381
+ process.exit(0);
382
+ }
383
+
280
384
  // Parse global flags
281
385
  PREVIEW_MODE = args.includes('--preview');
282
386
  DIFF_MODE = args.includes('--diff');
@@ -307,16 +411,15 @@ function main() {
307
411
  }
308
412
 
309
413
  if (args.includes('--all') || isSync) {
310
- const projectDirs = fs.readdirSync(PROJECTS_DIR)
311
- .filter((d) => d !== '_template' && fs.statSync(path.join(PROJECTS_DIR, d)).isDirectory());
414
+ const allProjects = discoverAllProjects();
312
415
 
313
- if (projectDirs.length === 0) {
314
- fail('No projects found in prompts/projects/');
416
+ if (allProjects.length === 0) {
417
+ fail('No projects found.');
315
418
  }
316
419
 
317
420
  const allResults = [];
318
- for (const projectName of projectDirs) {
319
- const result = compileProject(projectName, targets);
421
+ for (const proj of allProjects) {
422
+ const result = compileProject(proj.name, targets, proj.dir, proj.clientDir);
320
423
  if (result) allResults.push(result);
321
424
  }
322
425
 
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Renders knowledge-file templates for a project.
6
+ *
7
+ * Reads `knowledge_packs` from the project's prompt-config.yaml.
8
+ * Each entry specifies a template and vars. Renders to projects/<name>/knowledge/.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const yaml = require('js-yaml');
14
+ const { interpolate, findUnresolved } = require('../lib/assemblers');
15
+ const { parseFrontmatter, loadPraxisConfig, resolveProject, loadClientConfig, mergeClientDealConfig } = require('../lib/loader');
16
+
17
+ const PKG_DIR = path.resolve(__dirname, '..');
18
+ const TEMPLATES_DIR = path.join(PKG_DIR, 'prompts', 'templates', 'knowledge');
19
+
20
+ function fail(msg) {
21
+ console.error(`\x1b[31mERROR:\x1b[0m ${msg}`);
22
+ process.exit(1);
23
+ }
24
+
25
+ function warn(msg) {
26
+ console.error(`\x1b[33mWARN:\x1b[0m ${msg}`);
27
+ }
28
+
29
+ function ok(msg) {
30
+ console.log(`\x1b[32m\u2713\x1b[0m ${msg}`);
31
+ }
32
+
33
+ function main() {
34
+ const args = process.argv.slice(2);
35
+
36
+ if (args.length === 0 || args.includes('--help')) {
37
+ console.log('Usage: prompt-knowledge <project-name> [--preview] [--diff] [--list-templates]');
38
+ process.exit(0);
39
+ }
40
+
41
+ if (args.includes('--list-templates')) {
42
+ if (!fs.existsSync(TEMPLATES_DIR)) {
43
+ console.log('No templates found.');
44
+ process.exit(0);
45
+ }
46
+ const templates = fs.readdirSync(TEMPLATES_DIR).filter((f) => f.endsWith('.md'));
47
+ if (templates.length === 0) {
48
+ console.log('No templates found.');
49
+ process.exit(0);
50
+ }
51
+ console.log('\nAvailable knowledge templates:\n');
52
+ for (const t of templates) {
53
+ const content = fs.readFileSync(path.join(TEMPLATES_DIR, t), 'utf8');
54
+ const { meta } = parseFrontmatter(content);
55
+ console.log(` ${(meta.id || t).padEnd(28)} ${meta.description || '(no description)'}`);
56
+ if (meta.vars) {
57
+ console.log(` vars: ${meta.vars.join(', ')}`);
58
+ }
59
+ }
60
+ console.log('');
61
+ process.exit(0);
62
+ }
63
+
64
+ const preview = args.includes('--preview');
65
+ const diff = args.includes('--diff');
66
+ const projectName = args.find((a) => !a.startsWith('--'));
67
+
68
+ if (!projectName) fail('Specify a project name.');
69
+
70
+ const resolved = resolveProject(projectName);
71
+ if (!resolved.dealDir || !fs.existsSync(resolved.dealDir)) {
72
+ fail(`Project not found: ${projectName}`);
73
+ }
74
+ const projectDir = resolved.dealDir;
75
+ const configPath = path.join(projectDir, 'prompt-config.yaml');
76
+
77
+ if (!fs.existsSync(configPath)) {
78
+ fail(`Project config not found: ${configPath}`);
79
+ }
80
+
81
+ let projectConfig = yaml.load(fs.readFileSync(configPath, 'utf8'));
82
+
83
+ // Merge client config if in hierarchy
84
+ if (resolved.clientDir) {
85
+ const clientConfig = loadClientConfig(resolved.clientDir);
86
+ if (clientConfig) {
87
+ projectConfig = mergeClientDealConfig(clientConfig, projectConfig);
88
+ }
89
+ }
90
+
91
+ const praxisConfig = loadPraxisConfig();
92
+ const globalVars = {
93
+ ...praxisConfig,
94
+ ...(projectConfig.vars || {}),
95
+ project: projectConfig.project || projectConfig.deal || projectName,
96
+ };
97
+
98
+ const packs = projectConfig.knowledge_packs || [];
99
+ if (packs.length === 0) {
100
+ console.log(`No knowledge_packs defined in ${configPath}`);
101
+ process.exit(0);
102
+ }
103
+
104
+ const knowledgeDir = path.join(projectDir, 'knowledge');
105
+ if (!preview && !fs.existsSync(knowledgeDir)) {
106
+ fs.mkdirSync(knowledgeDir, { recursive: true });
107
+ }
108
+
109
+ console.log(`\nRendering knowledge packs for: ${projectName}\n`);
110
+
111
+ for (const pack of packs) {
112
+ const templatePath = path.join(TEMPLATES_DIR, `${pack.template}.md`);
113
+ if (!fs.existsSync(templatePath)) {
114
+ warn(`Template not found: ${pack.template} \u2014 skipping`);
115
+ continue;
116
+ }
117
+
118
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
119
+ const { body } = parseFrontmatter(templateContent);
120
+
121
+ const vars = { ...globalVars, ...(pack.vars || {}) };
122
+ const output = interpolate(body, vars);
123
+
124
+ const unresolved = findUnresolved(output);
125
+ if (unresolved.length > 0) {
126
+ warn(`Unresolved in ${pack.output}: ${unresolved.join(', ')} \u2014 these become placeholders for manual fill`);
127
+ }
128
+
129
+ const outputPath = path.join(knowledgeDir, pack.output);
130
+
131
+ if (preview) {
132
+ console.log(`--- ${pack.output} (${output.length} chars, targets: ${(pack.targets || ['all']).join(', ')}) ---`);
133
+ console.log(output);
134
+ console.log('');
135
+ } else if (diff && fs.existsSync(outputPath)) {
136
+ const existing = fs.readFileSync(outputPath, 'utf8');
137
+ if (existing === output) {
138
+ ok(`${pack.output} \u2014 unchanged`);
139
+ } else {
140
+ fs.writeFileSync(outputPath, output, 'utf8');
141
+ ok(`${pack.output} \u2014 updated (${output.length} chars)`);
142
+ }
143
+ } else {
144
+ fs.writeFileSync(outputPath, output, 'utf8');
145
+ ok(`${pack.output} \u2014 ${output.length} chars \u2192 ${outputPath}`);
146
+ }
147
+ }
148
+
149
+ console.log('\nDone.');
150
+ }
151
+
152
+ main();
package/lib/assemblers.js CHANGED
@@ -55,6 +55,13 @@ function assembleClaudeCode(blocks, projectConfig, vars) {
55
55
  for (const block of domainBlocks) lines.push(block.content, '');
56
56
  }
57
57
 
58
+ // Skills
59
+ const skillBlocks = blocks.filter((b) => b.category === 'skills');
60
+ if (skillBlocks.length > 0) {
61
+ lines.push('## Skills & Techniques');
62
+ for (const block of skillBlocks) lines.push(block.content, '');
63
+ }
64
+
58
65
  // Formats
59
66
  const formatBlocks = blocks.filter((b) => b.category === 'formats');
60
67
  if (formatBlocks.length > 0) {
@@ -117,24 +124,28 @@ function assembleClaudeProject(blocks, projectConfig, vars) {
117
124
  const behaviorBlocks = blocks.filter((b) => b.category === 'behaviors');
118
125
  if (behaviorBlocks.length > 0) {
119
126
  lines.push('## Behavioral Constraints');
120
- for (const block of behaviorBlocks) lines.push('- ' + block.content);
121
- lines.push('');
127
+ for (const block of behaviorBlocks) lines.push(block.content, '');
122
128
  }
123
129
 
124
130
  // Layer 3: Domain Expertise
125
131
  const domainBlocks = blocks.filter((b) => b.category === 'domains');
126
132
  if (domainBlocks.length > 0) {
127
133
  lines.push('## Domain Expertise');
128
- for (const block of domainBlocks) lines.push('- ' + block.content);
129
- lines.push('');
134
+ for (const block of domainBlocks) lines.push(block.content, '');
135
+ }
136
+
137
+ // Layer 3a: Skills & Techniques
138
+ const skillBlocks = blocks.filter((b) => b.category === 'skills');
139
+ if (skillBlocks.length > 0) {
140
+ lines.push('## Skills & Techniques');
141
+ for (const block of skillBlocks) lines.push(block.content, '');
130
142
  }
131
143
 
132
144
  // Layer 3b: Output Format
133
145
  const formatBlocks = blocks.filter((b) => b.category === 'formats');
134
146
  if (formatBlocks.length > 0) {
135
147
  lines.push('## Output Format');
136
- for (const block of formatBlocks) lines.push('- ' + block.content);
137
- lines.push('');
148
+ for (const block of formatBlocks) lines.push(block.content, '');
138
149
  }
139
150
 
140
151
  // Additional context
@@ -203,6 +214,14 @@ function assemblePerplexitySpace(blocks, projectConfig, vars) {
203
214
  lines.push('');
204
215
  }
205
216
 
217
+ // Capabilities (skills)
218
+ const skillBlocks = blocks.filter((b) => b.category === 'skills');
219
+ if (skillBlocks.length > 0) {
220
+ lines.push('## Capabilities');
221
+ for (const block of skillBlocks) lines.push(block.content);
222
+ lines.push('');
223
+ }
224
+
206
225
  // Research Domains
207
226
  const append = (projectConfig.overrides || {}).perplexity_space_append || {};
208
227
  if (append.research_domains) {