@esoteric-logic/praxis-harness 2.13.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/CLAUDE.md +7 -1
- package/base/configs/registry.json +4 -2
- package/base/hooks/context7-remind.sh +81 -0
- package/base/hooks/settings-hooks.json +9 -0
- package/base/rules/coding.md +7 -0
- package/base/skills/px-prompt/SKILL.md +373 -0
- package/base/skills/px-research/SKILL.md +13 -0
- package/bin/praxis.js +7 -0
- package/bin/prompt-blocks.js +145 -0
- package/bin/prompt-compile.js +313 -0
- package/kits/web-designer/rules/web-design.md +13 -2
- 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,145 @@
|
|
|
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 { BLOCKS_DIR, PROFILES_DIR, parseFrontmatter } = require('../lib/loader');
|
|
9
|
+
|
|
10
|
+
/** Collect all blocks from the blocks directory. */
|
|
11
|
+
function loadAllBlocks() {
|
|
12
|
+
const blocks = [];
|
|
13
|
+
if (!fs.existsSync(BLOCKS_DIR)) return blocks;
|
|
14
|
+
|
|
15
|
+
for (const category of fs.readdirSync(BLOCKS_DIR)) {
|
|
16
|
+
const catDir = path.join(BLOCKS_DIR, category);
|
|
17
|
+
if (!fs.statSync(catDir).isDirectory()) continue;
|
|
18
|
+
|
|
19
|
+
for (const file of fs.readdirSync(catDir)) {
|
|
20
|
+
if (!file.endsWith('.md')) continue;
|
|
21
|
+
const content = fs.readFileSync(path.join(catDir, file), 'utf8');
|
|
22
|
+
const { meta, body } = parseFrontmatter(content);
|
|
23
|
+
const hasCondensed = body.includes('<!-- CONDENSED -->');
|
|
24
|
+
blocks.push({
|
|
25
|
+
id: meta.id || file.replace('.md', ''),
|
|
26
|
+
category,
|
|
27
|
+
platforms: meta.platforms || [],
|
|
28
|
+
charEstimate: meta.char_estimate || null,
|
|
29
|
+
description: meta.description || '',
|
|
30
|
+
tags: meta.tags || [],
|
|
31
|
+
hasCondensed,
|
|
32
|
+
actualChars: body.length,
|
|
33
|
+
file: `prompts/blocks/${category}/${file}`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return blocks;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Collect all profiles and their block references. */
|
|
41
|
+
function loadAllProfiles() {
|
|
42
|
+
const profiles = {};
|
|
43
|
+
if (!fs.existsSync(PROFILES_DIR)) return profiles;
|
|
44
|
+
|
|
45
|
+
for (const file of fs.readdirSync(PROFILES_DIR)) {
|
|
46
|
+
if (!file.endsWith('.yaml')) continue;
|
|
47
|
+
const name = file.replace('.yaml', '');
|
|
48
|
+
const content = yaml.load(fs.readFileSync(path.join(PROFILES_DIR, file), 'utf8'));
|
|
49
|
+
const blockIds = content.blocks
|
|
50
|
+
? Object.values(content.blocks).filter(Array.isArray).flat()
|
|
51
|
+
: [];
|
|
52
|
+
profiles[name] = { ...content, blockIds };
|
|
53
|
+
}
|
|
54
|
+
return profiles;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── CLI ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function main() {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
|
|
62
|
+
if (args.includes('--help')) {
|
|
63
|
+
console.log('Usage: prompt-blocks [options]');
|
|
64
|
+
console.log('Options:');
|
|
65
|
+
console.log(' --category <cat> Filter by category (identity, behaviors, domains, formats, context)');
|
|
66
|
+
console.log(' --profile <name> Show blocks used by a specific profile');
|
|
67
|
+
console.log(' --unused Show blocks not referenced by any profile');
|
|
68
|
+
console.log(' --tags Group output by tags');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const blocks = loadAllBlocks();
|
|
73
|
+
const profiles = loadAllProfiles();
|
|
74
|
+
|
|
75
|
+
// Resolve profile inheritance to get full block lists
|
|
76
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
77
|
+
if (profile.extends && profiles[profile.extends]) {
|
|
78
|
+
const baseIds = profiles[profile.extends].blockIds || [];
|
|
79
|
+
profile.blockIds = [...new Set([...baseIds, ...profile.blockIds])];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// All block IDs referenced by any profile
|
|
84
|
+
const allProfileBlockIds = new Set();
|
|
85
|
+
for (const profile of Object.values(profiles)) {
|
|
86
|
+
for (const id of profile.blockIds) allProfileBlockIds.add(id);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Filter by category
|
|
90
|
+
const catIdx = args.indexOf('--category');
|
|
91
|
+
let filtered = blocks;
|
|
92
|
+
if (catIdx !== -1 && args[catIdx + 1]) {
|
|
93
|
+
const cat = args[catIdx + 1];
|
|
94
|
+
filtered = blocks.filter((b) => b.category === cat);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Filter by profile
|
|
98
|
+
const profIdx = args.indexOf('--profile');
|
|
99
|
+
if (profIdx !== -1 && args[profIdx + 1]) {
|
|
100
|
+
const profName = args[profIdx + 1];
|
|
101
|
+
const profile = profiles[profName];
|
|
102
|
+
if (!profile) {
|
|
103
|
+
console.error(`Profile not found: ${profName}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const profileIds = new Set(profile.blockIds);
|
|
107
|
+
filtered = blocks.filter((b) => profileIds.has(b.id));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Unused mode
|
|
111
|
+
if (args.includes('--unused')) {
|
|
112
|
+
filtered = blocks.filter((b) => !allProfileBlockIds.has(b.id));
|
|
113
|
+
if (filtered.length === 0) {
|
|
114
|
+
console.log('All blocks are referenced by at least one profile.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log(`${filtered.length} unused block(s):\n`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Display
|
|
121
|
+
console.log(`${'ID'.padEnd(28)} ${'Category'.padEnd(12)} ${'Platforms'.padEnd(35)} ${'Chars'.padEnd(8)} Condensed`);
|
|
122
|
+
console.log('-'.repeat(95));
|
|
123
|
+
|
|
124
|
+
for (const block of filtered) {
|
|
125
|
+
const platforms = block.platforms.join(', ') || 'all';
|
|
126
|
+
const chars = block.charEstimate ? String(block.charEstimate) : '—';
|
|
127
|
+
const condensed = block.hasCondensed ? 'yes' : '—';
|
|
128
|
+
console.log(
|
|
129
|
+
`${block.id.padEnd(28)} ${block.category.padEnd(12)} ${platforms.padEnd(35)} ${chars.padEnd(8)} ${condensed}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`\nTotal: ${filtered.length} blocks`);
|
|
134
|
+
|
|
135
|
+
// Show profile usage summary
|
|
136
|
+
if (!args.includes('--unused') && profIdx === -1) {
|
|
137
|
+
console.log('\nProfile usage:');
|
|
138
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
139
|
+
if (name === '_base') continue;
|
|
140
|
+
console.log(` ${name}: ${profile.blockIds.length} blocks`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|
|
@@ -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();
|
|
@@ -9,25 +9,30 @@ paths:
|
|
|
9
9
|
- "design-system/**"
|
|
10
10
|
- "styles/**"
|
|
11
11
|
---
|
|
12
|
+
|
|
12
13
|
# Web Design — Rules
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
Scope: Frontend component and design system files.
|
|
16
|
+
Part of: web-designer AI-Kit.
|
|
15
17
|
|
|
16
18
|
## Invariants (BLOCK on violation)
|
|
17
19
|
|
|
18
20
|
### Design Tokens
|
|
21
|
+
|
|
19
22
|
- No inline styles in component files — use design tokens or Tailwind utilities.
|
|
20
23
|
- No hardcoded color values (`#fff`, `rgb(...)`) — must reference token variables
|
|
21
24
|
(`var(--color-primary)`, Tailwind classes, or theme values).
|
|
22
25
|
- Check: `grep -rn "color:" src/components/ | grep -v "var(--" | grep -v "tailwind"`
|
|
23
26
|
|
|
24
27
|
### Semantic HTML
|
|
28
|
+
|
|
25
29
|
- No `<div>` click handlers — use `<button>` for actions, `<a>` for navigation.
|
|
26
30
|
- Check: `grep -rn 'onClick.*<div' src/components/`
|
|
27
31
|
- Every interactive element must have explicit keyboard handling (`onKeyDown` or
|
|
28
32
|
native keyboard support via semantic elements).
|
|
29
33
|
|
|
30
34
|
### Accessibility
|
|
35
|
+
|
|
31
36
|
- Every `<img>` must have an `alt` attribute (empty `alt=""` for decorative images).
|
|
32
37
|
- Form inputs must have associated `<label>` elements or `aria-label`.
|
|
33
38
|
- Color contrast must meet WCAG AA minimum (4.5:1 for text, 3:1 for large text).
|
|
@@ -35,6 +40,7 @@ paths:
|
|
|
35
40
|
## Conventions (WARN on violation)
|
|
36
41
|
|
|
37
42
|
### Component Structure
|
|
43
|
+
|
|
38
44
|
- Component files follow `ComponentName/index.tsx` + `ComponentName.module.css`
|
|
39
45
|
(or `ComponentName.tsx` with Tailwind — pick one pattern per project, don't mix).
|
|
40
46
|
- Design tokens live in `design-system/tokens/` or equivalent.
|
|
@@ -42,21 +48,25 @@ paths:
|
|
|
42
48
|
live in `src/components/[page-name]/`.
|
|
43
49
|
|
|
44
50
|
### Animation and Motion
|
|
51
|
+
|
|
45
52
|
- Animation durations use token values, not magic numbers.
|
|
46
53
|
- Respect `prefers-reduced-motion` — wrap animations in media query.
|
|
47
54
|
- CSS transitions preferred over JS animation libraries for simple effects.
|
|
48
55
|
|
|
49
56
|
### Performance
|
|
57
|
+
|
|
50
58
|
- Images have explicit `width` and `height` to prevent CLS (Cumulative Layout Shift).
|
|
51
59
|
- Lazy-load images below the fold (`loading="lazy"`).
|
|
52
60
|
- No layout-triggering CSS in animation loops (`top`, `left`, `width`, `height` — use `transform` and `opacity`).
|
|
53
61
|
|
|
54
62
|
### Design System Hygiene
|
|
63
|
+
|
|
55
64
|
- New components must use existing tokens before creating new ones.
|
|
56
65
|
- If a new token is needed: add to the token file, not inline.
|
|
57
66
|
- Component variants use a consistent API pattern (props, not className overrides).
|
|
58
67
|
|
|
59
68
|
## Verification Commands
|
|
69
|
+
|
|
60
70
|
```bash
|
|
61
71
|
# Token compliance — find hardcoded colors
|
|
62
72
|
grep -rn "color:" src/components/ | grep -v "var(--" | grep -v "token" | grep -v "tailwind"
|
|
@@ -75,5 +85,6 @@ npx axe-core src/ --exit
|
|
|
75
85
|
```
|
|
76
86
|
|
|
77
87
|
## Removal Condition
|
|
88
|
+
|
|
78
89
|
Remove when the project's design system is mature with >90% component coverage
|
|
79
90
|
and design review is handled by a dedicated design tool or CI pipeline.
|