@esoteric-logic/praxis-harness 1.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/base/CLAUDE.md +148 -0
  4. package/base/commands/context-reset.md +72 -0
  5. package/base/commands/debug.md +63 -0
  6. package/base/commands/discover.md +49 -0
  7. package/base/commands/gsd-discuss.md +53 -0
  8. package/base/commands/gsd-execute.md +60 -0
  9. package/base/commands/gsd-verify.md +78 -0
  10. package/base/commands/kit.md +62 -0
  11. package/base/commands/plan.md +91 -0
  12. package/base/commands/ralph.md +110 -0
  13. package/base/commands/review.md +81 -0
  14. package/base/commands/risk.md +53 -0
  15. package/base/commands/ship.md +74 -0
  16. package/base/commands/spec.md +121 -0
  17. package/base/commands/standup.md +57 -0
  18. package/base/rules/architecture.md +51 -0
  19. package/base/rules/azure.md +90 -0
  20. package/base/rules/code-quality.md +65 -0
  21. package/base/rules/coding.md +139 -0
  22. package/base/rules/communication.md +69 -0
  23. package/base/rules/context-management.md +136 -0
  24. package/base/rules/execution-loop.md +84 -0
  25. package/base/rules/git-workflow.md +51 -0
  26. package/base/rules/github-actions.md +48 -0
  27. package/base/rules/powershell.md +72 -0
  28. package/base/rules/profile.md +31 -0
  29. package/base/rules/security.md +40 -0
  30. package/base/rules/terraform.md +48 -0
  31. package/base/rules/vault.md +134 -0
  32. package/base/skills/code-gc/SKILL.md +205 -0
  33. package/base/skills/code-simplifier/SKILL.md +132 -0
  34. package/base/skills/prd-writer/SKILL.md +108 -0
  35. package/base/skills/prd-writer/references/prd-template.md +22 -0
  36. package/base/skills/pre-commit-lint/SKILL.md +71 -0
  37. package/base/skills/scaffold-exist/SKILL.md +85 -0
  38. package/base/skills/scaffold-new/SKILL.md +177 -0
  39. package/base/skills/scaffold-new/references/claude-progress-template.json +24 -0
  40. package/base/skills/scaffold-new/references/gitignore-template.txt +65 -0
  41. package/base/skills/scaffold-new/references/repo-CLAUDE-md-template.md +87 -0
  42. package/base/skills/scaffold-new/references/vault-index-template.md +31 -0
  43. package/base/skills/scaffold-new/references/vault-learnings-template.md +21 -0
  44. package/base/skills/scaffold-new/references/vault-status-template.md +21 -0
  45. package/base/skills/scaffold-new/references/vault-tasks-template.md +20 -0
  46. package/base/skills/session-retro/SKILL.md +146 -0
  47. package/base/skills/subagent-review/SKILL.md +126 -0
  48. package/base/skills/vault-gc/SKILL.md +93 -0
  49. package/base/skills/verify-app/SKILL.md +156 -0
  50. package/bin/praxis.js +385 -0
  51. package/kits/infrastructure/KIT.md +66 -0
  52. package/kits/infrastructure/commands/infra-apply.md +44 -0
  53. package/kits/infrastructure/commands/infra-compliance.md +65 -0
  54. package/kits/infrastructure/commands/infra-drift.md +45 -0
  55. package/kits/infrastructure/commands/infra-plan.md +45 -0
  56. package/kits/infrastructure/install.sh +43 -0
  57. package/kits/infrastructure/rules/infrastructure.md +82 -0
  58. package/kits/infrastructure/teardown.sh +14 -0
  59. package/kits/web-designer/KIT.md +76 -0
  60. package/kits/web-designer/commands/web-audit.md +67 -0
  61. package/kits/web-designer/commands/web-component.md +54 -0
  62. package/kits/web-designer/commands/web-init.md +42 -0
  63. package/kits/web-designer/commands/web-tokens-sync.md +49 -0
  64. package/kits/web-designer/install.sh +41 -0
  65. package/kits/web-designer/rules/web-design.md +79 -0
  66. package/kits/web-designer/teardown.sh +26 -0
  67. package/package.json +28 -0
  68. package/scripts/health-check.sh +160 -0
  69. package/scripts/lint-harness.sh +195 -0
  70. package/scripts/onboard-mcp.sh +326 -0
  71. package/scripts/update.sh +88 -0
  72. package/templates/_index.md +33 -0
  73. package/templates/adr.md +28 -0
  74. package/templates/claude-progress.json +24 -0
  75. package/templates/plan.md +46 -0
  76. package/templates/project-index.md +31 -0
  77. package/templates/session-note.md +21 -0
  78. package/templates/status.md +27 -0
  79. package/templates/tasks.md +27 -0
