haac-aikit 0.1.1 → 0.7.2

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 (44) hide show
  1. package/README.md +169 -69
  2. package/catalog/agents/tier1/architect.md +86 -0
  3. package/catalog/agents/tier1/debugger.md +64 -0
  4. package/catalog/agents/tier1/pr-describer.md +57 -0
  5. package/catalog/agents/{researcher.md → tier1/researcher.md} +1 -1
  6. package/catalog/agents/tier2/changelog-curator.md +60 -0
  7. package/catalog/agents/tier2/dependency-upgrader.md +67 -0
  8. package/catalog/agents/tier2/evals-author.md +65 -0
  9. package/catalog/agents/tier2/flake-hunter.md +57 -0
  10. package/catalog/agents/tier2/prompt-engineer.md +58 -0
  11. package/catalog/agents/tier2/simplifier.md +57 -0
  12. package/catalog/ci/aikit-rules.yml +55 -0
  13. package/catalog/docs/claude-md-reference.md +316 -0
  14. package/catalog/hooks/block-dangerous-bash.sh +0 -0
  15. package/catalog/hooks/block-force-push-main.sh +0 -0
  16. package/catalog/hooks/block-secrets-in-commits.sh +0 -0
  17. package/catalog/hooks/check-pattern-violations.sh +137 -0
  18. package/catalog/hooks/compaction-preservation.sh +0 -0
  19. package/catalog/hooks/file-guard.sh +0 -0
  20. package/catalog/hooks/format-on-save.sh +0 -0
  21. package/catalog/hooks/hooks.json +38 -0
  22. package/catalog/hooks/judge-rule-compliance.sh +197 -0
  23. package/catalog/hooks/log-rule-event.sh +80 -0
  24. package/catalog/hooks/session-start-prime.sh +0 -0
  25. package/catalog/husky/commit-msg +0 -0
  26. package/catalog/husky/pre-commit +0 -0
  27. package/catalog/husky/pre-push +0 -0
  28. package/catalog/rules/AGENTS.md.tmpl +28 -7
  29. package/catalog/rules/aikit-rules.json +37 -0
  30. package/catalog/rules/claude-rules/example.md +38 -0
  31. package/catalog/skills/tier1/software-architect.md +42 -0
  32. package/dist/cli.mjs +1516 -173
  33. package/dist/cli.mjs.map +1 -1
  34. package/package.json +4 -2
  35. /package/catalog/agents/{devops.md → tier1/devops.md} +0 -0
  36. /package/catalog/agents/{implementer.md → tier1/implementer.md} +0 -0
  37. /package/catalog/agents/{orchestrator.md → tier1/orchestrator.md} +0 -0
  38. /package/catalog/agents/{planner.md → tier1/planner.md} +0 -0
  39. /package/catalog/agents/{reviewer.md → tier1/reviewer.md} +0 -0
  40. /package/catalog/agents/{security-auditor.md → tier1/security-auditor.md} +0 -0
  41. /package/catalog/agents/{tester.md → tier1/tester.md} +0 -0
  42. /package/catalog/agents/{backend.md → tier2/backend.md} +0 -0
  43. /package/catalog/agents/{frontend.md → tier2/frontend.md} +0 -0
  44. /package/catalog/agents/{mobile.md → tier2/mobile.md} +0 -0
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env bash
2
+ # Telemetry hook: pattern-checks just-written files against rules in
3
+ # .claude/aikit-rules.json and logs violations.
4
+ # Fires on PostToolUse(Edit|Write).
5
+ #
6
+ # Output: appends one JSON line per violation to .aikit/events.jsonl.
7
+ # Schema: {"ts":"<iso>","event":"violation","rule_id":"<id>","file":"<path>","severity":"<level>","line":<n>}
8
+ #
9
+ # Read-only and non-blocking. Never fails the tool call.
10
+
11
+ set -euo pipefail
12
+
13
+ INPUT="$(cat)"
14
+ LOG_DIR=".aikit"
15
+ LOG_FILE="$LOG_DIR/events.jsonl"
16
+ RULES_FILE=".claude/aikit-rules.json"
17
+
18
+ mkdir -p "$LOG_DIR" 2>/dev/null || exit 0
19
+
20
+ # Bail if there are no rules to check.
21
+ [[ ! -f "$RULES_FILE" ]] && { echo '{"decision":"approve"}'; exit 0; }
22
+
23
+ # Extract the file_path from the tool input.
24
+ FILE_PATH="$(echo "$INPUT" | python3 -c "import sys,json
25
+ try:
26
+ d = json.load(sys.stdin)
27
+ print(d.get('file_path','') or d.get('path',''))
28
+ except Exception:
29
+ pass" 2>/dev/null || echo "")"
30
+
31
+ [[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]] && { echo '{"decision":"approve"}'; exit 0; }
32
+
33
+ TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
34
+
35
+ # Use python to parse JSON rules + apply globs + scan for patterns. Bash glob
36
+ # matching is too limited (no exclude support, no `**`).
37
+ HOOK_FILE_PATH="$FILE_PATH" HOOK_RULES_PATH="$RULES_FILE" HOOK_LOG_PATH="$LOG_FILE" HOOK_TS="$TS" \
38
+ python3 <<'PY' 2>/dev/null || true
39
+ import json, re, os, sys
40
+ file_path = os.environ["HOOK_FILE_PATH"]
41
+ rules_path = os.environ["HOOK_RULES_PATH"]
42
+ log_path = os.environ["HOOK_LOG_PATH"]
43
+ ts = os.environ["HOOK_TS"]
44
+ try:
45
+ with open(rules_path, "r", encoding="utf-8") as f:
46
+ cfg = json.load(f)
47
+ except Exception:
48
+ sys.exit(0)
49
+
50
+ def glob_to_regex(pattern: str) -> str:
51
+ # Custom translator because Python 3.9's pathlib doesn't handle ** recursion
52
+ # and fnmatch treats ** as a single segment.
53
+ out = []
54
+ i = 0
55
+ while i < len(pattern):
56
+ c = pattern[i]
57
+ if c == "*":
58
+ if i + 1 < len(pattern) and pattern[i + 1] == "*":
59
+ if i + 2 < len(pattern) and pattern[i + 2] == "/":
60
+ out.append(r"(?:[^/]+/)*") # zero-or-more directory segments
61
+ i += 3
62
+ continue
63
+ out.append(".*")
64
+ i += 2
65
+ continue
66
+ out.append(r"[^/]*")
67
+ i += 1
68
+ elif c == "?":
69
+ out.append(r"[^/]")
70
+ i += 1
71
+ elif c in ".+()[]{}|^$\\":
72
+ out.append("\\" + c)
73
+ i += 1
74
+ else:
75
+ out.append(c)
76
+ i += 1
77
+ return "^" + "".join(out) + "$"
78
+
79
+ def glob_match(path: str, patterns) -> bool:
80
+ for p in patterns:
81
+ rx = re.compile(glob_to_regex(p))
82
+ if rx.match(path):
83
+ return True
84
+ return False
85
+
86
+ try:
87
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
88
+ content = f.read()
89
+ except Exception:
90
+ sys.exit(0)
91
+
92
+ events = []
93
+ for rule in cfg.get("rules", []):
94
+ rid = rule.get("id") or ""
95
+ pat = rule.get("pattern")
96
+ if not rid or not pat:
97
+ continue
98
+ includes = rule.get("fileGlobs") or ["**/*"]
99
+ excludes = rule.get("exclude") or []
100
+ if not glob_match(file_path, includes):
101
+ continue
102
+ if excludes and glob_match(file_path, excludes):
103
+ continue
104
+ try:
105
+ rx = re.compile(pat, re.MULTILINE)
106
+ except re.error as compile_err:
107
+ events.append({
108
+ "ts": ts,
109
+ "event": "rule_compile_error",
110
+ "rule_id": rid,
111
+ "pattern": pat[:200],
112
+ "error": str(compile_err)[:200],
113
+ })
114
+ continue
115
+ for m in rx.finditer(content):
116
+ line = content.count("\n", 0, m.start()) + 1
117
+ events.append({
118
+ "ts": ts,
119
+ "event": "violation",
120
+ "rule_id": rid,
121
+ "file": file_path,
122
+ "severity": rule.get("severity", "warn"),
123
+ "line": line,
124
+ })
125
+
126
+ if events:
127
+ try:
128
+ os.makedirs(os.path.dirname(log_path) or ".", exist_ok=True)
129
+ with open(log_path, "a", encoding="utf-8") as f:
130
+ for e in events:
131
+ f.write(json.dumps(e, separators=(",", ":")) + "\n")
132
+ except Exception:
133
+ pass
134
+ PY
135
+
136
+ # Always approve — this hook only observes.
137
+ echo '{"decision":"approve"}'
File without changes
File without changes
File without changes
@@ -40,6 +40,44 @@
40
40
  "type": "command",
