@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.
- package/ARCHITECTURE.md +87 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/defaults/AGENTS.md +150 -0
- package/defaults/HEARTBEAT.md +3 -0
- package/defaults/IDENTITY.md +6 -0
- package/defaults/SOUL.md +39 -0
- package/defaults/TOOLS.md +15 -0
- package/defaults/fabrica/prompts/architect.md +147 -0
- package/defaults/fabrica/prompts/developer.md +211 -0
- package/defaults/fabrica/prompts/reviewer.md +114 -0
- package/defaults/fabrica/prompts/security-checklist.md +58 -0
- package/defaults/fabrica/prompts/tester.md +150 -0
- package/defaults/fabrica/workflow.yaml +184 -0
- package/dist/index.js +143075 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/worker.cjs +214 -0
- package/dist/worker.cjs +4754 -0
- package/fabrica.manifest.json +24 -0
- package/genesis/configs/classification-rules.json +32 -0
- package/genesis/configs/interview-templates.json +73 -0
- package/genesis/configs/labels.json +202 -0
- package/genesis/configs/triage-matrix.json +39 -0
- package/genesis/scripts/classify-idea.sh +161 -0
- package/genesis/scripts/conduct-interview.sh +199 -0
- package/genesis/scripts/create-task.sh +797 -0
- package/genesis/scripts/delivery-target-lib.sh +88 -0
- package/genesis/scripts/generate-qa-contract.sh +188 -0
- package/genesis/scripts/generate-spec.sh +171 -0
- package/genesis/scripts/genesis-telemetry.sh +97 -0
- package/genesis/scripts/genesis-utils.sh +617 -0
- package/genesis/scripts/impact-analysis.sh +135 -0
- package/genesis/scripts/interview.sh +98 -0
- package/genesis/scripts/map-project.sh +309 -0
- package/genesis/scripts/receive-idea.sh +69 -0
- package/genesis/scripts/register-project.sh +520 -0
- package/genesis/scripts/research-idea.sh +84 -0
- package/genesis/scripts/scaffold-project.sh +1396 -0
- package/genesis/scripts/security-review.sh +141 -0
- package/genesis/scripts/sideband-lib.sh +243 -0
- package/genesis/scripts/stack-detection-lib.sh +130 -0
- package/genesis/scripts/triage.sh +598 -0
- package/genesis/scripts/validate-step.sh +81 -0
- package/openclaw.plugin.json +45 -0
- 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
|
+
}
|