k-harness 0.1.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/src/init.js ADDED
@@ -0,0 +1,313 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const readline = require('node:readline');
6
+
7
+ const HARNESS_DIR = path.join(__dirname, '..', 'harness');
8
+
9
+ // ─── Template reader ─────────────────────────────────────────
10
+ function readTemplate(name) {
11
+ return fs.readFileSync(path.join(HARNESS_DIR, name), 'utf8');
12
+ }
13
+
14
+ // ─── File writer (mkdir -p + conflict check) ─────────────────
15
+ function writeFile(targetDir, relPath, content, overwrite) {
16
+ const fullPath = path.join(targetDir, relPath);
17
+ if (fs.existsSync(fullPath) && !overwrite) {
18
+ console.log(` ⏭ Skipped (exists): ${relPath}`);
19
+ return false;
20
+ }
21
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
22
+ fs.writeFileSync(fullPath, content, 'utf8');
23
+ console.log(` ✓ ${relPath}`);
24
+ return true;
25
+ }
26
+
27
+ // ─── IDE Generators ──────────────────────────────────────────
28
+
29
+ function generateVscode(targetDir, overwrite) {
30
+ const coreRules = readTemplate('core-rules.md');
31
+ const testingRules = readTemplate('testing-rules.md');
32
+ const backendRules = readTemplate('backend-rules.md');
33
+
34
+ // Global instructions
35
+ writeFile(targetDir, '.github/copilot-instructions.md', coreRules, overwrite);
36
+
37
+ // File-scoped instructions (add VS Code applyTo frontmatter)
38
+ const testingWithFrontmatter =
39
+ '---\napplyTo: "**/*.test.ts,**/*.test.js,**/*.spec.ts,**/*.spec.js,**/__mocks__/**,**/__tests__/**"\n---\n\n' +
40
+ testingRules;
41
+ writeFile(targetDir, '.vscode/instructions/testing.instructions.md', testingWithFrontmatter, overwrite);
42
+
43
+ const backendWithFrontmatter =
44
+ '---\napplyTo: "src/**/*.ts,src/**/*.js"\n---\n\n' +
45
+ backendRules;
46
+ writeFile(targetDir, '.vscode/instructions/backend.instructions.md', backendWithFrontmatter, overwrite);
47
+
48
+ // Skills (.github/skills — VS Code default search path)
49
+ writeFile(targetDir, '.github/skills/test-integrity/SKILL.md', readTemplate('skills/test-integrity.md'), overwrite);
50
+ writeFile(targetDir, '.github/skills/security-checklist/SKILL.md', readTemplate('skills/security-checklist.md'), overwrite);
51
+ writeFile(targetDir, '.github/skills/investigate/SKILL.md', readTemplate('skills/investigate.md'), overwrite);
52
+ writeFile(targetDir, '.github/skills/impact-analysis/SKILL.md', readTemplate('skills/impact-analysis.md'), overwrite);
53
+ writeFile(targetDir, '.github/skills/feature-breakdown/SKILL.md', readTemplate('skills/feature-breakdown.md'), overwrite);
54
+
55
+ // Agents (.github/agents — VS Code default search path)
56
+ const reviewerContent = readTemplate('agents/reviewer.md');
57
+ const reviewerAgent =
58
+ '---\nname: reviewer\ndescription: "Code review + auto-fix. Validates quality, security, and test integrity before commits."\n---\n\n' +
59
+ reviewerContent;
60
+ writeFile(targetDir, '.github/agents/reviewer.agent.md', reviewerAgent, overwrite);
61
+
62
+ const sprintContent = readTemplate('agents/sprint-manager.md');
63
+ const sprintAgent =
64
+ '---\nname: sprint-manager\ndescription: "Sprint/Story state tracking, next task guidance, scope drift prevention."\n---\n\n' +
65
+ sprintContent;
66
+ writeFile(targetDir, '.github/agents/sprint-manager.agent.md', sprintAgent, overwrite);
67
+
68
+ const plannerContent = readTemplate('agents/planner.md');
69
+ const plannerAgent =
70
+ '---\nname: planner\ndescription: "Feature planning and dependency management. Analyze architecture, break down features, track module relationships."\n---\n\n' +
71
+ plannerContent;
72
+ writeFile(targetDir, '.github/agents/planner.agent.md', plannerAgent, overwrite);
73
+
74
+ // State files
75
+ writeFile(targetDir, 'project-state.md', readTemplate('project-state.md'), overwrite);
76
+ writeFile(targetDir, 'failure-patterns.md', readTemplate('failure-patterns.md'), overwrite);
77
+ writeFile(targetDir, 'dependency-map.md', readTemplate('dependency-map.md'), overwrite);
78
+ writeFile(targetDir, 'features.md', readTemplate('features.md'), overwrite);
79
+ writeFile(targetDir, 'project-brief.md', readTemplate('project-brief.md'), overwrite);
80
+ }
81
+
82
+ function generateClaude(targetDir, overwrite) {
83
+ // CLAUDE.md — merge core + testing + backend rules
84
+ const merged = [
85
+ readTemplate('core-rules.md'),
86
+ '\n---\n\n',
87
+ readTemplate('testing-rules.md'),
88
+ '\n---\n\n',
89
+ readTemplate('backend-rules.md'),
90
+ ].join('');
91
+ writeFile(targetDir, 'CLAUDE.md', merged, overwrite);
92
+
93
+ // Skills (Claude uses same SKILL.md format)
94
+ writeFile(targetDir, '.claude/skills/test-integrity/SKILL.md', readTemplate('skills/test-integrity.md'), overwrite);
95
+ writeFile(targetDir, '.claude/skills/security-checklist/SKILL.md', readTemplate('skills/security-checklist.md'), overwrite);
96
+ writeFile(targetDir, '.claude/skills/investigate/SKILL.md', readTemplate('skills/investigate.md'), overwrite);
97
+ writeFile(targetDir, '.claude/skills/impact-analysis/SKILL.md', readTemplate('skills/impact-analysis.md'), overwrite);
98
+ writeFile(targetDir, '.claude/skills/feature-breakdown/SKILL.md', readTemplate('skills/feature-breakdown.md'), overwrite);
99
+
100
+ // State files
101
+ writeFile(targetDir, 'project-state.md', readTemplate('project-state.md'), overwrite);
102
+ writeFile(targetDir, 'failure-patterns.md', readTemplate('failure-patterns.md'), overwrite);
103
+ writeFile(targetDir, 'dependency-map.md', readTemplate('dependency-map.md'), overwrite);
104
+ writeFile(targetDir, 'features.md', readTemplate('features.md'), overwrite);
105
+ writeFile(targetDir, 'project-brief.md', readTemplate('project-brief.md'), overwrite);
106
+ }
107
+
108
+ function generateCursor(targetDir, overwrite) {
109
+ // .cursor/rules/*.mdc — each needs frontmatter
110
+ const coreRules = readTemplate('core-rules.md');
111
+ const coreMdc =
112
+ '---\ndescription: Core project rules — Iron Laws, completion protocol, concreteness\nalwaysApply: true\n---\n\n' +
113
+ coreRules;
114
+ writeFile(targetDir, '.cursor/rules/core.mdc', coreMdc, overwrite);
115
+
116
+ const testingRules = readTemplate('testing-rules.md');
117
+ const testingMdc =
118
+ '---\ndescription: Testing rules — mock sync, forbidden patterns\nglobs: "**/*.test.*,**/*.spec.*,**/__mocks__/**,**/__tests__/**"\nalwaysApply: false\n---\n\n' +
119
+ testingRules;
120
+ writeFile(targetDir, '.cursor/rules/testing.mdc', testingMdc, overwrite);
121
+
122
+ const backendRules = readTemplate('backend-rules.md');
123
+ const backendMdc =
124
+ '---\ndescription: Backend code rules — architecture enforcement, type safety\nglobs: "src/**/*.ts,src/**/*.js"\nalwaysApply: false\n---\n\n' +
125
+ backendRules;
126
+ writeFile(targetDir, '.cursor/rules/backend.mdc', backendMdc, overwrite);
127
+
128
+ // Skills as rules
129
+ const skills = ['test-integrity', 'security-checklist', 'investigate', 'impact-analysis', 'feature-breakdown'];
130
+ for (const skill of skills) {
131
+ const content = readTemplate(`skills/${skill}.md`);
132
+ const mdc =
133
+ `---\ndescription: Skill — ${skill}\nalwaysApply: false\n---\n\n` +
134
+ content;
135
+ writeFile(targetDir, `.cursor/rules/${skill}.mdc`, mdc, overwrite);
136
+ }
137
+
138
+ // Agents as rules
139
+ const agents = [
140
+ { name: 'reviewer', file: 'agents/reviewer.md' },
141
+ { name: 'sprint-manager', file: 'agents/sprint-manager.md' },
142
+ { name: 'planner', file: 'agents/planner.md' },
143
+ ];
144
+ for (const agent of agents) {
145
+ const content = readTemplate(agent.file);
146
+ const mdc =
147
+ `---\ndescription: Agent — ${agent.name}\nalwaysApply: false\n---\n\n` +
148
+ content;
149
+ writeFile(targetDir, `.cursor/rules/${agent.name}.mdc`, mdc, overwrite);
150
+ }
151
+
152
+ // State files
153
+ writeFile(targetDir, 'project-state.md', readTemplate('project-state.md'), overwrite);
154
+ writeFile(targetDir, 'failure-patterns.md', readTemplate('failure-patterns.md'), overwrite);
155
+ writeFile(targetDir, 'dependency-map.md', readTemplate('dependency-map.md'), overwrite);
156
+ writeFile(targetDir, 'features.md', readTemplate('features.md'), overwrite);
157
+ writeFile(targetDir, 'project-brief.md', readTemplate('project-brief.md'), overwrite);
158
+ }
159
+
160
+ function generateCodex(targetDir, overwrite) {
161
+ // AGENTS.md — merge core + testing + backend rules
162
+ const merged = [
163
+ readTemplate('core-rules.md'),
164
+ '\n---\n\n',
165
+ readTemplate('testing-rules.md'),
166
+ '\n---\n\n',
167
+ readTemplate('backend-rules.md'),
168
+ ].join('');
169
+ writeFile(targetDir, 'AGENTS.md', merged, overwrite);
170
+
171
+ // Skills (Codex uses .agents/skills/ format)
172
+ writeFile(targetDir, '.agents/skills/test-integrity/SKILL.md', readTemplate('skills/test-integrity.md'), overwrite);
173
+ writeFile(targetDir, '.agents/skills/security-checklist/SKILL.md', readTemplate('skills/security-checklist.md'), overwrite);
174
+ writeFile(targetDir, '.agents/skills/investigate/SKILL.md', readTemplate('skills/investigate.md'), overwrite);
175
+ writeFile(targetDir, '.agents/skills/impact-analysis/SKILL.md', readTemplate('skills/impact-analysis.md'), overwrite);
176
+ writeFile(targetDir, '.agents/skills/feature-breakdown/SKILL.md', readTemplate('skills/feature-breakdown.md'), overwrite);
177
+
178
+ // State files
179
+ writeFile(targetDir, 'project-state.md', readTemplate('project-state.md'), overwrite);
180
+ writeFile(targetDir, 'failure-patterns.md', readTemplate('failure-patterns.md'), overwrite);
181
+ writeFile(targetDir, 'dependency-map.md', readTemplate('dependency-map.md'), overwrite);
182
+ writeFile(targetDir, 'features.md', readTemplate('features.md'), overwrite);
183
+ writeFile(targetDir, 'project-brief.md', readTemplate('project-brief.md'), overwrite);
184
+ }
185
+
186
+ function generateWindsurf(targetDir, overwrite) {
187
+ // .windsurfrules — everything in one file
188
+ const sections = [
189
+ readTemplate('core-rules.md'),
190
+ readTemplate('testing-rules.md'),
191
+ readTemplate('backend-rules.md'),
192
+ '---\n\n# Skills\n\n',
193
+ readTemplate('skills/test-integrity.md'),
194
+ readTemplate('skills/security-checklist.md'),
195
+ readTemplate('skills/investigate.md'),
196
+ readTemplate('skills/impact-analysis.md'),
197
+ readTemplate('skills/feature-breakdown.md'),
198
+ '---\n\n# Agents\n\n',
199
+ readTemplate('agents/reviewer.md'),
200
+ readTemplate('agents/sprint-manager.md'),
201
+ readTemplate('agents/planner.md'),
202
+ ];
203
+ writeFile(targetDir, '.windsurfrules', sections.join('\n\n---\n\n'), overwrite);
204
+
205
+ // State files
206
+ writeFile(targetDir, 'project-state.md', readTemplate('project-state.md'), overwrite);
207
+ writeFile(targetDir, 'failure-patterns.md', readTemplate('failure-patterns.md'), overwrite);
208
+ writeFile(targetDir, 'dependency-map.md', readTemplate('dependency-map.md'), overwrite);
209
+ writeFile(targetDir, 'features.md', readTemplate('features.md'), overwrite);
210
+ writeFile(targetDir, 'project-brief.md', readTemplate('project-brief.md'), overwrite);
211
+ }
212
+
213
+ // ─── IDE registry ────────────────────────────────────────────
214
+ const GENERATORS = {
215
+ vscode: { name: 'VS Code Copilot', fn: generateVscode },
216
+ claude: { name: 'Claude Code', fn: generateClaude },
217
+ cursor: { name: 'Cursor', fn: generateCursor },
218
+ codex: { name: 'Codex (OpenAI)', fn: generateCodex },
219
+ windsurf: { name: 'Windsurf', fn: generateWindsurf },
220
+ };
221
+
222
+ // ─── Interactive prompt ──────────────────────────────────────
223
+ function askQuestion(query) {
224
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
225
+ return new Promise((resolve) => {
226
+ rl.question(query, (answer) => {
227
+ rl.close();
228
+ resolve(answer.trim());
229
+ });
230
+ });
231
+ }
232
+
233
+ async function promptIde() {
234
+ const keys = Object.keys(GENERATORS);
235
+ console.log('\n Select your IDE:\n');
236
+ keys.forEach((key, i) => {
237
+ console.log(` ${i + 1}. ${GENERATORS[key].name}`);
238
+ });
239
+ console.log();
240
+
241
+ const answer = await askQuestion(' Choice (1-5): ');
242
+ const idx = parseInt(answer, 10) - 1;
243
+ if (idx < 0 || idx >= keys.length || isNaN(idx)) {
244
+ console.error(' Invalid choice.');
245
+ process.exit(1);
246
+ }
247
+ return keys[idx];
248
+ }
249
+
250
+ // ─── CLI entry ───────────────────────────────────────────────
251
+ function showHelp() {
252
+ console.log(`
253
+ K-Harness — LLM Development Harness
254
+
255
+ Usage:
256
+ npx k-harness init [options]
257
+
258
+ Options:
259
+ --ide <name> IDE target: vscode, claude, cursor, codex, windsurf
260
+ --dir <path> Target directory (default: current directory)
261
+ --overwrite Overwrite existing files
262
+ --help Show this help
263
+
264
+ Examples:
265
+ npx k-harness init
266
+ npx k-harness init --ide vscode
267
+ npx k-harness init --ide claude --dir ./my-project
268
+ `);
269
+ }
270
+
271
+ function parseArgs(argv) {
272
+ const args = { command: null, ide: null, dir: process.cwd(), overwrite: false, help: false };
273
+ for (let i = 0; i < argv.length; i++) {
274
+ const arg = argv[i];
275
+ if (arg === 'init') args.command = 'init';
276
+ else if (arg === '--ide' && argv[i + 1]) { args.ide = argv[++i]; }
277
+ else if (arg === '--dir' && argv[i + 1]) { args.dir = path.resolve(argv[++i]); }
278
+ else if (arg === '--overwrite') args.overwrite = true;
279
+ else if (arg === '--help' || arg === '-h') args.help = true;
280
+ }
281
+ return args;
282
+ }
283
+
284
+ async function run(argv) {
285
+ const args = parseArgs(argv);
286
+
287
+ if (args.help || !args.command) {
288
+ showHelp();
289
+ process.exit(args.help ? 0 : 1);
290
+ }
291
+
292
+ if (args.command === 'init') {
293
+ console.log('\n K-Harness — LLM Development Harness\n');
294
+
295
+ // Determine IDE
296
+ let ide = args.ide;
297
+ if (ide && !GENERATORS[ide]) {
298
+ console.error(` Unknown IDE: ${ide}`);
299
+ console.error(` Available: ${Object.keys(GENERATORS).join(', ')}`);
300
+ process.exit(1);
301
+ }
302
+ if (!ide) {
303
+ ide = await promptIde();
304
+ }
305
+
306
+ const gen = GENERATORS[ide];
307
+ console.log(`\n Installing for ${gen.name}...\n`);
308
+ gen.fn(args.dir, args.overwrite);
309
+ console.log(`\n Done! Edit project-state.md to set up your first sprint.\n`);
310
+ }
311
+ }
312
+
313
+ module.exports = { run };