kastell 2.2.0 → 2.2.1
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/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +39 -0
- package/CHANGELOG.md +1266 -1266
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/bin/kastell +2 -2
- package/bin/kastell-mcp +5 -5
- package/dist/adapters/coolify.js +92 -92
- package/dist/adapters/dokploy.js +99 -99
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +4 -2
- package/dist/adapters/shared.js.map +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +6 -9
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +12 -12
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/evidence.d.ts.map +1 -1
- package/dist/commands/evidence.js +8 -9
- package/dist/commands/evidence.js.map +1 -1
- package/dist/commands/fix.js +3 -3
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +4 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/interactive/backup-maintenance.d.ts +8 -0
- package/dist/commands/interactive/backup-maintenance.d.ts.map +1 -0
- package/dist/commands/interactive/backup-maintenance.js +120 -0
- package/dist/commands/interactive/backup-maintenance.js.map +1 -0
- package/dist/commands/interactive/index.d.ts +4 -0
- package/dist/commands/interactive/index.d.ts.map +1 -0
- package/dist/commands/interactive/index.js +94 -0
- package/dist/commands/interactive/index.js.map +1 -0
- package/dist/commands/interactive/menu.d.ts +23 -0
- package/dist/commands/interactive/menu.d.ts.map +1 -0
- package/dist/commands/interactive/menu.js +121 -0
- package/dist/commands/interactive/menu.js.map +1 -0
- package/dist/commands/interactive/monitoring.d.ts +5 -0
- package/dist/commands/interactive/monitoring.d.ts.map +1 -0
- package/dist/commands/interactive/monitoring.js +96 -0
- package/dist/commands/interactive/monitoring.js.map +1 -0
- package/dist/commands/interactive/plugins.d.ts +2 -0
- package/dist/commands/interactive/plugins.d.ts.map +1 -0
- package/dist/commands/interactive/plugins.js +30 -0
- package/dist/commands/interactive/plugins.js.map +1 -0
- package/dist/commands/interactive/security.d.ts +9 -0
- package/dist/commands/interactive/security.d.ts.map +1 -0
- package/dist/commands/interactive/security.js +535 -0
- package/dist/commands/interactive/security.js.map +1 -0
- package/dist/commands/interactive/server-management.d.ts +5 -0
- package/dist/commands/interactive/server-management.d.ts.map +1 -0
- package/dist/commands/interactive/server-management.js +79 -0
- package/dist/commands/interactive/server-management.js.map +1 -0
- package/dist/commands/interactive/shared.d.ts +12 -0
- package/dist/commands/interactive/shared.d.ts.map +1 -0
- package/dist/commands/interactive/shared.js +30 -0
- package/dist/commands/interactive/shared.js.map +1 -0
- package/dist/commands/lock.js +1 -1
- package/dist/commands/lock.js.map +1 -1
- package/dist/commands/regression.d.ts.map +1 -1
- package/dist/commands/regression.js +1 -2
- package/dist/commands/regression.js.map +1 -1
- package/dist/commands/restart.d.ts.map +1 -1
- package/dist/commands/restart.js +3 -2
- package/dist/commands/restart.js.map +1 -1
- package/dist/commands/schedule.js +2 -2
- package/dist/commands/schedule.js.map +1 -1
- package/dist/core/audit/formatters/badge.js +20 -20
- package/dist/core/backup.d.ts.map +1 -1
- package/dist/core/backup.js +10 -5
- package/dist/core/backup.js.map +1 -1
- package/dist/core/completions.js +631 -631
- package/dist/core/deploy.d.ts.map +1 -1
- package/dist/core/deploy.js +7 -4
- package/dist/core/deploy.js.map +1 -1
- package/dist/core/lock/auth.d.ts +7 -0
- package/dist/core/lock/auth.d.ts.map +1 -0
- package/dist/core/lock/auth.js +59 -0
- package/dist/core/lock/auth.js.map +1 -0
- package/dist/core/lock/docker.d.ts +4 -0
- package/dist/core/lock/docker.d.ts.map +1 -0
- package/dist/core/lock/docker.js +28 -0
- package/dist/core/lock/docker.js.map +1 -0
- package/dist/core/lock/index.d.ts +11 -0
- package/dist/core/lock/index.d.ts.map +1 -0
- package/dist/core/lock/index.js +247 -0
- package/dist/core/lock/index.js.map +1 -0
- package/dist/core/lock/monitoring.d.ts +4 -0
- package/dist/core/lock/monitoring.d.ts.map +1 -0
- package/dist/core/lock/monitoring.js +55 -0
- package/dist/core/lock/monitoring.js.map +1 -0
- package/dist/core/lock/network.d.ts +6 -0
- package/dist/core/lock/network.d.ts.map +1 -0
- package/dist/core/lock/network.js +59 -0
- package/dist/core/lock/network.js.map +1 -0
- package/dist/core/lock/ssh.d.ts +5 -0
- package/dist/core/lock/ssh.d.ts.map +1 -0
- package/dist/core/lock/ssh.js +49 -0
- package/dist/core/lock/ssh.js.map +1 -0
- package/dist/core/lock/system.d.ts +9 -0
- package/dist/core/lock/system.d.ts.map +1 -0
- package/dist/core/lock/system.js +80 -0
- package/dist/core/lock/system.js.map +1 -0
- package/dist/core/lock/types.d.ts +41 -0
- package/dist/core/lock/types.d.ts.map +1 -0
- package/dist/core/lock/types.js +2 -0
- package/dist/core/lock/types.js.map +1 -0
- package/dist/core/maintain.d.ts.map +1 -1
- package/dist/core/maintain.js +3 -1
- package/dist/core/maintain.js.map +1 -1
- package/dist/core/manage.d.ts.map +1 -1
- package/dist/core/manage.js +5 -3
- package/dist/core/manage.js.map +1 -1
- package/dist/core/notifyStore.d.ts.map +1 -1
- package/dist/core/notifyStore.js +3 -1
- package/dist/core/notifyStore.js.map +1 -1
- package/dist/core/provision.d.ts.map +1 -1
- package/dist/core/provision.js +9 -4
- package/dist/core/provision.js.map +1 -1
- package/dist/core/scheduleManager.d.ts.map +1 -1
- package/dist/core/scheduleManager.js +5 -2
- package/dist/core/scheduleManager.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/schemas/audit.d.ts +34 -0
- package/dist/mcp/schemas/audit.d.ts.map +1 -0
- package/dist/mcp/schemas/audit.js +23 -0
- package/dist/mcp/schemas/audit.js.map +1 -0
- package/dist/mcp/schemas/common.d.ts +16 -0
- package/dist/mcp/schemas/common.d.ts.map +1 -0
- package/dist/mcp/schemas/common.js +14 -0
- package/dist/mcp/schemas/common.js.map +1 -0
- package/dist/mcp/schemas/health.d.ts +14 -0
- package/dist/mcp/schemas/health.d.ts.map +1 -0
- package/dist/mcp/schemas/health.js +13 -0
- package/dist/mcp/schemas/health.js.map +1 -0
- package/dist/mcp/schemas/index.d.ts +5 -0
- package/dist/mcp/schemas/index.d.ts.map +1 -0
- package/dist/mcp/schemas/index.js +5 -0
- package/dist/mcp/schemas/index.js.map +1 -0
- package/dist/mcp/schemas/server.d.ts +18 -0
- package/dist/mcp/schemas/server.d.ts.map +1 -0
- package/dist/mcp/schemas/server.js +16 -0
- package/dist/mcp/schemas/server.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +56 -39
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/serverAudit.d.ts +63 -1
- package/dist/mcp/tools/serverAudit.d.ts.map +1 -1
- package/dist/mcp/tools/serverAudit.js +63 -6
- package/dist/mcp/tools/serverAudit.js.map +1 -1
- package/dist/mcp/tools/serverBackup.d.ts +100 -2
- package/dist/mcp/tools/serverBackup.d.ts.map +1 -1
- package/dist/mcp/tools/serverBackup.handlers.d.ts.map +1 -1
- package/dist/mcp/tools/serverBackup.handlers.js +9 -0
- package/dist/mcp/tools/serverBackup.handlers.js.map +1 -1
- package/dist/mcp/tools/serverBackup.js +74 -0
- package/dist/mcp/tools/serverBackup.js.map +1 -1
- package/dist/mcp/tools/serverCompare.d.ts +33 -0
- package/dist/mcp/tools/serverCompare.d.ts.map +1 -1
- package/dist/mcp/tools/serverCompare.js +45 -2
- package/dist/mcp/tools/serverCompare.js.map +1 -1
- package/dist/mcp/tools/serverDoctor.d.ts +14 -0
- package/dist/mcp/tools/serverDoctor.d.ts.map +1 -1
- package/dist/mcp/tools/serverDoctor.js +15 -0
- package/dist/mcp/tools/serverDoctor.js.map +1 -1
- package/dist/mcp/tools/serverEvidence.d.ts +13 -0
- package/dist/mcp/tools/serverEvidence.d.ts.map +1 -1
- package/dist/mcp/tools/serverEvidence.js +17 -2
- package/dist/mcp/tools/serverEvidence.js.map +1 -1
- package/dist/mcp/tools/serverExplain.d.ts +17 -0
- package/dist/mcp/tools/serverExplain.d.ts.map +1 -1
- package/dist/mcp/tools/serverExplain.js +33 -1
- package/dist/mcp/tools/serverExplain.js.map +1 -1
- package/dist/mcp/tools/serverFix.d.ts +78 -0
- package/dist/mcp/tools/serverFix.d.ts.map +1 -1
- package/dist/mcp/tools/serverFix.js +84 -0
- package/dist/mcp/tools/serverFix.js.map +1 -1
- package/dist/mcp/tools/serverFleet.d.ts +24 -1
- package/dist/mcp/tools/serverFleet.d.ts.map +1 -1
- package/dist/mcp/tools/serverFleet.js +24 -1
- package/dist/mcp/tools/serverFleet.js.map +1 -1
- package/dist/mcp/tools/serverGuard.d.ts +12 -0
- package/dist/mcp/tools/serverGuard.d.ts.map +1 -1
- package/dist/mcp/tools/serverGuard.js +16 -0
- package/dist/mcp/tools/serverGuard.js.map +1 -1
- package/dist/mcp/tools/serverInfo.d.ts +77 -1
- package/dist/mcp/tools/serverInfo.d.ts.map +1 -1
- package/dist/mcp/tools/serverInfo.js +77 -4
- package/dist/mcp/tools/serverInfo.js.map +1 -1
- package/dist/mcp/tools/serverLock.d.ts +10 -0
- package/dist/mcp/tools/serverLock.d.ts.map +1 -1
- package/dist/mcp/tools/serverLock.js +15 -3
- package/dist/mcp/tools/serverLock.js.map +1 -1
- package/dist/mcp/tools/serverLogs.d.ts +43 -0
- package/dist/mcp/tools/serverLogs.d.ts.map +1 -1
- package/dist/mcp/tools/serverLogs.js +28 -0
- package/dist/mcp/tools/serverLogs.js.map +1 -1
- package/dist/mcp/tools/serverMaintain.d.ts +47 -0
- package/dist/mcp/tools/serverMaintain.d.ts.map +1 -1
- package/dist/mcp/tools/serverMaintain.js +75 -41
- package/dist/mcp/tools/serverMaintain.js.map +1 -1
- package/dist/mcp/tools/serverManage.d.ts +50 -0
- package/dist/mcp/tools/serverManage.d.ts.map +1 -1
- package/dist/mcp/tools/serverManage.js +49 -0
- package/dist/mcp/tools/serverManage.js.map +1 -1
- package/dist/mcp/tools/serverPlugin.d.ts +18 -0
- package/dist/mcp/tools/serverPlugin.d.ts.map +1 -1
- package/dist/mcp/tools/serverPlugin.js +26 -1
- package/dist/mcp/tools/serverPlugin.js.map +1 -1
- package/dist/mcp/tools/serverProvision.d.ts +22 -0
- package/dist/mcp/tools/serverProvision.d.ts.map +1 -1
- package/dist/mcp/tools/serverProvision.js +22 -2
- package/dist/mcp/tools/serverProvision.js.map +1 -1
- package/dist/mcp/tools/serverSecure.d.ts +120 -0
- package/dist/mcp/tools/serverSecure.d.ts.map +1 -1
- package/dist/mcp/tools/serverSecure.handlers.d.ts.map +1 -1
- package/dist/mcp/tools/serverSecure.handlers.js +39 -98
- package/dist/mcp/tools/serverSecure.handlers.js.map +1 -1
- package/dist/mcp/tools/serverSecure.js +101 -0
- package/dist/mcp/tools/serverSecure.js.map +1 -1
- package/dist/mcp/utils.d.ts +1 -0
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +5 -1
- package/dist/mcp/utils.js.map +1 -1
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +5 -3
- package/dist/plugin/registry.js.map +1 -1
- package/dist/providers/linode.d.ts +1 -0
- package/dist/providers/linode.d.ts.map +1 -1
- package/dist/providers/linode.js +4 -0
- package/dist/providers/linode.js.map +1 -1
- package/dist/utils/cloudInit.js +58 -58
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +11 -6
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/encryption.d.ts.map +1 -1
- package/dist/utils/encryption.js +4 -1
- package/dist/utils/encryption.js.map +1 -1
- package/dist/utils/migration.d.ts.map +1 -1
- package/dist/utils/migration.js +25 -14
- package/dist/utils/migration.js.map +1 -1
- package/dist/utils/safeMode.d.ts.map +1 -1
- package/dist/utils/safeMode.js +3 -2
- package/dist/utils/safeMode.js.map +1 -1
- package/dist/utils/securityLogger.d.ts.map +1 -1
- package/dist/utils/securityLogger.js +7 -3
- package/dist/utils/securityLogger.js.map +1 -1
- package/kastell-plugin/.claude-plugin/plugin.json +20 -0
- package/kastell-plugin/.mcp.json +8 -0
- package/kastell-plugin/README.md +113 -0
- package/kastell-plugin/agents/.gitkeep +0 -0
- package/kastell-plugin/agents/kastell-auditor.md +77 -0
- package/kastell-plugin/agents/scripts/bucket_mapper.sh +101 -0
- package/kastell-plugin/agents/scripts/trend_report.sh +91 -0
- package/kastell-plugin/hooks/destroy-block.cjs +31 -0
- package/kastell-plugin/hooks/hooks.json +57 -0
- package/kastell-plugin/hooks/pre-commit-audit-guard.cjs +75 -0
- package/kastell-plugin/hooks/session-audit.cjs +86 -0
- package/kastell-plugin/hooks/session-log.cjs +56 -0
- package/kastell-plugin/hooks/stop-quality-check.cjs +72 -0
- package/kastell-plugin/skills/.gitkeep +0 -0
- package/kastell-plugin/skills/kastell-careful/SKILL.md +64 -0
- package/kastell-plugin/skills/kastell-ops/SKILL.md +139 -0
- package/kastell-plugin/skills/kastell-ops/references/commands.md +45 -0
- package/kastell-plugin/skills/kastell-ops/references/mcp-tools.md +50 -0
- package/kastell-plugin/skills/kastell-ops/references/patterns.md +145 -0
- package/kastell-plugin/skills/kastell-ops/references/pitfalls.md +136 -0
- package/kastell-plugin/skills/kastell-ops/scripts/check_coverage.sh +101 -0
- package/kastell-plugin/skills/kastell-ops/scripts/fleet_report.sh +73 -0
- package/kastell-plugin/skills/kastell-ops/scripts/parse_audit.sh +76 -0
- package/kastell-plugin/skills/kastell-research/SKILL.md +90 -0
- package/kastell-plugin/skills/kastell-scaffold/SKILL.md +104 -0
- package/kastell-plugin/skills/kastell-scaffold/references/template-audit-check.md +150 -0
- package/kastell-plugin/skills/kastell-scaffold/references/template-command.md +80 -0
- package/kastell-plugin/skills/kastell-scaffold/references/template-mcp-tool.md +72 -0
- package/kastell-plugin/skills/kastell-scaffold/references/template-provider.md +67 -0
- package/kastell-plugin/skills/kastell-scaffold/scripts/scaffold.sh +180 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/check-test.ts.tpl +27 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/check.ts.tpl +50 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/command-core.ts.tpl +18 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/command-test.ts.tpl +17 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/command.ts.tpl +25 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/mcp-tool-test.ts.tpl +30 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/mcp-tool.ts.tpl +29 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/provider-test.ts.tpl +34 -0
- package/kastell-plugin/skills/kastell-scaffold/templates/provider.ts.tpl +32 -0
- package/package.json +122 -115
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kastell-scaffold
|
|
3
|
+
description: Generate new Kastell components from templates. Creates boilerplate for CLI commands, audit checks, providers, and MCP tools following current architecture (commands thin, core fat, adapters dispatch).
|
|
4
|
+
context: fork
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
effort: medium
|
|
7
|
+
allowed-tools: Read, Edit, Write, Bash, Glob, Grep
|
|
8
|
+
argument-hint: "[check|command|provider|mcp-tool] [name]"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Kastell Scaffold
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
Generate boilerplate files for new Kastell components. Each template follows the post-P63/P64 architecture: commands are thin wrappers, business logic lives in core/, providers handle cloud API, adapters abstract platform ops.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/kastell:scaffold command server-migrate # creates command + core + test files
|
|
21
|
+
/kastell:scaffold check filesystem-perms # creates audit check + catalog update
|
|
22
|
+
/kastell:scaffold provider ovhcloud # creates provider + registry entry + test
|
|
23
|
+
/kastell:scaffold mcp-tool server_migrate # creates MCP tool + registration + test
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`$ARGUMENTS[0]` is the component type. `$ARGUMENTS[1]` is the component name.
|
|
27
|
+
|
|
28
|
+
## Architecture Rules
|
|
29
|
+
|
|
30
|
+
These rules apply in every generated file. The forked subagent does not automatically have kastell-ops context — enforce these rules explicitly.
|
|
31
|
+
|
|
32
|
+
| Layer | Path | Rule |
|
|
33
|
+
|----------|-------------------|--------------------------------------------------------------|
|
|
34
|
+
| Commands | src/commands/ | Parse args + delegate. ZERO business logic. |
|
|
35
|
+
| Core | src/core/ | ALL business logic. No chalk/ora/UI imports. |
|
|
36
|
+
| Providers| src/providers/ | Cloud API per provider. Extends BaseProvider. |
|
|
37
|
+
| Adapters | src/adapters/ | Platform ops via PlatformAdapter. Access via getAdapter(). |
|
|
38
|
+
| MCP | src/mcp/tools/ | Zod schema + handler. Delegates to core. |
|
|
39
|
+
|
|
40
|
+
**Critical:** Never import CoolifyAdapter or DokployAdapter directly. Always use `getAdapter(platform)` from `src/adapters/factory.ts`.
|
|
41
|
+
|
|
42
|
+
**ESM:** `"type": "module"` — use `import`, not `require`. All imports use `.js` extension.
|
|
43
|
+
|
|
44
|
+
## Existing Components
|
|
45
|
+
|
|
46
|
+
**Commands:**
|
|
47
|
+
!`node -e "import('fs').then(f=>console.log(f.readdirSync('src/commands').filter(x=>x.endsWith('.ts')).map(x=>x.replace('.ts','')).join(', '))).catch(()=>console.log('commands dir not found'))"`
|
|
48
|
+
**Providers:**
|
|
49
|
+
!`node -e "import('fs').then(f=>console.log(f.readdirSync('src/providers').filter(x=>x.endsWith('.ts')&&x!=='base.ts').map(x=>x.replace('.ts','')).join(', '))).catch(()=>console.log('providers dir not found'))"`
|
|
50
|
+
**MCP tools:**
|
|
51
|
+
!`node -e "import('fs').then(f=>console.log(f.readdirSync('src/mcp/tools').filter(x=>x.endsWith('.ts')).map(x=>x.replace('.ts','')).join(', '))).catch(()=>console.log('mcp/tools dir not found'))"`
|
|
52
|
+
**Audit categories:**
|
|
53
|
+
!`node -e "import('fs').then(f=>console.log(f.readdirSync('src/core/audit',{withFileTypes:true}).filter(d=>d.isDirectory()).map(d=>d.name).join(', '))).catch(()=>console.log('audit dir not found'))"`
|
|
54
|
+
|
|
55
|
+
## Script (Deterministic)
|
|
56
|
+
|
|
57
|
+
Run the scaffold script to generate boilerplate files:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bash scripts/scaffold.sh $ARGUMENTS[0] $ARGUMENTS[1]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The script reads `.tpl` templates from `templates/`, replaces `__NAME__`/`__NAME_PASCAL__`/`__NAME_CAMEL__`/`__NAME_UPPER__` placeholders, and creates files in the project. Add `--dry-run` to preview without writing.
|
|
64
|
+
|
|
65
|
+
**Files generated per type:**
|
|
66
|
+
- `command` → 3 files: `src/commands/`, `src/core/`, `src/__tests__/core/`
|
|
67
|
+
- `check` → 2 files: `src/core/audit/checks/`, `src/__tests__/core/audit/checks/`
|
|
68
|
+
- `provider` → 2 files: `src/providers/`, `src/__tests__/providers/`
|
|
69
|
+
- `mcp-tool` → 2 files: `src/mcp/tools/`, `src/__tests__/mcp/`
|
|
70
|
+
|
|
71
|
+
## Template Reference (Context for LLM)
|
|
72
|
+
|
|
73
|
+
After running the script, read the matching reference for registration details:
|
|
74
|
+
|
|
75
|
+
| Type | Reference File |
|
|
76
|
+
|------------|------------------------------------------------|
|
|
77
|
+
| `command` | references/template-command.md |
|
|
78
|
+
| `check` | references/template-audit-check.md |
|
|
79
|
+
| `provider` | references/template-provider.md |
|
|
80
|
+
| `mcp-tool` | references/template-mcp-tool.md |
|
|
81
|
+
|
|
82
|
+
## After Generation
|
|
83
|
+
|
|
84
|
+
Perform these steps after creating the boilerplate files:
|
|
85
|
+
|
|
86
|
+
- [ ] Write tests first (TDD preferred — test core, not command)
|
|
87
|
+
- [ ] Register the component:
|
|
88
|
+
- Commands: add import in `src/index.ts`
|
|
89
|
+
- MCP tools: `registerTool()` in `src/mcp/server.ts`
|
|
90
|
+
- Providers: add to `PROVIDER_REGISTRY` in `src/constants.ts`
|
|
91
|
+
- Audit checks: add entry to `src/core/audit/catalog.ts`
|
|
92
|
+
- [ ] Run `npm run build && npm test && npm run lint`
|
|
93
|
+
- [ ] Update README.md
|
|
94
|
+
|
|
95
|
+
## Skill/Agent Oluşturma Kuralları (Yeni skill/agent scaffold ediliyorsa)
|
|
96
|
+
|
|
97
|
+
Yeni skill veya agent oluşturulurken aşağıdaki koşullar ZORUNLU:
|
|
98
|
+
- [ ] **Ersin kriteri:** scripts/ klasörü oluştur — deterministik iş LLM'e bırakılmayacak
|
|
99
|
+
- [ ] **Progressive disclosure:** SKILL.md < 500 satır, detaylar references/ klasöründe
|
|
100
|
+
- [ ] **Frontmatter:** `effort` + `allowed-tools` ekle (minimum zorunlu)
|
|
101
|
+
- [ ] **Yan etki varsa:** `disable-model-invocation: true` ekle
|
|
102
|
+
- [ ] **Agent ise:** `maxTurns` belirle, `memory` scope tanımla
|
|
103
|
+
- [ ] **Script dili:** Shell (sh/bash) tercih et — Node.js ekosistemiyle uyumlu
|
|
104
|
+
- [ ] **Evaluation:** Gerçek task'ta test et, struggle noktalarını gözlemle
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Template: Audit Check — $1
|
|
2
|
+
|
|
3
|
+
## Files to Create/Modify
|
|
4
|
+
|
|
5
|
+
1. `src/core/audit/checks/$1.ts` — check definitions + parser
|
|
6
|
+
2. `src/core/audit/commands.ts` — SSH batch section
|
|
7
|
+
3. `src/core/audit/checks/index.ts` — registry entry
|
|
8
|
+
4. `src/core/audit/compliance/mapper.ts` — compliance mapping (optional)
|
|
9
|
+
5. `src/__tests__/core/audit/checks/$1.test.ts` — test file
|
|
10
|
+
|
|
11
|
+
## Step 1: Check File
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// src/core/audit/checks/$1.ts
|
|
15
|
+
import type { AuditCheck, CheckParser } from "../../types.js";
|
|
16
|
+
|
|
17
|
+
interface $1CheckDef {
|
|
18
|
+
id: string; // "CATEGORY-CHECK-NAME" (uppercase, hyphen-separated)
|
|
19
|
+
name: string; // Human-readable description
|
|
20
|
+
severity: "critical" | "warning" | "info";
|
|
21
|
+
check: (output: string) => { passed: boolean; currentValue: string };
|
|
22
|
+
expectedValue: string;
|
|
23
|
+
fixCommand: string;
|
|
24
|
+
explain: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const $1_CHECKS: $1CheckDef[] = [
|
|
28
|
+
{
|
|
29
|
+
id: "CAT-CHECK-ONE",
|
|
30
|
+
name: "Check description",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
check: (output) => {
|
|
33
|
+
const match = output.includes("SENTINEL_PASS");
|
|
34
|
+
return { passed: match, currentValue: match ? "configured" : "not found" };
|
|
35
|
+
},
|
|
36
|
+
expectedValue: "configured",
|
|
37
|
+
fixCommand: "command to fix",
|
|
38
|
+
explain: "Why this matters and what the fix does.",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export const parse$1Checks: CheckParser = (
|
|
43
|
+
sectionOutput: string,
|
|
44
|
+
_platform: string,
|
|
45
|
+
): AuditCheck[] => {
|
|
46
|
+
// Conditional category: return [] if not applicable (excluded from score)
|
|
47
|
+
if (!sectionOutput || sectionOutput.includes("SKIP_MARKER")) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return $1_CHECKS.map((def) => {
|
|
52
|
+
const { passed, currentValue } = def.check(sectionOutput);
|
|
53
|
+
return {
|
|
54
|
+
id: def.id,
|
|
55
|
+
category: "Category Name", // MUST match CHECK_REGISTRY.name exactly
|
|
56
|
+
name: def.name,
|
|
57
|
+
severity: def.severity,
|
|
58
|
+
passed,
|
|
59
|
+
currentValue,
|
|
60
|
+
expectedValue: def.expectedValue,
|
|
61
|
+
fixCommand: def.fixCommand,
|
|
62
|
+
explain: def.explain,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Step 2: SSH Batch Section
|
|
69
|
+
|
|
70
|
+
`src/core/audit/commands.ts` — add bash commands that run on the server.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
function new$1Section(): string {
|
|
74
|
+
return [
|
|
75
|
+
NAMED_SEP("$1UPPER"), // MUST match CHECK_REGISTRY.sectionName exactly
|
|
76
|
+
`command1 || echo 'N/A'`,
|
|
77
|
+
`command2 || echo 'N/A'`,
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Add to the correct batch array:
|
|
83
|
+
- `BATCH_FAST` — fast commands (<1s)
|
|
84
|
+
- `BATCH_MEDIUM` — medium commands (1-5s)
|
|
85
|
+
- `BATCH_SLOW` — slow commands (5s+, e.g., `nginx -T`)
|
|
86
|
+
|
|
87
|
+
**Sentinel matching is critical:** `NAMED_SEP("FOO")` produces `---SECTION:FOO---`. This string MUST match `CHECK_REGISTRY.sectionName` exactly.
|
|
88
|
+
|
|
89
|
+
For conditional categories, add a skip marker as first command:
|
|
90
|
+
```bash
|
|
91
|
+
command -v nginx >/dev/null 2>&1 || echo 'NGINX_NOT_INSTALLED'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Step 3: Registry
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// src/core/audit/checks/index.ts
|
|
98
|
+
import { parse$1Checks } from "./$1.js";
|
|
99
|
+
|
|
100
|
+
export const CHECK_REGISTRY: CategoryEntry[] = [
|
|
101
|
+
// ... existing categories ...
|
|
102
|
+
{ name: "Category Name", sectionName: "$1UPPER", parser: parse$1Checks },
|
|
103
|
+
];
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Triple-check:**
|
|
107
|
+
- [ ] `sectionName` === `NAMED_SEP()` parameter
|
|
108
|
+
- [ ] `name` === check file's `category` string
|
|
109
|
+
- [ ] Import path has `.js` extension (ESM)
|
|
110
|
+
|
|
111
|
+
## Step 4: Compliance Mapping (Optional)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// src/core/audit/compliance/mapper.ts
|
|
115
|
+
export const COMPLIANCE_MAP: Record<string, ComplianceRef[]> = {
|
|
116
|
+
// ─── $1 ────────────────────────────────────────
|
|
117
|
+
"CAT-CHECK-ONE": [
|
|
118
|
+
cis("5.x.x", "Control description", "full"),
|
|
119
|
+
pci("x.x.x", "PCI control", "partial"),
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Helpers: `cis()`, `pci()`, `hipaa()`. Coverage: `"full"` or `"partial"`.
|
|
125
|
+
|
|
126
|
+
## Severity Guide
|
|
127
|
+
|
|
128
|
+
| Severity | When | Score weight |
|
|
129
|
+
|----------|------|-------------|
|
|
130
|
+
| `critical` | Exploitable, immediate fix required | 3x |
|
|
131
|
+
| `warning` | Risk exists but not urgent | 2x |
|
|
132
|
+
| `info` | Best practice, informational | 1x |
|
|
133
|
+
|
|
134
|
+
## Anti-Patterns
|
|
135
|
+
|
|
136
|
+
- **DON'T:** Typo in sentinel strings — parser silently returns empty array, no error
|
|
137
|
+
- **DON'T:** Throw exceptions in `check()` — no try/catch wrapper, entire category breaks
|
|
138
|
+
- **DON'T:** Reuse check ID across categories — compliance mapping will conflict
|
|
139
|
+
- **DON'T:** Multi-step scripts in `fixCommand` — keep it one-line, reference `kastell` command if needed
|
|
140
|
+
- **DON'T:** Forget `|| echo 'N/A'` fallback in SSH commands — parser breaks if command missing
|
|
141
|
+
|
|
142
|
+
## Next Steps
|
|
143
|
+
|
|
144
|
+
- [ ] Choose correct audit category (30 categories exist)
|
|
145
|
+
- [ ] Assign unique check key: `<CATEGORY>-NN` pattern
|
|
146
|
+
- [ ] Verify `passed` logic matches `currentValue` evaluation
|
|
147
|
+
- [ ] Add SSH command to category batch (avoid extra SSH round-trips)
|
|
148
|
+
- [ ] Write test: mock SSH output, verify correct passed/failed
|
|
149
|
+
- [ ] Run: `npm test -- --testPathPattern=audit`
|
|
150
|
+
- [ ] Verify check count: `kastell audit --json | jq '.summary.totalChecks'`
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Template: CLI Command — $1
|
|
2
|
+
|
|
3
|
+
## Files to Create
|
|
4
|
+
|
|
5
|
+
1. `src/commands/$1.ts` — thin wrapper (parse args, call core, display result)
|
|
6
|
+
2. `src/core/$1.ts` — all business logic (no UI imports)
|
|
7
|
+
3. `src/__tests__/core/$1.test.ts` — test the core function, not the command
|
|
8
|
+
|
|
9
|
+
## Command File (thin wrapper)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/commands/$1.ts
|
|
13
|
+
import { program } from 'commander';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
import { $1Core } from '../core/$1.js';
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('$1')
|
|
20
|
+
.description('TODO: describe what this command does')
|
|
21
|
+
.option('--server <name>', 'Target server name or IP')
|
|
22
|
+
.option('--json', 'Output as JSON')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
const spinner = ora('Running $1...').start();
|
|
25
|
+
try {
|
|
26
|
+
const result = await $1Core(options);
|
|
27
|
+
spinner.succeed('Done');
|
|
28
|
+
if (options.json) {
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
} else {
|
|
31
|
+
console.log(chalk.green(result.message));
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
spinner.fail(error instanceof Error ? error.message : 'Unknown error');
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Core File (all logic here)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// src/core/$1.ts
|
|
44
|
+
export interface $1Options { server?: string; json?: boolean; }
|
|
45
|
+
export interface $1Result { success: boolean; message: string; }
|
|
46
|
+
|
|
47
|
+
export async function $1Core(options: $1Options): Promise<$1Result> {
|
|
48
|
+
// ALL business logic here. NO chalk/ora/UI imports.
|
|
49
|
+
// assertValidIp() before SSH. getAdapter(platform) for platform ops.
|
|
50
|
+
// withProviderErrorHandling() for provider calls.
|
|
51
|
+
throw new Error('Not implemented');
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Test File
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// src/__tests__/core/$1.test.ts
|
|
59
|
+
import { $1Core } from '../../core/$1.js';
|
|
60
|
+
jest.mock('../../utils/ssh.js');
|
|
61
|
+
|
|
62
|
+
describe('$1Core', () => {
|
|
63
|
+
beforeEach(() => jest.resetAllMocks());
|
|
64
|
+
|
|
65
|
+
it('should TODO: describe expected behavior', async () => {
|
|
66
|
+
const result = await $1Core({ server: 'test-server' });
|
|
67
|
+
expect(result.success).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Next Steps
|
|
73
|
+
|
|
74
|
+
- [ ] Register command in `src/index.ts`
|
|
75
|
+
- [ ] Write tests first (TDD: test core function, not command)
|
|
76
|
+
- [ ] Add `isSafeMode()` check if operation is destructive
|
|
77
|
+
- [ ] Add `assertValidIp()` before any SSH operation
|
|
78
|
+
- [ ] Use `getAdapter(platform)` for platform-specific operations (never import adapters directly)
|
|
79
|
+
- [ ] Run `npm run build && npm test && npm run lint`
|
|
80
|
+
- [ ] Update README.md command table
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Template: MCP Tool — $1
|
|
2
|
+
|
|
3
|
+
## Files to Create
|
|
4
|
+
|
|
5
|
+
1. `src/mcp/tools/$1.ts` — Zod schema + handler
|
|
6
|
+
2. Update `src/mcp/server.ts` — import + registerTool()
|
|
7
|
+
3. `src/__tests__/mcp/$1.test.ts` — test handler function
|
|
8
|
+
|
|
9
|
+
## Tool File (Zod schema + handler)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/mcp/tools/$1.ts
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import type { McpResponse } from '../types.js';
|
|
15
|
+
|
|
16
|
+
// IMPORTANT: Schema is a flat object, NOT wrapped in z.object()
|
|
17
|
+
// The SDK wraps it automatically
|
|
18
|
+
export const $1Schema = {
|
|
19
|
+
server: z.string().optional().describe('Server name or IP. Auto-selected if only one server exists.'),
|
|
20
|
+
action: z.enum(['TODO']).describe('TODO: describe actions'),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function handle$1(params: {
|
|
24
|
+
server?: string;
|
|
25
|
+
action: string;
|
|
26
|
+
}): Promise<McpResponse> {
|
|
27
|
+
// Delegate to core function (NOT direct SSH/provider calls)
|
|
28
|
+
// Example: const result = await someCoreFunction(params);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text' as const,
|
|
34
|
+
text: JSON.stringify({ success: true }, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Server Registration
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// In src/mcp/server.ts
|
|
45
|
+
import { $1Schema, handle$1 } from './tools/$1.js';
|
|
46
|
+
|
|
47
|
+
server.registerTool('$1', {
|
|
48
|
+
description: 'TODO: 50-150 tokens. What it does. Actions. Constraints. Requirements.',
|
|
49
|
+
inputSchema: $1Schema,
|
|
50
|
+
annotations: {
|
|
51
|
+
title: 'TODO: Human-readable title',
|
|
52
|
+
readOnlyHint: false, // true if read-only
|
|
53
|
+
destructiveHint: false, // true if destructive
|
|
54
|
+
idempotentHint: false, // true if idempotent
|
|
55
|
+
openWorldHint: true,
|
|
56
|
+
},
|
|
57
|
+
}, async (params) => {
|
|
58
|
+
return handle$1(params);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Next Steps
|
|
63
|
+
|
|
64
|
+
- [ ] Define Zod schema as flat object (NOT z.object() wrapper)
|
|
65
|
+
- [ ] Handler delegates to core/ functions (not direct SSH/provider)
|
|
66
|
+
- [ ] Add `server` param as `.optional()` with standard description
|
|
67
|
+
- [ ] Set annotations correctly (readOnlyHint, destructiveHint)
|
|
68
|
+
- [ ] Write description: "What it does. Actions: 'x' does Y. Constraints. For Z, use other_tool instead."
|
|
69
|
+
- [ ] Add SAFE_MODE check if destructive: `if (isSafeMode()) throw ...`
|
|
70
|
+
- [ ] Write test: call handler directly with mock params
|
|
71
|
+
- [ ] Run `npm run build && npm test && npm run lint`
|
|
72
|
+
- [ ] Update README.md MCP tools table
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Template: Provider — $1
|
|
2
|
+
|
|
3
|
+
## Files to Create
|
|
4
|
+
|
|
5
|
+
1. `src/providers/$1.ts` — provider implementation (extends BaseProvider)
|
|
6
|
+
2. Update `src/constants.ts` — add to PROVIDER_REGISTRY
|
|
7
|
+
3. `src/__tests__/providers/$1.test.ts` — test with mocked API calls
|
|
8
|
+
|
|
9
|
+
## Provider Implementation
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// src/providers/$1.ts
|
|
13
|
+
import {
|
|
14
|
+
BaseProvider,
|
|
15
|
+
type ProviderServer,
|
|
16
|
+
type CreateServerParams,
|
|
17
|
+
type ProviderResponse,
|
|
18
|
+
} from './base.js';
|
|
19
|
+
|
|
20
|
+
export class $1Provider extends BaseProvider {
|
|
21
|
+
constructor(token: string) {
|
|
22
|
+
super(token, 'https://api.$1.com/v1'); // adjust API base URL
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async listServers(): Promise<ProviderResponse<ProviderServer[]>> {
|
|
26
|
+
return this.request<ProviderServer[]>('GET', '/servers');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async createServer(params: CreateServerParams): Promise<ProviderResponse<ProviderServer>> {
|
|
30
|
+
return this.request<ProviderServer>('POST', '/servers', { body: params });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async deleteServer(serverId: string): Promise<ProviderResponse<void>> {
|
|
34
|
+
return this.request<void>('DELETE', `/servers/${serverId}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getServer(serverId: string): Promise<ProviderResponse<ProviderServer>> {
|
|
38
|
+
return this.request<ProviderServer>('GET', `/servers/${serverId}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## PROVIDER_REGISTRY Entry
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// In src/constants.ts — add to PROVIDER_REGISTRY (single source of truth)
|
|
47
|
+
import { $1Provider } from './providers/$1.js';
|
|
48
|
+
|
|
49
|
+
export const PROVIDER_REGISTRY = {
|
|
50
|
+
// ... existing providers
|
|
51
|
+
$1: {
|
|
52
|
+
name: '$1',
|
|
53
|
+
envKey: '$1_TOKEN', // e.g., 'OVHCLOUD_TOKEN'
|
|
54
|
+
class: $1Provider,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Next Steps
|
|
60
|
+
|
|
61
|
+
- [ ] Register in `PROVIDER_REGISTRY` (src/constants.ts) — single source of truth
|
|
62
|
+
- [ ] Implement `stripSensitiveData()` to remove API tokens from error responses
|
|
63
|
+
- [ ] Use `assertValidIp()` before any SSH operation
|
|
64
|
+
- [ ] Use `withProviderErrorHandling()` HOF for API error consistency
|
|
65
|
+
- [ ] Write tests with mocked axios (mock `axios.create()` → `jest.fn(() => axios)`)
|
|
66
|
+
- [ ] Add "Getting Your API Token" section to README.md
|
|
67
|
+
- [ ] Run `npm run build && npm test && npm run lint`
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scaffold.sh — Generate Kastell component boilerplate from templates.
|
|
3
|
+
# Usage: scaffold.sh <type> <name> [--dry-run]
|
|
4
|
+
# type: command | check | provider | mcp-tool
|
|
5
|
+
# name: kebab-case component name (e.g., server-migrate, filesystem-perms)
|
|
6
|
+
#
|
|
7
|
+
# Ersin criterion: This script runs without an LLM. Deterministic file generation.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# ── Resolve script directory (works in symlink/Git Bash/etc) ──
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
TEMPLATE_DIR="$SCRIPT_DIR/../templates"
|
|
14
|
+
PROJECT_ROOT="${PROJECT_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
15
|
+
|
|
16
|
+
# ── Colors ──
|
|
17
|
+
RED='\033[0;31m'
|
|
18
|
+
GREEN='\033[0;32m'
|
|
19
|
+
YELLOW='\033[1;33m'
|
|
20
|
+
CYAN='\033[0;36m'
|
|
21
|
+
NC='\033[0m' # No Color
|
|
22
|
+
|
|
23
|
+
info() { echo -e "${CYAN}ℹ${NC} $*"; }
|
|
24
|
+
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
|
25
|
+
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
|
26
|
+
err() { echo -e "${RED}✗${NC} $*" >&2; }
|
|
27
|
+
|
|
28
|
+
# ── Args ──
|
|
29
|
+
TYPE="${1:-}"
|
|
30
|
+
NAME="${2:-}"
|
|
31
|
+
DRY_RUN=false
|
|
32
|
+
for arg in "$@"; do [[ "$arg" == "--dry-run" ]] && DRY_RUN=true; done
|
|
33
|
+
|
|
34
|
+
if [[ -z "$TYPE" || -z "$NAME" ]]; then
|
|
35
|
+
echo "Usage: scaffold.sh <type> <name> [--dry-run]"
|
|
36
|
+
echo " type: command | check | provider | mcp-tool"
|
|
37
|
+
echo " name: kebab-case (e.g., server-migrate)"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Validate type
|
|
42
|
+
case "$TYPE" in
|
|
43
|
+
command|check|provider|mcp-tool) ;;
|
|
44
|
+
*) err "Unknown type: $TYPE (valid: command, check, provider, mcp-tool)"; exit 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# Validate name (kebab-case or snake_case, lowercase)
|
|
48
|
+
if [[ ! "$NAME" =~ ^[a-z][a-z0-9_-]*$ ]]; then
|
|
49
|
+
err "Invalid name: $NAME (must be lowercase kebab-case or snake_case)"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# ── Name transformations ──
|
|
54
|
+
to_pascal() {
|
|
55
|
+
echo "$1" | sed 's/[-_]/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1' | tr -d ' '
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
to_camel() {
|
|
59
|
+
local pascal
|
|
60
|
+
pascal=$(to_pascal "$1")
|
|
61
|
+
echo "$(echo "${pascal:0:1}" | tr 'A-Z' 'a-z')${pascal:1}"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
to_upper() {
|
|
65
|
+
echo "$1" | tr '[:lower:]' '[:upper:]' | tr '-' '_'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
NAME_PASCAL=$(to_pascal "$NAME")
|
|
69
|
+
NAME_CAMEL=$(to_camel "$NAME")
|
|
70
|
+
NAME_UPPER=$(to_upper "$NAME")
|
|
71
|
+
|
|
72
|
+
info "Scaffolding $TYPE: $NAME"
|
|
73
|
+
info " PascalCase: $NAME_PASCAL"
|
|
74
|
+
info " camelCase: $NAME_CAMEL"
|
|
75
|
+
info " UPPER: $NAME_UPPER"
|
|
76
|
+
echo ""
|
|
77
|
+
|
|
78
|
+
# ── Template processing ──
|
|
79
|
+
process_template() {
|
|
80
|
+
local tpl_file="$1"
|
|
81
|
+
local output_file="$2"
|
|
82
|
+
|
|
83
|
+
if [[ ! -f "$tpl_file" ]]; then
|
|
84
|
+
err "Template not found: $tpl_file"
|
|
85
|
+
return 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ -f "$output_file" ]] && [[ "$DRY_RUN" == false ]]; then
|
|
89
|
+
err "File already exists: $output_file (won't overwrite)"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
local content
|
|
94
|
+
content=$(sed \
|
|
95
|
+
-e "s/__NAME__/$NAME/g" \
|
|
96
|
+
-e "s/__NAME_PASCAL__/$NAME_PASCAL/g" \
|
|
97
|
+
-e "s/__NAME_CAMEL__/$NAME_CAMEL/g" \
|
|
98
|
+
-e "s/__NAME_UPPER__/$NAME_UPPER/g" \
|
|
99
|
+
"$tpl_file")
|
|
100
|
+
|
|
101
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
102
|
+
warn "[dry-run] Would create: $output_file"
|
|
103
|
+
echo "$content"
|
|
104
|
+
echo "---"
|
|
105
|
+
else
|
|
106
|
+
mkdir -p "$(dirname "$output_file")"
|
|
107
|
+
echo "$content" > "$output_file"
|
|
108
|
+
ok "Created: $output_file"
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ── File mapping per type ──
|
|
113
|
+
CREATED=0
|
|
114
|
+
|
|
115
|
+
case "$TYPE" in
|
|
116
|
+
command)
|
|
117
|
+
process_template "$TEMPLATE_DIR/command.ts.tpl" "$PROJECT_ROOT/src/commands/$NAME.ts" && ((CREATED++)) || true
|
|
118
|
+
process_template "$TEMPLATE_DIR/command-core.ts.tpl" "$PROJECT_ROOT/src/core/$NAME.ts" && ((CREATED++)) || true
|
|
119
|
+
process_template "$TEMPLATE_DIR/command-test.ts.tpl" "$PROJECT_ROOT/src/__tests__/core/$NAME.test.ts" && ((CREATED++)) || true
|
|
120
|
+
|
|
121
|
+
echo ""
|
|
122
|
+
warn "Next steps:"
|
|
123
|
+
echo " 1. Register command in src/index.ts"
|
|
124
|
+
echo " 2. Implement core logic in src/core/$NAME.ts"
|
|
125
|
+
echo " 3. Add isSafeMode() check if destructive"
|
|
126
|
+
echo " 4. Run: npm run build && npm test && npm run lint"
|
|
127
|
+
echo " 5. Update README.md command table"
|
|
128
|
+
;;
|
|
129
|
+
|
|
130
|
+
check)
|
|
131
|
+
process_template "$TEMPLATE_DIR/check.ts.tpl" "$PROJECT_ROOT/src/core/audit/checks/$NAME.ts" && ((CREATED++)) || true
|
|
132
|
+
process_template "$TEMPLATE_DIR/check-test.ts.tpl" "$PROJECT_ROOT/src/__tests__/core/audit/checks/$NAME.test.ts" && ((CREATED++)) || true
|
|
133
|
+
|
|
134
|
+
echo ""
|
|
135
|
+
warn "Next steps:"
|
|
136
|
+
echo " 1. Add SSH section in src/core/audit/commands.ts:"
|
|
137
|
+
echo " NAMED_SEP(\"${NAME_UPPER}\") + bash commands"
|
|
138
|
+
echo " 2. Register in src/core/audit/checks/index.ts:"
|
|
139
|
+
echo " { name: \"$NAME_PASCAL\", sectionName: \"$NAME_UPPER\", parser: parse${NAME_PASCAL}Checks }"
|
|
140
|
+
echo " 3. Add compliance mapping in src/core/audit/compliance/mapper.ts (optional)"
|
|
141
|
+
echo " 4. Run: npm run build && npm test && npm run lint"
|
|
142
|
+
echo " 5. Test: kastell audit --list-checks | grep $NAME_UPPER"
|
|
143
|
+
;;
|
|
144
|
+
|
|
145
|
+
provider)
|
|
146
|
+
process_template "$TEMPLATE_DIR/provider.ts.tpl" "$PROJECT_ROOT/src/providers/$NAME.ts" && ((CREATED++)) || true
|
|
147
|
+
process_template "$TEMPLATE_DIR/provider-test.ts.tpl" "$PROJECT_ROOT/src/__tests__/providers/$NAME.test.ts" && ((CREATED++)) || true
|
|
148
|
+
|
|
149
|
+
echo ""
|
|
150
|
+
warn "Next steps:"
|
|
151
|
+
echo " 1. Add to PROVIDER_REGISTRY in src/constants.ts:"
|
|
152
|
+
echo " $NAME: { name: \"$NAME_PASCAL\", envKey: \"${NAME_UPPER}_TOKEN\", class: ${NAME_PASCAL}Provider }"
|
|
153
|
+
echo " 2. Implement API methods (adjust base URL)"
|
|
154
|
+
echo " 3. Add stripSensitiveData() token cleanup"
|
|
155
|
+
echo " 4. Run: npm run build && npm test && npm run lint"
|
|
156
|
+
echo " 5. Add 'Getting Your API Token' to README.md"
|
|
157
|
+
;;
|
|
158
|
+
|
|
159
|
+
mcp-tool)
|
|
160
|
+
process_template "$TEMPLATE_DIR/mcp-tool.ts.tpl" "$PROJECT_ROOT/src/mcp/tools/$NAME.ts" && ((CREATED++)) || true
|
|
161
|
+
process_template "$TEMPLATE_DIR/mcp-tool-test.ts.tpl" "$PROJECT_ROOT/src/__tests__/mcp/$NAME.test.ts" && ((CREATED++)) || true
|
|
162
|
+
|
|
163
|
+
echo ""
|
|
164
|
+
warn "Next steps:"
|
|
165
|
+
echo " 1. Register in src/mcp/server.ts:"
|
|
166
|
+
echo " registerTool('$NAME', { schema: ${NAME_CAMEL}Schema, handler: handle${NAME_PASCAL} })"
|
|
167
|
+
echo " 2. Define Zod schema (flat object, NOT z.object() wrapper)"
|
|
168
|
+
echo " 3. Handler delegates to core/ (NOT direct SSH/provider)"
|
|
169
|
+
echo " 4. Add SAFE_MODE check if destructive"
|
|
170
|
+
echo " 5. Run: npm run build && npm test && npm run lint"
|
|
171
|
+
echo " 6. Update README.md MCP tools table"
|
|
172
|
+
;;
|
|
173
|
+
esac
|
|
174
|
+
|
|
175
|
+
echo ""
|
|
176
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
177
|
+
info "[dry-run] No files were created."
|
|
178
|
+
else
|
|
179
|
+
ok "Scaffolded $CREATED file(s) for $TYPE: $NAME"
|
|
180
|
+
fi
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { parse__NAME_PASCAL__Checks } from "../../core/audit/checks/__NAME__.js";
|
|
2
|
+
|
|
3
|
+
describe("parse__NAME_PASCAL__Checks", () => {
|
|
4
|
+
beforeEach(() => jest.resetAllMocks());
|
|
5
|
+
|
|
6
|
+
it("returns checks when section output is valid", () => {
|
|
7
|
+
const result = parse__NAME_PASCAL__Checks("TODO_SENTINEL present", "linux");
|
|
8
|
+
expect(result.length).toBeGreaterThan(0);
|
|
9
|
+
expect(result[0].passed).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns empty array when section output is empty", () => {
|
|
13
|
+
const result = parse__NAME_PASCAL__Checks("", "linux");
|
|
14
|
+
expect(result).toEqual([]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns empty array when skip marker present", () => {
|
|
18
|
+
const result = parse__NAME_PASCAL__Checks("SKIP_MARKER", "linux");
|
|
19
|
+
expect(result).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns failed check when sentinel missing", () => {
|
|
23
|
+
const result = parse__NAME_PASCAL__Checks("some other output", "linux");
|
|
24
|
+
expect(result.length).toBeGreaterThan(0);
|
|
25
|
+
expect(result[0].passed).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|