package/bin/praxis.js ADDED
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const VERSION = require('../package.json').version;
10
+ const PKG_DIR = path.resolve(__dirname, '..');
11
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
12
+ const CONFIG_FILE = path.join(CLAUDE_DIR, 'praxis.config.json');
13
+
14
+ // ── Helpers ──────────────────────────────────────────────────
15
+
16
+ function header(msg) { console.log('\n\x1b[1m\x1b[36m' + msg + '\x1b[0m'); }
17
+ function ok(msg) { console.log(' \x1b[32m\u2713\x1b[0m ' + msg); }
18
+ function fail(msg) { console.error(' \x1b[31m\u2717\x1b[0m ' + msg); }
19
+ function dim(msg) { console.log(' \x1b[2m' + msg + '\x1b[0m'); }
20
+
21
+ function toolExists(name) {
22
+ const r = spawnSync('which', [name], { stdio: 'pipe' });
23
+ return r.status === 0;
24
+ }
25
+
26
+ function copyDir(src, dest) {
27
+ fs.cpSync(src, dest, { recursive: true, force: true });
28
+ }
29
+
30
+ function copyFile(src, dest) {
31
+ fs.cpSync(src, dest, { force: true });
32
+ }
33
+
34
+ // ── Install ──────────────────────────────────────────────────
35
+
36
+ async function install() {
37
+ header('Praxis Harness v' + VERSION);
38
+
39
+ // Ensure ~/.claude/ structure
40
+ for (const sub of ['rules', 'commands', 'skills']) {
41
+ fs.mkdirSync(path.join(CLAUDE_DIR, sub), { recursive: true });
42
+ }
43
+
44
+ // Copy base/CLAUDE.md → ~/.claude/CLAUDE.md
45
+ const claudeMdSrc = path.join(PKG_DIR, 'base', 'CLAUDE.md');
46
+ if (fs.existsSync(claudeMdSrc)) {
47
+ copyFile(claudeMdSrc, path.join(CLAUDE_DIR, 'CLAUDE.md'));
48
+ ok('CLAUDE.md installed');
49
+ }
50
+
51
+ // Copy base/rules/* → ~/.claude/rules/
52
+ const rulesDir = path.join(PKG_DIR, 'base', 'rules');
53
+ if (fs.existsSync(rulesDir)) {
54
+ let count = 0;
55
+ for (const f of fs.readdirSync(rulesDir)) {
56
+ copyFile(path.join(rulesDir, f), path.join(CLAUDE_DIR, 'rules', f));
57
+ count++;
58
+ }
59
+ ok(count + ' rules installed');
60
+ }
61
+
62
+ // Copy base/commands/* → ~/.claude/commands/
63
+ const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
64
+ if (fs.existsSync(cmdsDir)) {
65
+ let count = 0;
66
+ for (const f of fs.readdirSync(cmdsDir)) {
67
+ copyFile(path.join(cmdsDir, f), path.join(CLAUDE_DIR, 'commands', f));
68
+ count++;
69
+ }
70
+ ok(count + ' commands installed');
71
+ }
72
+
73
+ // Copy base/skills/* → ~/.claude/skills/
74
+ const skillsDir = path.join(PKG_DIR, 'base', 'skills');
75
+ if (fs.existsSync(skillsDir)) {
76
+ let count = 0;
77
+ for (const entry of fs.readdirSync(skillsDir)) {
78
+ const src = path.join(skillsDir, entry);
79
+ const dest = path.join(CLAUDE_DIR, 'skills', entry);
80
+ if (fs.statSync(src).isDirectory()) {
81
+ copyDir(src, dest);
82
+ } else {
83
+ copyFile(src, dest);
84
+ }
85
+ count++;
86
+ }
87
+ ok(count + ' skills installed');
88
+ }
89
+
90
+ // Copy kits/ → ~/.claude/kits/
91
+ const kitsDir = path.join(PKG_DIR, 'kits');
92
+ if (fs.existsSync(kitsDir)) {
93
+ copyDir(kitsDir, path.join(CLAUDE_DIR, 'kits'));
94
+ ok('kits installed');
95
+ }
96
+
97
+ // Orphan cleanup: obsidian.md renamed to vault.md
98
+ const legacyObsidian = path.join(CLAUDE_DIR, 'rules', 'obsidian.md');
99
+ if (fs.existsSync(legacyObsidian)) {
100
+ fs.unlinkSync(legacyObsidian);
101
+ ok('Removed legacy obsidian.md (renamed to vault.md)');
102
+ }
103
+
104
+ // Vault configuration
105
+ if (!fs.existsSync(CONFIG_FILE)) {
106
+ header('Vault configuration');
107
+ const readline = require('readline/promises');
108
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
109
+
110
+ console.log('');
111
+ console.log(' Choose a vault backend:');
112
+ console.log(' [1] Obsidian (default)');
113
+ console.log(' [2] Logseq');
114
+ console.log(' [3] Plain markdown (~/.praxis-vault)');
115
+ console.log(' [4] Custom path');
116
+ console.log('');
117
+ const backendChoice = await rl.question(' Choice [1]: ');
118
+ const choice = (backendChoice || '1').trim();
119
+
120
+ let vaultBackend = 'obsidian';
121
+ let vaultPath = '';
122
+
123
+ switch (choice) {
124
+ case '1':
125
+ vaultBackend = 'obsidian';
126
+ vaultPath = await rl.question(' Obsidian vault path: ');
127
+ break;
128
+ case '2':
129
+ vaultBackend = 'logseq';
130
+ vaultPath = await rl.question(' Logseq vault path: ');
131
+ break;
132
+ case '3':
133
+ vaultBackend = 'plain';
134
+ vaultPath = path.join(os.homedir(), '.praxis-vault');
135
+ fs.mkdirSync(vaultPath, { recursive: true });
136
+ ok('Created ' + vaultPath);
137
+ break;
138
+ case '4':
139
+ vaultBackend = 'custom';
140
+ vaultPath = await rl.question(' Vault path: ');
141
+ break;
142
+ default:
143
+ vaultBackend = 'obsidian';
144
+ vaultPath = await rl.question(' Vault path: ');
145
+ break;
146
+ }
147
+ rl.close();
148
+
149
+ if (vaultPath) {
150
+ vaultPath = vaultPath.replace(/^~/, os.homedir());
151
+ }
152
+
153
+ const config = {
154
+ version: '1.1.0',
155
+ vault_path: vaultPath || '',
156
+ vault_backend: vaultBackend,
157
+ repo_path: PKG_DIR
158
+ };
159
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
160
+ ok('praxis.config.json written');
161
+ } else {
162
+ dim('praxis.config.json already exists — skipping');
163
+ }
164
+
165
+ // Tool checks (conditional on backend)
166
+ header('Tool check');
167
+ let vaultBackendForCheck = 'obsidian';
168
+ if (fs.existsSync(CONFIG_FILE)) {
169
+ try {
170
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
171
+ vaultBackendForCheck = cfg.vault_backend || 'obsidian';
172
+ } catch {}
173
+ }
174
+
175
+ const baseTools = ['node', 'claude', 'jq'];
176
+ if (vaultBackendForCheck === 'obsidian') {
177
+ baseTools.push('obsidian');
178
+ } else {
179
+ baseTools.push('rg');
180
+ }
181
+ for (const tool of baseTools) {
182
+ if (toolExists(tool)) {
183
+ ok(tool + ' available');
184
+ } else {
185
+ fail(tool + ' not found (optional)');
186
+ }
187
+ }
188
+
189
+ // Summary
190
+ header('Install complete');
191
+ console.log(' Files copied to ' + CLAUDE_DIR);
192
+ console.log(' Run: npx praxis-harness health');
193
+ console.log('');
194
+ }
195
+
196
+ // ── Update ───────────────────────────────────────────────────
197
+
198
+ async function update() {
199
+ header('Updating Praxis Harness v' + VERSION);
200
+ await install();
201
+ }
202
+
203
+ // ── Health ───────────────────────────────────────────────────
204
+
205
+ function health() {
206
+ header('Praxis Health Check');
207
+ let pass = 0;
208
+ let total = 0;
209
+
210
+ function check(condition, label) {
211
+ total++;
212
+ if (condition) { ok(label); pass++; }
213
+ else { fail(label); }
214
+ }
215
+
216
+ // Core files
217
+ console.log('\nCore:');
218
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'CLAUDE.md')), 'CLAUDE.md installed');
219
+
220
+ // Rules
221
+ console.log('\nRules:');
222
+ const rulesDir = path.join(PKG_DIR, 'base', 'rules');
223
+ if (fs.existsSync(rulesDir)) {
224
+ for (const f of fs.readdirSync(rulesDir)) {
225
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'rules', f)), 'rules/' + f + ' installed');
226
+ }
227
+ }
228
+
229
+ // Commands
230
+ console.log('\nCommands:');
231
+ const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
232
+ if (fs.existsSync(cmdsDir)) {
233
+ for (const f of fs.readdirSync(cmdsDir)) {
234
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'commands', f)), 'commands/' + f + ' installed');
235
+ }
236
+ }
237
+
238
+ // Skills
239
+ console.log('\nSkills:');
240
+ const skillsDir = path.join(PKG_DIR, 'base', 'skills');
241
+ if (fs.existsSync(skillsDir)) {
242
+ for (const entry of fs.readdirSync(skillsDir)) {
243
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'skills', entry)), 'skills/' + entry + ' installed');
244
+ }
245
+ }
246
+
247
+ // Kits
248
+ console.log('\nKits:');
249
+ check(fs.existsSync(path.join(CLAUDE_DIR, 'kits')), 'kits directory installed');
250
+
251
+ // Config
252
+ console.log('\nConfig:');
253
+ check(fs.existsSync(CONFIG_FILE), 'praxis.config.json exists');
254
+ if (fs.existsSync(CONFIG_FILE)) {
255
+ try {
256
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
257
+ if (cfg.vault_path) {
258
+ check(fs.existsSync(cfg.vault_path), 'vault_path (' + cfg.vault_path + ') exists');
259
+ } else {
260
+ total++; fail('vault_path not set in config');
261
+ }
262
+ } catch { total++; fail('praxis.config.json is invalid JSON'); }
263
+ }
264
+
265
+ // Tools (conditional on backend)
266
+ console.log('\nTools:');
267
+ let healthBackend = 'obsidian';
268
+ if (fs.existsSync(CONFIG_FILE)) {
269
+ try {
270
+ const cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
271
+ healthBackend = cfg.vault_backend || 'obsidian';
272
+ } catch {}
273
+ }
274
+ const healthTools = ['node', 'claude', 'jq'];
275
+ if (healthBackend === 'obsidian') {
276
+ healthTools.push('obsidian');
277
+ } else {
278
+ healthTools.push('rg');
279
+ }
280
+ for (const tool of healthTools) {
281
+ check(toolExists(tool), tool + ' available');
282
+ }
283
+
284
+ // Summary
285
+ console.log('\n' + '\u2501'.repeat(38));
286
+ console.log(' Results: ' + pass + '/' + total + ' passed');
287
+ if (pass < total) {
288
+ console.log(' ' + (total - pass) + ' check(s) failed');
289
+ process.exit(1);
290
+ } else {
291
+ console.log(' All checks passed');
292
+ }
293
+ }
294
+
295
+ // ── Uninstall ────────────────────────────────────────────────
296
+
297
+ function uninstall() {
298
+ header('Uninstalling Praxis Harness');
299
+
300
+ // Remove files that came from base/
301
+ const claudeMd = path.join(CLAUDE_DIR, 'CLAUDE.md');
302
+ if (fs.existsSync(claudeMd)) { fs.unlinkSync(claudeMd); ok('CLAUDE.md removed'); }
303
+
304
+ // Remove rules from base/rules/
305
+ const rulesDir = path.join(PKG_DIR, 'base', 'rules');
306
+ if (fs.existsSync(rulesDir)) {
307
+ for (const f of fs.readdirSync(rulesDir)) {
308
+ const target = path.join(CLAUDE_DIR, 'rules', f);
309
+ if (fs.existsSync(target)) fs.unlinkSync(target);
310
+ }
311
+ // Also remove legacy obsidian.md if present
312
+ const legacyRule = path.join(CLAUDE_DIR, 'rules', 'obsidian.md');
313
+ if (fs.existsSync(legacyRule)) fs.unlinkSync(legacyRule);
314
+ ok('rules removed');
315
+ }
316
+
317
+ // Remove commands from base/commands/
318
+ const cmdsDir = path.join(PKG_DIR, 'base', 'commands');
319
+ if (fs.existsSync(cmdsDir)) {
320
+ for (const f of fs.readdirSync(cmdsDir)) {
321
+ const target = path.join(CLAUDE_DIR, 'commands', f);
322
+ if (fs.existsSync(target)) fs.unlinkSync(target);
323
+ }
324
+ ok('commands removed');
325
+ }
326
+
327
+ // Remove skills from base/skills/
328
+ const skillsDir = path.join(PKG_DIR, 'base', 'skills');
329
+ if (fs.existsSync(skillsDir)) {
330
+ for (const entry of fs.readdirSync(skillsDir)) {
331
+ const target = path.join(CLAUDE_DIR, 'skills', entry);
332
+ if (fs.existsSync(target)) {
333
+ fs.rmSync(target, { recursive: true, force: true });
334
+ }
335
+ }
336
+ ok('skills removed');
337
+ }
338
+
339
+ // Remove kits
340
+ const kitsTarget = path.join(CLAUDE_DIR, 'kits');
341
+ if (fs.existsSync(kitsTarget)) {
342
+ fs.rmSync(kitsTarget, { recursive: true, force: true });
343
+ ok('kits removed');
344
+ }
345
+
346
+ header('Uninstall complete');
347
+ dim('Config file preserved: ' + CONFIG_FILE);
348
+ dim('Run: rm ' + CONFIG_FILE + ' to remove config');
349
+ console.log('');
350
+ }
351
+
352
+ // ── Help ─────────────────────────────────────────────────────
353
+
354
+ function printHelp() {
355
+ console.log(`
356
+ praxis-harness v${VERSION}
357
+
358
+ Usage: npx praxis-harness [command]
359
+
360
+ Commands:
361
+ install Copy rules, commands, skills, and kits to ~/.claude/ (default)
362
+ update Re-copy from latest npm package version
363
+ health Verify install integrity
364
+ uninstall Remove Praxis-owned files from ~/.claude/
365
+
366
+ Flags:
367
+ --help, -h Show this help
368
+ --version, -v Show version
369
+ `);
370
+ }
371
+
372
+ // ── Main ─────────────────────────────────────────────────────
373
+
374
+ const arg = process.argv[2] || 'install';
375
+ const commands = { install, update, health, uninstall };
376
+
377
+ if (arg === '--help' || arg === '-h') { printHelp(); }
378
+ else if (arg === '--version' || arg === '-v') { console.log(VERSION); }
379
+ else if (commands[arg]) {
380
+ const result = commands[arg]();
381
+ if (result && typeof result.catch === 'function') {
382
+ result.catch(err => { fail(err.message); process.exit(1); });
383
+ }
384
+ }
385
+ else { fail('Unknown command: ' + arg); printHelp(); process.exit(1); }
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: infrastructure
3
+ version: 1.0.0
4
+ description: Infrastructure as Code — Terraform, Azure, compliance, drift detection
5
+ activation: /kit:infrastructure
6
+ deactivation: /kit:off
7
+ skills_chain:
8
+ - phase: plan
9
+ skills: []
10
+ status: planned
11
+ - phase: apply
12
+ skills: []
13
+ status: planned
14
+ - phase: drift
15
+ skills: []
16
+ status: planned
17
+ - phase: compliance
18
+ skills: []
19
+ status: planned
20
+ mcp_servers: []
21
+ rules:
22
+ - infrastructure.md
23
+ removal_condition: >
24
+ Remove when infrastructure work is fully handled by a dedicated IaC pipeline
25
+ with no manual Claude-driven operations remaining.
26
+ ---
27
+
28
+ # Infrastructure Kit
29
+
30
+ ## Purpose
31
+ Chain infrastructure-as-code skills into a phased workflow for planning,
32
+ applying, drift detection, and compliance checking against Azure/Terraform
33
+ environments.
34
+
35
+ ## Skills Chain
36
+
37
+ | # | Phase | Command | What It Provides |
38
+ |---|-------|---------|-----------------|
39
+ | 1 | Plan | `/infra:plan` | Terraform plan + review, flag destructive changes |
40
+ | 2 | Apply | `/infra:apply` | Gated behind plan approval, write result to vault |
41
+ | 3 | Drift | `/infra:drift` | Detect configuration drift via `terraform plan -detailed-exitcode` |
42
+ | 4 | Compliance | `/infra:compliance` | NIST CSF mapping, public endpoint check, missing tags |
43
+
44
+ ## Workflow Integration
45
+
46
+ This kit operates WITHIN the universal base workflow:
47
+ - **GSD** structures the work (discuss → plan → execute → verify)
48
+ - **Superpowers** enforces TDD and code review during execution
49
+ - **This kit** adds infrastructure-specific rules and commands
50
+
51
+ ## Ralph Integration
52
+
53
+ To persist this kit across Ralph iterations, add to project `CLAUDE.md`:
54
+
55
+ ```markdown
56
+ ## Active kit
57
+ On session start, activate: /kit:infrastructure
58
+ ```
59
+
60
+ Each Ralph iteration reads project CLAUDE.md and activates the kit automatically.
61
+ The `/kit` command is idempotent — double-activation is a no-op.
62
+
63
+ ## Prerequisites
64
+
65
+ Run `install.sh` in this directory to check for required CLI tools.
66
+ Verify with `/kit:infrastructure` after install.
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: Apply a reviewed terraform plan. Gated behind plan approval. Writes result to vault.
3
+ ---
4
+
5
+ You are applying an infrastructure plan.
6
+
7
+ **Step 1 — Verify plan exists**
8
+ - Check for `tfplan` file in working directory
9
+ - If missing: STOP. "No plan file found. Run `/infra:plan` first."
10
+ - Confirm user has reviewed the plan output
11
+
12
+ **Step 2 — Apply**
13
+ ```bash
14
+ terraform apply tfplan
15
+ ```
16
+ Capture full output.
17
+
18
+ **Step 3 — Verify**
19
+ Run `terraform plan -detailed-exitcode` after apply:
20
+ - Exit 0 = state matches config (success)
21
+ - Exit 2 = drift remains (partial apply — investigate)
22
+
23
+ **Step 4 — Write to vault**
24
+ Write apply result to vault:
25
+ - Update `{vault_path}/status.md` with What / So What / Now What
26
+ - If this was a significant change: write to `{vault_path}/specs/` as a decision record
27
+ - Vault indexing is automatic.
28
+
29
+ **Step 5 — Report**
30
+ ```
31
+ INFRA APPLY — {project} ({date})
32
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
33
+
34
+ Status: {SUCCESS | PARTIAL | FAILED}
35
+ Resources applied: {n}
36
+ Post-apply drift: {none | details}
37
+
38
+ Vault updated: {status.md path}
39
+ ```
40
+
41
+ **Rules:**
42
+ - Never apply without a saved plan file.
43
+ - Never use `-auto-approve` without explicit user request.
44
+ - Clean up tfplan file after successful apply.
@@ -0,0 +1,65 @@
1
+ ---
2
+ description: Check infrastructure compliance — NIST CSF mapping, public endpoints, missing tags.
3
+ ---
4
+
5
+ You are running an infrastructure compliance check.
6
+
7
+ **Step 1 — Load engagement context**
8
+ - Read vault_path from `~/.claude/praxis.config.json`
9
+ - Read `{vault_path}/_index.md` for engagement compliance requirements
10
+ - Identify applicable frameworks (NIST CSF, SOC 2, ISO 27001, FedRAMP, VITA)
11
+
12
+ **Step 2 — Public endpoint check**
13
+ ```bash
14
+ # Azure: find public endpoints
15
+ az storage account list -g {rg} --query "[?publicNetworkAccess!='Disabled'].[name]" -o table 2>/dev/null
16
+ az sql server list -g {rg} --query "[?publicNetworkAccess!='Disabled'].[name]" -o table 2>/dev/null
17
+
18
+ # Terraform: check for public access in config
19
+ rg 'public_network_access\s*=\s*"Enabled"' --glob '*.tf'
20
+ rg 'public_access\s*=\s*true' --glob '*.tf'
21
+ ```
22
+
23
+ **Step 3 — Tag compliance**
24
+ Check for mandatory tags defined in `_index.md`:
25
+ ```bash
26
+ # Find resources missing required tags
27
+ rg 'tags\s*=' --glob '*.tf' -l # files with tags
28
+ rg 'resource\s+"' --glob '*.tf' -l # files with resources
29
+ # Compare: resources without tags blocks
30
+ ```
31
+
32
+ **Step 4 — NIST CSF mapping**
33
+ For security-relevant resources, map to NIST CSF functions:
34
+ - **Identify**: asset inventory, data classification
35
+ - **Protect**: encryption, access control, network segmentation
36
+ - **Detect**: monitoring, logging, alerting
37
+ - **Respond**: incident response procedures
38
+ - **Recover**: backup, disaster recovery
39
+
40
+ **Step 5 — Report**
41
+ ```
42
+ INFRA COMPLIANCE — {project} ({date})
43
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
44
+
45
+ Framework: {applicable frameworks}
46
+
47
+ PUBLIC ENDPOINTS: {n found}
48
+ {list with resource and recommendation}
49
+
50
+ TAG COMPLIANCE: {n non-compliant}
51
+ {list with resource and missing tags}
52
+
53
+ NIST CSF GAPS: {n identified}
54
+ {list with function, gap, recommendation}
55
+
56
+ Overall: {PASS | ISSUES FOUND}
57
+ ```
58
+
59
+ **Step 6 — Document**
60
+ Write compliance report to `{vault_path}/specs/{date}_compliance-check.md`
61
+ Vault indexing is automatic.
62
+
63
+ **Rules:**
64
+ - Compliance checks are advisory. They do not modify infrastructure.
65
+ - Critical findings (public endpoints on data services, missing encryption) should be flagged as risks via `/risk`.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: Detect infrastructure drift via terraform plan. Reports divergence between state and config.
3
+ ---
4
+
5
+ You are checking for infrastructure drift.
6
+
7
+ **Step 1 — Run drift check**
8
+ ```bash
9
+ terraform init -input=false
10
+ terraform plan -detailed-exitcode
11
+ ```
12
+ Capture exit code:
13
+ - 0 = no drift, config matches state
14
+ - 1 = error running plan
15
+ - 2 = drift detected
16
+
17
+ **Step 2 — Analyze drift**
18
+ If exit code 2:
19
+ - List each drifted resource with: resource address, attribute changed, expected vs actual
20
+ - Classify drift:
21
+ - **Benign**: tags, descriptions, metadata (manual update outside Terraform)
22
+ - **Concerning**: security groups, RBAC, network config (possible unauthorized change)
23
+ - **Critical**: data resources, encryption settings, public access (investigate immediately)
24
+
25
+ **Step 3 — Report**
26
+ ```
27
+ INFRA DRIFT — {project} ({date})
28
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
29
+
30
+ Status: {NO DRIFT | DRIFT DETECTED}
31
+ Drifted resources: {n}
32
+
33
+ {resource list with classification}
34
+
35
+ Recommendation: {accept drift | remediate | investigate}
36
+ ```
37
+
38
+ **Step 4 — Document**
39
+ If drift is Concerning or Critical:
40
+ - Write findings to `{vault_path}/notes/{date}_drift-report.md`
41
+ - Vault indexing is automatic.
42
+
43
+ **Rules:**
44
+ - Drift detection is read-only. Never modify state or config during drift check.
45
+ - Benign drift can be accepted. Concerning/Critical drift requires action.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: Run terraform plan, review output, flag destructive changes. Use before any infrastructure apply.
3
+ ---
4
+
5
+ You are running an infrastructure plan review.
6
+
7
+ **Step 1 — Validate environment**
8
+ - Confirm `terraform` is available: `command -v terraform`
9
+ - Confirm working directory has `.tf` files
10
+ - Check for remote backend configuration: `rg 'backend' *.tf`
11
+ - If no backend: STOP. "Remote state backend not configured. Set up backend before planning."
12
+
13
+ **Step 2 — Run plan**
14
+ ```bash
15
+ terraform init -input=false
16
+ terraform plan -out=tfplan -detailed-exitcode
17
+ ```
18
+ Capture the exit code:
19
+ - 0 = no changes
20
+ - 1 = error
21
+ - 2 = changes detected
22
+
23
+ **Step 3 — Review output**
24
+ - Flag **destructive changes** (destroy, replace) prominently
25
+ - Flag **new resources** that introduce cost
26
+ - Flag **security-relevant changes** (NSG rules, RBAC, public endpoints)
27
+ - Summarize: {N} to add, {N} to change, {N} to destroy
28
+
29
+ **Step 4 — Report**
30
+ ```
31
+ INFRA PLAN — {project} ({date})
32
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
33
+
34
+ Add: {n} | Change: {n} | Destroy: {n}
35
+
36
+ {destructive changes if any, highlighted}
37
+ {security-relevant changes if any}
38
+
39
+ Plan saved to: tfplan
40
+ Run /infra:apply to proceed.
41
+ ```
42
+
43
+ **Rules:**
44
+ - Never auto-approve. Always require explicit user confirmation.
45
+ - If destroy count >0: require user to acknowledge before `/infra:apply`.
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ echo "=== Praxis: Installing infrastructure kit ==="
5
+ echo ""
6
+
7
+ PASS=0
8
+ TOTAL=0
9
+
10
+ check() {
11
+ TOTAL=$((TOTAL + 1))
12
+ if command -v "$1" &>/dev/null; then
13
+ echo " ✓ $1 found ($(command -v "$1"))"
14
+ PASS=$((PASS + 1))
15
+ else
16
+ echo " ✗ $1 not found"
17
+ echo " Install: $2"
18
+ fi
19
+ }
20
+
21
+ echo "Checking required CLI tools..."
22
+ echo ""
23
+
24
+ check "az" "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli"
25
+ check "terraform" "https://developer.hashicorp.com/terraform/install"
26
+ check "tflint" "brew install tflint OR https://github.com/terraform-linters/tflint"
27
+ check "jq" "brew install jq OR apt-get install jq"
28
+
29
+ echo ""
30
+ echo " $PASS/$TOTAL tools found"
31
+ echo ""
32
+
33
+ if [[ $PASS -lt $TOTAL ]]; then
34
+ echo " ⚠ Some tools missing. Install them before using infrastructure commands."
35
+ fi
36
+
37
+ echo ""
38
+ echo "Note: Skills chain phases are status: planned."
39
+ echo "Commands available: /infra:plan, /infra:apply, /infra:drift, /infra:compliance"
40
+ echo ""
41
+ echo "=== infrastructure kit check complete ==="
42
+ echo "Activate with: /kit:infrastructure"
43
+ echo ""