@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,88 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ genesis_normalize_delivery_target() {
5
+ local raw="${1:-}"
6
+ raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[[:space:]_]+/-/g')"
7
+ case "$raw" in
8
+ web|web-ui|webui|frontend|front-end|ui|interface|site|website|pwa)
9
+ printf '%s' "web-ui"
10
+ ;;
11
+ api|backend|service|rest|graphql|webhook)
12
+ printf '%s' "api"
13
+ ;;
14
+ cli|terminal|console|command-line|linha-de-comando)
15
+ printf '%s' "cli"
16
+ ;;
17
+ hybrid|fullstack|full-stack)
18
+ printf '%s' "hybrid"
19
+ ;;
20
+ *)
21
+ printf '%s' "unknown"
22
+ ;;
23
+ esac
24
+ }
25
+
26
+ genesis_detect_delivery_target_from_text() {
27
+ local text="${1:-}"
28
+ local lower
29
+ lower="$(printf '%s' "$text" | tr '[:upper:]' '[:lower:]')"
30
+
31
+ local has_web="false"
32
+ local has_api="false"
33
+ local has_cli="false"
34
+
35
+ if echo "$lower" | grep -Eqi '\b(app web|web app|site|website|frontend|front-end|interface|ui|ux|tela|página|pagina|dashboard|painel|pwa)\b'; then
36
+ has_web="true"
37
+ fi
38
+ if echo "$lower" | grep -Eqi '\b(api|endpoint|backend|rest|graphql|webhook|serviço|servico)\b'; then
39
+ has_api="true"
40
+ fi
41
+ if echo "$lower" | grep -Eqi '\b(cli|terminal|console|linha de comando|command line|comando|programinha|programa|script|calcular|converter|ferramenta)\b'; then
42
+ has_cli="true"
43
+ fi
44
+
45
+ local count=0
46
+ [[ "$has_web" == "true" ]] && count=$((count + 1))
47
+ [[ "$has_api" == "true" ]] && count=$((count + 1))
48
+ [[ "$has_cli" == "true" ]] && count=$((count + 1))
49
+
50
+ if [[ "$count" -gt 1 ]]; then
51
+ printf '%s' "hybrid"
52
+ return 0
53
+ fi
54
+ if [[ "$has_web" == "true" ]]; then
55
+ printf '%s' "web-ui"
56
+ return 0
57
+ fi
58
+ if [[ "$has_api" == "true" ]]; then
59
+ printf '%s' "api"
60
+ return 0
61
+ fi
62
+ if [[ "$has_cli" == "true" ]]; then
63
+ printf '%s' "cli"
64
+ return 0
65
+ fi
66
+
67
+ printf '%s' "unknown"
68
+ }
69
+
70
+ genesis_cross_validate_delivery_target() {
71
+ local spec_target="${1:-}"
72
+ local raw_idea="${2:-}"
73
+ local text_target
74
+ text_target="$(genesis_detect_delivery_target_from_text "$raw_idea")"
75
+
76
+ if [[ "$text_target" != "unknown" && "$spec_target" != "unknown" && "$text_target" != "$spec_target" ]]; then
77
+ echo "WARNING: delivery_target conflict — spec='$spec_target' vs text='$text_target'. Preferring text." >&2
78
+ printf '%s' "$text_target"
79
+ return 0
80
+ fi
81
+
82
+ if [[ "$spec_target" == "unknown" && "$text_target" != "unknown" ]]; then
83
+ printf '%s' "$text_target"
84
+ return 0
85
+ fi
86
+
87
+ printf '%s' "$spec_target"
88
+ }
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Step 7: Generate QA contract (qa.sh) from spec
5
+ # Input: stdin JSON (with spec.acceptance_criteria)
6
+ # Output: JSON with qa_contract to stdout
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/stack-detection-lib.sh"
10
+ source "$SCRIPT_DIR/genesis-telemetry.sh"
11
+
12
+ if [[ -n "${1:-}" && -f "${1:-}" ]]; then
13
+ INPUT="$(cat "$1")"
14
+ else
15
+ INPUT="$(cat)"
16
+ fi
17
+ SESSION_ID="$(echo "$INPUT" | jq -r '.session_id')"
18
+ SPEC="$(echo "$INPUT" | jq '.spec // {}')"
19
+ TYPE="$(echo "$INPUT" | jq -r '.classification.type // "feature"')"
20
+ METADATA="$(echo "$INPUT" | jq '.metadata // {}')"
21
+ CLASSIFICATION="$(echo "$INPUT" | jq '.classification // {}')"
22
+ INTERVIEW="$(echo "$INPUT" | jq '.interview // {}')"
23
+ IMPACT="$(echo "$INPUT" | jq '.impact // {}')"
24
+ PROJECT_MAP="$(echo "$INPUT" | jq '.project_map // {}')"
25
+ SCAFFOLD="$(echo "$INPUT" | jq '.scaffold // {}')"
26
+ STACK_HINT="$(echo "$SCAFFOLD" | jq -r '.stack // ""' | tr '[:upper:]' '[:lower:]')"
27
+
28
+ genesis_metric_start "generate-qa-contract" "$SESSION_ID"
29
+ echo "Generating QA contract for session $SESSION_ID..." >&2
30
+
31
+ # Detect stack flags using shared stack-detection-lib.sh
32
+ LANGUAGES="$(echo "$PROJECT_MAP" | jq -r '.stats.languages // [] | .[]' 2>/dev/null || echo "")"
33
+ SCOPE_TEXT="$(echo "$SPEC" | jq -r '(.scope_v1 // []) | join(" ")' | tr '[:upper:]' '[:lower:]')"
34
+
35
+ read -r IS_PY IS_JS IS_GO <<< "$(genesis_detect_stack_flags_from_context "$STACK_HINT" "$LANGUAGES" "$SCOPE_TEXT")"
36
+
37
+ if ! $IS_PY && ! $IS_JS && ! $IS_GO; then
38
+ echo "No stack detected — qa.sh will fail closed until stack is explicit" >&2
39
+ fi
40
+
41
+ echo "Stack detected: PY=$IS_PY JS=$IS_JS GO=$IS_GO" >&2
42
+
43
+ # Build acceptance criteria comments for qa.sh
44
+ AC_COMMENTS=""
45
+ AC_INDEX=0
46
+ while IFS= read -r ac; do
47
+ [[ -z "$ac" ]] && continue
48
+ AC_INDEX=$((AC_INDEX + 1))
49
+ AC_COMMENTS="$AC_COMMENTS
50
+ # AC$AC_INDEX: $ac"
51
+ done < <(echo "$SPEC" | jq -r '.acceptance_criteria // [] | .[]')
52
+
53
+ # Generate qa.sh script content
54
+ QA_SCRIPT='#!/usr/bin/env bash
55
+ set -euo pipefail
56
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
57
+ cd "$ROOT_DIR"
58
+ QA_VENV="${QA_VENV:-$HOME/.openclaw/qa-venv}"
59
+ [ -d "$QA_VENV/bin" ] && export PATH="$QA_VENV/bin:$PATH"
60
+ PASS=0; FAIL=0
61
+ gate() { local n="$1"; shift; printf "==> %s\n" "$n"; if "$@"; then PASS=$((PASS+1)); printf " PASS\n"; else FAIL=$((FAIL+1)); printf " FAIL\n"; return 1; fi; }
62
+
63
+ # SECURITY: This script output may be embedded in PR descriptions.
64
+ # It must NEVER print environment variables, tokens, API keys, or host paths.
65
+
66
+ # === Acceptance Criteria ==='"$AC_COMMENTS"'
67
+
68
+ # === Stack Detection ==='
69
+
70
+ QA_SCRIPT="$QA_SCRIPT"'
71
+ if ! '"$IS_PY"' && ! '"$IS_JS"' && ! '"$IS_GO"'; then
72
+ echo "UNKNOWN STACK: unable to infer stack from scaffold/project map/spec."
73
+ echo "Update scaffold.stack or project_map.stats.languages and regenerate QA contract."
74
+ exit 1
75
+ fi'
76
+
77
+ if $IS_PY; then
78
+ QA_SCRIPT="$QA_SCRIPT"'
79
+ IS_PY=true
80
+ $IS_PY && command -v ruff >/dev/null && gate "Lint (Python)" ruff check . || true
81
+ $IS_PY && command -v mypy >/dev/null && gate "Types (Python)" mypy . --ignore-missing-imports || true'
82
+ fi
83
+
84
+ if $IS_JS; then
85
+ QA_SCRIPT="$QA_SCRIPT"'
86
+ IS_JS=true
87
+ $IS_JS && [ -f node_modules/.bin/eslint ] && gate "Lint (JS/TS)" npx eslint . || true
88
+ $IS_JS && [ -f tsconfig.json ] && gate "Types (TS)" npx tsc --noEmit || true'
89
+ fi
90
+
91
+ if $IS_GO; then
92
+ QA_SCRIPT="$QA_SCRIPT"'
93
+ IS_GO=true
94
+ $IS_GO && gate "Lint (Go)" go vet ./... || true'
95
+ fi
96
+
97
+ QA_SCRIPT="$QA_SCRIPT"'
98
+
99
+ # === Security Gate ===
100
+ command -v openclaw >/dev/null && gate "Security (ClawScan)" openclaw invoke --skill clawscan --tool scan-local --args-json '"'"'{"path":"."}'"'"' || true
101
+
102
+ # === Test Gate ==='
103
+
104
+ if $IS_PY; then
105
+ QA_SCRIPT="$QA_SCRIPT"'
106
+ $IS_PY && gate "Tests (Python)" python -m pytest -v --tb=short || true'
107
+ fi
108
+
109
+ if $IS_JS; then
110
+ QA_SCRIPT="$QA_SCRIPT"'
111
+ $IS_JS && gate "Tests (JS)" npm test || true'
112
+ fi
113
+
114
+ if $IS_GO; then
115
+ QA_SCRIPT="$QA_SCRIPT"'
116
+ $IS_GO && gate "Tests (Go)" go test -v ./... || true'
117
+ fi
118
+
119
+ QA_SCRIPT="$QA_SCRIPT"'
120
+
121
+ # === Coverage Gate (>= 80%) ===
122
+ '
123
+
124
+ if $IS_PY; then
125
+ QA_SCRIPT="$QA_SCRIPT"'
126
+ $IS_PY && gate "Coverage (Python >=80%)" python -m pytest -q --cov=. --cov-report=term-missing --cov-fail-under=80 || true'
127
+ fi
128
+
129
+ if $IS_JS; then
130
+ QA_SCRIPT="$QA_SCRIPT"'
131
+ $IS_JS && gate "Coverage (JS/TS >=80%)" npx vitest run --coverage --coverage.thresholds.lines=80 || true'
132
+ fi
133
+
134
+ if $IS_GO; then
135
+ QA_SCRIPT="$QA_SCRIPT"'
136
+ $IS_GO && gate "Coverage (Go >=80%)" bash -lc '\''go test -coverprofile=coverage.out ./... && go tool cover -func=coverage.out | awk "/^total:/ {gsub(/%/,\"\",\\$3); exit !(\\$3>=80)}"'\'' || true'
137
+ fi
138
+
139
+ QA_SCRIPT="$QA_SCRIPT"'
140
+
141
+ TOTAL=$((PASS+FAIL))
142
+ if [[ "$TOTAL" -eq 0 ]]; then
143
+ echo "NO QA GATES EXECUTED"
144
+ exit 1
145
+ fi
146
+ echo "QA: $PASS/$TOTAL passed"
147
+ [ "$FAIL" -gt 0 ] && echo "FAILED" && exit 1
148
+ echo "ALL QA GATES PASSED"'
149
+
150
+ # Build gates list
151
+ GATES='["lint", "types", "security", "tests", "coverage"]'
152
+
153
+ # Build acceptance tests from ACs
154
+ ACCEPTANCE_TESTS="$(echo "$SPEC" | jq '[.acceptance_criteria // [] | .[] | {criterion: ., gate: "tests", automated: false}]')"
155
+
156
+ echo "QA contract generated: $AC_INDEX acceptance criteria, gates=$(echo "$GATES" | jq 'length')" >&2
157
+
158
+ jq -n \
159
+ --arg sid "$SESSION_ID" \
160
+ --arg script "$QA_SCRIPT" \
161
+ --argjson gates "$GATES" \
162
+ --argjson tests "$ACCEPTANCE_TESTS" \
163
+ --argjson spec "$SPEC" \
164
+ --argjson cls "$CLASSIFICATION" \
165
+ --argjson interview "$INTERVIEW" \
166
+ --argjson impact "$IMPACT" \
167
+ --argjson map "$PROJECT_MAP" \
168
+ --argjson scaffold "$SCAFFOLD" \
169
+ --argjson meta "$METADATA" \
170
+ '{
171
+ session_id: $sid,
172
+ step: "qa",
173
+ qa_contract: {
174
+ script_content: $script,
175
+ gates: $gates,
176
+ coverage_threshold: 80,
177
+ acceptance_tests: $tests
178
+ },
179
+ spec: $spec,
180
+ classification: $cls,
181
+ interview: $interview,
182
+ impact: $impact,
183
+ project_map: $map,
184
+ scaffold: $scaffold,
185
+ metadata: $meta
186
+ }'
187
+
188
+ genesis_metric_end "ok"
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Step 4: Generate structured specification from classification + interview answers
5
+ # Input: stdin JSON (classification + interview with answers)
6
+ # Output: JSON with full spec to stdout
7
+ # Note: The LLM fills answers via llm-task. This script structures them into
8
+ # the mandatory headings required by issue_checklist.py:
9
+ # Objetivo, Escopo V1, Fora de escopo, Acceptance Criteria, DoD, Restrições
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ source "$SCRIPT_DIR/delivery-target-lib.sh"
13
+
14
+ if [[ -n "${1:-}" && -f "${1:-}" ]]; then
15
+ INPUT="$(cat "$1")"
16
+ else
17
+ INPUT="$(cat)"
18
+ fi
19
+ SESSION_ID="$(echo "$INPUT" | jq -r '.session_id')"
20
+ RAW_IDEA="$(echo "$INPUT" | jq -r '.raw_idea')"
21
+ TYPE="$(echo "$INPUT" | jq -r '.classification.type')"
22
+ CLASSIFICATION="$(echo "$INPUT" | jq '.classification')"
23
+ INTERVIEW="$(echo "$INPUT" | jq '.interview // {}')"
24
+ METADATA="$(echo "$INPUT" | jq '.metadata // {}')"
25
+
26
+ echo "Generating specification for session $SESSION_ID (type=$TYPE)..." >&2
27
+
28
+ # Extract structured data from the LLM interview output
29
+ # The llm-task step should return a JSON with these fields
30
+ TITLE="$(echo "$INPUT" | jq -r '.spec_data.title // .interview.spec_data.title // .raw_idea' | cut -c1-120)"
31
+ OBJECTIVE="$(echo "$INPUT" | jq -r '.spec_data.objective // .interview.spec_data.objective // "See raw idea"')"
32
+ SCOPE="$(echo "$INPUT" | jq '.spec_data.scope_v1 // .interview.spec_data.scope_v1 // [.raw_idea]')"
33
+ OUT_OF_SCOPE="$(echo "$INPUT" | jq '.spec_data.out_of_scope // .interview.spec_data.out_of_scope // ["To be defined during implementation"]')"
34
+ ACS="$(echo "$INPUT" | jq '.spec_data.acceptance_criteria // .interview.spec_data.acceptance_criteria // ["Feature works as described in the objective"]')"
35
+ DOD="$(echo "$INPUT" | jq '.spec_data.definition_of_done // .interview.spec_data.definition_of_done // ["Code reviewed and merged", "Tests pass", "QA contract passes"]')"
36
+ CONSTRAINTS="$(echo "$INPUT" | jq -r '.spec_data.constraints // .interview.spec_data.constraints // "None specified"')"
37
+ RISKS="$(echo "$INPUT" | jq '.spec_data.risks // .interview.spec_data.risks // []')"
38
+ DELIVERY_TARGET_RAW="$(echo "$INPUT" | jq -r '.spec_data.delivery_target // .interview.spec_data.delivery_target // .classification.delivery_target // .metadata.delivery_target // empty')"
39
+ if [[ -z "$DELIVERY_TARGET_RAW" || "$DELIVERY_TARGET_RAW" == "null" ]]; then
40
+ DELIVERY_TARGET="$(genesis_detect_delivery_target_from_text "$RAW_IDEA")"
41
+ else
42
+ DELIVERY_TARGET_NORMALIZED="$(genesis_normalize_delivery_target "$DELIVERY_TARGET_RAW")"
43
+ DELIVERY_TARGET="$(genesis_cross_validate_delivery_target "$DELIVERY_TARGET_NORMALIZED" "$RAW_IDEA")"
44
+ fi
45
+
46
+ # Validate minimum requirements
47
+ scope_count="$(echo "$SCOPE" | jq 'length')"
48
+ ac_count="$(echo "$ACS" | jq 'length')"
49
+ dod_count="$(echo "$DOD" | jq 'length')"
50
+
51
+ if [[ "$scope_count" -lt 1 ]]; then
52
+ echo "WARNING: Empty scope — using raw idea as scope item" >&2
53
+ SCOPE="$(jq -n --arg i "$RAW_IDEA" '[$i]')"
54
+ fi
55
+
56
+ if [[ "$ac_count" -lt 1 ]]; then
57
+ echo "WARNING: No acceptance criteria — adding default" >&2
58
+ ACS='["Feature works as described in the objective"]'
59
+ fi
60
+
61
+ if [[ "$dod_count" -lt 1 ]]; then
62
+ echo "WARNING: No definition of done — adding defaults" >&2
63
+ DOD='["Code reviewed and merged", "Tests pass", "QA contract passes"]'
64
+ fi
65
+
66
+ ACS_JOINED_LOWER="$(echo "$ACS" | jq -r 'join(" ")' | tr '[:upper:]' '[:lower:]')"
67
+ append_ac() {
68
+ local item="$1"
69
+ ACS="$(echo "$ACS" | jq --arg i "$item" '. + [$i]')"
70
+ }
71
+ append_dod() {
72
+ local item="$1"
73
+ DOD="$(echo "$DOD" | jq --arg i "$item" '. + [$i]')"
74
+ }
75
+
76
+ case "$DELIVERY_TARGET" in
77
+ web-ui)
78
+ if ! echo "$ACS_JOINED_LOWER" | grep -Eqi '\b(tela|página|pagina|ui|interface|dashboard|fluxo)\b'; then
79
+ append_ac "Existe ao menos uma tela funcional do fluxo principal, navegavel de ponta a ponta."
80
+ fi
81
+ ;;
82
+ api)
83
+ if ! echo "$ACS_JOINED_LOWER" | grep -Eqi '\b(api|endpoint|rota|route|http|rest)\b'; then
84
+ append_ac "Existe ao menos um endpoint/API funcional do fluxo principal, com resposta valida."
85
+ fi
86
+ ;;
87
+ cli)
88
+ if ! echo "$ACS_JOINED_LOWER" | grep -Eqi '\b(cli|terminal|comando|linha de comando|console)\b'; then
89
+ append_ac "Existe ao menos um comando CLI funcional do fluxo principal."
90
+ fi
91
+ ;;
92
+ hybrid)
93
+ if ! echo "$ACS_JOINED_LOWER" | grep -Eqi '\b(tela|página|pagina|ui|interface|dashboard|fluxo)\b'; then
94
+ append_ac "Existe ao menos uma interface/tela funcional para o fluxo principal."
95
+ fi
96
+ if ! echo "$ACS_JOINED_LOWER" | grep -Eqi '\b(api|endpoint|rota|route|http|rest)\b'; then
97
+ append_ac "Existe ao menos uma API/endpoint funcional para suportar o fluxo principal."
98
+ fi
99
+ ;;
100
+ esac
101
+
102
+ # Auth requirements gate (hybrid policy):
103
+ # - If idea indicates auth/per-profile needs and spec has no auth evidence,
104
+ # auto-append minimal AC/DoD to prevent contract drift.
105
+ AUTH_REGEX='\b(login|autentic|senha|perfil|permiss|acesso|rbac|admin)\b'
106
+ AUTH_SIGNAL=false
107
+ AUTH_EVIDENCE=false
108
+ AUTH_SIGNAL_TEXT="$(printf '%s %s' "$RAW_IDEA" "$OBJECTIVE" | tr '[:upper:]' '[:lower:]')"
109
+ AUTH_EVIDENCE_TEXT="$(printf '%s %s %s' "$OBJECTIVE" "$(echo "$SCOPE" | jq -r 'join(" ")')" "$(echo "$ACS" | jq -r 'join(" ")')" | tr '[:upper:]' '[:lower:]')"
110
+
111
+ if echo "$AUTH_SIGNAL_TEXT" | grep -Eqi "$AUTH_REGEX"; then
112
+ AUTH_SIGNAL=true
113
+ fi
114
+ if echo "$AUTH_EVIDENCE_TEXT" | grep -Eqi "$AUTH_REGEX"; then
115
+ AUTH_EVIDENCE=true
116
+ fi
117
+ if [[ "$AUTH_SIGNAL" == "true" && "$AUTH_EVIDENCE" != "true" ]]; then
118
+ append_ac "Usuarios autenticados conseguem iniciar sessao com credenciais validas."
119
+ append_ac "Acoes criticas exigem autorizacao por perfil (ex.: admin/operador/leitura)."
120
+ append_dod "Existe teste cobrindo autorizacao por perfil em ao menos um fluxo critico."
121
+ AUTH_EVIDENCE=true
122
+ fi
123
+
124
+ CONSTRAINTS_BASE="$CONSTRAINTS"
125
+ if [[ -n "$CONSTRAINTS_BASE" && "$CONSTRAINTS_BASE" != "None specified" ]]; then
126
+ CONSTRAINTS="$CONSTRAINTS_BASE Delivery target: $DELIVERY_TARGET."
127
+ else
128
+ CONSTRAINTS="Delivery target: $DELIVERY_TARGET."
129
+ fi
130
+ ac_count="$(echo "$ACS" | jq 'length')"
131
+ dod_count="$(echo "$DOD" | jq 'length')"
132
+ META_ENRICHED="$(echo "$METADATA" | jq --argjson signal "$AUTH_SIGNAL" --argjson evidence "$AUTH_EVIDENCE" '. + {auth_gate: {signal: $signal, evidence: $evidence}}')"
133
+
134
+ echo "Spec: title='$TITLE', scope=$scope_count items, ACs=$ac_count, DoD=$dod_count" >&2
135
+
136
+ jq -n \
137
+ --arg sid "$SESSION_ID" \
138
+ --arg raw_idea "$RAW_IDEA" \
139
+ --arg title "$TITLE" \
140
+ --arg type "$TYPE" \
141
+ --arg objective "$OBJECTIVE" \
142
+ --argjson scope "$SCOPE" \
143
+ --argjson oos "$OUT_OF_SCOPE" \
144
+ --argjson acs "$ACS" \
145
+ --argjson dod "$DOD" \
146
+ --arg constraints "$CONSTRAINTS" \
147
+ --argjson risks "$RISKS" \
148
+ --arg delivery_target "$DELIVERY_TARGET" \
149
+ --argjson cls "$CLASSIFICATION" \
150
+ --argjson interview "$INTERVIEW" \
151
+ --argjson meta "$META_ENRICHED" \
152
+ '{
153
+ session_id: $sid,
154
+ step: "spec",
155
+ raw_idea: $raw_idea,
156
+ spec: {
157
+ title: $title,
158
+ type: $type,
159
+ objective: $objective,
160
+ scope_v1: $scope,
161
+ out_of_scope: $oos,
162
+ acceptance_criteria: $acs,
163
+ definition_of_done: $dod,
164
+ constraints: $constraints,
165
+ risks: $risks,
166
+ delivery_target: $delivery_target
167
+ },
168
+ classification: $cls,
169
+ interview: $interview,
170
+ metadata: $meta
171
+ }'
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env bash
2
+ # genesis-telemetry.sh — Structured telemetry for Genesis pipeline steps
3
+ # Emits JSON metrics to a log file for observability.
4
+ # shellcheck shell=bash
5
+
6
+ if [ -z "${BASH_VERSION:-}" ]; then
7
+ echo "ERROR: genesis-telemetry.sh requires bash." >&2
8
+ return 1 2>/dev/null || exit 1
9
+ fi
10
+
11
+ [[ -n "${_GENESIS_TELEMETRY_LOADED:-}" ]] && return 0
12
+ _GENESIS_TELEMETRY_LOADED=1
13
+
14
+ GENESIS_METRICS_FILE="${GENESIS_METRICS_FILE:-$HOME/.openclaw/workspace/logs/genesis-metrics.jsonl}"
15
+ mkdir -p "$(dirname "$GENESIS_METRICS_FILE")" 2>/dev/null || true
16
+
17
+ # _genesis_epoch_ms — current time in milliseconds
18
+ _genesis_epoch_ms() {
19
+ if date +%s%3N >/dev/null 2>&1; then
20
+ date +%s%3N
21
+ else
22
+ echo "$(date +%s)000"
23
+ fi
24
+ }
25
+
26
+ # genesis_metric_start <step_name> <session_id>
27
+ # Call at the beginning of a pipeline step. Stores start time in env.
28
+ # Returns: nothing (sets _GENESIS_METRIC_START_MS)
29
+ genesis_metric_start() {
30
+ local step="${1:-unknown}"
31
+ local session_id="${2:-}"
32
+ export _GENESIS_METRIC_STEP="$step"
33
+ export _GENESIS_METRIC_SESSION="$session_id"
34
+ export _GENESIS_METRIC_START_MS="$(_genesis_epoch_ms)"
35
+ }
36
+
37
+ # genesis_metric_end <status> [error_message]
38
+ # Call at the end of a pipeline step. Emits JSON metric line.
39
+ # status: "ok", "error", "skipped"
40
+ genesis_metric_end() {
41
+ local status="${1:-ok}"
42
+ local error_msg="${2:-}"
43
+ local end_ms duration_ms
44
+
45
+ end_ms="$(_genesis_epoch_ms)"
46
+ if [[ -n "${_GENESIS_METRIC_START_MS:-}" ]]; then
47
+ duration_ms=$(( end_ms - _GENESIS_METRIC_START_MS ))
48
+ else
49
+ duration_ms=0
50
+ fi
51
+
52
+ jq -n -c \
53
+ --arg step "${_GENESIS_METRIC_STEP:-unknown}" \
54
+ --arg session_id "${_GENESIS_METRIC_SESSION:-}" \
55
+ --arg status "$status" \
56
+ --argjson duration_ms "$duration_ms" \
57
+ --arg error "$error_msg" \
58
+ --arg timestamp "$(date -Iseconds)" \
59
+ '{
60
+ step: $step,
61
+ session_id: $session_id,
62
+ status: $status,
63
+ duration_ms: $duration_ms,
64
+ timestamp: $timestamp
65
+ }
66
+ + (if $error != "" then {error: $error} else {} end)
67
+ ' >> "$GENESIS_METRICS_FILE" 2>/dev/null || true
68
+
69
+ unset _GENESIS_METRIC_STEP _GENESIS_METRIC_SESSION _GENESIS_METRIC_START_MS
70
+ }
71
+
72
+ # genesis_emit_metric <step> <session_id> <status> <duration_ms> [error]
73
+ # Direct metric emission (for scripts that don't use start/end pattern).
74
+ genesis_emit_metric() {
75
+ local step="${1:-unknown}"
76
+ local session_id="${2:-}"
77
+ local status="${3:-ok}"
78
+ local duration_ms="${4:-0}"
79
+ local error_msg="${5:-}"
80
+
81
+ jq -n -c \
82
+ --arg step "$step" \
83
+ --arg session_id "$session_id" \
84
+ --arg status "$status" \
85
+ --argjson duration_ms "$duration_ms" \
86
+ --arg error "$error_msg" \
87
+ --arg timestamp "$(date -Iseconds)" \
88
+ '{
89
+ step: $step,
90
+ session_id: $session_id,
91
+ status: $status,
92
+ duration_ms: $duration_ms,
93
+ timestamp: $timestamp
94
+ }
95
+ + (if $error != "" then {error: $error} else {} end)
96
+ ' >> "$GENESIS_METRICS_FILE" 2>/dev/null || true
97
+ }