@mestreyoda/fabrica 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/ARCHITECTURE.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +289 -0
  4. package/defaults/AGENTS.md +150 -0
  5. package/defaults/HEARTBEAT.md +3 -0
  6. package/defaults/IDENTITY.md +6 -0
  7. package/defaults/SOUL.md +39 -0
  8. package/defaults/TOOLS.md +15 -0
  9. package/defaults/fabrica/prompts/architect.md +147 -0
  10. package/defaults/fabrica/prompts/developer.md +211 -0
  11. package/defaults/fabrica/prompts/reviewer.md +114 -0
  12. package/defaults/fabrica/prompts/security-checklist.md +58 -0
  13. package/defaults/fabrica/prompts/tester.md +150 -0
  14. package/defaults/fabrica/workflow.yaml +184 -0
  15. package/dist/index.js +143075 -0
  16. package/dist/index.js.map +7 -0
  17. package/dist/lib/worker.cjs +214 -0
  18. package/dist/worker.cjs +4754 -0
  19. package/fabrica.manifest.json +24 -0
  20. package/genesis/configs/classification-rules.json +32 -0
  21. package/genesis/configs/interview-templates.json +73 -0
  22. package/genesis/configs/labels.json +202 -0
  23. package/genesis/configs/triage-matrix.json +39 -0
  24. package/genesis/scripts/classify-idea.sh +161 -0
  25. package/genesis/scripts/conduct-interview.sh +199 -0
  26. package/genesis/scripts/create-task.sh +797 -0
  27. package/genesis/scripts/delivery-target-lib.sh +88 -0
  28. package/genesis/scripts/generate-qa-contract.sh +188 -0
  29. package/genesis/scripts/generate-spec.sh +171 -0
  30. package/genesis/scripts/genesis-telemetry.sh +97 -0
  31. package/genesis/scripts/genesis-utils.sh +617 -0
  32. package/genesis/scripts/impact-analysis.sh +135 -0
  33. package/genesis/scripts/interview.sh +98 -0
  34. package/genesis/scripts/map-project.sh +309 -0
  35. package/genesis/scripts/receive-idea.sh +69 -0
  36. package/genesis/scripts/register-project.sh +520 -0
  37. package/genesis/scripts/research-idea.sh +84 -0
  38. package/genesis/scripts/scaffold-project.sh +1396 -0
  39. package/genesis/scripts/security-review.sh +141 -0
  40. package/genesis/scripts/sideband-lib.sh +243 -0
  41. package/genesis/scripts/stack-detection-lib.sh +130 -0
  42. package/genesis/scripts/triage.sh +598 -0
  43. package/genesis/scripts/validate-step.sh +81 -0
  44. package/openclaw.plugin.json +45 -0
  45. package/package.json +60 -0
