@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,141 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Step 8: Security review — run SecureClaw audit + pattern matching on spec
5
+ # Input: stdin JSON (session state)
6
+ # Output: JSON with security findings to stdout
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ SECURECLAW_AUDIT="$HOME/.openclaw/extensions/secureclaw/skill/scripts/quick-audit.sh"
10
+
11
+ if [[ -n "${1:-}" && -f "${1:-}" ]]; then
12
+ INPUT="$(cat "$1")"
13
+ else
14
+ INPUT="$(cat)"
15
+ fi
16
+ SESSION_ID="$(echo "$INPUT" | jq -r '.session_id')"
17
+ SPEC="$(echo "$INPUT" | jq '.spec // {}')"
18
+ METADATA="$(echo "$INPUT" | jq '.metadata // {}')"
19
+ CLASSIFICATION="$(echo "$INPUT" | jq '.classification // {}')"
20
+ INTERVIEW="$(echo "$INPUT" | jq '.interview // {}')"
21
+ IMPACT="$(echo "$INPUT" | jq '.impact // {}')"
22
+ QA_CONTRACT="$(echo "$INPUT" | jq '.qa_contract // {}')"
23
+ PROJECT_MAP="$(echo "$INPUT" | jq '.project_map // {}')"
24
+ SCAFFOLD="$(echo "$INPUT" | jq '.scaffold // {}')"
25
+
26
+ echo "Running security review for session $SESSION_ID..." >&2
27
+
28
+ # Run SecureClaw audit if available
29
+ AUDIT_RAN=false
30
+ AUDIT_SCORE=0
31
+ AUDIT_FINDINGS="[]"
32
+
33
+ if [[ -f "$SECURECLAW_AUDIT" ]]; then
34
+ echo "Running SecureClaw quick-audit..." >&2
35
+ AUDIT_OUTPUT="$(bash "$SECURECLAW_AUDIT" 2>&1 || true)"
36
+ AUDIT_RAN=true
37
+
38
+ # Extract score
39
+ AUDIT_SCORE="$(echo "$AUDIT_OUTPUT" | grep -oP 'Security Score: \K[0-9]+' || echo "0")"
40
+
41
+ # Extract findings (FAIL lines)
42
+ while IFS= read -r line; do
43
+ [[ -z "$line" ]] && continue
44
+ AUDIT_FINDINGS="$(echo "$AUDIT_FINDINGS" | jq --arg f "$line" '. + [$f]')"
45
+ done < <(echo "$AUDIT_OUTPUT" | grep -E '🔴|🟠|🟡' | sed 's/^[[:space:]]*//' || true)
46
+
47
+ echo "SecureClaw score: $AUDIT_SCORE/100" >&2
48
+ else
49
+ echo "SecureClaw not installed — skipping audit" >&2
50
+ fi
51
+
52
+ # Pattern matching on spec for security concerns
53
+ SPEC_TEXT="$(echo "$SPEC" | jq -r '[.title, .objective, (.scope_v1 // [] | .[]), (.acceptance_criteria // [] | .[])] | join(" ")' | tr '[:upper:]' '[:lower:]')"
54
+ AUTH_SIGNAL_FROM_META="$(echo "$INPUT" | jq -r '.metadata.auth_gate.signal // false')"
55
+ AUTH_EVIDENCE_FROM_META="$(echo "$INPUT" | jq -r '.metadata.auth_gate.evidence // false')"
56
+
57
+ SECURITY_NOTES="[]"
58
+
59
+ # Check for common security-sensitive patterns
60
+ declare -A PATTERNS=(
61
+ ["auth"]="Authentication/authorization detected — ensure proper session management, password hashing, and token validation"
62
+ ["login"]="Login flow detected — protect against brute force, credential stuffing, and session fixation"
63
+ ["password"]="Password handling detected — use bcrypt/argon2, never store plaintext"
64
+ ["api"]="API detected — validate all inputs, implement rate limiting, use HTTPS"
65
+ ["database"]="Database access detected — use parameterized queries, prevent SQL injection"
66
+ ["banco de dados"]="Database access detected — use parameterized queries, prevent SQL injection"
67
+ ["upload"]="File upload detected — validate file types, limit size, scan for malware"
68
+ ["payment"]="Payment processing detected — PCI DSS compliance required"
69
+ ["pagamento"]="Payment processing detected — PCI DSS compliance required"
70
+ ["email"]="Email handling detected — sanitize inputs, prevent header injection"
71
+ ["jwt"]="JWT detected — validate signatures, check expiration, use strong secrets"
72
+ ["token"]="Token handling detected — secure storage, rotation policy, revocation"
73
+ ["admin"]="Admin functionality detected — enforce RBAC, audit logging"
74
+ ["webhook"]="Webhook detected — validate signatures, implement replay protection"
75
+ ["secret"]="Secrets handling detected — use env vars or vault, never hardcode"
76
+ ["encrypt"]="Encryption detected — use standard libraries, proper key management"
77
+ ["criptograf"]="Encryption detected — use standard libraries, proper key management"
78
+ )
79
+
80
+ for pattern in "${!PATTERNS[@]}"; do
81
+ if [[ "$SPEC_TEXT" == *"$pattern"* ]]; then
82
+ SECURITY_NOTES="$(echo "$SECURITY_NOTES" | jq --arg n "${PATTERNS[$pattern]}" '. + [$n]')"
83
+ fi
84
+ done
85
+
86
+ if [[ "$AUTH_SIGNAL_FROM_META" == "true" ]]; then
87
+ SECURITY_NOTES="$(echo "$SECURITY_NOTES" | jq '. + ["Auth/perfil requirement detected from intake context. Keep authz/authn checks mandatory in implementation and review."]')"
88
+ if [[ "$AUTH_EVIDENCE_FROM_META" != "true" ]]; then
89
+ SECURITY_NOTES="$(echo "$SECURITY_NOTES" | jq '. + ["Potential contract drift: auth signal detected without explicit acceptance evidence in spec."]')"
90
+ fi
91
+ fi
92
+
93
+ NOTE_COUNT="$(echo "$SECURITY_NOTES" | jq 'length')"
94
+
95
+ # Recommendation
96
+ if [[ "$AUTH_SIGNAL_FROM_META" == "true" && "$AUTH_EVIDENCE_FROM_META" != "true" ]]; then
97
+ RECOMMENDATION="HIGH security sensitivity — auth requirements appear incomplete and must be reconciled before dispatch"
98
+ elif [[ "$NOTE_COUNT" -gt 3 ]]; then
99
+ RECOMMENDATION="HIGH security sensitivity — require security-focused code review and penetration testing before release"
100
+ elif [[ "$NOTE_COUNT" -gt 0 ]]; then
101
+ RECOMMENDATION="MODERATE security sensitivity — standard security review during code review phase"
102
+ else
103
+ RECOMMENDATION="LOW security sensitivity — standard QA gates should suffice"
104
+ fi
105
+
106
+ echo "Security notes: $NOTE_COUNT concerns found. $RECOMMENDATION" >&2
107
+
108
+ jq -n \
109
+ --arg sid "$SESSION_ID" \
110
+ --argjson audit_ran "$AUDIT_RAN" \
111
+ --argjson score "$AUDIT_SCORE" \
112
+ --argjson findings "$AUDIT_FINDINGS" \
113
+ --argjson notes "$SECURITY_NOTES" \
114
+ --arg rec "$RECOMMENDATION" \
115
+ --argjson spec "$SPEC" \
116
+ --argjson cls "$CLASSIFICATION" \
117
+ --argjson interview "$INTERVIEW" \
118
+ --argjson impact "$IMPACT" \
119
+ --argjson qa "$QA_CONTRACT" \
120
+ --argjson map "$PROJECT_MAP" \
121
+ --argjson scaffold "$SCAFFOLD" \
122
+ --argjson meta "$METADATA" \
123
+ '{
124
+ session_id: $sid,
125
+ step: "security",
126
+ security: {
127
+ audit_ran: $audit_ran,
128
+ score: $score,
129
+ findings: $findings,
130
+ spec_security_notes: $notes,
131
+ recommendation: $rec
132
+ },
133
+ spec: $spec,
134
+ classification: $cls,
135
+ interview: $interview,
136
+ impact: $impact,
137
+ qa_contract: $qa,
138
+ project_map: $map,
139
+ scaffold: $scaffold,
140
+ metadata: $meta
141
+ }'
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # sideband-lib.sh — Genesis sideband IPC + shared utilities
4
+ # Now delegates utility functions to genesis-utils.sh.
5
+ # Existing callers that `source sideband-lib.sh` get all functions
6
+ # (utils + sideband IPC) for backward compatibility.
7
+ # shellcheck shell=bash
8
+
9
+ if [ -z "${BASH_VERSION:-}" ]; then
10
+ echo "ERROR: sideband-lib.sh requires bash." >&2
11
+ return 1 2>/dev/null || exit 1
12
+ fi
13
+
14
+ set -euo pipefail
15
+
16
+ # Load shared utilities (categories A-E: parsing, project queries, CLI, factory logic)
17
+ _SIDEBAND_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ source "$_SIDEBAND_LIB_DIR/genesis-utils.sh"
19
+
20
+ # === Category C: Sideband IPC Protocol (Secure Envelope Exchange) ===
21
+
22
+ genesis_sideband_dir() {
23
+ local dir="${GENESIS_SIDEBAND_DIR:-$HOME/.openclaw/workspace/devclaw/sideband}"
24
+
25
+ if [[ -L "$dir" ]]; then
26
+ echo "ERROR: Sideband dir must not be a symlink: $dir" >&2
27
+ return 1
28
+ fi
29
+ if [[ -e "$dir" && ! -d "$dir" ]]; then
30
+ echo "ERROR: Sideband path is not a directory: $dir" >&2
31
+ return 1
32
+ fi
33
+
34
+ if ! (umask 077 && mkdir -p "$dir"); then
35
+ echo "ERROR: Failed to create sideband dir: $dir" >&2
36
+ return 1
37
+ fi
38
+ chmod 700 "$dir" 2>/dev/null || true
39
+ if [[ ! -d "$dir" || -L "$dir" || ! -O "$dir" ]]; then
40
+ echo "ERROR: Sideband dir is not secure: $dir" >&2
41
+ return 1
42
+ fi
43
+
44
+ printf '%s\n' "$dir"
45
+ }
46
+
47
+ genesis_sideband_secret() {
48
+ local session_id="${1:-}"
49
+ local secret="${GENESIS_SIDEBAND_SECRET:-${OPENCLAW_SIDEBAND_SECRET:-}}"
50
+
51
+ if [[ -z "$secret" ]]; then
52
+ local dir secret_file
53
+ dir="$(genesis_sideband_dir)"
54
+ secret_file="$dir/.genesis-sideband-secret"
55
+
56
+ if [[ -f "$secret_file" ]]; then
57
+ if [[ ! -O "$secret_file" || -L "$secret_file" ]]; then
58
+ echo "ERROR: Sideband secret file is not secure: $secret_file" >&2
59
+ return 1
60
+ fi
61
+ secret="$(head -n 1 "$secret_file" 2>/dev/null || true)"
62
+ fi
63
+
64
+ if [[ -z "$secret" ]]; then
65
+ secret="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
66
+ if [[ -z "$secret" ]]; then
67
+ secret="$(hostname)-$(id -u)-${GENESIS_RUN_ID:-$session_id}"
68
+ fi
69
+ if ! (umask 077 && printf '%s\n' "$secret" > "$secret_file"); then
70
+ echo "WARNING: Failed to persist sideband secret file; using in-memory secret" >&2
71
+ fi
72
+ chmod 600 "$secret_file" 2>/dev/null || true
73
+ fi
74
+ fi
75
+
76
+ if [[ -z "$secret" ]]; then
77
+ secret="$(hostname)-$(id -u)-${GENESIS_RUN_ID:-$session_id}"
78
+ fi
79
+
80
+ printf '%s\n' "$secret"
81
+ }
82
+
83
+ genesis_sideband_key() {
84
+ local session_id="${1:-}"
85
+ local seed=""
86
+
87
+ # Use session_id as canonical key to keep sideband stable across workflow steps.
88
+ # GENESIS_RUN_ID may vary depending on runner internals and must not break reads.
89
+ if [[ -n "$session_id" ]]; then
90
+ seed="$session_id"
91
+ else
92
+ seed="${GENESIS_RUN_ID:-}"
93
+ fi
94
+
95
+ if [[ -z "$seed" ]]; then
96
+ echo "ERROR: Missing session seed for sideband key" >&2
97
+ return 1
98
+ fi
99
+
100
+ printf '%s' "$seed" | sha256sum | cut -c1-24
101
+ }
102
+
103
+ genesis_sideband_signature() {
104
+ local session_id="$1"
105
+ local key="$2"
106
+ local nonce="$3"
107
+ local created_at="$4"
108
+ local payload_json="$5"
109
+ local secret
110
+ secret="$(genesis_sideband_secret "$session_id")"
111
+ printf '%s' "${secret}|${key}|${nonce}|${created_at}|${payload_json}" | sha256sum | cut -d' ' -f1
112
+ }
113
+
114
+ genesis_sideband_write() {
115
+ local kind="$1"
116
+ local session_id="$2"
117
+ local payload_json="$3"
118
+
119
+ local dir key nonce created_at signature tmp_path
120
+ if [[ -z "$kind" || -z "$session_id" || -z "$payload_json" ]]; then
121
+ echo "ERROR: genesis_sideband_write requires kind, session_id, and payload" >&2
122
+ return 1
123
+ fi
124
+
125
+ dir="$(genesis_sideband_dir)"
126
+ key="$(genesis_sideband_key "$session_id")"
127
+ nonce="$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')"
128
+ created_at="$(date +%s)"
129
+ signature="$(genesis_sideband_signature "$session_id" "$key" "$nonce" "$created_at" "$payload_json")"
130
+
131
+ tmp_path="$(mktemp "$dir/genesis-${key}-${kind}-${nonce}-XXXXXX.json")"
132
+ chmod 600 "$tmp_path"
133
+
134
+ if ! jq -n \
135
+ --arg kind "$kind" \
136
+ --arg key "$key" \
137
+ --arg nonce "$nonce" \
138
+ --argjson created_at "$created_at" \
139
+ --arg signature "$signature" \
140
+ --argjson payload "$payload_json" \
141
+ '{
142
+ kind: $kind,
143
+ key: $key,
144
+ nonce: $nonce,
145
+ created_at: $created_at,
146
+ signature: $signature,
147
+ payload: $payload
148
+ }' > "$tmp_path"; then
149
+ rm -f "$tmp_path"
150
+ return 1
151
+ fi
152
+
153
+ if [[ ! -O "$tmp_path" || -L "$tmp_path" ]]; then
154
+ rm -f "$tmp_path"
155
+ return 1
156
+ fi
157
+
158
+ printf '%s\n' "$tmp_path"
159
+ }
160
+
161
+ genesis_sideband_resolve() {
162
+ local kind="$1"
163
+ local session_id="$2"
164
+ local key dir latest
165
+ key="$(genesis_sideband_key "$session_id")"
166
+ dir="$(genesis_sideband_dir)"
167
+
168
+ latest="$(ls -1t -- "$dir"/genesis-"$key"-"$kind"-*.json 2>/dev/null | head -n 1 || true)"
169
+ [[ -n "$latest" ]] || return 1
170
+ printf '%s\n' "$latest"
171
+ }
172
+
173
+ genesis_sideband_read_payload() {
174
+ local kind="$1"
175
+ local session_id="$2"
176
+ local ttl_seconds="${3:-1800}"
177
+
178
+ local file_path now created_at key nonce signature payload_json expected expected_key perms
179
+
180
+ if [[ ! "$ttl_seconds" =~ ^[0-9]+$ || "$ttl_seconds" -le 0 ]]; then
181
+ ttl_seconds=1800
182
+ fi
183
+
184
+ if ! file_path="$(genesis_sideband_resolve "$kind" "$session_id" 2>/dev/null)"; then
185
+ return 1
186
+ fi
187
+ if [[ ! -f "$file_path" ]]; then
188
+ return 1
189
+ fi
190
+ if [[ ! -O "$file_path" || -L "$file_path" ]]; then
191
+ return 1
192
+ fi
193
+ perms="$(stat -c '%a' "$file_path" 2>/dev/null || true)"
194
+ if [[ -n "$perms" ]] && (( (8#$perms & 077) != 0 )); then
195
+ return 1
196
+ fi
197
+
198
+ now="$(date +%s)"
199
+ created_at="$(jq -r '.created_at // 0' "$file_path" 2>/dev/null || echo 0)"
200
+ if [[ "$created_at" == "null" || "$created_at" == "" || ! "$created_at" =~ ^[0-9]+$ ]]; then
201
+ return 1
202
+ fi
203
+ if (( created_at > now + 60 )); then
204
+ return 1
205
+ fi
206
+ if (( now - created_at > ttl_seconds )); then
207
+ return 1
208
+ fi
209
+
210
+ key="$(jq -r '.key // ""' "$file_path" 2>/dev/null || true)"
211
+ nonce="$(jq -r '.nonce // ""' "$file_path" 2>/dev/null || true)"
212
+ signature="$(jq -r '.signature // ""' "$file_path" 2>/dev/null || true)"
213
+ payload_json="$(jq -c '.payload' "$file_path" 2>/dev/null || true)"
214
+ expected_key="$(genesis_sideband_key "$session_id")"
215
+
216
+ if [[ -z "$key" || -z "$nonce" || -z "$signature" || -z "$payload_json" || "$payload_json" == "null" ]]; then
217
+ return 1
218
+ fi
219
+ if [[ "$key" != "$expected_key" ]]; then
220
+ return 1
221
+ fi
222
+ if [[ ! "$nonce" =~ ^[0-9a-f]{16,64}$ ]]; then
223
+ return 1
224
+ fi
225
+ if [[ ! "$signature" =~ ^[0-9a-f]{64}$ ]]; then
226
+ return 1
227
+ fi
228
+
229
+ expected="$(genesis_sideband_signature "$session_id" "$key" "$nonce" "$created_at" "$payload_json")"
230
+ if [[ "$expected" != "$signature" ]]; then
231
+ return 1
232
+ fi
233
+
234
+ printf '%s\n' "$payload_json"
235
+ }
236
+
237
+ genesis_sideband_cleanup() {
238
+ local session_id="$1"
239
+ local dir key
240
+ dir="$(genesis_sideband_dir)"
241
+ key="$(genesis_sideband_key "$session_id")"
242
+ rm -f "$dir"/genesis-"$key"-*.json 2>/dev/null || true
243
+ }
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # stack-detection-lib.sh — Unified stack detection for Genesis pipeline
3
+ # Single source of truth: scaffold-project.sh and generate-qa-contract.sh
4
+ # both import this instead of maintaining their own detection logic.
5
+ # shellcheck shell=bash
6
+
7
+ if [ -z "${BASH_VERSION:-}" ]; then
8
+ echo "ERROR: stack-detection-lib.sh requires bash." >&2
9
+ return 1 2>/dev/null || exit 1
10
+ fi
11
+
12
+ # genesis_detect_stack_from_hint <stack_hint>
13
+ # Maps a raw stack hint (from GENESIS_STACK, scaffold.stack, etc.) to a
14
+ # canonical stack name, or returns empty string if unknown.
15
+ # Canonical stacks: nextjs, express, fastapi, flask, django, python-cli
16
+ genesis_normalize_stack_hint() {
17
+ local hint="${1:-}"
18
+ hint="$(echo "$hint" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
19
+ case "$hint" in
20
+ nextjs|express|fastapi|flask|django|python-cli)
21
+ printf '%s\n' "$hint"
22
+ ;;
23
+ *)
24
+ # Unknown or generic (e.g., "python") — caller should auto-detect
25
+ ;;
26
+ esac
27
+ }
28
+
29
+ # genesis_detect_stack_from_text <text>
30
+ # Keyword-based stack detection from spec/idea text.
31
+ # Returns canonical stack name or empty string.
32
+ genesis_detect_stack_from_text() {
33
+ local text="${1:-}"
34
+ [[ -n "$text" ]] || return 0
35
+
36
+ if echo "$text" | grep -qiE '\bnext\.?js\b|nextjs|\bnext\s+app\b'; then
37
+ echo "nextjs"
38
+ elif echo "$text" | grep -qiE '\bexpress\b|\bnode\.?js.*api\b|\bexpress\.?js\b'; then
39
+ echo "express"
40
+ elif echo "$text" | grep -qiE '\bfastapi\b|\bfast.?api\b'; then
41
+ echo "fastapi"
42
+ elif echo "$text" | grep -qiE '\bflask\b'; then
43
+ echo "flask"
44
+ elif echo "$text" | grep -qiE '\bdjango\b'; then
45
+ echo "django"
46
+ elif echo "$text" | grep -qiE '\bpython\b.*\bcli\b|\bcli\b.*\bpython\b|\bclick\b|\bargparse\b|\btyper\b'; then
47
+ echo "python-cli"
48
+ elif echo "$text" | grep -qiE '\bpython\b'; then
49
+ echo "fastapi"
50
+ elif echo "$text" | grep -qiE '\breact\b|\btypescript\b|\bfrontend\b|\bdashboard\b'; then
51
+ echo "nextjs"
52
+ elif echo "$text" | grep -qiE '\bapi\b|\bbackend\b|\brest\b|\bendpoint\b'; then
53
+ echo "fastapi"
54
+ fi
55
+ }
56
+
57
+ # genesis_detect_stack_from_delivery_target <delivery_target>
58
+ # Fallback: infer stack from delivery target.
59
+ genesis_detect_stack_from_delivery_target() {
60
+ local target="${1:-}"
61
+ case "$target" in
62
+ web-ui|hybrid) echo "nextjs" ;;
63
+ api) echo "fastapi" ;;
64
+ cli) echo "python-cli" ;;
65
+ *) echo "fastapi" ;;
66
+ esac
67
+ }
68
+
69
+ # genesis_stack_flags <stack>
70
+ # Given a canonical stack name, outputs IS_PY IS_JS IS_GO (space-separated booleans).
71
+ # Usage: read IS_PY IS_JS IS_GO <<< "$(genesis_stack_flags "$STACK")"
72
+ genesis_stack_flags() {
73
+ local stack="${1:-}"
74
+ case "$stack" in
75
+ fastapi|flask|django|python-cli|python)
76
+ echo "true false false"
77
+ ;;
78
+ nextjs|express|node|javascript|typescript)
79
+ echo "false true false"
80
+ ;;
81
+ go|golang)
82
+ echo "false false true"
83
+ ;;
84
+ *)
85
+ echo "false false false"
86
+ ;;
87
+ esac
88
+ }
89
+
90
+ # genesis_detect_stack_flags_from_context <stack_hint> <languages_text> <scope_text>
91
+ # Comprehensive flag detection used by generate-qa-contract.sh.
92
+ # Sets IS_PY, IS_JS, IS_GO based on multiple signal sources.
93
+ # Outputs: IS_PY IS_JS IS_GO (space-separated)
94
+ genesis_detect_stack_flags_from_context() {
95
+ local stack_hint="${1:-}"
96
+ local languages="${2:-}"
97
+ local scope_text="${3:-}"
98
+ local IS_PY=false IS_JS=false IS_GO=false
99
+
100
+ # Priority 1: explicit scaffold stack
101
+ case "$stack_hint" in
102
+ fastapi|flask|django|python|python-cli) IS_PY=true ;;
103
+ nextjs|express|node|javascript|typescript) IS_JS=true ;;
104
+ go|golang) IS_GO=true ;;
105
+ esac
106
+
107
+ # Priority 2: language/scope signals (additive — a project can be multi-stack)
108
+ if [[ "$languages" == *"python"* ]] || [[ "$scope_text" == *"python"* ]] || \
109
+ [[ "$scope_text" == *"django"* ]] || [[ "$scope_text" == *"flask"* ]] || \
110
+ [[ "$scope_text" == *"fastapi"* ]]; then
111
+ IS_PY=true
112
+ fi
113
+ if [[ "$languages" == *"javascript"* ]] || [[ "$languages" == *"typescript"* ]] || \
114
+ [[ "$scope_text" == *"node"* ]] || [[ "$scope_text" == *"react"* ]] || \
115
+ [[ "$scope_text" == *"next"* ]]; then
116
+ IS_JS=true
117
+ fi
118
+ if [[ "$languages" == *"go"* ]] || [[ "$scope_text" == *"golang"* ]]; then
119
+ IS_GO=true
120
+ fi
121
+
122
+ # Priority 3: file detection (only if nothing found yet)
123
+ if ! $IS_PY && ! $IS_JS && ! $IS_GO; then
124
+ [[ -f "package.json" ]] && IS_JS=true
125
+ [[ -f "requirements.txt" || -f "pyproject.toml" || -f "setup.py" || -f "Pipfile" ]] && IS_PY=true
126
+ [[ -f "go.mod" ]] && IS_GO=true
127
+ fi
128
+
129
+ echo "$IS_PY $IS_JS $IS_GO"
130
+ }