41
41
  "command": "bash .claude/hooks/format-on-save.sh",
42
42
  "description": "Auto-format after file write"
43
+ },
44
+ {
45
+ "type": "command",
46
+ "command": "bash .claude/hooks/check-pattern-violations.sh",
47
+ "description": "Telemetry: pattern-check just-written file against .claude/aikit-rules.json"
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "InstructionsLoaded": [
53
+ {
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "command": "bash .claude/hooks/log-rule-event.sh",
58
+ "description": "Telemetry: record which rule IDs loaded this session"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "Stop": [
64
+ {
65
+ "hooks": [
66
+ {
67
+ "type": "command",
68
+ "command": "bash .claude/hooks/judge-rule-compliance.sh",
69
+ "description": "Telemetry: opt-in LLM judge of rule compliance (set AIKIT_JUDGE=1)"
70
+ }
71
+ ]
72
+ }
73
+ ],
74
+ "SubagentStop": [
75
+ {
76
+ "hooks": [
77
+ {
78
+ "type": "command",
79
+ "command": "bash .claude/hooks/judge-rule-compliance.sh",
80
+ "description": "Telemetry: opt-in LLM judge of rule compliance (set AIKIT_JUDGE=1)"
43
81
  }
44
82
  ]
45
83
  }
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bash
2
+ # Telemetry hook: opt-in LLM judge that verdicts whether the assistant's last
3
+ # turn cited / violated / ignored each loaded rule.
4
+ # Fires on Stop and SubagentStop.
5
+ #
6
+ # Output: appends one JSON line per rule verdict to .aikit/events.jsonl.
7
+ # Schema: {"ts":"<iso>","event":"cited"|"judged_violation","rule_id":"<id>","verdict":"<text>"}
8
+ #
9
+ # Activation:
10
+ # - Set AIKIT_JUDGE=1 (otherwise the hook exits immediately, free).
11
+ # - Set ANTHROPIC_API_KEY (otherwise the hook exits silently).
12
+ # Cost: ~$0.001/turn with claude-haiku-4-5. Disabled by default for that reason.
13
+ #
14
+ # Read-only and non-blocking. Never fails the parent tool call.
15
+
16
+ set -uo pipefail
17
+
18
+ INPUT="$(cat)"
19
+ LOG_DIR=".aikit"
20
+ LOG_FILE="$LOG_DIR/events.jsonl"
21
+ MODEL="${AIKIT_JUDGE_MODEL:-claude-haiku-4-5-20251001}"
22
+
23
+ # Always echo approve at the end, even on early exit.
24
+ trap 'echo "{\"decision\":\"approve\"}"' EXIT
25
+
26
+ # Cheap exits before any expensive work.
27
+ [[ "${AIKIT_JUDGE:-0}" != "1" ]] && exit 0
28
+ [[ -z "${ANTHROPIC_API_KEY:-}" ]] && exit 0
29
+ command -v curl >/dev/null 2>&1 || exit 0
30
+ mkdir -p "$LOG_DIR" 2>/dev/null || exit 0
31
+
32
+ TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
33
+
34
+ # Build the judge call entirely in python so JSON escaping is correct. The
35
+ # script reads the hook input (assistant transcript) from $HOOK_INPUT, scans
36
+ # the project's rule files for declared IDs, and asks Haiku for a verdict.
37
+ HOOK_INPUT="$INPUT" HOOK_LOG="$LOG_FILE" HOOK_TS="$TS" HOOK_MODEL="$MODEL" \
38
+ python3 <<'PY' 2>/dev/null || true
39
+ import json, os, re, sys, urllib.request, urllib.error
40
+
41
+ api_key = os.environ.get("ANTHROPIC_API_KEY", "")
42
+ if not api_key:
43
+ sys.exit(0)
44
+
45
+ raw = os.environ.get("HOOK_INPUT", "")
46
+ log_path = os.environ["HOOK_LOG"]
47
+ ts = os.environ["HOOK_TS"]
48
+ model = os.environ["HOOK_MODEL"]
49
+
50
+ try:
51
+ payload = json.loads(raw) if raw else {}
52
+ except Exception:
53
+ payload = {}
54
+
55
+ # Try to extract the assistant's last response. Hook input shape varies across
56
+ # Claude Code versions; handle a few likely fields.
57
+ transcript = ""
58
+ for key in ("assistant_message", "last_response", "transcript", "messages"):
59
+ val = payload.get(key)
60
+ if isinstance(val, str):
61
+ transcript = val
62
+ break
63
+ if isinstance(val, list) and val:
64
+ last = val[-1]
65
+ if isinstance(last, dict):
66
+ transcript = str(last.get("content") or last.get("text") or "")
67
+ else:
68
+ transcript = str(last)
69
+ break
70
+
71
+ if not transcript or len(transcript) < 20:
72
+ sys.exit(0)
73
+
74
+ # Cap transcript size to keep the judge prompt cheap.
75
+ if len(transcript) > 8000:
76
+ transcript = transcript[:8000] + "\n...[truncated]"
77
+
78
+ # Collect declared rule IDs from project rule files.
79
+ id_rx = re.compile(r"<!--\s*id:\s*([a-zA-Z][a-zA-Z0-9_-]*\.[a-zA-Z0-9._-]+)(?:\s+[a-zA-Z][a-zA-Z0-9_-]*=[^>\s]+)*\s*-->")
80
+ rules = {} # id -> surrounding line for context
81
+ candidates = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"]
82
+ rules_dir = ".claude/rules"
83
+ if os.path.isdir(rules_dir):
84
+ for f in os.listdir(rules_dir):
85
+ if f.endswith(".md"):
86
+ candidates.append(os.path.join(rules_dir, f))
87
+
88
+ for path in candidates:
89
+ if not os.path.isfile(path):
90
+ continue
91
+ try:
92
+ with open(path, "r", encoding="utf-8", errors="replace") as fh:
93
+ content = fh.read()
94
+ except Exception:
95
+ continue
96
+ for line in content.splitlines():
97
+ m = id_rx.search(line)
98
+ if not m:
99
+ continue
100
+ rid = m.group(1)
101
+ # Strip the comment to get the rule text in human-readable form.
102
+ text = id_rx.sub("", line).strip(" -*\t")
103
+ if rid and text and rid not in rules:
104
+ rules[rid] = text[:240]
105
+
106
+ if not rules:
107
+ sys.exit(0)
108
+
109
+ # Construct judge prompt. Keep it tight — Haiku rewards specificity.
110
+ rules_block = "\n".join(f"- {rid}: {txt}" for rid, txt in rules.items())
111
+ prompt = (
112
+ "You are a precise auditor. Given the assistant's last response and a list "
113
+ "of project rules, decide for each rule whether the response CITED it "
114
+ "(referenced it explicitly), VIOLATED it (acted against it), or was "
115
+ "IRRELEVANT (the rule didn't apply this turn). Respond with JSON only.\n\n"
116
+ f"RULES:\n{rules_block}\n\n"
117
+ f"ASSISTANT RESPONSE:\n{transcript}\n\n"
118
+ 'Output JSON exactly: {"verdicts":[{"rule_id":"...","status":"cited|violated|irrelevant","reason":"<10 words"}]}'
119
+ )
120
+
121
+ req = urllib.request.Request(
122
+ "https://api.anthropic.com/v1/messages",
123
+ data=json.dumps({
124
+ "model": model,
125
+ "max_tokens": 1024,
126
+ "messages": [{"role": "user", "content": prompt}],
127
+ }).encode("utf-8"),
128
+ headers={
129
+ "Content-Type": "application/json",
130
+ "x-api-key": api_key,
131
+ "anthropic-version": "2023-06-01",
132
+ },
133
+ )
134
+
135
+ def log_error(reason):
136
+ # Errors land in a sibling log so the user (who explicitly opted in via
137
+ # AIKIT_JUDGE=1 and is paying for verdicts) can see why a turn produced
138
+ # nothing. Stays out of events.jsonl to keep the rule-event stream clean.
139
+ try:
140
+ err_path = os.path.join(os.path.dirname(log_path) or ".", "judge-errors.log")
141
+ os.makedirs(os.path.dirname(err_path) or ".", exist_ok=True)
142
+ with open(err_path, "a", encoding="utf-8") as eh:
143
+ eh.write(f"{ts} {reason}\n")
144
+ except Exception:
145
+ pass
146
+
147
+ try:
148
+ with urllib.request.urlopen(req, timeout=15) as resp:
149
+ body = json.loads(resp.read().decode("utf-8"))
150
+ except urllib.error.HTTPError as e:
151
+ log_error(f"http {e.code}: {e.reason}")
152
+ sys.exit(0)
153
+ except urllib.error.URLError as e:
154
+ log_error(f"url-error: {e.reason}")
155
+ sys.exit(0)
156
+ except (TimeoutError, json.JSONDecodeError) as e:
157
+ log_error(f"transport: {type(e).__name__}: {e}")
158
+ sys.exit(0)
159
+
160
+ # Extract the model's text content.
161
+ text_chunks = [c.get("text", "") for c in body.get("content", []) if c.get("type") == "text"]
162
+ verdict_text = "\n".join(text_chunks).strip()
163
+
164
+ # Pull out the first {...} JSON object (Haiku usually emits clean JSON, but be defensive).
165
+ match = re.search(r"\{.*\}", verdict_text, re.DOTALL)
166
+ if not match:
167
+ log_error(f"no JSON object in model response (len={len(verdict_text)})")
168
+ sys.exit(0)
169
+ try:
170
+ verdicts = json.loads(match.group(0)).get("verdicts", [])
171
+ except Exception as e:
172
+ log_error(f"verdicts json parse failed: {e}")
173
+ sys.exit(0)
174
+
175
+ # Append events. cited → "cited", violated → "judged_violation", irrelevant → skip.
176
+ events = []
177
+ for v in verdicts:
178
+ rid = v.get("rule_id")
179
+ status = v.get("status")
180
+ if not rid or status not in {"cited", "violated"}:
181
+ continue
182
+ events.append({
183
+ "ts": ts,
184
+ "event": "cited" if status == "cited" else "judged_violation",
185
+ "rule_id": rid,
186
+ "verdict": (v.get("reason") or "")[:200],
187
+ })
188
+
189
+ if events:
190
+ try:
191
+ os.makedirs(os.path.dirname(log_path) or ".", exist_ok=True)
192
+ with open(log_path, "a", encoding="utf-8") as fh:
193
+ for e in events:
194
+ fh.write(json.dumps(e, separators=(",", ":")) + "\n")
195
+ except Exception:
196
+ pass
197
+ PY
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # Telemetry hook: records when instruction files load and which rule IDs they contain.
3
+ # Fires on InstructionsLoaded.
4
+ #
5
+ # Output: appends one JSON line per loaded rule to .aikit/events.jsonl.
6
+ # Schema: {"ts":"<iso>","event":"loaded","rule_id":"<id>","source":"<path>"}
7
+ #
8
+ # This hook is read-only and never blocks. If anything fails it exits silently
9
+ # so it can never break a Claude Code session.
10
+
11
+ set -euo pipefail
12
+
13
+ INPUT="$(cat)"
14
+ LOG_DIR=".aikit"
15
+ LOG_FILE="$LOG_DIR/events.jsonl"
16
+
17
+ # Bail early if we can't even create the log dir (read-only fs, etc.).
18
+ mkdir -p "$LOG_DIR" 2>/dev/null || { echo '{"decision":"approve"}'; exit 0; }
19
+
20
+ TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
21
+
22
+ # Do all work inside python so file paths with quotes/backslashes can't break
23
+ # the JSONL output (json.dumps escapes them safely).
24
+ HOOK_INPUT="$INPUT" HOOK_LOG="$LOG_FILE" HOOK_TS="$TS" \
25
+ python3 <<'PY' 2>/dev/null || true
26
+ import json, os, re
27
+ try:
28
+ payload = json.loads(os.environ.get("HOOK_INPUT", "") or "{}")
29
+ if not isinstance(payload, dict):
30
+ payload = {}
31
+ except Exception:
32
+ payload = {}
33
+
34
+ candidates = []
35
+ for key in ("files", "paths", "instructionFiles", "loaded", "memory"):
36
+ val = payload.get(key)
37
+ if isinstance(val, list):
38
+ candidates.extend(str(v) for v in val if v)
39
+ elif isinstance(val, str):
40
+ candidates.append(val)
41
+ # Always probe the standard memory file locations even if not echoed back.
42
+ for default in ("AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"):
43
+ candidates.append(default)
44
+
45
+ # Dedupe while preserving order.
46
+ seen = set()
47
+ files = [c for c in candidates if not (c in seen or seen.add(c))]
48
+
49
+ # Match a dotted-slug rule ID, starting with a letter so docstring examples
50
+ # like <!-- id: ... --> don't produce phantom events. Allows optional
51
+ # space-separated key=value metadata between the ID and the closing -->.
52
+ id_rx = re.compile(r"<!--\s*id:\s*([a-zA-Z][a-zA-Z0-9_-]*\.[a-zA-Z0-9._-]+)(?:\s+[a-zA-Z][a-zA-Z0-9_-]*=[^>\s]+)*\s*-->")
53
+ ts = os.environ["HOOK_TS"]
54
+ log_path = os.environ["HOOK_LOG"]
55
+
56
+ events = []
57
+ for path in files:
58
+ if not os.path.isfile(path):
59
+ continue
60
+ try:
61
+ with open(path, "r", encoding="utf-8", errors="replace") as fh:
62
+ content = fh.read()
63
+ except Exception:
64
+ continue
65
+ for match in id_rx.finditer(content):
66
+ rule_id = match.group(1)
67
+ if rule_id:
68
+ events.append({"ts": ts, "event": "loaded", "rule_id": rule_id, "source": path})
69
+
70
+ if events:
71
+ try:
72
+ with open(log_path, "a", encoding="utf-8") as fh:
73
+ for e in events:
74
+ fh.write(json.dumps(e, separators=(",", ":")) + "\n")
75
+ except Exception:
76
+ pass
77
+ PY
78
+
79
+ # Always approve — this hook only observes.
80
+ echo '{"decision":"approve"}'
File without changes
File without changes
File without changes
File without changes
@@ -27,20 +27,41 @@ TODO: 3-5 bullets on where things live.
27
27
  TODO: How to run a single test; framework quirks.
28
28
 
29
29
  ## Commit & PR conventions
30
- - Conventional commits: `type(scope): description`
31
- - Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`
32
- - PR title < 70 characters; body: Summary + Test Plan.
33
- - Never force-push to `main`/`master`/`develop`.
30
+ <!-- Rule IDs (the `<!-- id: ... -->` comments below) are used by
31
+ `aikit doctor --rules` to track which rules fire and which are followed.
32
+ They cost zero context tokens (HTML comments are stripped before injection).
33
+ Optional metadata after the ID:
34
+ emphasis=high prefix with **bold** in dialect translators
35
+ paths=glob,glob restrict to matching files (Cursor `globs:`, Claude `paths:`)
36
+ Add IDs to your own rules to opt them into observability + translation. -->
37
+ - <!-- id: commit.conventional-commits --> Conventional commits: `type(scope): description`
38
+ - <!-- id: commit.types --> Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`
39
+ - <!-- id: pr.title-under-70 --> PR title < 70 characters; body: Summary + Test Plan.
40
+ - <!-- id: git.no-force-push-protected emphasis=high --> Never force-push to `main`/`master`/`develop`.
34
41
 
35
42
  ## Security
36
- Never read, write, or commit:
43
+ <!-- id: security.no-sensitive-files emphasis=high --> Never read, write, or commit:
37
44
  - `.env*`, `secrets/`, `.ssh/`, `.aws/`, `*.pem`, `id_rsa*`
38
45
 
39
- Never force-push to `main`/`master`/`develop`.
46
+ <!-- id: git.no-force-push-protected emphasis=high --> Never force-push to `main`/`master`/`develop`.
40
47
 
41
48
  TODO: Project-specific security notes.
42
49
 
43
50
  ## Gotchas
44
- <!-- High-signal section. Add things the agent repeatedly gets wrong. -->
51
+ <!-- High-signal section. Add things the agent repeatedly gets wrong.
52
+ Tip: prefix the most load-bearing rules with `IMPORTANT:` or `YOU MUST` —
53
+ Anthropic guidance says emphasis tokens improve adherence noticeably.
54
+ HTML block comments like this one are stripped before injection, so they
55
+ cost zero context tokens. -->
45
56
  TODO
57
+
58
+ ## When compacting
59
+ <!-- Tells Claude what to preserve when /compact runs. The project-root
60
+ CLAUDE.md / AGENTS.md is re-injected after compaction; nested files are
61
+ not. Use this block to lock in must-keep context. -->
62
+ Always preserve: the full list of files modified this session, any failing test names + error messages, and the current task being worked on.
63
+
64
+ ## Further reading
65
+ - `docs/claude-md-reference.md` — Anthropic 2026 reference for CLAUDE.md / memory / `.claude/rules/` (shipped by `aikit` when `claude` is selected at scope ≥ standard).
66
+ - `.claude/rules/` — drop topic-scoped or path-scoped rule files here (see the example shipped with this kit). Path-scoped rules load only when Claude reads matching files, saving context.
46
67
  <!-- END:haac-aikit -->
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/Hamad-Center/haac-aikit/main/schema/aikit-rules.schema.json",
3
+ "version": 1,
4
+ "comment": "Maps rule IDs (in AGENTS.md / .claude/rules/*.md) to machine-checkable patterns. The PostToolUse hook reads this to detect violations in just-written files. Pattern-checkable rules are a SUBSET of all rules — many rules can only be observed (loaded/cited) but not auto-validated. Add your own; remove what you don't want.",
5
+ "rules": [
6
+ {
7
+ "id": "code-style.no-console-log",
8
+ "title": "No console.log in production code",
9
+ "pattern": "(?<!//\\s)console\\.log\\(",
10
+ "fileGlobs": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"],
11
+ "exclude": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"],
12
+ "severity": "warn"
13
+ },
14
+ {
15
+ "id": "code-style.no-default-export",
16
+ "title": "Use named exports, not default",
17
+ "pattern": "^\\s*export\\s+default\\b",
18
+ "fileGlobs": ["src/**/*.ts", "src/**/*.tsx"],
19
+ "severity": "warn"
20
+ },
21
+ {
22
+ "id": "code-style.no-any",
23
+ "title": "Use unknown + type guards, not any",
24
+ "pattern": ":\\s*any\\b|<any>|as\\s+any\\b",
25
+ "fileGlobs": ["src/**/*.ts", "src/**/*.tsx"],
26
+ "exclude": ["**/*.d.ts"],
27
+ "severity": "warn"
28
+ },
29
+ {
30
+ "id": "code-style.no-ts-ignore-without-comment",
31
+ "title": "@ts-ignore must include a reason",
32
+ "pattern": "@ts-ignore(?!\\s*[—:-])",
33
+ "fileGlobs": ["src/**/*.ts", "src/**/*.tsx"],
34
+ "severity": "warn"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,38 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.ts"
4
+ - "src/**/*.tsx"
5
+ ---
6
+
7
+ # Example path-scoped rule (CUSTOMISE OR DELETE)
8
+
9
+ > This file was shipped by `haac-aikit` as a starter. It demonstrates the 2026
10
+ > `.claude/rules/` mechanism: rules with a `paths:` frontmatter load **only when
11
+ > Claude reads a matching file**, saving context.
12
+ >
13
+ > Edit it to match your project's conventions, change the `paths:` glob, or
14
+ > delete the file entirely. See `docs/claude-md-reference.md` §6 for the full
15
+ > spec.
16
+
17
+ ## Conventions for files matching the `paths:` glob
18
+
19
+ <!-- Rule IDs let `aikit doctor --rules` track each rule's fire-rate and
20
+ violation-rate over time. Match the ID exactly in `aikit-rules.json` if
21
+ you want pattern-based violation detection on top of the load-time log. -->
22
+
23
+ - <!-- id: code-style.no-default-export --> Use named exports, not `export default`.
24
+ - <!-- id: code-style.no-any --> Use `unknown` + type guards instead of `any`.
25
+ - <!-- id: code-style.no-console-log --> No `console.log` in production code (remove before commit).
26
+ - Prefix the most load-bearing rules with `IMPORTANT:` or `YOU MUST` —
27
+ Anthropic guidance says emphasis improves adherence.
28
+
29
+ ## How to add another rule
30
+
31
+ Create another `.md` file in `.claude/rules/`. Without `paths:` frontmatter, the
32
+ rule loads on every session. With `paths:`, it loads only when Claude reads a
33
+ matching file. Subdirectories like `.claude/rules/frontend/` are supported.
34
+
35
+ ## How to delete this file
36
+
37
+ `rm .claude/rules/example.md`. `aikit diff` will then report it as missing on
38
+ the next run; ignore the warning or run `aikit sync --force` to suppress it.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: software-architect
3
+ description: Use when a task introduces new services, cross-module data flow, external integrations, schema changes, or API design — pauses implementation to produce an RFC before planning begins. Also invokable explicitly.
4
+ version: "1.0.0"
5
+ source: haac-aikit
6
+ license: MIT
7
+ ---
8
+
9
+ ## When to use
10
+
11
+ Auto-trigger when ANY of:
12
+ - New service, module, or package being introduced
13
+ - Cross-module data flow or shared state
14
+ - New external integration (API, DB, queue, auth provider)
15
+ - Schema or data model changes
16
+ - API surface being designed (REST, GraphQL, events)
17
+ - Feature touches ≥ 3 files across different domains
18
+
19
+ Explicit trigger: user says "run the architect", "design this first", "RFC for X"
20
+
21
+ ## Process
22
+
23
+ 1. **Pause** — do not write code or a plan yet.
24
+
25
+ 2. **Explore** — read the codebase for existing patterns relevant to the decision; identify files, modules, and conventions already in use. Never design around patterns you haven't verified exist.
26
+
27
+ 3. **Identify decisions** — surface the 2-3 key architectural choices this feature requires. Name each one explicitly.
28
+
29
+ 4. **Dispatch the `architect` agent** with:
30
+ - Feature description and goals
31
+ - Relevant existing files and patterns found in exploration
32
+ - Known constraints (performance, security, backwards compat, deadline)
33
+
34
+ 5. **Wait** — do not invoke `writing-plans` until the RFC artifact exists at `docs/decisions/YYYY-MM-DD-<topic>.md` and is committed.
35
+
36
+ ## Anti-patterns to avoid
37
+
38
+ - Skipping to implementation when trigger conditions are met
39
+ - Producing a plan before an RFC exists
40
+ - Making design decisions inline in conversation without a committed artifact
41
+ - Inventing patterns that already exist in the codebase
42
+ - Running the architect agent without first exploring existing code (the agent needs grounded context)