feat: Add hooks, steering, and skills system with multi-tool integration#55
feat: Add hooks, steering, and skills system with multi-tool integration#55josealekhine merged 1 commit intomainfrom
Conversation
Deploying ctx with
|
| Latest commit: |
1d05145
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b87d0109.ctx-bhl.pages.dev |
| Branch Preview URL: | https://kiro-cline.ctx-bhl.pages.dev |
9b354f9 to
61a84e8
Compare
There was a problem hiding this comment.
Hey @parlakisik — this is an ambitious contribution, thank you for the effort 🙏 .
Before we can review this for merge, there are several things that need to be addressed:
- Naming conflict: ctx hook
We explicitly renamed ctx hook → ctx setup on April 1st (https://github.com/ActiveMemory/ctx) to avoid ambiguity with the PreToolUse/PostToolUse hook system that ctx uses internally.
This PR reintroduces ctx hook as a lifecycle hooks command, which recreates the exact naming collision we resolved. The lifecycle hooks subsystem would need a different name (e.g. ctx
trigger, ctx lifecycle, or similar).
- Audit test modifications
This PR modifies cross_package_types_test.go, dead_exports_test.go, and compliance_test.go to add allowlist entries for the new code. Our convention is that new code should conform to
the audit checks, not loosen them.
Please review whether the new packages can be structured to pass the existing checks without allowlist additions.
- Convention compliance
After rebasing (you're 1 commit behind), please verify locally:
make lint # must be 0 issues
make test # all tests must pass, including audit checksThe audit tests in internal/audit/ enforce: magic strings, line length (≤80 chars), doc comments, no raw file I/O (os.ReadFile → ctxio.ReadFile), no inline regexp, import/variable shadowing, type file placement, and more.
After rebasing onto current main, TestTypeFileConvention will fail. The fix is mechanical: move type definitions from implementation files to types.go in each package.
Here are the exact fixes:
Design Issues
A. ctx hook naming conflict
We explicitly renamed ctx hook → ctx setup on April 1st to avoid ambiguity with ctx's internal hook system (PreToolUse/PostToolUse hooks in Claude Code's settings.json). This PR reintroduces ctx hook as a lifecycle hooks command — different functionality, same name, same collision we already resolved.
The lifecycle hooks subsystem needs a different command name. Some options: ctx trigger, ctx lifecycle, ctx on — open to your suggestion, but ctx hook is off the table.
This affects: internal/cli/hook/, internal/config/embed/cmd/hook.go, internal/err/hook/, internal/exec/hook/, internal/hook/, internal/write/hook/, and the bootstrap registration.
B. Audit test modifications
This PR modifies 3 existing audit/compliance tests. The allowlists and logic in these tests exist for pre-existing violations that predate the checks. New code must conform, not widen the exemptions. Here's what needs to change:
B1. compliance_test.go — TestCmdDirPurity allowlist
You added:
"steering/cmd/initcmd/cmd.go": {"foundationFile": true},This is new code, not a pre-existing violation. Remove this entry and move foundationFile out of cmd/ (see fix #5 below).
B2. dead_exports_test.go — 3 new exemption maps
-
linuxOnlyExports(12 entries) — This is legitimate. We have a known issue wherego/packagesonly loads files matching the current GOOS, so_linux.gocallers are invisible on macOS. Good catch, and the separate map with explanation is the right approach. -
yamlOnlyExports(5 DescKey entries) — These are described as "forward declarations for future drift checks and hook tooling." We tested without the exemption and confirmed all 5 are dead code with zero callers. Delete these constants from the source files and remove theyamlOnlyExportsmap entirely. Add them when the consuming code lands.The dead constants to remove:
DescKeyDriftCheckSteeringToolsininternal/config/embed/text/drift.go:61DescKeyDriftCheckHookPermsininternal/config/embed/text/drift.go:62DescKeyDriftCheckSyncStalenessininternal/config/embed/text/drift.go:63DescKeyDriftCheckRCToolininternal/config/embed/text/drift.go:64DescKeyHookCursorininternal/config/embed/text/hook.go:16
-
testOnlyExports:bootstrap.ResolveTool— We tested without the exemption and confirmed this is a dead export (zero callers in non-test code). DeleteResolveToolfrominternal/bootstrap/cmd.go:140and remove thetestOnlyExportsentry. If it's needed for tests, make it unexported and access it via a test helper.
B3. cross_package_types_test.go — sameModule logic widened
You added mcp/ as a consumer layer alongside cli/ via a new isConsumerLayer() helper. This means any mcp/* package can now import types from any domain package without triggering TestCrossPackageTypes.
We tested without the change and found exactly 2 cross-package types that motivated it:
hook.HookType(defined ininternal/hook/types.go:10, used frommcp/handler)hook.HookInput(defined ininternal/hook/types.go:54, used frommcp/handler)
The fix is to move HookType and HookInput to internal/entity/ (the designated home for cross-package types) instead of widening sameModule. Then revert the isConsumerLayer change entirely — restore the original cli/-only consumer layer logic.
// In internal/entity/hook.go (new file):
package entity
// HookType identifies the lifecycle hook event type.
type HookType string // + copy the const block
// HookInput carries the payload for a lifecycle hook invocation.
type HookInput struct { ... } // + copy fieldsThen update imports in internal/hook/ and internal/mcp/handler/ to use entity.HookType and entity.HookInput.
Type Placement Fixes
Here are the exact fixes for TestTypeFileConvention:
1. internal/cli/setup/core/cline/types.go (new file)
Move from cline.go:
package cline
// vscodeMCPConfig is the top-level mcp.json structure for Cline.
type vscodeMCPConfig struct {
Servers map[string]vscodeMCPServer `json:"servers"`
}
// vscodeMCPServer describes one MCP server entry in mcp.json.
type vscodeMCPServer struct {
Command string `json:"command"`
Args []string `json:"args"`
}2. internal/cli/setup/core/cursor/types.go (new file)
Move from cursor.go:
package cursor
// mcpConfig is the top-level mcp.json structure for Cursor.
type mcpConfig struct {
MCPServers map[string]serverEntry `json:"mcpServers"`
}
// serverEntry describes one MCP server entry in mcp.json.
type serverEntry struct {
Command string `json:"command"`
Args []string `json:"args"`
}3. internal/cli/setup/core/kiro/types.go (new file)
Move from kiro.go:
package kiro
// mcpConfig is the top-level mcp.json structure for Kiro.
type mcpConfig struct {
MCPServers map[string]serverEntry `json:"mcpServers"`
}
// serverEntry describes one MCP server entry in mcp.json.
type serverEntry struct {
Command string `json:"command"`
Args []string `json:"args"`
Disabled bool `json:"disabled"`
AutoApprove []string `json:"autoApprove"`
}4. internal/steering/types.go (append to existing)
Move from sync_helpers.go:
// cursorFrontmatter is the YAML frontmatter for Cursor rule files.
type cursorFrontmatter struct {
Description string `yaml:"description"`
Globs []any `yaml:"globs"`
AlwaysApply bool `yaml:"alwaysApply"`
}
// kiroFrontmatter is the YAML frontmatter for Kiro steering files.
type kiroFrontmatter struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
Mode string `yaml:"mode"`
}5. internal/cli/steering/cmd/initcmd/cmd.go → internal/steering/types.go
Move foundationFile and the foundationFiles var to internal/steering/types.go, then import from cmd/initcmd. Also remove the allowlist entry you added to compliance_test.go:
- "steering/cmd/initcmd/cmd.go": {"foundationFile": true},The allowlist in TestCmdDirPurity exists only for pre-existing violations that predate the check. New code must conform — types and data don't belong in cmd/ directories. cmd/ should only contain cmd.go, run.go, and doc.go.
6. Rename helpers.go files
Our convention is to name files after what they contain, not their role. helpers.go is a lazy name that doesn't communicate intent. Please rename:
| Current | Rename to | Rationale |
|---|---|---|
setup/core/cline/helpers.go |
deploy.go |
Contains ensureMCPConfig + syncSteering — these are deployment steps, matching the existing Deploy* naming in the write layer |
setup/core/cursor/helpers.go |
deploy.go |
Same pattern |
setup/core/kiro/helpers.go |
deploy.go |
Same pattern |
steering/sync_helpers.go |
format.go |
Contains formatNative, nativePath, validateOutputPath — this is the native format conversion layer for sync |
Verify
After making these moves, rebase and run:
git rebase main
make lint # must be 0 issues
make test # all 38 audit checks must passEverything else looks clean. Nice work on matching the project's conventions — the doc.go files, copyright headers, write/ package separation, and error package taxonomy are all spot on.
Add lifecycle hooks, steering files, and skills packages with full convention alignment to main's audit and compliance requirements. Key additions: - Hook discovery, runner, and security validation (internal/hook/) - Steering file parser, sync engine, and filter (internal/steering/) - Skill install, load, and remove (internal/skill/) - CLI commands: ctx hook, ctx steering, ctx skill - Setup support for Cursor, Kiro, and Cline - MCP steering search tool - Drift checks for steering sync staleness Convention alignment: - All fmt.Errorf moved to internal/err/ helpers - All cmd.Println/Printf moved to internal/write/ helpers - All raw file I/O replaced with internal/io/Safe* wrappers - All bare err := renamed to descriptive names - All flag bindings use flagbind.* helpers - exec.CommandContext moved to internal/exec/hook/ - Magic strings/numbers replaced with config constants - Line length, doc comments, mixed visibility fixes Signed-off-by: Murat Parlakisik <parlakisik@gmail.com>
All user-facing format strings in write/steering, write/skill, write/trigger, write/setup, mcp/handler, trigger/, and drift/ now go through desc.Text() with YAML-backed DescKeys. Added 42 YAML entries in ui.yaml and 30 DescKey constants across 6 config/embed/text files (steering.go, trigger.go new; skill.go, setup.go, mcp_tool.go, drift.go updated). Also adds predicate naming convention section to audit-conventions.md. Spec: specs/ast-audit-contributor-guide.md Signed-off-by: Jose Alekhinne <jose@ctx.ist>
Extend ctx from a persistence-only tool into a behavioral guidance and lifecycle automation platform. Adds steering files for AI behavioral rules, lifecycle hooks for event-driven automation, reusable skill bundles, and cross-tool setup for Kiro, Cursor, and Cline.
New commands: ctx steering, ctx hook, ctx skill.
New MCP tools: ctx_steering_get, ctx_search, ctx_session_start, ctx_session_end.
New setup targets: ctx setup kiro/cursor/cline --write.
Fix: MCP tool descriptions now resolve correctly at runtime.