ima-claude 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "compound-bridge"
|
|
3
|
+
description: "Compound Engineering integration — memory bridge (Compound → Vestige/Qdrant), Vestige → Compound research, role separation (plan vs task-master), per-project config template. Triggers when Compound workflows complete or when orchestrating plan/review workflows. Triggers on: /workflows:compound, /workflows:plan, /workflows:review, compound engineering, compound-engineering.local."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compound Bridge — Compound Engineering + ima-claude Integration
|
|
7
|
+
|
|
8
|
+
Minimal connective tissue between Compound Engineering (structured workflows) and ima-claude (coding standards + memory). Removing this skill returns both systems to standalone behavior.
|
|
9
|
+
|
|
10
|
+
## Memory Bridge: Compound → Vestige/Qdrant
|
|
11
|
+
|
|
12
|
+
After Compound workflow events, **automatically store insights** without being asked:
|
|
13
|
+
|
|
14
|
+
| After This Event | Store This | Where |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `/workflows:compound` writes a solution | Root cause + key insight (1-2 sentences) | Vestige `smart_ingest`, node_type: `pattern` |
|
|
17
|
+
| `/workflows:compound` writes a solution (>500 words) | Full solution content | Qdrant `qdrant-store`, collection: `compound-solutions` |
|
|
18
|
+
| `/workflows:plan` completes with research | Key decisions + approach chosen | Vestige `smart_ingest`, node_type: `decision` |
|
|
19
|
+
| `/workflows:review` finds P1/P2 issues | Pattern summary of findings | Vestige `smart_ingest`, node_type: `pattern` |
|
|
20
|
+
|
|
21
|
+
### Compound → Vestige Example
|
|
22
|
+
|
|
23
|
+
After `/workflows:compound` finishes writing to `docs/solutions/`:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
mcp__vestige__smart_ingest
|
|
27
|
+
content: "Root cause: {concise root cause}. Fix: {approach taken}. Key insight: {what we learned}"
|
|
28
|
+
node_type: "pattern"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Compound → Qdrant Example (Large Solutions)
|
|
32
|
+
|
|
33
|
+
For solutions over ~500 words, also store the full document in Qdrant for RAG retrieval:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
mcp__qdrant-memory__qdrant-store
|
|
37
|
+
information: "{full solution content}"
|
|
38
|
+
metadata: {"type": "compound-solution", "source": "docs/solutions/{filename}"}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Memory Bridge: Vestige → Compound Research
|
|
42
|
+
|
|
43
|
+
When orchestrating `/workflows:plan`, **search Vestige before/alongside** the learnings-researcher agent:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
mcp__vestige__search query: "{feature keywords}" limit: 5
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Include Vestige results alongside file-based learnings, marked as cross-project provenance:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
### Prior Knowledge (Cross-Project — Vestige)
|
|
53
|
+
- {vestige result 1}
|
|
54
|
+
- {vestige result 2}
|
|
55
|
+
|
|
56
|
+
### Prior Solutions (Project — docs/solutions/)
|
|
57
|
+
- {learnings-researcher results}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This supplements but does not replace the learnings-researcher's `docs/solutions/` grep.
|
|
61
|
+
|
|
62
|
+
## Role Separation: Planning
|
|
63
|
+
|
|
64
|
+
Both `task-master` and `/workflows:plan` handle planning. They have different lanes:
|
|
65
|
+
|
|
66
|
+
| Need | Use | Why |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| Formal feature planning with research | `/workflows:plan` | Research agents, structured documentation, living plan file |
|
|
69
|
+
| Ad-hoc work breakdown during implementation | `task-master` | Decomposition patterns, storage strategy, agent delegation |
|
|
70
|
+
| Breaking a plan into executable tasks | Both | Plan creates the doc; task-master principles guide breakdown |
|
|
71
|
+
|
|
72
|
+
### task-master Principles That Enhance Compound Workflows
|
|
73
|
+
|
|
74
|
+
These apply when `/workflows:work` creates its task list:
|
|
75
|
+
|
|
76
|
+
- **Two-level max** agent delegation (Compound's swarm already respects this)
|
|
77
|
+
- **Model selection**: Sonnet for execution, Opus for orchestration
|
|
78
|
+
- **Minimal context principle** for subagents — only include what they need
|
|
79
|
+
- **Vertical decomposition** for sequential work, **horizontal** for parallel
|
|
80
|
+
|
|
81
|
+
## Per-Project Config: `compound-engineering.local.md`
|
|
82
|
+
|
|
83
|
+
Create this file in project roots where both systems are used. It tells Compound's review agents about our coding standards.
|
|
84
|
+
|
|
85
|
+
### Template
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
---
|
|
89
|
+
review_agents:
|
|
90
|
+
- code-simplicity-reviewer
|
|
91
|
+
- security-sentinel
|
|
92
|
+
- performance-oracle
|
|
93
|
+
- kieran-typescript-reviewer
|
|
94
|
+
- kieran-python-reviewer
|
|
95
|
+
- architecture-strategist
|
|
96
|
+
- pattern-recognition-specialist
|
|
97
|
+
- julik-frontend-races-reviewer
|
|
98
|
+
- agent-native-reviewer
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Coding Standards (ima-claude)
|
|
102
|
+
|
|
103
|
+
FP-first: pure functions, composition, immutability. Anti-over-engineering: YAGNI strictly, boring code wins. Native patterns only — no custom pipe/compose/curry utilities.
|
|
104
|
+
|
|
105
|
+
Language skills auto-activate by context:
|
|
106
|
+
- JavaScript: js-fp | PHP: php-fp | Vue: js-fp-vue | React: js-fp-react
|
|
107
|
+
- WordPress JS: js-fp-wordpress, jquery | WordPress PHP: php-fp-wordpress
|
|
108
|
+
- Quasar: quasar-fp | Bootstrap: ima-bootstrap
|
|
109
|
+
|
|
110
|
+
## Work Decomposition (task-master)
|
|
111
|
+
|
|
112
|
+
- Two-level max agent delegation
|
|
113
|
+
- Sonnet for execution, Opus for orchestration
|
|
114
|
+
- Minimal context principle for subagents
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### When to Create
|
|
118
|
+
|
|
119
|
+
Create `compound-engineering.local.md` when:
|
|
120
|
+
- A project uses both ima-claude skills AND Compound Engineering workflows
|
|
121
|
+
- You're about to run `/workflows:review` for the first time in a project
|
|
122
|
+
|
|
123
|
+
Don't create it for projects that only use one system.
|
|
124
|
+
|
|
125
|
+
## What Works Without This Skill
|
|
126
|
+
|
|
127
|
+
These integrate naturally — no bridge needed:
|
|
128
|
+
|
|
129
|
+
- ima-claude language/FP skills auto-activate during `/workflows:work` by file type
|
|
130
|
+
- Compound's research agents (learnings-researcher, best-practices-researcher) fill gaps ima-claude doesn't cover
|
|
131
|
+
- Compound's 15 specialized review agents complement our FP-focused standards
|
|
132
|
+
- Compound's brainstorm workflow is genuinely new capability
|
|
133
|
+
|
|
134
|
+
## Artifact Resilience: Surviving Branch Switches & Context Compaction
|
|
135
|
+
|
|
136
|
+
Compound workflows write artifacts to the working tree (`docs/brainstorms/`, `docs/plans/`, `docs/solutions/`, `todos/`). These files are **not committed** during workflows. Git branch switching during `/workflows:work` **destroys them**. Context compaction loses agent results that reference them. This section prevents that.
|
|
137
|
+
|
|
138
|
+
### Rule 1: Shadow Copy to `.claude/compound/`
|
|
139
|
+
|
|
140
|
+
**After EVERY workflow artifact write**, immediately copy the file to `.claude/compound/`:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
# After /workflows:brainstorm writes to docs/brainstorms/
|
|
144
|
+
cp docs/brainstorms/{file}.md .claude/compound/brainstorms/{file}.md
|
|
145
|
+
|
|
146
|
+
# After /workflows:plan writes to docs/plans/
|
|
147
|
+
cp docs/plans/{file}.md .claude/compound/plans/{file}.md
|
|
148
|
+
|
|
149
|
+
# After /workflows:compound writes to docs/solutions/
|
|
150
|
+
cp docs/solutions/{category}/{file}.md .claude/compound/solutions/{category}/{file}.md
|
|
151
|
+
|
|
152
|
+
# After /workflows:review writes to todos/
|
|
153
|
+
cp todos/{file}.md .claude/compound/todos/{file}.md
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Create directories with `mkdir -p` as needed. The `.claude/` directory is gitignored and **survives branch switches** — just like Claude Code's own plan files.
|
|
157
|
+
|
|
158
|
+
**Also shadow-copy on edits**: When `/workflows:work` updates plan checkboxes (`- [ ]` → `- [x]`), copy the updated plan to the shadow location too.
|
|
159
|
+
|
|
160
|
+
### Rule 2: Eager Memory Bridge (Store Immediately, Not Just at Completion)
|
|
161
|
+
|
|
162
|
+
Don't wait until a workflow finishes to bridge to memory. Store **immediately after each artifact write**:
|
|
163
|
+
|
|
164
|
+
| After Writing | Store Immediately |
|
|
165
|
+
|---|---|
|
|
166
|
+
| Brainstorm document | Vestige `smart_ingest`: key decisions + open questions, node_type: `decision` |
|
|
167
|
+
| Plan document | Vestige `smart_ingest`: approach + task list summary, node_type: `decision` |
|
|
168
|
+
| Plan checkbox update | Vestige `smart_ingest`: progress snapshot (X of Y tasks done), node_type: `observation` |
|
|
169
|
+
| Review todo file | Vestige `smart_ingest`: finding summary + priority, node_type: `pattern` |
|
|
170
|
+
| Solution document | Vestige + Qdrant (per existing rules above) |
|
|
171
|
+
|
|
172
|
+
This ensures that even if context compacts or the session dies, the knowledge survives in memory.
|
|
173
|
+
|
|
174
|
+
### Rule 3: Pre-Branch-Switch Checkpoint
|
|
175
|
+
|
|
176
|
+
**Before ANY `git checkout`, `git switch`, or worktree operation** during a Compound workflow:
|
|
177
|
+
|
|
178
|
+
1. Verify all workflow artifacts have shadow copies in `.claude/compound/`
|
|
179
|
+
2. If any are missing, create them immediately
|
|
180
|
+
3. Store a Vestige snapshot: `smart_ingest` with content summarizing current workflow state (which phase, what's done, what's next), node_type: `observation`
|
|
181
|
+
|
|
182
|
+
### Rule 4: Recovery from Shadow Copies
|
|
183
|
+
|
|
184
|
+
If workflow artifacts are lost (branch switch, reset, or interrupted session):
|
|
185
|
+
|
|
186
|
+
1. Check `.claude/compound/` for shadow copies
|
|
187
|
+
2. Restore them to their working-tree locations (`docs/plans/`, etc.)
|
|
188
|
+
3. Check Vestige for the most recent workflow state snapshot
|
|
189
|
+
4. Resume from where we left off
|
|
190
|
+
|
|
191
|
+
### Rule 5: Commit `compound-engineering.local.md` Early
|
|
192
|
+
|
|
193
|
+
This file is **persistent project config**, not a transient artifact. When creating or modifying `compound-engineering.local.md`, commit it to the current branch promptly so it survives branch switches via git rather than shadow copies.
|
|
194
|
+
|
|
195
|
+
## What This Skill Does NOT Do
|
|
196
|
+
|
|
197
|
+
- Modify the Compound Engineering plugin — it stays as-is from the marketplace
|
|
198
|
+
- Create custom scripts or utilities — all integration is skill instructions
|
|
199
|
+
- Add new MCP servers — uses existing Vestige, Qdrant, Serena
|
|
200
|
+
- Force workflows — both systems remain independently functional
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "discourse"
|
|
3
|
+
description: "Discourse plugin development - plugin.rb, after_initialize, admin routes, Guardian auth, security patterns"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Discourse Plugin Development
|
|
7
|
+
|
|
8
|
+
Discourse plugins are Rails engines with conventions. Work with the framework — its plugin API, Guardian authorization system, and event hooks exist for good reason.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Building or modifying Discourse plugins
|
|
13
|
+
- Adding admin UI to a Discourse plugin
|
|
14
|
+
- Implementing authentication hooks or user lifecycle extensions
|
|
15
|
+
- Migrating data into Discourse (import scripts)
|
|
16
|
+
- Reviewing Discourse plugin security
|
|
17
|
+
|
|
18
|
+
## Core Philosophy
|
|
19
|
+
|
|
20
|
+
> Discourse plugins are Rails. Discourse adds its own authorization layer (Guardian). Respect both.
|
|
21
|
+
|
|
22
|
+
- **`after_initialize`** is where plugin logic wires into Discourse
|
|
23
|
+
- **Guardian** is the authorization layer — every action checks it
|
|
24
|
+
- **Plugin API** hooks (`register_*`, `add_*`) are the extension points — don't monkey-patch core
|
|
25
|
+
- **StaffConstraint** protects admin routes — use it, never roll your own
|
|
26
|
+
- **RuboCop** is enforced — Discourse ships lint rules
|
|
27
|
+
|
|
28
|
+
**Foundation**: Reference `../rails/SKILL.md` for Rails security practices and `../ruby-fp/SKILL.md` for Ruby patterns.
|
|
29
|
+
|
|
30
|
+
## Plugin Structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
my-plugin/
|
|
34
|
+
├── plugin.rb # Manifest + bootstrap (required)
|
|
35
|
+
├── about.json # Metadata for admin panel
|
|
36
|
+
├── app/
|
|
37
|
+
│ ├── controllers/
|
|
38
|
+
│ │ └── admin/
|
|
39
|
+
│ │ └── my_plugin_controller.rb
|
|
40
|
+
│ ├── models/
|
|
41
|
+
│ │ └── my_plugin_record.rb
|
|
42
|
+
│ └── serializers/
|
|
43
|
+
│ └── my_plugin_serializer.rb
|
|
44
|
+
├── config/
|
|
45
|
+
│ ├── locales/
|
|
46
|
+
│ │ └── server.en.yml
|
|
47
|
+
│ └── settings.yml # Site settings
|
|
48
|
+
├── db/
|
|
49
|
+
│ └── migrate/
|
|
50
|
+
│ └── 20250101000000_create_my_plugin.rb
|
|
51
|
+
├── assets/
|
|
52
|
+
│ └── javascripts/
|
|
53
|
+
│ └── admin/ # Ember components for admin UI
|
|
54
|
+
├── lib/
|
|
55
|
+
│ └── my_plugin/ # Pure logic (no Discourse deps)
|
|
56
|
+
└── spec/
|
|
57
|
+
└── plugin_helper.rb
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## plugin.rb: Manifest + Bootstrap
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# frozen_string_literal: true
|
|
64
|
+
|
|
65
|
+
# name: my-plugin
|
|
66
|
+
# about: Brief plugin description
|
|
67
|
+
# version: 1.0.0
|
|
68
|
+
# authors: Your Name
|
|
69
|
+
# url: https://github.com/yourorg/my-plugin
|
|
70
|
+
|
|
71
|
+
# Autoloading — define module first, then require engine
|
|
72
|
+
module ::MyPlugin
|
|
73
|
+
PLUGIN_NAME = "my-plugin"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
require_relative "lib/my_plugin/engine"
|
|
77
|
+
|
|
78
|
+
# All wiring happens inside after_initialize
|
|
79
|
+
after_initialize do
|
|
80
|
+
# Register settings, hooks, extensions here
|
|
81
|
+
# This runs after Discourse core is fully loaded
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## after_initialize: The Plugin Entry Point
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
after_initialize do
|
|
89
|
+
# Extend models — use class_eval in after_initialize, not top-level monkey-patches
|
|
90
|
+
User.class_eval do
|
|
91
|
+
has_one :my_plugin_profile, dependent: :destroy
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Register custom fields
|
|
95
|
+
register_post_custom_field_type('wp_original_id', :integer)
|
|
96
|
+
register_topic_custom_field_type('imported_from', :string)
|
|
97
|
+
|
|
98
|
+
# Wire into Discourse events (preferred over overriding methods)
|
|
99
|
+
on(:user_created) do |user|
|
|
100
|
+
MyPlugin::UserSetup.call(user)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
on(:post_created) do |post, opts, user|
|
|
104
|
+
MyPlugin::PostSync.call(post, user)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Add serializer extensions
|
|
108
|
+
add_to_serializer(:user, :wp_user_id) do
|
|
109
|
+
object.custom_fields['wp_user_id']
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Admin Routes + Controller
|
|
115
|
+
|
|
116
|
+
Every admin route needs `StaffConstraint` — never expose admin actions without it.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# In plugin.rb — register the admin nav link
|
|
120
|
+
add_admin_route 'my_plugin.title', 'my-plugin'
|
|
121
|
+
|
|
122
|
+
# Wire the route
|
|
123
|
+
Discourse::Application.routes.append do
|
|
124
|
+
get '/admin/plugins/my-plugin' => 'admin/my_plugin#index',
|
|
125
|
+
constraints: StaffConstraint.new
|
|
126
|
+
post '/admin/plugins/my-plugin/action' => 'admin/my_plugin#action',
|
|
127
|
+
constraints: StaffConstraint.new
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# app/controllers/admin/my_plugin_controller.rb
|
|
133
|
+
# frozen_string_literal: true
|
|
134
|
+
|
|
135
|
+
class ::Admin::MyPluginController < ::Admin::AdminController
|
|
136
|
+
# Admin::AdminController already requires:
|
|
137
|
+
# - User is logged in
|
|
138
|
+
# - User is staff (moderator or admin)
|
|
139
|
+
# For admin-only actions, add:
|
|
140
|
+
before_action :ensure_admin, only: [:dangerous_action]
|
|
141
|
+
|
|
142
|
+
def index
|
|
143
|
+
render json: {
|
|
144
|
+
stats: MyPlugin::Stats.summary,
|
|
145
|
+
settings: SiteSetting.my_plugin_enabled
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def action
|
|
150
|
+
# Strong parameters for any mutating action
|
|
151
|
+
attrs = params.require(:my_plugin).permit(:field_one, :field_two)
|
|
152
|
+
result = MyPlugin::SomeService.call(attrs.to_h.symbolize_keys)
|
|
153
|
+
|
|
154
|
+
if result.success?
|
|
155
|
+
render json: { success: true, data: result.data }
|
|
156
|
+
else
|
|
157
|
+
render json: { success: false, errors: result.errors }, status: :unprocessable_entity
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Guardian: Authorization Layer
|
|
164
|
+
|
|
165
|
+
Guardian is Discourse's authorization system. Always check it for user-facing actions.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# Check permissions before acting
|
|
169
|
+
def update_post
|
|
170
|
+
post = Post.find(params[:id])
|
|
171
|
+
|
|
172
|
+
# guardian.can_edit_post? checks ownership, trust level, staff status
|
|
173
|
+
unless guardian.can_edit_post?(post)
|
|
174
|
+
return render json: failed_json, status: :forbidden
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
post.update!(body: params[:body])
|
|
178
|
+
render json: PostSerializer.new(post, scope: guardian).as_json
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Extending Guardian for plugin-specific permissions
|
|
182
|
+
# In after_initialize:
|
|
183
|
+
module ::Guardian::MyPluginExtensions
|
|
184
|
+
def can_use_my_feature?
|
|
185
|
+
authenticated? && (is_staff? || user.trust_level >= 2)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
Guardian.prepend(::Guardian::MyPluginExtensions)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Security Non-Negotiables
|
|
193
|
+
|
|
194
|
+
### 1. Never Raw SQL with Interpolation — Use ActiveRecord or Named Params
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
# BAD — SQL injection
|
|
198
|
+
User.where("username = '#{params[:username]}'")
|
|
199
|
+
DB.query("SELECT * FROM users WHERE id = #{user_id}")
|
|
200
|
+
|
|
201
|
+
# GOOD — ActiveRecord parameterization
|
|
202
|
+
User.where(username: params[:username])
|
|
203
|
+
User.where("created_at > ?", params[:since])
|
|
204
|
+
|
|
205
|
+
# GOOD — Discourse DB helper with named bind params (always use this for raw SQL)
|
|
206
|
+
DB.query("SELECT * FROM users WHERE id = :id", id: user_id.to_i)
|
|
207
|
+
DB.query("UPDATE users SET flag = TRUE WHERE id = :id", id: user_id.to_i)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 2. Staff-Only Admin Routes
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
# ALWAYS use StaffConstraint on admin routes
|
|
214
|
+
get '/admin/plugins/my-plugin' => 'admin/my_plugin#index',
|
|
215
|
+
constraints: StaffConstraint.new
|
|
216
|
+
|
|
217
|
+
# Admin::AdminController gives you these checks automatically:
|
|
218
|
+
# - ensure_logged_in
|
|
219
|
+
# - ensure_staff
|
|
220
|
+
# For admin-only (not just moderator), add ensure_admin
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 3. No Sensitive Data in Logs
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
# BAD — logs hash values, tokens, passwords
|
|
227
|
+
Rails.logger.warn("[MyPlugin] Processing hash: #{user_hash}")
|
|
228
|
+
Rails.logger.info("[MyPlugin] Token: #{token}")
|
|
229
|
+
|
|
230
|
+
# GOOD — log what happened, not the sensitive value
|
|
231
|
+
Rails.logger.info("[MyPlugin] Processing user #{user.id}")
|
|
232
|
+
Rails.logger.warn("[MyPlugin] Auth failed for user #{user.id}")
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 4. Custom Fields — Validate Types
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# Register field types to prevent type confusion
|
|
239
|
+
register_user_custom_field_type('my_plugin_id', :integer)
|
|
240
|
+
register_post_custom_field_type('source_url', :string)
|
|
241
|
+
|
|
242
|
+
# Whitelist custom fields for API/serializer exposure
|
|
243
|
+
DiscoursePluginRegistry.serialized_current_user_fields << 'my_plugin_id'
|
|
244
|
+
|
|
245
|
+
# Access safely — always coerce type
|
|
246
|
+
user_id = (user.custom_fields['my_plugin_id'].to_i rescue nil)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 5. Rate Limiting on Sensitive Endpoints
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
# For endpoints that test credentials or perform expensive operations
|
|
253
|
+
RateLimiter.new(current_user, "my_plugin_sensitive_action", 5, 1.minute).performed!
|
|
254
|
+
# Raises RateLimiter::LimitExceeded if over limit
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Site Settings
|
|
258
|
+
|
|
259
|
+
```yaml
|
|
260
|
+
# config/settings.yml
|
|
261
|
+
plugins:
|
|
262
|
+
my_plugin_enabled:
|
|
263
|
+
default: false
|
|
264
|
+
client: false # server-side only unless UI needs it
|
|
265
|
+
my_plugin_max_items:
|
|
266
|
+
default: 100
|
|
267
|
+
min: 1
|
|
268
|
+
max: 1000
|
|
269
|
+
type: integer
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
# Access in code
|
|
274
|
+
SiteSetting.my_plugin_enabled
|
|
275
|
+
SiteSetting.my_plugin_max_items
|
|
276
|
+
|
|
277
|
+
# Guard features
|
|
278
|
+
return unless SiteSetting.my_plugin_enabled
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Migrations
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
# db/migrate/20250101000000_create_my_plugin_records.rb
|
|
285
|
+
# frozen_string_literal: true
|
|
286
|
+
|
|
287
|
+
class CreateMyPluginRecords < ActiveRecord::Migration[7.0]
|
|
288
|
+
def change
|
|
289
|
+
create_table :my_plugin_records do |t|
|
|
290
|
+
t.integer :user_id, null: false
|
|
291
|
+
t.string :source_id, null: false
|
|
292
|
+
t.text :data
|
|
293
|
+
t.timestamps
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
add_index :my_plugin_records, :user_id
|
|
297
|
+
add_index :my_plugin_records, :source_id, unique: true
|
|
298
|
+
add_foreign_key :my_plugin_records, :users
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Import Script Pattern (Standalone Ruby)
|
|
304
|
+
|
|
305
|
+
Import scripts inherit from `ImportScripts::Base` and run outside the Rails request cycle.
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
# frozen_string_literal: true
|
|
309
|
+
|
|
310
|
+
require_relative "base"
|
|
311
|
+
|
|
312
|
+
class ImportScripts::MyImport < ImportScripts::Base
|
|
313
|
+
def initialize
|
|
314
|
+
super
|
|
315
|
+
@client = Mysql2::Client.new(
|
|
316
|
+
host: ENV.fetch('SOURCE_DB_HOST'),
|
|
317
|
+
username: ENV.fetch('SOURCE_DB_USER'),
|
|
318
|
+
password: ENV.fetch('SOURCE_DB_PASSWORD'),
|
|
319
|
+
database: ENV.fetch('SOURCE_DB_NAME')
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def perform
|
|
324
|
+
import_users
|
|
325
|
+
import_categories
|
|
326
|
+
import_posts
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
private
|
|
330
|
+
|
|
331
|
+
def import_users
|
|
332
|
+
puts "Importing users..."
|
|
333
|
+
|
|
334
|
+
# Parameterized query — never interpolate
|
|
335
|
+
users = @client.query(
|
|
336
|
+
"SELECT id, email, username, display_name FROM wp_users WHERE user_status = 0",
|
|
337
|
+
as: :hash
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
create_users(users) do |user|
|
|
341
|
+
{
|
|
342
|
+
id: user['id'],
|
|
343
|
+
email: user['email'],
|
|
344
|
+
username: normalize_username(user['username']),
|
|
345
|
+
name: user['display_name']
|
|
346
|
+
}
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def normalize_username(raw)
|
|
351
|
+
# Pure function — transform only, no side effects
|
|
352
|
+
raw.to_s.strip.downcase.gsub(/[^a-z0-9_]/, '_').truncate(20)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
ImportScripts::MyImport.new.perform
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Testing
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
# spec/plugin_helper.rb — loads Discourse test env
|
|
363
|
+
require 'rails_helper'
|
|
364
|
+
|
|
365
|
+
# spec/requests/admin/my_plugin_spec.rb
|
|
366
|
+
RSpec.describe Admin::MyPluginController do
|
|
367
|
+
fab!(:admin) { Fabricate(:admin) }
|
|
368
|
+
fab!(:user) { Fabricate(:user) }
|
|
369
|
+
|
|
370
|
+
before { sign_in(admin) }
|
|
371
|
+
|
|
372
|
+
describe "GET #index" do
|
|
373
|
+
it "returns success for admin" do
|
|
374
|
+
get "/admin/plugins/my-plugin.json"
|
|
375
|
+
expect(response.status).to eq(200)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
describe "authorization" do
|
|
380
|
+
it "rejects non-staff" do
|
|
381
|
+
sign_in(user)
|
|
382
|
+
get "/admin/plugins/my-plugin.json"
|
|
383
|
+
expect(response.status).to eq(404) # Discourse returns 404 for staff routes
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Pure logic specs — no Discourse dependencies
|
|
389
|
+
RSpec.describe MyPlugin::UserSetup do
|
|
390
|
+
describe ".call" do
|
|
391
|
+
it "normalizes username" do
|
|
392
|
+
expect(described_class.normalize("John Doe!")).to eq("john_doe_")
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Security Checklist
|
|
399
|
+
|
|
400
|
+
- [ ] `StaffConstraint.new` on all admin routes
|
|
401
|
+
- [ ] Inherited from `Admin::AdminController` for admin endpoints
|
|
402
|
+
- [ ] Strong parameters on all mutating endpoints
|
|
403
|
+
- [ ] No string interpolation in SQL — use ActiveRecord or `DB.query` with `:named` params
|
|
404
|
+
- [ ] `guardian.can_*?` checked before user-facing mutations
|
|
405
|
+
- [ ] No sensitive values (tokens, hashes, passwords) in log output
|
|
406
|
+
- [ ] Rate limiting on credential-testing or expensive endpoints
|
|
407
|
+
- [ ] Custom field types registered (`register_*_custom_field_type`)
|
|
408
|
+
- [ ] Site settings used for feature flags, not hardcoded booleans
|
|
409
|
+
|
|
410
|
+
## Common Pitfalls
|
|
411
|
+
|
|
412
|
+
| Pitfall | Correct Approach |
|
|
413
|
+
|---------|-----------------|
|
|
414
|
+
| Monkey-patching Discourse classes | Use `class_eval` / `prepend` in `after_initialize` |
|
|
415
|
+
| Direct DB string interpolation | `DB.query("... WHERE id = :id", id: val)` |
|
|
416
|
+
| Checking `current_user.staff?` in controller | Inherit `Admin::AdminController` instead |
|
|
417
|
+
| Logging `user.password_hash` for debugging | Log `user.id` only |
|
|
418
|
+
| `params[:field]` without strong params | Always `params.require().permit()` |
|
|
419
|
+
| Hardcoded credentials in plugin.rb | `ENV.fetch('KEY')` or Rails credentials |
|
|
420
|
+
|
|
421
|
+
## When to Load Reference Files
|
|
422
|
+
|
|
423
|
+
### Security Examples
|
|
424
|
+
**File**: [`references/security.md`](references/security.md)
|
|
425
|
+
**Load when**: Implementing auth hooks, custom Guardian checks, SQL safety in import scripts
|
|
426
|
+
**Contains**: Guardian extension patterns, DB.query vs ActiveRecord, rate limiting, log hygiene
|
|
427
|
+
|
|
428
|
+
### Admin UI (Ember)
|
|
429
|
+
**File**: [`references/admin-ui.md`](references/admin-ui.md)
|
|
430
|
+
**Load when**: Building admin panel Ember components
|
|
431
|
+
**Contains**: Route setup, Ember component patterns, REST adapter, admin nav
|
|
432
|
+
|
|
433
|
+
### Import Scripts
|
|
434
|
+
**File**: [`references/import-scripts.md`](references/import-scripts.md)
|
|
435
|
+
**Load when**: Writing data migration scripts
|
|
436
|
+
**Contains**: ImportScripts::Base lifecycle, batching, lookup maps, resume/idempotency
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
**Evidence Base**: Discourse Developer Docs, Discourse GitHub (discourse-solved, discourse-data-explorer), Discourse CVE history (2024–2026), Rails Security Guide.
|