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.
- package/.claude/commands/dev.md +2 -2
- package/.claude/commands/plan.md +2 -2
- package/.claude/commands/ship.md +2 -2
- package/.claude/commands/status.md +4 -4
- package/.cline/workflows/dev.md +2 -2
- package/.cline/workflows/plan.md +2 -2
- package/.cline/workflows/ship.md +2 -2
- package/.cline/workflows/status.md +4 -4
- package/.codex/skills/dev/SKILL.md +2 -2
- package/.codex/skills/plan/SKILL.md +2 -2
- package/.codex/skills/ship/SKILL.md +2 -2
- package/.codex/skills/status/SKILL.md +4 -4
- package/.cursor/commands/dev.md +2 -2
- package/.cursor/commands/plan.md +2 -2
- package/.cursor/commands/ship.md +2 -2
- package/.cursor/commands/status.md +4 -4
- package/.github/prompts/dev.prompt.md +2 -2
- package/.github/prompts/plan.prompt.md +2 -2
- package/.github/prompts/ship.prompt.md +2 -2
- package/.github/prompts/status.prompt.md +4 -4
- package/.github/workflows/beads-to-github.yml +43 -10
- package/.github/workflows/github-to-beads.yml +10 -7
- package/.kilocode/workflows/dev.md +2 -2
- package/.kilocode/workflows/plan.md +2 -2
- package/.kilocode/workflows/ship.md +2 -2
- package/.kilocode/workflows/status.md +4 -4
- package/.opencode/commands/dev.md +2 -2
- package/.opencode/commands/plan.md +2 -2
- package/.opencode/commands/ship.md +2 -2
- package/.opencode/commands/status.md +4 -4
- package/.roo/commands/dev.md +2 -2
- package/.roo/commands/plan.md +2 -2
- package/.roo/commands/ship.md +2 -2
- package/.roo/commands/status.md +4 -4
- package/AGENTS.md +1 -0
- package/CLAUDE.md +12 -0
- package/bin/forge.js +10 -5
- package/docs/BEADS_GITHUB_SYNC.md +26 -0
- package/docs/TOOLCHAIN.md +130 -148
- package/lib/beads-bootstrap.js +225 -0
- package/lib/beads-health-check.js +55 -10
- package/lib/beads-setup.js +104 -28
- package/lib/beads-sync-scaffold.js +11 -6
- package/lib/commands/_issue.js +11 -1
- package/lib/commands/issues.js +49 -0
- package/lib/commands/recommend.js +22 -1
- package/lib/commands/setup.js +16 -10
- package/lib/commands/status.js +181 -0
- package/lib/commands/team.js +11 -1
- package/lib/commands/test.js +37 -2
- package/lib/commands/validate.js +14 -8
- package/lib/commands/worktree.js +27 -54
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/detect-worktree.js +9 -10
- package/lib/forge-issues.js +326 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/lefthook-check.js +5 -2
- package/lib/project-memory.js +564 -0
- package/lib/runtime-health.js +100 -12
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +177 -0
- package/lib/status/beads-snapshot.js +102 -0
- package/lib/status/presenter.js +65 -0
- package/lib/workflow/enforce-stage.js +3 -1
- package/lib/workflow/state-manager.js +164 -8
- package/package.json +12 -4
- package/scripts/beads-context.sh +124 -5
- package/scripts/beads-context.test.js +21 -4
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +263 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +45 -232
- package/scripts/forge-team/lib/sync-github.sh +160 -28
- package/scripts/forge-team/tests/sync-github.test.sh +195 -58
- package/scripts/github-beads-sync/index.mjs +122 -98
- package/scripts/github-beads-sync/mapping.mjs +54 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
- package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/preflight.sh +181 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +74 -329
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/test-ci-shard.js +244 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +219 -28
- package/scripts/validate.js +143 -0
- 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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|