forge-workflow 0.0.9 → 0.0.10

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 (102) hide show
  1. package/.claude/commands/dev.md +2 -2
  2. package/.claude/commands/plan.md +2 -2
  3. package/.claude/commands/ship.md +2 -2
  4. package/.claude/commands/status.md +4 -4
  5. package/.cline/workflows/dev.md +2 -2
  6. package/.cline/workflows/plan.md +2 -2
  7. package/.cline/workflows/ship.md +2 -2
  8. package/.cline/workflows/status.md +4 -4
  9. package/.codex/skills/dev/SKILL.md +2 -2
  10. package/.codex/skills/plan/SKILL.md +2 -2
  11. package/.codex/skills/ship/SKILL.md +2 -2
  12. package/.codex/skills/status/SKILL.md +4 -4
  13. package/.cursor/commands/dev.md +2 -2
  14. package/.cursor/commands/plan.md +2 -2
  15. package/.cursor/commands/ship.md +2 -2
  16. package/.cursor/commands/status.md +4 -4
  17. package/.github/prompts/dev.prompt.md +2 -2
  18. package/.github/prompts/plan.prompt.md +2 -2
  19. package/.github/prompts/ship.prompt.md +2 -2
  20. package/.github/prompts/status.prompt.md +4 -4
  21. package/.github/workflows/beads-to-github.yml +43 -10
  22. package/.github/workflows/github-to-beads.yml +10 -7
  23. package/.kilocode/workflows/dev.md +2 -2
  24. package/.kilocode/workflows/plan.md +2 -2
  25. package/.kilocode/workflows/ship.md +2 -2
  26. package/.kilocode/workflows/status.md +4 -4
  27. package/.opencode/commands/dev.md +2 -2
  28. package/.opencode/commands/plan.md +2 -2
  29. package/.opencode/commands/ship.md +2 -2
  30. package/.opencode/commands/status.md +4 -4
  31. package/.roo/commands/dev.md +2 -2
  32. package/.roo/commands/plan.md +2 -2
  33. package/.roo/commands/ship.md +2 -2
  34. package/.roo/commands/status.md +4 -4
  35. package/AGENTS.md +1 -0
  36. package/CLAUDE.md +12 -0
  37. package/bin/forge.js +10 -5
  38. package/docs/BEADS_GITHUB_SYNC.md +26 -0
  39. package/docs/TOOLCHAIN.md +130 -148
  40. package/lib/beads-bootstrap.js +225 -0
  41. package/lib/beads-health-check.js +55 -10
  42. package/lib/beads-setup.js +104 -28
  43. package/lib/beads-sync-scaffold.js +11 -6
  44. package/lib/commands/_issue.js +11 -1
  45. package/lib/commands/issues.js +49 -0
  46. package/lib/commands/recommend.js +22 -1
  47. package/lib/commands/setup.js +16 -10
  48. package/lib/commands/status.js +181 -0
  49. package/lib/commands/team.js +11 -1
  50. package/lib/commands/test.js +37 -2
  51. package/lib/commands/validate.js +14 -8
  52. package/lib/commands/worktree.js +27 -54
  53. package/lib/dep-guard/keyword-ripple.js +184 -0
  54. package/lib/detect-worktree.js +9 -10
  55. package/lib/forge-issues.js +326 -0
  56. package/lib/issue-sync/authority.js +100 -0
  57. package/lib/issue-sync/github-pull.js +184 -0
  58. package/lib/issue-sync/import-primitives.js +98 -0
  59. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  60. package/lib/issue-sync/link-store.js +292 -0
  61. package/lib/issue-sync/project-github.js +123 -0
  62. package/lib/issue-sync/reconcile.js +195 -0
  63. package/lib/issue-sync/schema.js +126 -0
  64. package/lib/lefthook-check.js +5 -2
  65. package/lib/project-memory.js +564 -0
  66. package/lib/runtime-health.js +100 -12
  67. package/lib/smart-status/conflicts.js +205 -0
  68. package/lib/smart-status/scoring.js +177 -0
  69. package/lib/status/beads-snapshot.js +102 -0
  70. package/lib/status/presenter.js +65 -0
  71. package/lib/workflow/enforce-stage.js +3 -1
  72. package/lib/workflow/state-manager.js +164 -8
  73. package/package.json +12 -4
  74. package/scripts/beads-context.sh +124 -5
  75. package/scripts/beads-context.test.js +21 -4
  76. package/scripts/beads-migrate-to-dolt.sh +7 -0
  77. package/scripts/beads-upgrade-smoke.sh +263 -0
  78. package/scripts/behavioral-judge.sh +115 -11
  79. package/scripts/benchmark.js +349 -63
  80. package/scripts/dep-guard-analyze.js +52 -17
  81. package/scripts/dep-guard-keyword-ripple.js +29 -0
  82. package/scripts/dep-guard-render-review.js +86 -0
  83. package/scripts/dep-guard.sh +45 -232
  84. package/scripts/forge-team/lib/sync-github.sh +160 -28
  85. package/scripts/forge-team/tests/sync-github.test.sh +195 -58
  86. package/scripts/github-beads-sync/index.mjs +122 -98
  87. package/scripts/github-beads-sync/mapping.mjs +54 -0
  88. package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
  89. package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
  90. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  91. package/scripts/preflight.sh +181 -0
  92. package/scripts/smart-status-score.js +31 -0
  93. package/scripts/smart-status-sessions.js +51 -0
  94. package/scripts/smart-status.sh +74 -329
  95. package/scripts/sync-agentic-workflow.js +48 -0
  96. package/scripts/test-ci-shard.js +244 -0
  97. package/scripts/test-dashboard.js +188 -52
  98. package/scripts/test-full-suite.js +186 -0
  99. package/scripts/test-profile.js +278 -0
  100. package/scripts/test.js +219 -28
  101. package/scripts/validate.js +143 -0
  102. package/scripts/validate.sh +18 -1
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BD_CMD="${BD_CMD:-bd}"
5
+ NODE_CMD="${NODE_CMD:-node}"
6
+ PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
7
+ ARTIFACT_DIR="${BEADS_UPGRADE_SMOKE_ARTIFACT_DIR:-${PROJECT_ROOT}/.artifacts/beads-upgrade-smoke}"
8
+ COMMAND_LOG_PATH="${ARTIFACT_DIR}/commands.jsonl"
9
+ SUMMARY_PATH="${ARTIFACT_DIR}/summary.json"
10
+ CLEANUP_REASON="Beads upgrade smoke cleanup"
11
+ SMOKE_RUN_ID="${BEADS_UPGRADE_SMOKE_RUN_ID:-$(date +%s)-$$}"
12
+ PRIMARY_ISSUE_TITLE="Beads upgrade smoke primary (${SMOKE_RUN_ID})"
13
+ DEPENDENT_ISSUE_TITLE="Beads upgrade smoke dependent (${SMOKE_RUN_ID})"
14
+
15
+ mkdir -p "${ARTIFACT_DIR}"
16
+ : > "${COMMAND_LOG_PATH}"
17
+
18
+ created_issue_ids=()
19
+ closed_issue_ids=()
20
+ failed_step=""
21
+ failure_message=""
22
+
23
+ parse_issue_id() {
24
+ local output="$1"
25
+ if [[ "${output}" =~ Created[[:space:]]+issue:[[:space:]]*([[:alnum:]-]+) ]]; then
26
+ printf '%s' "${BASH_REMATCH[1]}"
27
+ return 0
28
+ fi
29
+ if [[ "${output}" =~ \"id\"[[:space:]]*:[[:space:]]*\"([[:alnum:]-]+)\" ]]; then
30
+ printf '%s' "${BASH_REMATCH[1]}"
31
+ return 0
32
+ fi
33
+ return 1
34
+ }
35
+
36
+ find_issue_id_by_title() {
37
+ local issue_title="$1"
38
+ local list_output
39
+ list_output="$("${BD_CMD}" list --json --limit=50 2>/dev/null)" || return 1
40
+
41
+ "${NODE_CMD}" -e '
42
+ const fs = require("node:fs");
43
+ const [issueTitle] = process.argv.slice(1);
44
+ try {
45
+ const rows = JSON.parse(fs.readFileSync(0, "utf8"));
46
+ if (!Array.isArray(rows)) {
47
+ process.exit(1);
48
+ }
49
+ const match = rows.find((row) => row && row.title === issueTitle && typeof row.id === "string");
50
+ if (!match) {
51
+ process.exit(1);
52
+ }
53
+ process.stdout.write(match.id);
54
+ } catch {
55
+ process.exit(1);
56
+ }
57
+ ' "${issue_title}" <<< "${list_output}"
58
+ }
59
+
60
+ append_command() {
61
+ local step="$1"
62
+ local command_name="$2"
63
+ local command_text="$3"
64
+ local exit_code="$4"
65
+ local stdout_path="$5"
66
+ local stderr_path="$6"
67
+
68
+ "${NODE_CMD}" -e '
69
+ const fs = require("node:fs");
70
+ const [logPath, step, commandName, commandText, exitCode, stdoutPath, stderrPath] = process.argv.slice(1);
71
+ const entry = {
72
+ step,
73
+ commandName,
74
+ commandText,
75
+ exitCode: Number(exitCode),
76
+ stdoutPath,
77
+ stderrPath,
78
+ };
79
+ fs.appendFileSync(logPath, JSON.stringify(entry) + "\n");
80
+ ' "${COMMAND_LOG_PATH}" "${step}" "${command_name}" "${command_text}" "${exit_code}" "${stdout_path}" "${stderr_path}"
81
+ }
82
+
83
+ read_output_snippet() {
84
+ local file_path="$1"
85
+ if [[ ! -f "${file_path}" ]]; then
86
+ return 0
87
+ fi
88
+
89
+ "${NODE_CMD}" -e '
90
+ const fs = require("node:fs");
91
+ const [filePath] = process.argv.slice(1);
92
+ const text = fs.readFileSync(filePath, "utf8");
93
+ process.stdout.write(text.slice(0, 4096));
94
+ ' "${file_path}"
95
+ }
96
+
97
+ write_summary() {
98
+ local ok="$1"
99
+
100
+ "${NODE_CMD}" -e '
101
+ const fs = require("node:fs");
102
+ const [
103
+ commandLogPath,
104
+ summaryPath,
105
+ okValue,
106
+ failedStep,
107
+ failureMessage,
108
+ artifactDir,
109
+ createdJson,
110
+ closedJson,
111
+ ] = process.argv.slice(1);
112
+ const commands = fs.readFileSync(commandLogPath, "utf8")
113
+ .split(/\r?\n/)
114
+ .filter(Boolean)
115
+ .map((line) => JSON.parse(line));
116
+ const summary = {
117
+ ok: okValue === "true",
118
+ failedStep: failedStep || null,
119
+ failureMessage: failureMessage || null,
120
+ commands,
121
+ cleanup: {
122
+ createdIssueIds: JSON.parse(createdJson),
123
+ closedIssueIds: JSON.parse(closedJson),
124
+ },
125
+ failureArtifact: summaryPath,
126
+ artifactDir,
127
+ };
128
+ fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2) + "\n");
129
+ ' "${COMMAND_LOG_PATH}" "${SUMMARY_PATH}" "${ok}" "${failed_step}" "${failure_message}" "${ARTIFACT_DIR}" "$(printf '%s\n' "${created_issue_ids[@]:-}" | "${NODE_CMD}" -e 'const fs=require("node:fs"); const lines=fs.readFileSync(0,"utf8").split(/\r?\n/).filter(Boolean); process.stdout.write(JSON.stringify(lines));')" "$(printf '%s\n' "${closed_issue_ids[@]:-}" | "${NODE_CMD}" -e 'const fs=require("node:fs"); const lines=fs.readFileSync(0,"utf8").split(/\r?\n/).filter(Boolean); process.stdout.write(JSON.stringify(lines));')"
130
+ }
131
+
132
+ run_bd_step() {
133
+ local step="$1"
134
+ local command_name="$2"
135
+ shift 2
136
+
137
+ local stdout_file stderr_file exit_code command_text
138
+ stdout_file="${ARTIFACT_DIR}/$(printf '%s' "${step}" | tr '[:upper:]' '[:lower:]').stdout"
139
+ stderr_file="${ARTIFACT_DIR}/$(printf '%s' "${step}" | tr '[:upper:]' '[:lower:]').stderr"
140
+ command_text="$*"
141
+
142
+ if "${BD_CMD}" "$@" >"${stdout_file}" 2>"${stderr_file}"; then
143
+ exit_code=0
144
+ else
145
+ exit_code=$?
146
+ fi
147
+
148
+ append_command "${step}" "${command_name}" "${command_text}" "${exit_code}" "${stdout_file}" "${stderr_file}"
149
+
150
+ if [[ "${exit_code}" -ne 0 ]]; then
151
+ failed_step="${command_name}"
152
+ local stderr_text stdout_text
153
+ stderr_text="$(read_output_snippet "${stderr_file}")"
154
+ stdout_text="$(read_output_snippet "${stdout_file}")"
155
+ failure_message="${stderr_text:-${stdout_text:-${command_text}}}"
156
+ return "${exit_code}"
157
+ fi
158
+
159
+ cat "${stdout_file}"
160
+ }
161
+
162
+ is_closed_issue() {
163
+ local issue_id="$1"
164
+ local existing
165
+ for existing in "${closed_issue_ids[@]:-}"; do
166
+ if [[ "${existing}" == "${issue_id}" ]]; then
167
+ return 0
168
+ fi
169
+ done
170
+ return 1
171
+ }
172
+
173
+ rollback_unclosed_issues() {
174
+ local idx issue_id
175
+ for (( idx=${#created_issue_ids[@]} - 1; idx>=0; idx-- )); do
176
+ issue_id="${created_issue_ids[idx]}"
177
+ if is_closed_issue "${issue_id}"; then
178
+ continue
179
+ fi
180
+ if run_bd_step "rollback-close-${issue_id}" "close" close "${issue_id}" "--reason=${CLEANUP_REASON}" >/dev/null; then
181
+ closed_issue_ids+=("${issue_id}")
182
+ fi
183
+ done
184
+ }
185
+
186
+ handle_issue_id_parse_failure() {
187
+ local issue_title="$1"
188
+ local message="$2"
189
+ local recovered_issue_id=""
190
+
191
+ failed_step="create"
192
+ failure_message="${message}"
193
+
194
+ recovered_issue_id="$(find_issue_id_by_title "${issue_title}" || true)"
195
+ if [[ -n "${recovered_issue_id}" ]]; then
196
+ created_issue_ids+=("${recovered_issue_id}")
197
+ fi
198
+
199
+ rollback_unclosed_issues
200
+ write_summary "false"
201
+ exit 1
202
+ }
203
+
204
+ primary_output="$(run_bd_step "create-primary" "create" create --title="${PRIMARY_ISSUE_TITLE}" --type=task --priority=4)" || {
205
+ rollback_unclosed_issues
206
+ write_summary "false"
207
+ exit 1
208
+ }
209
+ primary_issue_id="$(parse_issue_id "${primary_output}")" || {
210
+ handle_issue_id_parse_failure "${PRIMARY_ISSUE_TITLE}" "Could not parse primary smoke issue ID"
211
+ }
212
+ created_issue_ids+=("${primary_issue_id}")
213
+
214
+ dependent_output="$(run_bd_step "create-dependent" "create" create --title="${DEPENDENT_ISSUE_TITLE}" --type=task --priority=4)" || {
215
+ rollback_unclosed_issues
216
+ write_summary "false"
217
+ exit 1
218
+ }
219
+ dependent_issue_id="$(parse_issue_id "${dependent_output}")" || {
220
+ handle_issue_id_parse_failure "${DEPENDENT_ISSUE_TITLE}" "Could not parse dependent smoke issue ID"
221
+ }
222
+ created_issue_ids+=("${dependent_issue_id}")
223
+
224
+ run_bd_step "list" "list" list --json --limit=50 >/dev/null || {
225
+ rollback_unclosed_issues
226
+ write_summary "false"
227
+ exit 1
228
+ }
229
+
230
+ run_bd_step "show-primary" "show" show "${primary_issue_id}" --json >/dev/null || {
231
+ rollback_unclosed_issues
232
+ write_summary "false"
233
+ exit 1
234
+ }
235
+
236
+ run_bd_step "dep-add" "dep" dep add "${primary_issue_id}" "${dependent_issue_id}" >/dev/null || {
237
+ rollback_unclosed_issues
238
+ write_summary "false"
239
+ exit 1
240
+ }
241
+
242
+ run_bd_step "close-dependent" "close" close "${dependent_issue_id}" "--reason=${CLEANUP_REASON}" >/dev/null || {
243
+ rollback_unclosed_issues
244
+ write_summary "false"
245
+ exit 1
246
+ }
247
+ closed_issue_ids+=("${dependent_issue_id}")
248
+
249
+ run_bd_step "close-primary" "close" close "${primary_issue_id}" "--reason=${CLEANUP_REASON}" >/dev/null || {
250
+ rollback_unclosed_issues
251
+ write_summary "false"
252
+ exit 1
253
+ }
254
+ closed_issue_ids+=("${primary_issue_id}")
255
+
256
+ run_bd_step "sync" "sync" sync >/dev/null || {
257
+ rollback_unclosed_issues
258
+ write_summary "false"
259
+ exit 1
260
+ }
261
+
262
+ write_summary "true"
263
+ printf '%s\n' "${SUMMARY_PATH}"
@@ -66,10 +66,25 @@ else
66
66
  INPUT="$(cat)"
67
67
  fi
68
68
 
69
- # Extract plan_output text if input is JSON, otherwise use raw input
70
- PLAN_TEXT="$INPUT"
71
- if command -v python3 >/dev/null 2>&1; then
72
- EXTRACTED=$(echo "$INPUT" | python3 -c "
69
+ extract_plan_text() {
70
+ if command -v node >/dev/null 2>&1; then
71
+ node -e "
72
+ let input = '';
73
+ process.stdin.setEncoding('utf8');
74
+ process.stdin.on('data', chunk => { input += chunk; });
75
+ process.stdin.on('end', () => {
76
+ try {
77
+ const parsed = JSON.parse(input);
78
+ const value = parsed && parsed.plan_output;
79
+ if (value) process.stdout.write(String(value));
80
+ } catch (_) {}
81
+ });
82
+ " 2>/dev/null
83
+ return $?
84
+ fi
85
+
86
+ if command -v python3 >/dev/null 2>&1; then
87
+ python3 -c "
73
88
  import sys, json
74
89
  try:
75
90
  d = json.load(sys.stdin)
@@ -78,10 +93,16 @@ try:
78
93
  print(val)
79
94
  except:
80
95
  pass
81
- " 2>/dev/null) || true
82
- if [ -n "$EXTRACTED" ]; then
83
- PLAN_TEXT="$EXTRACTED"
96
+ " 2>/dev/null
97
+ return $?
84
98
  fi
99
+ }
100
+
101
+ # Extract plan_output text if input is JSON, otherwise use raw input
102
+ PLAN_TEXT="$INPUT"
103
+ EXTRACTED=$(printf '%s' "$INPUT" | extract_plan_text) || true
104
+ if [ -n "$EXTRACTED" ]; then
105
+ PLAN_TEXT="$EXTRACTED"
85
106
  fi
86
107
 
87
108
  # ─── Judge prompt ────────────────────────────────────────────────────────────
@@ -158,7 +179,22 @@ parse_scores() {
158
179
  local response="$1"
159
180
  # Extract the JSON object from the LLM response content
160
181
  local content
161
- content=$(echo "$response" | python3 -c "
182
+ if command -v node >/dev/null 2>&1; then
183
+ content=$(printf '%s' "$response" | node -e "
184
+ let input = '';
185
+ process.stdin.setEncoding('utf8');
186
+ process.stdin.on('data', chunk => { input += chunk; });
187
+ process.stdin.on('end', () => {
188
+ try {
189
+ const data = JSON.parse(input);
190
+ const choice = (data.choices || [])[0] || {};
191
+ const message = choice.message || {};
192
+ process.stdout.write(String(message.content || ''));
193
+ } catch (_) {}
194
+ });
195
+ " 2>/dev/null) || true
196
+ elif command -v python3 >/dev/null 2>&1; then
197
+ content=$(printf '%s' "$response" | python3 -c "
162
198
  import sys, json
163
199
  try:
164
200
  data = json.load(sys.stdin)
@@ -170,6 +206,9 @@ try:
170
206
  except Exception as e:
171
207
  pass
172
208
  " 2>/dev/null) || true
209
+ else
210
+ content=""
211
+ fi
173
212
 
174
213
  if [ -z "$content" ]; then
175
214
  echo ""
@@ -178,7 +217,37 @@ except Exception as e:
178
217
 
179
218
  # Parse scores from content JSON
180
219
  local scores
181
- scores=$(echo "$content" | python3 -c "
220
+ if command -v node >/dev/null 2>&1; then
221
+ scores=$(printf '%s' "$content" | node -e "
222
+ let text = '';
223
+ process.stdin.setEncoding('utf8');
224
+ process.stdin.on('data', chunk => { text += chunk; });
225
+ process.stdin.on('end', () => {
226
+ function emit(value) {
227
+ const security = Number.parseInt(value.security, 10);
228
+ const tdd = Number.parseInt(value.tdd, 10);
229
+ const design = Number.parseInt(value.design, 10);
230
+ const structural = Number.parseInt(value.structural, 10);
231
+ if ([security, tdd, design, structural].every(score => Number.isInteger(score) && score >= 0 && score <= 5)) {
232
+ console.log([security, tdd, design, structural].join(' '));
233
+ return true;
234
+ }
235
+ return false;
236
+ }
237
+ try {
238
+ if (emit(JSON.parse(text.trim()))) return;
239
+ } catch (_) {}
240
+ const match = text.match(/\{[^}]+\}/s);
241
+ if (match) {
242
+ try {
243
+ if (emit(JSON.parse(match[0]))) return;
244
+ } catch (_) {}
245
+ }
246
+ process.exit(1);
247
+ });
248
+ " 2>/dev/null) || { echo ""; return 1; }
249
+ elif command -v python3 >/dev/null 2>&1; then
250
+ scores=$(printf '%s' "$content" | python3 -c "
182
251
  import sys, json, re
183
252
  text = sys.stdin.read().strip()
184
253
  # Try direct JSON parse first
@@ -209,6 +278,10 @@ if m:
209
278
  pass
210
279
  sys.exit(1)
211
280
  " 2>/dev/null) || { echo ""; return 1; }
281
+ else
282
+ echo ""
283
+ return 1
284
+ fi
212
285
 
213
286
  echo "$scores"
214
287
  }
@@ -221,10 +294,23 @@ call_openrouter() {
221
294
 
222
295
  # Escape prompt for JSON
223
296
  local escaped_prompt
224
- escaped_prompt=$(echo "$prompt" | python3 -c "
297
+ if command -v node >/dev/null 2>&1; then
298
+ escaped_prompt=$(printf '%s' "$prompt" | node -e "
299
+ let input = '';
300
+ process.stdin.setEncoding('utf8');
301
+ process.stdin.on('data', chunk => { input += chunk; });
302
+ process.stdin.on('end', () => {
303
+ process.stdout.write(JSON.stringify(input));
304
+ });
305
+ " 2>/dev/null) || escaped_prompt='""'
306
+ elif command -v python3 >/dev/null 2>&1; then
307
+ escaped_prompt=$(printf '%s' "$prompt" | python3 -c "
225
308
  import sys, json
226
309
  print(json.dumps(sys.stdin.read()))
227
310
  " 2>/dev/null) || escaped_prompt='""'
311
+ else
312
+ escaped_prompt='""'
313
+ fi
228
314
 
229
315
  local body
230
316
  body=$(cat <<JSON
@@ -295,7 +381,22 @@ if [ "${BEHAVIORAL_JUDGE_TEST_MODE}" = "1" ]; then
295
381
  fi
296
382
 
297
383
  # Parse mock scores
298
- scores=$(echo "$mock_scores" | python3 -c "
384
+ if command -v node >/dev/null 2>&1; then
385
+ scores=$(printf '%s' "$mock_scores" | node -e "
386
+ let input = '';
387
+ process.stdin.setEncoding('utf8');
388
+ process.stdin.on('data', chunk => { input += chunk; });
389
+ process.stdin.on('end', () => {
390
+ const scores = JSON.parse(input);
391
+ const security = Number.parseInt(scores.security ?? 3, 10);
392
+ const tdd = Number.parseInt(scores.tdd ?? 3, 10);
393
+ const design = Number.parseInt(scores.design ?? 3, 10);
394
+ const structural = Number.parseInt(scores.structural ?? 3, 10);
395
+ console.log([security, tdd, design, structural].join(' '));
396
+ });
397
+ " 2>/dev/null) || scores="3 3 3 3"
398
+ elif command -v python3 >/dev/null 2>&1; then
399
+ scores=$(printf '%s' "$mock_scores" | python3 -c "
299
400
  import sys, json
300
401
  d = json.loads(sys.stdin.read())
301
402
  s = int(d.get('security', 3))
@@ -304,6 +405,9 @@ de = int(d.get('design', 3))
304
405
  st = int(d.get('structural', 3))
305
406
  print(f'{s} {t} {de} {st}')
306
407
  " 2>/dev/null) || scores="3 3 3 3"
408
+ else
409
+ scores="3 3 3 3"
410
+ fi
307
411
 
308
412
  read -r sec tdd_s des str <<< "$scores"
309
413
  compute_result "$sec" "$tdd_s" "$des" "$str" "$judge_model" "$judge_calls"