@@ -0,0 +1,24 @@
1
+ {
2
+ "layoutVersion": "fabrica-v1",
3
+ "assets": {
4
+ "defaultsDir": "defaults",
5
+ "promptsDir": "defaults/fabrica/prompts",
6
+ "securityChecklistPath": "defaults/fabrica/prompts/security-checklist.md",
7
+ "workflowPath": "defaults/fabrica/workflow.yaml",
8
+ "templatesDir": "defaults",
9
+ "genesis": {
10
+ "root": "genesis",
11
+ "scriptsDir": "genesis/scripts",
12
+ "configsDir": "genesis/configs"
13
+ },
14
+ "migrations": [
15
+ "layout",
16
+ "workflow"
17
+ ]
18
+ },
19
+ "layout": {
20
+ "primaryDataDir": "fabrica",
21
+ "legacyDataDir": "devclaw",
22
+ "versionFile": ".layout-version"
23
+ }
24
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "version": "1.0",
3
+ "default_type": "feature",
4
+ "confidence_threshold": 0.6,
5
+ "types": {
6
+ "bugfix": {
7
+ "keywords": ["bug", "fix", "erro", "error", "crash", "broken", "quebrado", "falha", "fail", "exception", "not working", "nao funciona", "não funciona", "regression", "regressão"],
8
+ "patterns": ["(?i)\\b(fix|corrig|resolv|repair|patch)\\b", "(?i)\\b(bug|erro|falha|crash|quebr)\\b"],
9
+ "weight": 1.2
10
+ },
11
+ "feature": {
12
+ "keywords": ["add", "create", "new", "implement", "build", "fazer", "criar", "adicionar", "novo", "nova", "implementar", "construir", "develop", "desenvolver", "want", "quero", "need", "preciso"],
13
+ "patterns": ["(?i)\\b(add|creat|implement|build|desenvolv|faz)\\b", "(?i)\\b(nov[oa]|new)\\b"],
14
+ "weight": 1.0
15
+ },
16
+ "refactor": {
17
+ "keywords": ["refactor", "refatorar", "cleanup", "clean up", "reorganize", "reorganizar", "simplify", "simplificar", "extract", "extrair", "rename", "renomear", "move", "mover", "split", "dividir", "merge", "consolidar"],
18
+ "patterns": ["(?i)\\b(refactor|refator|cleanup|reorganiz|simplif)\\b", "(?i)\\b(extract|split|merg|consolid)\\b"],
19
+ "weight": 0.9
20
+ },
21
+ "research": {
22
+ "keywords": ["research", "pesquisar", "investigate", "investigar", "explore", "explorar", "evaluate", "avaliar", "compare", "comparar", "spike", "poc", "proof of concept", "prova de conceito", "prototype", "protótipo", "study", "estudar"],
23
+ "patterns": ["(?i)\\b(research|pesquis|investigat|explor|avaliar|compar)\\b", "(?i)\\b(spike|poc|proof.of.concept|prova.de.conceito|protot)\\b"],
24
+ "weight": 0.8
25
+ },
26
+ "infra": {
27
+ "keywords": ["deploy", "ci", "cd", "pipeline", "docker", "kubernetes", "k8s", "terraform", "ansible", "monitoring", "monitoramento", "logging", "infrastructure", "infraestrutura", "devops", "server", "servidor", "database", "banco de dados", "migration", "migração", "config", "configuração", "env", "environment", "ambiente"],
28
+ "patterns": ["(?i)\\b(deploy|docker|k8s|kubernetes|terraform|ansible|ci.?cd)\\b", "(?i)\\b(infra|devops|pipeline|migration|migra[çc])\\b"],
29
+ "weight": 0.9
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "version": "1.0",
3
+ "max_rounds": 2,
4
+ "types": {
5
+ "feature": {
6
+ "round1": [
7
+ { "id": "f1", "question": "Qual problema específico essa feature resolve para o usuário?", "required": true, "follow_up_if_vague": "Pode dar um exemplo concreto de quando o usuário sentiria falta dessa funcionalidade?" },
8
+ { "id": "f2", "question": "Quem vai usar isso? Descreva o perfil do usuário principal.", "required": true, "follow_up_if_vague": "É um usuário final, admin, desenvolvedor, ou outro perfil?" },
9
+ { "id": "f3", "question": "Qual é o fluxo principal? O que o usuário faz passo a passo?", "required": true, "follow_up_if_vague": "Tente descrever: 'O usuário clica em X, preenche Y, vê Z'." },
10
+ { "id": "f4", "question": "Existe alguma restrição de tecnologia, prazo ou compatibilidade?", "required": false, "follow_up_if_vague": null },
11
+ { "id": "f5", "question": "Esse fluxo exige login? Se sim, quais perfis existem e quais permissões cada perfil deve ter?", "required": false, "follow_up_if_vague": "Descreva pelo menos um exemplo de ação permitida e uma ação bloqueada por perfil." }
12
+ ],
13
+ "technical_additions": [
14
+ { "id": "ft1", "question": "Quais APIs ou serviços externos essa feature precisa consumir?", "required": false },
15
+ { "id": "ft2", "question": "Há requisitos de performance (latência, throughput)?", "required": false },
16
+ { "id": "ft3", "question": "Qual o modelo de dados? Precisa de novas tabelas/coleções?", "required": false }
17
+ ],
18
+ "non_technical_additions": [
19
+ { "id": "fn1", "question": "Como você saberia que essa feature está funcionando bem? O que muda na sua rotina?", "required": false }
20
+ ]
21
+ },
22
+ "bugfix": {
23
+ "round1": [
24
+ { "id": "b1", "question": "O que está acontecendo de errado? Descreva o comportamento atual.", "required": true, "follow_up_if_vague": "Você vê alguma mensagem de erro? O que aparece na tela?" },
25
+ { "id": "b2", "question": "O que deveria acontecer? Qual o comportamento esperado?", "required": true, "follow_up_if_vague": "Antes funcionava corretamente? Quando parou?" },
26
+ { "id": "b3", "question": "Como reproduzir o bug? Passos exatos.", "required": true, "follow_up_if_vague": "Em qual página/tela? Com qual tipo de dado?" },
27
+ { "id": "b4", "question": "Com que frequência acontece? Sempre, às vezes, em condições específicas?", "required": false, "follow_up_if_vague": null }
28
+ ],
29
+ "technical_additions": [
30
+ { "id": "bt1", "question": "Em qual ambiente ocorre? (produção, staging, local)", "required": false },
31
+ { "id": "bt2", "question": "Tem logs ou stack trace disponível?", "required": false }
32
+ ],
33
+ "non_technical_additions": [
34
+ { "id": "bn1", "question": "Quantas pessoas são afetadas por esse problema?", "required": false }
35
+ ]
36
+ },
37
+ "refactor": {
38
+ "round1": [
39
+ { "id": "r1", "question": "Qual parte do código precisa ser refatorada? Qual módulo/arquivo?", "required": true, "follow_up_if_vague": "Qual funcionalidade está nesse código?" },
40
+ { "id": "r2", "question": "Qual o problema atual? (duplicação, complexidade, acoplamento, performance)", "required": true, "follow_up_if_vague": "O que dificulta trabalhar nesse código hoje?" },
41
+ { "id": "r3", "question": "Qual o resultado esperado da refatoração?", "required": true, "follow_up_if_vague": "Quer que fique mais legível, mais rápido, ou mais testável?" }
42
+ ],
43
+ "technical_additions": [
44
+ { "id": "rt1", "question": "A refatoração pode mudar interfaces públicas/APIs?", "required": false },
45
+ { "id": "rt2", "question": "Há testes existentes cobrindo esse código?", "required": false }
46
+ ],
47
+ "non_technical_additions": []
48
+ },
49
+ "research": {
50
+ "round1": [
51
+ { "id": "s1", "question": "O que precisa ser investigado ou avaliado?", "required": true, "follow_up_if_vague": "É uma tecnologia, abordagem, ou viabilidade de algo?" },
52
+ { "id": "s2", "question": "Qual decisão essa pesquisa vai informar?", "required": true, "follow_up_if_vague": "O que muda se a conclusão for positiva vs negativa?" },
53
+ { "id": "s3", "question": "Quais critérios definem sucesso da pesquisa?", "required": true, "follow_up_if_vague": "Qual entregável esperado? Documento, PoC, comparativo?" }
54
+ ],
55
+ "technical_additions": [
56
+ { "id": "st1", "question": "Há restrições de stack ou compatibilidade a considerar?", "required": false }
57
+ ],
58
+ "non_technical_additions": []
59
+ },
60
+ "infra": {
61
+ "round1": [
62
+ { "id": "i1", "question": "O que precisa ser configurado/implantado/migrado?", "required": true, "follow_up_if_vague": "É CI/CD, deploy, banco de dados, monitoramento?" },
63
+ { "id": "i2", "question": "Qual o ambiente alvo? (produção, staging, dev, local)", "required": true, "follow_up_if_vague": null },
64
+ { "id": "i3", "question": "Há requisitos de downtime, rollback ou compatibilidade?", "required": false, "follow_up_if_vague": null }
65
+ ],
66
+ "technical_additions": [
67
+ { "id": "it1", "question": "Quais serviços/ferramentas estão envolvidos?", "required": false },
68
+ { "id": "it2", "question": "Há secrets ou credenciais que precisam ser provisionadas?", "required": false }
69
+ ],
70
+ "non_technical_additions": []
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,202 @@
1
+ [
2
+ {
3
+ "name": "Planning",
4
+ "color": "95a5a6",
5
+ "description": "Issue in planning phase"
6
+ },
7
+ {
8
+ "name": "To Do",
9
+ "color": "428bca",
10
+ "description": "Ready for development"
11
+ },
12
+ {
13
+ "name": "Doing",
14
+ "color": "f0ad4e",
15
+ "description": "In progress"
16
+ },
17
+ {
18
+ "name": "To Review",
19
+ "color": "7057ff",
20
+ "description": "Awaiting code review"
21
+ },
22
+ {
23
+ "name": "Reviewing",
24
+ "color": "c5def5",
25
+ "description": "Review in progress"
26
+ },
27
+ {
28
+ "name": "To Test",
29
+ "color": "5bc0de",
30
+ "description": "Awaiting QA testing"
31
+ },
32
+ {
33
+ "name": "Testing",
34
+ "color": "9b59b6",
35
+ "description": "Testing in progress"
36
+ },
37
+ {
38
+ "name": "Done",
39
+ "color": "5cb85c",
40
+ "description": "Completed"
41
+ },
42
+ {
43
+ "name": "Rejected",
44
+ "color": "e11d48",
45
+ "description": "Rejected"
46
+ },
47
+ {
48
+ "name": "To Improve",
49
+ "color": "d9534f",
50
+ "description": "Changes requested"
51
+ },
52
+ {
53
+ "name": "Refining",
54
+ "color": "f39c12",
55
+ "description": "Needs refinement"
56
+ },
57
+ {
58
+ "name": "To Research",
59
+ "color": "0075ca",
60
+ "description": "Needs investigation"
61
+ },
62
+ {
63
+ "name": "Researching",
64
+ "color": "4a90e2",
65
+ "description": "Research in progress"
66
+ },
67
+ {
68
+ "name": "approved",
69
+ "color": "0e8a16",
70
+ "description": "Approved for dispatch"
71
+ },
72
+ {
73
+ "name": "review:agent",
74
+ "color": "d93f0b",
75
+ "description": "Review by agent"
76
+ },
77
+ {
78
+ "name": "review:human",
79
+ "color": "d93f0b",
80
+ "description": "Review by human"
81
+ },
82
+ {
83
+ "name": "review:skip",
84
+ "color": "d93f0b",
85
+ "description": "Skip review"
86
+ },
87
+ {
88
+ "name": "test:skip",
89
+ "color": "d93f0b",
90
+ "description": "Skip testing"
91
+ },
92
+ {
93
+ "name": "type:feature",
94
+ "color": "0E8A16",
95
+ "description": "Feature request"
96
+ },
97
+ {
98
+ "name": "type:bugfix",
99
+ "color": "D93F0B",
100
+ "description": "Bug fix"
101
+ },
102
+ {
103
+ "name": "type:refactor",
104
+ "color": "FBCA04",
105
+ "description": "Code refactoring"
106
+ },
107
+ {
108
+ "name": "type:research",
109
+ "color": "0075CA",
110
+ "description": "Research/investigation"
111
+ },
112
+ {
113
+ "name": "type:infra",
114
+ "color": "5319E7",
115
+ "description": "Infrastructure/DevOps"
116
+ },
117
+ {
118
+ "name": "priority:critical",
119
+ "color": "B60205",
120
+ "description": "P0 - Critical"
121
+ },
122
+ {
123
+ "name": "priority:high",
124
+ "color": "D93F0B",
125
+ "description": "P1 - High"
126
+ },
127
+ {
128
+ "name": "priority:medium",
129
+ "color": "FBCA04",
130
+ "description": "P2 - Medium"
131
+ },
132
+ {
133
+ "name": "priority:normal",
134
+ "color": "0E8A16",
135
+ "description": "P3 - Normal"
136
+ },
137
+ {
138
+ "name": "effort:small",
139
+ "color": "C2E0C6",
140
+ "description": "≤3 files, ≤3 ACs"
141
+ },
142
+ {
143
+ "name": "effort:medium",
144
+ "color": "FEF2C0",
145
+ "description": "≤10 files, ≤7 ACs"
146
+ },
147
+ {
148
+ "name": "effort:large",
149
+ "color": "F9D0C4",
150
+ "description": "≤25 files, ≤15 ACs"
151
+ },
152
+ {
153
+ "name": "developer:junior",
154
+ "color": "0e8a16",
155
+ "description": ""
156
+ },
157
+ {
158
+ "name": "developer:medior",
159
+ "color": "0e8a16",
160
+ "description": ""
161
+ },
162
+ {
163
+ "name": "developer:senior",
164
+ "color": "0e8a16",
165
+ "description": ""
166
+ },
167
+ {
168
+ "name": "reviewer:junior",
169
+ "color": "d93f0b",
170
+ "description": ""
171
+ },
172
+ {
173
+ "name": "reviewer:senior",
174
+ "color": "d93f0b",
175
+ "description": ""
176
+ },
177
+ {
178
+ "name": "tester:junior",
179
+ "color": "5319e7",
180
+ "description": ""
181
+ },
182
+ {
183
+ "name": "tester:medior",
184
+ "color": "5319e7",
185
+ "description": ""
186
+ },
187
+ {
188
+ "name": "tester:senior",
189
+ "color": "5319e7",
190
+ "description": ""
191
+ },
192
+ {
193
+ "name": "needs-human",
194
+ "color": "d73a4a",
195
+ "description": "Requires human intervention"
196
+ },
197
+ {
198
+ "name": "effort:xlarge",
199
+ "color": "BFD4F2",
200
+ "description": ">25 files or >15 ACs"
201
+ }
202
+ ]
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": "1.0",
3
+ "priority_rules": [
4
+ { "condition": "type == 'bugfix' && risk_count > 2", "priority": "P0", "label": "priority:critical" },
5
+ { "condition": "type == 'bugfix'", "priority": "P1", "label": "priority:high" },
6
+ { "condition": "type == 'feature' && effort == 'small'", "priority": "P2", "label": "priority:medium" },
7
+ { "condition": "type == 'feature'", "priority": "P3", "label": "priority:normal" },
8
+ { "condition": "type == 'refactor'", "priority": "P3", "label": "priority:normal" },
9
+ { "condition": "type == 'infra'", "priority": "P2", "label": "priority:medium" },
10
+ { "condition": "type == 'research'", "priority": "P3", "label": "priority:normal" }
11
+ ],
12
+ "priority_rules_v2": [
13
+ { "when": { "type": "bugfix", "min_risk_count": 3 }, "priority": "P0", "label": "priority:critical" },
14
+ { "when": { "type": "bugfix" }, "priority": "P1", "label": "priority:high" },
15
+ { "when": { "type": "feature", "effort": "small" }, "priority": "P2", "label": "priority:medium" },
16
+ { "when": { "type": "feature" }, "priority": "P3", "label": "priority:normal" },
17
+ { "when": { "type": "refactor" }, "priority": "P3", "label": "priority:normal" },
18
+ { "when": { "type": "infra" }, "priority": "P2", "label": "priority:medium" },
19
+ { "when": { "type": "research" }, "priority": "P3", "label": "priority:normal" }
20
+ ],
21
+ "effort_rules": {
22
+ "small": { "max_files": 3, "max_acs": 3, "label": "effort:small" },
23
+ "medium": { "max_files": 10, "max_acs": 7, "label": "effort:medium" },
24
+ "large": { "max_files": 25, "max_acs": 15, "label": "effort:large" },
25
+ "xlarge": { "max_files": 999, "max_acs": 999, "label": "effort:xlarge" }
26
+ },
27
+ "auto_labels": {
28
+ "feature": "type:feature",
29
+ "bugfix": "type:bugfix",
30
+ "refactor": "type:refactor",
31
+ "research": "type:research",
32
+ "infra": "type:infra"
33
+ },
34
+ "target_state_by_type": {
35
+ "default": "To Do",
36
+ "research": "To Research"
37
+ },
38
+ "dispatch_label": "approved"
39
+ }
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Step 2: Classify idea type
5
+ # Input: stdin JSON (from receive-idea.sh)
6
+ # Output: JSON with classification to stdout
7
+
8
+ GENESIS_LOG="${GENESIS_LOG:-$HOME/.openclaw/workspace/logs/genesis.log}"
9
+ mkdir -p "$(dirname "$GENESIS_LOG")"
10
+ exec 2> >(tee -a "$GENESIS_LOG" >&2)
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ RULES="$SCRIPT_DIR/../configs/classification-rules.json"
14
+ source "$SCRIPT_DIR/delivery-target-lib.sh"
15
+
16
+ score_gt_zero() {
17
+ local value="${1:-0}"
18
+ [[ "$(echo "$value > 0" | bc 2>/dev/null || echo 0)" == "1" ]]
19
+ }
20
+
21
+ if [[ -n "${1:-}" && -f "${1:-}" ]]; then
22
+ INPUT="$(cat "$1")"
23
+ else
24
+ INPUT="$(cat)"
25
+ fi
26
+ SESSION_ID="$(echo "$INPUT" | jq -r '.session_id')"
27
+ echo "=== $(date -Iseconds) | classify-idea.sh | session=$SESSION_ID ===" >&2
28
+ RAW_IDEA="$(echo "$INPUT" | jq -r '.raw_idea')"
29
+ METADATA="$(echo "$INPUT" | jq '.metadata // {}')"
30
+ THRESHOLD="$(jq -r '.confidence_threshold' "$RULES")"
31
+ DEFAULT_TYPE="$(jq -r '.default_type' "$RULES")"
32
+
33
+ echo "Classifying idea for session $SESSION_ID..." >&2
34
+
35
+ # === Try LLM-based classification first (via openclaw agent --local) ===
36
+ LLM_CLASSIFICATION=""
37
+ if command -v openclaw &>/dev/null; then
38
+ echo "Trying LLM-based classification..." >&2
39
+ VALID_TYPES="$(jq -r '.types | keys | join(", ")' "$RULES")"
40
+ LLM_PROMPT="Classify this software project idea into exactly one type.
41
+
42
+ Valid types: $VALID_TYPES
43
+
44
+ Idea: $RAW_IDEA
45
+
46
+ Return ONLY valid JSON (no markdown fences, no explanation):
47
+ {\"type\": \"<one of: $VALID_TYPES>\", \"confidence\": <0.0-1.0>, \"reasoning\": \"<1 sentence>\"}"
48
+
49
+ LLM_RAW="$(timeout 60 openclaw agent --local \
50
+ -m "$LLM_PROMPT" \
51
+ --session-id "genesis-classify-${SESSION_ID}" \
52
+ --json 2>&1)" || echo "[classify-idea] LLM call failed (exit $?)" >&2
53
+
54
+ if [[ -n "$LLM_RAW" ]]; then
55
+ LLM_TEXT="$(echo "$LLM_RAW" | jq -r '.payloads[0].text // empty' 2>/dev/null || true)"
56
+ LLM_TEXT="$(echo "$LLM_TEXT" | sed '/^```/d; /^json$/d')"
57
+ # Validate: must have type field with a known type
58
+ LLM_TYPE="$(echo "$LLM_TEXT" | jq -r '.type // empty' 2>/dev/null || true)"
59
+ if [[ -n "$LLM_TYPE" ]] && jq -e --arg t "$LLM_TYPE" '.types[$t]' "$RULES" &>/dev/null; then
60
+ LLM_CLASSIFICATION="$LLM_TEXT"
61
+ echo "LLM classification: $LLM_TYPE" >&2
62
+ fi
63
+ fi
64
+ fi
65
+
66
+ # === LLM classification succeeded ===
67
+ if [[ -n "$LLM_CLASSIFICATION" ]]; then
68
+ best_type="$(echo "$LLM_CLASSIFICATION" | jq -r '.type')"
69
+ confidence="$(echo "$LLM_CLASSIFICATION" | jq -r '.confidence // 0.85')"
70
+ reasoning="$(echo "$LLM_CLASSIFICATION" | jq -r '.reasoning // "LLM-based classification"')"
71
+ reasoning="[LLM] $reasoning"
72
+ alternatives="[]"
73
+ else
74
+ # === Fallback: keyword/pattern matching ===
75
+ echo "LLM unavailable, using keyword-based classification..." >&2
76
+
77
+ IDEA_LOWER="$(echo "$RAW_IDEA" | tr '[:upper:]' '[:lower:]')"
78
+
79
+ best_type="$DEFAULT_TYPE"
80
+ best_score=0
81
+ results="[]"
82
+
83
+ for type in $(jq -r '.types | keys[]' "$RULES"); do
84
+ score=0
85
+ weight="$(jq -r ".types.\"$type\".weight" "$RULES")"
86
+
87
+ # Keyword matching
88
+ while IFS= read -r kw; do
89
+ if [[ "$IDEA_LOWER" == *"$kw"* ]]; then
90
+ score=$((score + 1))
91
+ fi
92
+ done < <(jq -r ".types.\"$type\".keywords[]" "$RULES")
93
+
94
+ # Pattern matching
95
+ while IFS= read -r pat; do
96
+ if echo "$RAW_IDEA" | grep -qP "$pat" 2>/dev/null; then
97
+ score=$((score + 2))
98
+ fi
99
+ done < <(jq -r ".types.\"$type\".patterns[]" "$RULES")
100
+
101
+ # Apply weight
102
+ weighted="$(echo "$score * $weight" | bc 2>/dev/null || echo "$score")"
103
+
104
+ results="$(echo "$results" | jq --arg t "$type" --argjson s "$score" '. + [{"type": $t, "raw_score": $s}]')"
105
+
106
+ if [[ "$(echo "$weighted > $best_score" | bc 2>/dev/null || echo 0)" == "1" ]] || \
107
+ { [[ "$score" -gt 0 ]] && [[ "$best_score" == "0" ]]; }; then
108
+ best_score="$weighted"
109
+ best_type="$type"
110
+ fi
111
+ done
112
+
113
+ # Calculate confidence
114
+ if score_gt_zero "$best_score"; then
115
+ confidence="$(echo "scale=2; $best_score / ($best_score + 2)" | bc 2>/dev/null || echo "0.50")"
116
+ else
117
+ confidence="0.30"
118
+ fi
119
+
120
+ # Build alternatives
121
+ alternatives="$(echo "$results" | jq --arg best "$best_type" '[.[] | select(.type != $best and .raw_score > 0) | {type: .type, confidence: ((.raw_score / (.raw_score + 3)) * 100 | floor / 100)}]')"
122
+
123
+ if score_gt_zero "$best_score"; then
124
+ reasoning="[Keywords] Classified as '$best_type' based on $best_score keyword/pattern matches."
125
+ else
126
+ reasoning="[Keywords] No strong signals found. Defaulting to '$DEFAULT_TYPE'."
127
+ fi
128
+ fi
129
+
130
+ DELIVERY_TARGET_RAW="$(echo "$INPUT" | jq -r '.metadata.delivery_target // empty')"
131
+ if [[ -z "$DELIVERY_TARGET_RAW" || "$DELIVERY_TARGET_RAW" == "null" ]]; then
132
+ DELIVERY_TARGET="$(genesis_detect_delivery_target_from_text "$RAW_IDEA")"
133
+ else
134
+ DELIVERY_TARGET="$(genesis_normalize_delivery_target "$DELIVERY_TARGET_RAW")"
135
+ fi
136
+
137
+ echo "Result: $best_type (confidence: $confidence)" >&2
138
+ echo "Delivery target: $DELIVERY_TARGET" >&2
139
+
140
+ jq -n \
141
+ --arg sid "$SESSION_ID" \
142
+ --arg idea "$RAW_IDEA" \
143
+ --arg type "$best_type" \
144
+ --argjson conf "$confidence" \
145
+ --argjson alts "$alternatives" \
146
+ --arg reasoning "$reasoning" \
147
+ --arg delivery_target "$DELIVERY_TARGET" \
148
+ --argjson meta "$METADATA" \
149
+ '{
150
+ session_id: $sid,
151
+ step: "classify",
152
+ raw_idea: $idea,
153
+ classification: {
154
+ type: $type,
155
+ confidence: $conf,
156
+ alternatives: $alts,
157
+ reasoning: $reasoning,
158
+ delivery_target: $delivery_target
159
+ },
160
+ metadata: $meta
161
+ }'