@kleber.mottajr/juninho 1.3.0 → 2.0.1
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/README.md +14 -15
- package/dist/config.d.ts +29 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +57 -3
- package/dist/config.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +159 -53
- package/dist/installer.js.map +1 -1
- package/dist/project-types.d.ts.map +1 -1
- package/dist/project-types.js +6 -0
- package/dist/project-types.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +925 -162
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/commands.d.ts.map +1 -1
- package/dist/templates/commands.js +747 -626
- package/dist/templates/commands.js.map +1 -1
- package/dist/templates/docs.d.ts.map +1 -1
- package/dist/templates/docs.js +49 -24
- package/dist/templates/docs.js.map +1 -1
- package/dist/templates/lib.d.ts +2 -0
- package/dist/templates/lib.d.ts.map +1 -0
- package/dist/templates/lib.js +506 -0
- package/dist/templates/lib.js.map +1 -0
- package/dist/templates/plugins.d.ts.map +1 -1
- package/dist/templates/plugins.js +2530 -856
- package/dist/templates/plugins.js.map +1 -1
- package/dist/templates/skills.d.ts.map +1 -1
- package/dist/templates/skills.js +30 -0
- package/dist/templates/skills.js.map +1 -1
- package/dist/templates/state.d.ts.map +1 -1
- package/dist/templates/state.js +159 -186
- package/dist/templates/state.js.map +1 -1
- package/dist/templates/support-scripts.d.ts.map +1 -1
- package/dist/templates/support-scripts.js +1014 -249
- package/dist/templates/support-scripts.js.map +1 -1
- package/package.json +8 -2
|
@@ -11,7 +11,12 @@ function writeSupportScripts(projectDir, projectType = "node-nextjs", isKotlin =
|
|
|
11
11
|
writeExecutable(path_1.default.join(scriptsDir, "pre-commit.sh"), PRE_COMMIT);
|
|
12
12
|
writeExecutable(path_1.default.join(scriptsDir, "lint-structure.sh"), lintStructure(projectType, isKotlin, lintTool));
|
|
13
13
|
writeExecutable(path_1.default.join(scriptsDir, "test-related.sh"), testRelated(projectType, isKotlin));
|
|
14
|
+
writeExecutable(path_1.default.join(scriptsDir, "run-test-scope.sh"), runTestScope(projectType, isKotlin));
|
|
14
15
|
writeExecutable(path_1.default.join(scriptsDir, "check-all.sh"), checkAll(projectType, isKotlin, lintTool));
|
|
16
|
+
writeExecutable(path_1.default.join(scriptsDir, "scaffold-spec-state.sh"), SCAFFOLD_SPEC_STATE);
|
|
17
|
+
writeExecutable(path_1.default.join(scriptsDir, "harness-feature-integration.sh"), HARNESS_FEATURE_INTEGRATION);
|
|
18
|
+
writeExecutable(path_1.default.join(scriptsDir, "build-verify.sh"), buildVerify(projectType, isKotlin));
|
|
19
|
+
writeExecutable(path_1.default.join(scriptsDir, "install-git-hooks.sh"), INSTALL_GIT_HOOKS);
|
|
15
20
|
}
|
|
16
21
|
function writeExecutable(filePath, content) {
|
|
17
22
|
(0, fs_1.writeFileSync)(filePath, content);
|
|
@@ -44,7 +49,6 @@ echo "[juninho:pre-commit] Running related tests..."
|
|
|
44
49
|
|
|
45
50
|
echo "[juninho:pre-commit] Local checks passed"
|
|
46
51
|
`;
|
|
47
|
-
/* ─── Lint Structure ─── */
|
|
48
52
|
function lintStructure(projectType, isKotlin, lintTool) {
|
|
49
53
|
const header = `#!/bin/sh
|
|
50
54
|
set -e
|
|
@@ -72,16 +76,14 @@ fi
|
|
|
72
76
|
case "go":
|
|
73
77
|
return header + lintGoBody(lintTool);
|
|
74
78
|
case "java":
|
|
75
|
-
return isKotlin
|
|
76
|
-
? header + lintKotlinBody(lintTool)
|
|
77
|
-
: header + lintJavaBody(lintTool);
|
|
79
|
+
return isKotlin ? header + lintKotlinBody(lintTool) : header + lintJavaBody(lintTool);
|
|
78
80
|
case "generic":
|
|
79
81
|
return header + lintGenericBody();
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
function lintNodeBody(lintTool) {
|
|
83
85
|
const priority = lintTool
|
|
84
|
-
?
|
|
86
|
+
? `if command -v npx >/dev/null 2>&1; then\n npx ${lintTool} $FILES\n exit 0\nfi\n\n`
|
|
85
87
|
: "";
|
|
86
88
|
return `${priority}has_package_script() {
|
|
87
89
|
[ -f package.json ] || return 1
|
|
@@ -104,15 +106,14 @@ if command -v npx >/dev/null 2>&1 && npx --yes eslint --version >/dev/null 2>&1;
|
|
|
104
106
|
fi
|
|
105
107
|
|
|
106
108
|
echo "[juninho:lint-structure] No structure lint configured."
|
|
107
|
-
echo "[juninho:lint-structure] Customize .opencode/scripts/lint-structure.sh or run /j.
|
|
109
|
+
echo "[juninho:lint-structure] Customize .opencode/scripts/lint-structure.sh or run /j.finish-setup."
|
|
108
110
|
`;
|
|
109
111
|
}
|
|
110
112
|
function lintPythonBody(lintTool) {
|
|
111
113
|
const priority = lintTool
|
|
112
|
-
?
|
|
114
|
+
? `if command -v ${lintTool} >/dev/null 2>&1; then\n ${lintTool} check $FILES\n exit 0\nfi\n\n`
|
|
113
115
|
: "";
|
|
114
|
-
return `${priority}
|
|
115
|
-
if command -v ruff >/dev/null 2>&1; then
|
|
116
|
+
return `${priority}if command -v ruff >/dev/null 2>&1; then
|
|
116
117
|
ruff check $FILES
|
|
117
118
|
exit 0
|
|
118
119
|
fi
|
|
@@ -132,10 +133,9 @@ echo "[juninho:lint-structure] No Python linter found. Install ruff, flake8, or
|
|
|
132
133
|
}
|
|
133
134
|
function lintGoBody(lintTool) {
|
|
134
135
|
const priority = lintTool
|
|
135
|
-
?
|
|
136
|
+
? `if command -v ${lintTool} >/dev/null 2>&1; then\n ${lintTool} run\n exit 0\nfi\n\n`
|
|
136
137
|
: "";
|
|
137
|
-
return `${priority}
|
|
138
|
-
if command -v golangci-lint >/dev/null 2>&1; then
|
|
138
|
+
return `${priority}if command -v golangci-lint >/dev/null 2>&1; then
|
|
139
139
|
golangci-lint run
|
|
140
140
|
exit 0
|
|
141
141
|
fi
|
|
@@ -143,12 +143,8 @@ fi
|
|
|
143
143
|
go vet ./...
|
|
144
144
|
`;
|
|
145
145
|
}
|
|
146
|
-
function lintJavaBody(
|
|
147
|
-
|
|
148
|
-
? `# Priority linter: ${lintTool}\n`
|
|
149
|
-
: "";
|
|
150
|
-
return `${priority}# Java lint chain: gradle checkstyle → maven checkstyle
|
|
151
|
-
if [ -x "./gradlew" ]; then
|
|
146
|
+
function lintJavaBody(_lintTool) {
|
|
147
|
+
return `if [ -x "./gradlew" ]; then
|
|
152
148
|
./gradlew checkstyleMain 2>/dev/null && exit 0
|
|
153
149
|
echo "[juninho:lint-structure] Gradle checkstyle not configured. Add checkstyle plugin to build.gradle."
|
|
154
150
|
exit 0
|
|
@@ -163,33 +159,24 @@ fi
|
|
|
163
159
|
echo "[juninho:lint-structure] No Java build tool found."
|
|
164
160
|
`;
|
|
165
161
|
}
|
|
166
|
-
function lintKotlinBody(
|
|
167
|
-
|
|
168
|
-
? `# Priority linter: ${lintTool}\n`
|
|
169
|
-
: "";
|
|
170
|
-
return `${priority}# Kotlin lint chain: ktlint → detekt → compileKotlin warnings
|
|
171
|
-
if [ -x "./gradlew" ]; then
|
|
172
|
-
# Try ktlint first (most common for Kotlin formatting/style)
|
|
162
|
+
function lintKotlinBody(_lintTool) {
|
|
163
|
+
return `if [ -x "./gradlew" ]; then
|
|
173
164
|
if ./gradlew tasks --all 2>/dev/null | grep -q "ktlintCheck"; then
|
|
174
165
|
./gradlew ktlintCheck
|
|
175
166
|
exit 0
|
|
176
167
|
fi
|
|
177
168
|
|
|
178
|
-
# Try detekt (static analysis)
|
|
179
169
|
if ./gradlew tasks --all 2>/dev/null | grep -q "detekt"; then
|
|
180
170
|
./gradlew detekt
|
|
181
171
|
exit 0
|
|
182
172
|
fi
|
|
183
173
|
|
|
184
|
-
# Fallback: compile with warnings treated as errors
|
|
185
174
|
./gradlew compileKotlin 2>&1
|
|
186
175
|
exit 0
|
|
187
176
|
fi
|
|
188
177
|
|
|
189
178
|
if [ -x "./mvnw" ]; then
|
|
190
|
-
# Maven ktlint plugin
|
|
191
179
|
./mvnw antrun:run@ktlint-check 2>/dev/null && exit 0
|
|
192
|
-
# Fallback to compile
|
|
193
180
|
./mvnw compile 2>&1
|
|
194
181
|
exit 0
|
|
195
182
|
fi
|
|
@@ -199,8 +186,7 @@ echo "[juninho:lint-structure] Add ktlint or detekt Gradle plugin for structural
|
|
|
199
186
|
`;
|
|
200
187
|
}
|
|
201
188
|
function lintGenericBody() {
|
|
202
|
-
return
|
|
203
|
-
if [ -f package.json ]; then
|
|
189
|
+
return `if [ -f package.json ]; then
|
|
204
190
|
if command -v npx >/dev/null 2>&1; then
|
|
205
191
|
npx eslint --max-warnings=0 $FILES 2>/dev/null && exit 0
|
|
206
192
|
fi
|
|
@@ -221,43 +207,36 @@ fi
|
|
|
221
207
|
echo "[juninho:lint-structure] No linter detected. Customize .opencode/scripts/lint-structure.sh."
|
|
222
208
|
`;
|
|
223
209
|
}
|
|
224
|
-
/* ─── Test Related ─── */
|
|
225
210
|
function testRelated(projectType, isKotlin) {
|
|
226
|
-
|
|
211
|
+
switch (projectType) {
|
|
212
|
+
case "node-nextjs":
|
|
213
|
+
case "node-generic":
|
|
214
|
+
return nodeTestRelated();
|
|
215
|
+
case "python":
|
|
216
|
+
return pythonTestRelated();
|
|
217
|
+
case "go":
|
|
218
|
+
return goTestRelated();
|
|
219
|
+
case "java":
|
|
220
|
+
return isKotlin ? kotlinTestRelated() : javaTestRelated();
|
|
221
|
+
case "generic":
|
|
222
|
+
return genericTestRelated();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function nodeTestRelated() {
|
|
226
|
+
return `#!/bin/sh
|
|
227
227
|
set -e
|
|
228
228
|
|
|
229
229
|
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
230
230
|
cd "$ROOT_DIR"
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
printf '%s\\n' "$JUNINHO_STAGED_FILES" | sed '/^$/d' | tr '\\n' ' '
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
FILES="$(staged_files_as_args)"
|
|
232
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
237
233
|
|
|
238
234
|
if [ -z "$FILES" ]; then
|
|
239
235
|
echo "[juninho:test-related] No staged files. Skipping."
|
|
240
236
|
exit 0
|
|
241
237
|
fi
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
case "node-nextjs":
|
|
245
|
-
case "node-generic":
|
|
246
|
-
return header + testNodeBody();
|
|
247
|
-
case "python":
|
|
248
|
-
return header + testPythonBody();
|
|
249
|
-
case "go":
|
|
250
|
-
return header + testGoBody();
|
|
251
|
-
case "java":
|
|
252
|
-
return isKotlin
|
|
253
|
-
? header + testKotlinBody()
|
|
254
|
-
: header + testJavaBody();
|
|
255
|
-
case "generic":
|
|
256
|
-
return header + testGenericBody();
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function testNodeBody() {
|
|
260
|
-
return `has_package_script() {
|
|
238
|
+
|
|
239
|
+
has_package_script() {
|
|
261
240
|
[ -f package.json ] || return 1
|
|
262
241
|
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); process.exit(pkg.scripts && pkg.scripts[process.argv[1]] ? 0 : 1)" "$1" >/dev/null 2>&1
|
|
263
242
|
}
|
|
@@ -278,46 +257,66 @@ if command -v npx >/dev/null 2>&1 && npx --yes vitest --version >/dev/null 2>&1;
|
|
|
278
257
|
fi
|
|
279
258
|
|
|
280
259
|
echo "[juninho:test-related] No related-test command configured."
|
|
281
|
-
echo "[juninho:test-related] Customize .opencode/scripts/test-related.sh or run /j.
|
|
282
|
-
`;
|
|
283
|
-
}
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
260
|
+
echo "[juninho:test-related] Customize .opencode/scripts/test-related.sh or run /j.finish-setup."
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
function pythonTestRelated() {
|
|
264
|
+
return `#!/bin/sh
|
|
265
|
+
set -e
|
|
266
|
+
|
|
267
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
268
|
+
cd "$ROOT_DIR"
|
|
269
|
+
|
|
270
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
271
|
+
|
|
272
|
+
if [ -z "$FILES" ]; then
|
|
273
|
+
echo "[juninho:test-related] No staged files. Skipping."
|
|
274
|
+
exit 0
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
PY_FILES=""
|
|
278
|
+
for f in $FILES; do
|
|
279
|
+
case "$f" in *.py) PY_FILES="$PY_FILES $f" ;; esac
|
|
280
|
+
done
|
|
281
|
+
|
|
282
|
+
if [ -z "$PY_FILES" ]; then
|
|
283
|
+
echo "[juninho:test-related] No Python files staged. Skipping tests."
|
|
284
|
+
exit 0
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
TEST_TARGETS=""
|
|
288
|
+
for f in $PY_FILES; do
|
|
289
|
+
dir=$(dirname "$f")
|
|
290
|
+
base=$(basename "$f" .py)
|
|
291
|
+
for candidate in "\${dir}/test_\${base}.py" "\${dir}/\${base}_test.py" "tests/test_\${base}.py" "tests/\${dir}/test_\${base}.py"; do
|
|
292
|
+
if [ -f "$candidate" ]; then
|
|
293
|
+
TEST_TARGETS="$TEST_TARGETS $candidate"
|
|
294
|
+
fi
|
|
295
|
+
done
|
|
296
|
+
done
|
|
297
|
+
|
|
298
|
+
if [ -n "$TEST_TARGETS" ]; then
|
|
299
|
+
pytest $TEST_TARGETS --no-header -q 2>/dev/null && exit 0
|
|
300
|
+
python -m pytest $TEST_TARGETS --no-header -q 2>/dev/null && exit 0
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
echo "[juninho:test-related] No related tests found for staged Python files."
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
function goTestRelated() {
|
|
307
|
+
return `#!/bin/sh
|
|
308
|
+
set -e
|
|
309
|
+
|
|
310
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
311
|
+
cd "$ROOT_DIR"
|
|
312
|
+
|
|
313
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
314
|
+
|
|
315
|
+
if [ -z "$FILES" ]; then
|
|
316
|
+
echo "[juninho:test-related] No staged files. Skipping."
|
|
317
|
+
exit 0
|
|
318
|
+
fi
|
|
319
|
+
|
|
321
320
|
GO_FILES=""
|
|
322
321
|
for f in $FILES; do
|
|
323
322
|
case "$f" in *.go) GO_FILES="$GO_FILES $f" ;; esac
|
|
@@ -328,7 +327,6 @@ if [ -z "$GO_FILES" ]; then
|
|
|
328
327
|
exit 0
|
|
329
328
|
fi
|
|
330
329
|
|
|
331
|
-
# Extract unique package directories
|
|
332
330
|
PACKAGES=""
|
|
333
331
|
for f in $GO_FILES; do
|
|
334
332
|
pkg="./$(dirname "$f")"
|
|
@@ -341,102 +339,132 @@ done
|
|
|
341
339
|
go test -count=1 $PACKAGES
|
|
342
340
|
`;
|
|
343
341
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
342
|
+
function javaTestRelated() {
|
|
343
|
+
return `#!/bin/sh
|
|
344
|
+
set -e
|
|
345
|
+
|
|
346
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
347
|
+
cd "$ROOT_DIR"
|
|
348
|
+
|
|
349
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
350
|
+
|
|
351
|
+
if [ -z "$FILES" ]; then
|
|
352
|
+
echo "[juninho:test-related] No staged files. Skipping."
|
|
353
|
+
exit 0
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
JAVA_FILES=""
|
|
357
|
+
for f in $FILES; do
|
|
358
|
+
case "$f" in *.java) JAVA_FILES="$JAVA_FILES $f" ;; esac
|
|
359
|
+
done
|
|
360
|
+
|
|
361
|
+
if [ -z "$JAVA_FILES" ]; then
|
|
362
|
+
echo "[juninho:test-related] No Java files staged. Skipping tests."
|
|
363
|
+
exit 0
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
TEST_FILTER=""
|
|
367
|
+
for f in $JAVA_FILES; do
|
|
368
|
+
base=$(basename "$f" .java)
|
|
369
|
+
case "$base" in *Test|*Tests|*IT)
|
|
370
|
+
TEST_FILTER="$TEST_FILTER --tests *\${base}"
|
|
371
|
+
continue
|
|
372
|
+
;;
|
|
373
|
+
esac
|
|
374
|
+
TEST_FILTER="$TEST_FILTER --tests *\${base}Test"
|
|
375
|
+
done
|
|
376
|
+
|
|
377
|
+
if [ -x "./gradlew" ]; then
|
|
378
|
+
./gradlew test $TEST_FILTER 2>/dev/null || ./gradlew test
|
|
379
|
+
exit 0
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
if [ -x "./mvnw" ]; then
|
|
383
|
+
MAVEN_FILTER=""
|
|
384
|
+
for f in $JAVA_FILES; do
|
|
385
|
+
base=$(basename "$f" .java)
|
|
386
|
+
MAVEN_FILTER="\${MAVEN_FILTER},\${base}Test"
|
|
387
|
+
done
|
|
388
|
+
MAVEN_FILTER=$(echo "$MAVEN_FILTER" | sed 's/^,//')
|
|
389
|
+
./mvnw test -Dtest="$MAVEN_FILTER" 2>/dev/null || ./mvnw test
|
|
390
|
+
exit 0
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
echo "[juninho:test-related] No Java build tool found."
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
function kotlinTestRelated() {
|
|
397
|
+
return `#!/bin/sh
|
|
398
|
+
set -e
|
|
399
|
+
|
|
400
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
401
|
+
cd "$ROOT_DIR"
|
|
402
|
+
|
|
403
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
404
|
+
|
|
405
|
+
if [ -z "$FILES" ]; then
|
|
406
|
+
echo "[juninho:test-related] No staged files. Skipping."
|
|
407
|
+
exit 0
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
KT_FILES=""
|
|
411
|
+
JAVA_FILES=""
|
|
412
|
+
for f in $FILES; do
|
|
413
|
+
case "$f" in
|
|
414
|
+
*.kt|*.kts) KT_FILES="$KT_FILES $f" ;;
|
|
415
|
+
*.java) JAVA_FILES="$JAVA_FILES $f" ;;
|
|
416
|
+
esac
|
|
417
|
+
done
|
|
418
|
+
|
|
419
|
+
ALL_FILES="$KT_FILES $JAVA_FILES"
|
|
420
|
+
if [ -z "$(echo "$ALL_FILES" | tr -d ' ')" ]; then
|
|
421
|
+
echo "[juninho:test-related] No Kotlin/Java files staged. Skipping tests."
|
|
422
|
+
exit 0
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
TEST_FILTER=""
|
|
426
|
+
for f in $KT_FILES $JAVA_FILES; do
|
|
427
|
+
ext="\${f##*.}"
|
|
428
|
+
base=$(basename "$f" ".$ext")
|
|
429
|
+
case "$base" in *Test|*Tests|*IT|*Spec)
|
|
430
|
+
TEST_FILTER="$TEST_FILTER --tests *\${base}"
|
|
431
|
+
continue
|
|
432
|
+
;;
|
|
433
|
+
esac
|
|
434
|
+
TEST_FILTER="$TEST_FILTER --tests *\${base}Test"
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
if [ -x "./gradlew" ]; then
|
|
438
|
+
if [ -n "$TEST_FILTER" ]; then
|
|
439
|
+
./gradlew test $TEST_FILTER 2>/dev/null || ./gradlew test
|
|
440
|
+
else
|
|
441
|
+
./gradlew test
|
|
442
|
+
fi
|
|
443
|
+
exit 0
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if [ -x "./mvnw" ]; then
|
|
447
|
+
./mvnw test
|
|
448
|
+
exit 0
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
echo "[juninho:test-related] No Kotlin build tool found."
|
|
452
|
+
`;
|
|
453
|
+
}
|
|
454
|
+
function genericTestRelated() {
|
|
455
|
+
return `#!/bin/sh
|
|
456
|
+
set -e
|
|
457
|
+
|
|
458
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
459
|
+
cd "$ROOT_DIR"
|
|
460
|
+
|
|
461
|
+
FILES="\${JUNINHO_STAGED_FILES:-}"
|
|
462
|
+
|
|
463
|
+
if [ -z "$FILES" ]; then
|
|
464
|
+
echo "[juninho:test-related] No staged files. Skipping."
|
|
465
|
+
exit 0
|
|
466
|
+
fi
|
|
467
|
+
|
|
440
468
|
if [ -f package.json ]; then
|
|
441
469
|
if command -v npx >/dev/null 2>&1; then
|
|
442
470
|
npx jest --findRelatedTests --passWithNoTests $FILES 2>/dev/null && exit 0
|
|
@@ -459,13 +487,34 @@ fi
|
|
|
459
487
|
echo "[juninho:test-related] No test runner detected. Customize .opencode/scripts/test-related.sh."
|
|
460
488
|
`;
|
|
461
489
|
}
|
|
462
|
-
|
|
490
|
+
function runTestScope(projectType, isKotlin) {
|
|
491
|
+
switch (projectType) {
|
|
492
|
+
case "node-nextjs":
|
|
493
|
+
case "node-generic":
|
|
494
|
+
return RUN_TEST_SCOPE_GENERIC_NODE;
|
|
495
|
+
case "python":
|
|
496
|
+
return RUN_TEST_SCOPE_PYTHON;
|
|
497
|
+
case "go":
|
|
498
|
+
return RUN_TEST_SCOPE_GO;
|
|
499
|
+
case "java":
|
|
500
|
+
return isKotlin ? RUN_TEST_SCOPE_KOTLIN : RUN_TEST_SCOPE_JAVA;
|
|
501
|
+
case "generic":
|
|
502
|
+
return RUN_TEST_SCOPE_GENERIC;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
463
505
|
function checkAll(projectType, isKotlin, lintTool) {
|
|
464
506
|
const header = `#!/bin/sh
|
|
465
507
|
set -e
|
|
466
508
|
|
|
467
509
|
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
468
510
|
cd "$ROOT_DIR"
|
|
511
|
+
|
|
512
|
+
sh "$ROOT_DIR/.opencode/scripts/harness-feature-integration.sh" switch-active >/dev/null 2>&1 || true
|
|
513
|
+
|
|
514
|
+
CURRENT_BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
|
|
515
|
+
if [ -n "$CURRENT_BRANCH" ]; then
|
|
516
|
+
echo "[juninho:check-all] Running on branch: $CURRENT_BRANCH"
|
|
517
|
+
fi
|
|
469
518
|
`;
|
|
470
519
|
switch (projectType) {
|
|
471
520
|
case "node-nextjs":
|
|
@@ -476,9 +525,7 @@ cd "$ROOT_DIR"
|
|
|
476
525
|
case "go":
|
|
477
526
|
return header + checkAllGoBody(lintTool);
|
|
478
527
|
case "java":
|
|
479
|
-
return isKotlin
|
|
480
|
-
? header + checkAllKotlinBody(lintTool)
|
|
481
|
-
: header + checkAllJavaBody(lintTool);
|
|
528
|
+
return isKotlin ? header + checkAllKotlinBody(lintTool) : header + checkAllJavaBody(lintTool);
|
|
482
529
|
case "generic":
|
|
483
530
|
return header + checkAllGenericBody();
|
|
484
531
|
}
|
|
@@ -489,35 +536,35 @@ function checkAllNodeBody() {
|
|
|
489
536
|
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); process.exit(pkg.scripts && pkg.scripts[process.argv[1]] ? 0 : 1)" "$1" >/dev/null 2>&1
|
|
490
537
|
}
|
|
491
538
|
|
|
539
|
+
echo "[juninho:check-all] Running formatting checks..."
|
|
540
|
+
if has_package_script "lint"; then
|
|
541
|
+
npm run lint
|
|
542
|
+
elif has_package_script "check:all"; then
|
|
543
|
+
npm run check:all
|
|
544
|
+
fi
|
|
545
|
+
|
|
546
|
+
echo "[juninho:check-all] Running repo-wide tests..."
|
|
492
547
|
if has_package_script "check:all"; then
|
|
493
548
|
npm run check:all
|
|
494
549
|
exit 0
|
|
495
550
|
fi
|
|
496
551
|
|
|
497
|
-
if
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
fi
|
|
501
|
-
|
|
502
|
-
if has_package_script "lint"; then
|
|
503
|
-
npm run lint
|
|
504
|
-
fi
|
|
505
|
-
|
|
506
|
-
if has_package_script "test"; then
|
|
507
|
-
npm test -- --runInBand
|
|
508
|
-
fi
|
|
552
|
+
if has_package_script "typecheck"; then
|
|
553
|
+
npm run typecheck
|
|
554
|
+
fi
|
|
509
555
|
|
|
556
|
+
if has_package_script "test"; then
|
|
557
|
+
npm test
|
|
510
558
|
exit 0
|
|
511
559
|
fi
|
|
512
560
|
|
|
513
561
|
echo "[juninho:check-all] No full verification command configured."
|
|
514
|
-
echo "[juninho:check-all] Customize .opencode/scripts/check-all.sh or run /j.
|
|
562
|
+
echo "[juninho:check-all] Customize .opencode/scripts/check-all.sh or run /j.finish-setup."
|
|
515
563
|
`;
|
|
516
564
|
}
|
|
517
565
|
function checkAllPythonBody(lintTool) {
|
|
518
566
|
const lint = lintTool ?? "ruff";
|
|
519
|
-
return
|
|
520
|
-
echo "[juninho:check-all] Running lint..."
|
|
567
|
+
return `echo "[juninho:check-all] Running formatting checks..."
|
|
521
568
|
if command -v ${lint} >/dev/null 2>&1; then
|
|
522
569
|
${lint} check .
|
|
523
570
|
elif command -v ruff >/dev/null 2>&1; then
|
|
@@ -526,82 +573,215 @@ elif command -v flake8 >/dev/null 2>&1; then
|
|
|
526
573
|
flake8 .
|
|
527
574
|
fi
|
|
528
575
|
|
|
529
|
-
echo "[juninho:check-all] Running tests..."
|
|
530
|
-
|
|
531
|
-
pytest
|
|
532
|
-
elif command -v python >/dev/null 2>&1; then
|
|
533
|
-
python -m pytest
|
|
534
|
-
fi
|
|
576
|
+
echo "[juninho:check-all] Running repo-wide tests..."
|
|
577
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
535
578
|
`;
|
|
536
579
|
}
|
|
537
580
|
function checkAllGoBody(lintTool) {
|
|
538
581
|
const lint = lintTool ?? "golangci-lint";
|
|
539
|
-
return
|
|
540
|
-
echo "[juninho:check-all] Running go vet..."
|
|
582
|
+
return `echo "[juninho:check-all] Running formatting checks..."
|
|
541
583
|
go vet ./...
|
|
542
|
-
|
|
543
|
-
echo "[juninho:check-all] Running lint..."
|
|
544
584
|
if command -v ${lint} >/dev/null 2>&1; then
|
|
545
585
|
${lint} run
|
|
546
586
|
fi
|
|
547
587
|
|
|
548
|
-
echo "[juninho:check-all] Running tests..."
|
|
549
|
-
|
|
588
|
+
echo "[juninho:check-all] Running repo-wide tests..."
|
|
589
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
550
590
|
`;
|
|
551
591
|
}
|
|
552
592
|
function checkAllJavaBody(lintTool) {
|
|
553
|
-
return
|
|
593
|
+
return `echo "[juninho:check-all] Running formatting checks..."
|
|
554
594
|
if [ -x "./gradlew" ]; then
|
|
555
|
-
${lintTool ?
|
|
556
|
-
|
|
595
|
+
${lintTool ? "./gradlew checkstyleMain 2>/dev/null || true\n" : ""}echo "[juninho:check-all] Running repo-wide tests..."
|
|
596
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
557
597
|
exit 0
|
|
558
598
|
fi
|
|
559
599
|
|
|
560
600
|
if [ -x "./mvnw" ]; then
|
|
561
|
-
${lintTool ?
|
|
562
|
-
|
|
601
|
+
${lintTool ? "./mvnw checkstyle:check 2>/dev/null || true\n" : ""}echo "[juninho:check-all] Running repo-wide tests..."
|
|
602
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
563
603
|
exit 0
|
|
564
604
|
fi
|
|
565
605
|
|
|
566
606
|
echo "[juninho:check-all] No Java build tool found."
|
|
567
607
|
`;
|
|
568
608
|
}
|
|
569
|
-
function checkAllKotlinBody(
|
|
570
|
-
return
|
|
609
|
+
function checkAllKotlinBody(_lintTool) {
|
|
610
|
+
return `echo "[juninho:check-all] Running formatting checks..."
|
|
571
611
|
if [ -x "./gradlew" ]; then
|
|
572
|
-
echo "[juninho:check-all] Running Kotlin lint..."
|
|
573
|
-
# Try ktlint first, then detekt
|
|
574
612
|
./gradlew ktlintCheck 2>/dev/null || ./gradlew detekt 2>/dev/null || true
|
|
613
|
+
fi
|
|
614
|
+
|
|
615
|
+
echo "[juninho:check-all] Running repo-wide tests..."
|
|
616
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
function checkAllGenericBody() {
|
|
620
|
+
return `echo "[juninho:check-all] Running repo-wide tests..."
|
|
621
|
+
sh "$ROOT_DIR/.opencode/scripts/run-test-scope.sh" full
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
const RUN_TEST_SCOPE_GENERIC_NODE = `#!/bin/sh
|
|
625
|
+
set -e
|
|
626
|
+
|
|
627
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
628
|
+
cd "$ROOT_DIR"
|
|
629
|
+
|
|
630
|
+
TEST_SCOPE="\${1:-}"
|
|
631
|
+
|
|
632
|
+
if [ -z "$TEST_SCOPE" ]; then
|
|
633
|
+
echo "[juninho:run-test-scope] Missing test scope. Pass related files or 'full'."
|
|
634
|
+
exit 1
|
|
635
|
+
fi
|
|
636
|
+
|
|
637
|
+
has_package_script() {
|
|
638
|
+
[ -f package.json ] || return 1
|
|
639
|
+
node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); process.exit(pkg.scripts && pkg.scripts[process.argv[1]] ? 0 : 1)" "$1" >/dev/null 2>&1
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
643
|
+
if has_package_script "check:all"; then
|
|
644
|
+
npm run check:all
|
|
645
|
+
exit 0
|
|
646
|
+
fi
|
|
647
|
+
if has_package_script "test"; then
|
|
648
|
+
npm test -- --runInBand
|
|
649
|
+
exit 0
|
|
650
|
+
fi
|
|
651
|
+
fi
|
|
575
652
|
|
|
576
|
-
|
|
577
|
-
|
|
653
|
+
if has_package_script "test:related"; then
|
|
654
|
+
npm run test:related -- $TEST_SCOPE
|
|
655
|
+
exit 0
|
|
656
|
+
fi
|
|
657
|
+
|
|
658
|
+
echo "[juninho:run-test-scope] No test scope runner configured."
|
|
659
|
+
`;
|
|
660
|
+
const RUN_TEST_SCOPE_PYTHON = `#!/bin/sh
|
|
661
|
+
set -e
|
|
578
662
|
|
|
579
|
-
|
|
580
|
-
|
|
663
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
664
|
+
cd "$ROOT_DIR"
|
|
665
|
+
|
|
666
|
+
TEST_SCOPE="\${1:-}"
|
|
667
|
+
|
|
668
|
+
if [ -z "$TEST_SCOPE" ]; then
|
|
669
|
+
echo "[juninho:run-test-scope] Missing test scope. Pass a file path, expression, or 'full'."
|
|
670
|
+
exit 1
|
|
671
|
+
fi
|
|
672
|
+
|
|
673
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
674
|
+
pytest 2>/dev/null || python -m pytest
|
|
675
|
+
exit 0
|
|
676
|
+
fi
|
|
677
|
+
|
|
678
|
+
pytest $TEST_SCOPE 2>/dev/null || python -m pytest $TEST_SCOPE
|
|
679
|
+
`;
|
|
680
|
+
const RUN_TEST_SCOPE_GO = `#!/bin/sh
|
|
681
|
+
set -e
|
|
682
|
+
|
|
683
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
684
|
+
cd "$ROOT_DIR"
|
|
685
|
+
|
|
686
|
+
TEST_SCOPE="\${1:-}"
|
|
687
|
+
|
|
688
|
+
if [ -z "$TEST_SCOPE" ] || [ "$TEST_SCOPE" = "full" ]; then
|
|
689
|
+
go test ./...
|
|
690
|
+
exit 0
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
go test -count=1 $TEST_SCOPE
|
|
694
|
+
`;
|
|
695
|
+
const RUN_TEST_SCOPE_JAVA = `#!/bin/sh
|
|
696
|
+
set -e
|
|
697
|
+
|
|
698
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
699
|
+
cd "$ROOT_DIR"
|
|
700
|
+
|
|
701
|
+
TEST_SCOPE="\${1:-}"
|
|
702
|
+
|
|
703
|
+
if [ -z "$TEST_SCOPE" ]; then
|
|
704
|
+
echo "[juninho:run-test-scope] Missing test scope. Pass a Maven/Gradle test selector or 'full'."
|
|
705
|
+
exit 1
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
if [ -x "./gradlew" ]; then
|
|
709
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
710
|
+
./gradlew test
|
|
711
|
+
else
|
|
712
|
+
./gradlew test --tests "$TEST_SCOPE" 2>/dev/null || ./gradlew test
|
|
713
|
+
fi
|
|
581
714
|
exit 0
|
|
582
715
|
fi
|
|
583
716
|
|
|
584
717
|
if [ -x "./mvnw" ]; then
|
|
585
|
-
|
|
586
|
-
|
|
718
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
719
|
+
./mvnw test
|
|
720
|
+
else
|
|
721
|
+
./mvnw test -Dtest="$TEST_SCOPE" 2>/dev/null || ./mvnw test
|
|
722
|
+
fi
|
|
587
723
|
exit 0
|
|
588
724
|
fi
|
|
589
725
|
|
|
590
|
-
echo "[juninho:
|
|
726
|
+
echo "[juninho:run-test-scope] No Java build tool found."
|
|
591
727
|
`;
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
728
|
+
const RUN_TEST_SCOPE_KOTLIN = `#!/bin/sh
|
|
729
|
+
set -e
|
|
730
|
+
|
|
731
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
732
|
+
cd "$ROOT_DIR"
|
|
733
|
+
|
|
734
|
+
TEST_SCOPE="\${1:-}"
|
|
735
|
+
|
|
736
|
+
if [ -z "$TEST_SCOPE" ]; then
|
|
737
|
+
echo "[juninho:run-test-scope] Missing test scope. Pass a Maven/Gradle test selector or 'full'."
|
|
738
|
+
exit 1
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
if [ -x "./gradlew" ]; then
|
|
742
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
743
|
+
./gradlew test
|
|
744
|
+
else
|
|
745
|
+
./gradlew test --tests "$TEST_SCOPE" 2>/dev/null || ./gradlew test
|
|
746
|
+
fi
|
|
747
|
+
exit 0
|
|
748
|
+
fi
|
|
749
|
+
|
|
750
|
+
if [ -x "./mvnw" ]; then
|
|
751
|
+
if [ "$TEST_SCOPE" = "full" ]; then
|
|
752
|
+
./mvnw test
|
|
753
|
+
else
|
|
754
|
+
./mvnw test -Dtest="$TEST_SCOPE" 2>/dev/null || ./mvnw test
|
|
755
|
+
fi
|
|
756
|
+
exit 0
|
|
757
|
+
fi
|
|
758
|
+
|
|
759
|
+
echo "[juninho:run-test-scope] No Kotlin build tool found."
|
|
760
|
+
`;
|
|
761
|
+
const RUN_TEST_SCOPE_GENERIC = `#!/bin/sh
|
|
762
|
+
set -e
|
|
763
|
+
|
|
764
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
765
|
+
cd "$ROOT_DIR"
|
|
766
|
+
|
|
767
|
+
TEST_SCOPE="\${1:-}"
|
|
768
|
+
|
|
769
|
+
if [ -f package.json ] && [ "$TEST_SCOPE" = "full" ]; then
|
|
596
770
|
npm test 2>/dev/null && exit 0
|
|
597
771
|
fi
|
|
598
772
|
|
|
599
773
|
if command -v pytest >/dev/null 2>&1; then
|
|
600
|
-
|
|
774
|
+
if [ "$TEST_SCOPE" = "full" ] || [ -z "$TEST_SCOPE" ]; then
|
|
775
|
+
pytest && exit 0
|
|
776
|
+
fi
|
|
777
|
+
pytest $TEST_SCOPE && exit 0
|
|
601
778
|
fi
|
|
602
779
|
|
|
603
780
|
if [ -f go.mod ]; then
|
|
604
|
-
|
|
781
|
+
if [ "$TEST_SCOPE" = "full" ] || [ -z "$TEST_SCOPE" ]; then
|
|
782
|
+
go test ./... && exit 0
|
|
783
|
+
fi
|
|
784
|
+
go test -count=1 $TEST_SCOPE && exit 0
|
|
605
785
|
fi
|
|
606
786
|
|
|
607
787
|
if [ -x "./gradlew" ]; then
|
|
@@ -612,8 +792,593 @@ if [ -x "./mvnw" ]; then
|
|
|
612
792
|
./mvnw test 2>/dev/null && exit 0
|
|
613
793
|
fi
|
|
614
794
|
|
|
615
|
-
echo "[juninho:
|
|
616
|
-
|
|
795
|
+
echo "[juninho:run-test-scope] No test scope runner detected."
|
|
796
|
+
`;
|
|
797
|
+
const SCAFFOLD_SPEC_STATE = `#!/bin/sh
|
|
798
|
+
set -e
|
|
799
|
+
|
|
800
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
801
|
+
FEATURE_SLUG="\${1:-}"
|
|
802
|
+
|
|
803
|
+
[ -n "$FEATURE_SLUG" ] || {
|
|
804
|
+
echo "Usage: $0 <feature-slug>" >&2
|
|
805
|
+
exit 1
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
STATE_DIR="$ROOT_DIR/docs/specs/$FEATURE_SLUG/state"
|
|
809
|
+
TEMPLATE_PATH="$ROOT_DIR/.opencode/templates/spec-state-readme.md"
|
|
810
|
+
|
|
811
|
+
mkdir -p "$STATE_DIR/tasks" "$STATE_DIR/sessions"
|
|
812
|
+
|
|
813
|
+
if [ -f "$TEMPLATE_PATH" ] && [ ! -f "$STATE_DIR/README.md" ]; then
|
|
814
|
+
sed "s/{feature-slug}/$FEATURE_SLUG/g" "$TEMPLATE_PATH" > "$STATE_DIR/README.md"
|
|
815
|
+
fi
|
|
617
816
|
`;
|
|
817
|
+
const HARNESS_FEATURE_INTEGRATION = `#!/bin/sh
|
|
818
|
+
set -e
|
|
819
|
+
|
|
820
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
821
|
+
cd "$ROOT_DIR"
|
|
822
|
+
TAB="$(printf '\t')"
|
|
823
|
+
|
|
824
|
+
if command -v node >/dev/null 2>&1; then
|
|
825
|
+
JS_RUNTIME="node"
|
|
826
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
827
|
+
JS_RUNTIME="bun"
|
|
828
|
+
else
|
|
829
|
+
echo "[juninho:feature-integration] Missing JavaScript runtime (node or bun)" >&2
|
|
830
|
+
exit 1
|
|
831
|
+
fi
|
|
832
|
+
|
|
833
|
+
fail() {
|
|
834
|
+
echo "[juninho:feature-integration] $*" >&2
|
|
835
|
+
exit 1
|
|
618
836
|
}
|
|
837
|
+
|
|
838
|
+
current_branch() {
|
|
839
|
+
git symbolic-ref --quiet --short HEAD 2>/dev/null || true
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
state_file_path() {
|
|
843
|
+
local_name="$1"
|
|
844
|
+
printf '%s/.opencode/state/%s\n' "$ROOT_DIR" "$local_name"
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
default_base_ref() {
|
|
848
|
+
if git show-ref --verify --quiet "refs/remotes/origin/main"; then
|
|
849
|
+
printf '%s\n' "refs/remotes/origin/main"
|
|
850
|
+
return
|
|
851
|
+
fi
|
|
852
|
+
if git show-ref --verify --quiet "refs/remotes/origin/master"; then
|
|
853
|
+
printf '%s\n' "refs/remotes/origin/master"
|
|
854
|
+
return
|
|
855
|
+
fi
|
|
856
|
+
|
|
857
|
+
branch="$(current_branch)"
|
|
858
|
+
[ -n "$branch" ] || fail "Detached HEAD. Provide an explicit base branch."
|
|
859
|
+
printf '%s\n' "$branch"
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
normalize_base_branch() {
|
|
863
|
+
input="$1"
|
|
864
|
+
case "$input" in
|
|
865
|
+
refs/remotes/origin/*)
|
|
866
|
+
printf '%s\n' "\${input#refs/remotes/origin/}"
|
|
867
|
+
;;
|
|
868
|
+
origin/*)
|
|
869
|
+
printf '%s\n' "\${input#origin/}"
|
|
870
|
+
;;
|
|
871
|
+
refs/heads/*)
|
|
872
|
+
printf '%s\n' "\${input#refs/heads/}"
|
|
873
|
+
;;
|
|
874
|
+
*)
|
|
875
|
+
printf '%s\n' "$input"
|
|
876
|
+
;;
|
|
877
|
+
esac
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
resolve_base_ref() {
|
|
881
|
+
input="$1"
|
|
882
|
+
if [ -z "$input" ]; then
|
|
883
|
+
default_base_ref
|
|
884
|
+
return
|
|
885
|
+
fi
|
|
886
|
+
|
|
887
|
+
case "$input" in
|
|
888
|
+
refs/remotes/*|refs/heads/*)
|
|
889
|
+
printf '%s\n' "$input"
|
|
890
|
+
;;
|
|
891
|
+
origin/*)
|
|
892
|
+
printf '%s\n' "refs/remotes/$input"
|
|
893
|
+
;;
|
|
894
|
+
*)
|
|
895
|
+
if git show-ref --verify --quiet "refs/remotes/origin/$input"; then
|
|
896
|
+
printf '%s\n' "refs/remotes/origin/$input"
|
|
897
|
+
else
|
|
898
|
+
printf '%s\n' "$input"
|
|
899
|
+
fi
|
|
900
|
+
;;
|
|
901
|
+
esac
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
task_branch_name() {
|
|
905
|
+
printf 'feature/%s-task-%s' "$1" "$2"
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
find_existing_feature_commit() {
|
|
909
|
+
feature_branch="$1"
|
|
910
|
+
validated_commit="$2"
|
|
911
|
+
git log "$feature_branch" --format='%H' --grep="cherry picked from commit $validated_commit" -n 1 2>/dev/null || true
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
feature_branch_name() {
|
|
915
|
+
printf 'feature/%s' "$1"
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
manifest_path() {
|
|
919
|
+
printf '%s/docs/specs/%s/state/integration-state.json' "$ROOT_DIR" "$1"
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
ensure_manifest_dir() {
|
|
923
|
+
sh "$ROOT_DIR/.opencode/scripts/scaffold-spec-state.sh" "$1"
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
json_read_field() {
|
|
927
|
+
MANIFEST_PATH="$1" FIELD_PATH="$2" "$JS_RUNTIME" - <<'NODE'
|
|
928
|
+
const fs = require("fs")
|
|
929
|
+
|
|
930
|
+
const manifestPath = process.env.MANIFEST_PATH
|
|
931
|
+
const fieldPath = process.env.FIELD_PATH || ""
|
|
932
|
+
|
|
933
|
+
if (!manifestPath || !fs.existsSync(manifestPath)) process.exit(1)
|
|
934
|
+
|
|
935
|
+
const data = JSON.parse(fs.readFileSync(manifestPath, "utf8"))
|
|
936
|
+
let value = data
|
|
937
|
+
for (const key of fieldPath.split(".").filter(Boolean)) {
|
|
938
|
+
if (value == null || !(key in value)) process.exit(1)
|
|
939
|
+
value = value[key]
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (value == null) process.exit(1)
|
|
943
|
+
if (typeof value === "string") {
|
|
944
|
+
process.stdout.write(value)
|
|
945
|
+
process.exit(0)
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
process.stdout.write(JSON.stringify(value))
|
|
949
|
+
NODE
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
parse_active_feature_slug() {
|
|
953
|
+
execution_state="$(state_file_path execution-state.md)"
|
|
954
|
+
[ -f "$execution_state" ] || return 0
|
|
955
|
+
grep "Feature slug" "$execution_state" 2>/dev/null | head -n 1 | cut -d':' -f2 | tr -d ' '
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
cmd="\${1:-}"
|
|
959
|
+
|
|
960
|
+
case "$cmd" in
|
|
961
|
+
ensure)
|
|
962
|
+
feature_slug="\${2:-}"
|
|
963
|
+
[ -n "$feature_slug" ] || fail "Usage: ensure <feature-slug> [base-branch]"
|
|
964
|
+
|
|
965
|
+
base_ref="$(resolve_base_ref "\${3:-}")"
|
|
966
|
+
base_branch="$(normalize_base_branch "$base_ref")"
|
|
967
|
+
|
|
968
|
+
ensure_manifest_dir "$feature_slug"
|
|
969
|
+
feature_branch="$(feature_branch_name "$feature_slug")"
|
|
970
|
+
base_sha="$(git rev-parse "$base_ref" 2>/dev/null)" || fail "Unknown base branch/ref: $base_ref"
|
|
971
|
+
|
|
972
|
+
if ! git show-ref --verify --quiet "refs/heads/$feature_branch"; then
|
|
973
|
+
git branch "$feature_branch" "$base_sha" >/dev/null
|
|
974
|
+
fi
|
|
975
|
+
|
|
976
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
977
|
+
FEATURE_SLUG="$feature_slug" FEATURE_BRANCH="$feature_branch" BASE_BRANCH="$base_branch" BASE_REF="$base_ref" BASE_SHA="$base_sha" MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
978
|
+
const fs = require("fs")
|
|
979
|
+
const path = require("path")
|
|
980
|
+
|
|
981
|
+
const manifestPath = process.env.MANIFEST_PATH
|
|
982
|
+
const featureSlug = process.env.FEATURE_SLUG
|
|
983
|
+
const featureBranch = process.env.FEATURE_BRANCH
|
|
984
|
+
const baseBranch = process.env.BASE_BRANCH
|
|
985
|
+
const baseRef = process.env.BASE_REF
|
|
986
|
+
const baseSha = process.env.BASE_SHA
|
|
987
|
+
|
|
988
|
+
const now = new Date().toISOString()
|
|
989
|
+
const next = fs.existsSync(manifestPath)
|
|
990
|
+
? JSON.parse(fs.readFileSync(manifestPath, "utf8"))
|
|
991
|
+
: {}
|
|
992
|
+
|
|
993
|
+
const manifest = {
|
|
994
|
+
featureSlug,
|
|
995
|
+
featureBranch,
|
|
996
|
+
baseBranch,
|
|
997
|
+
baseRef,
|
|
998
|
+
baseStartPoint: next.baseStartPoint || baseSha,
|
|
999
|
+
createdAt: next.createdAt || now,
|
|
1000
|
+
lastUpdatedAt: now,
|
|
1001
|
+
tasks: next.tasks || {},
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
1005
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8")
|
|
1006
|
+
NODE
|
|
1007
|
+
|
|
1008
|
+
printf '%s\n' "$feature_branch"
|
|
1009
|
+
;;
|
|
1010
|
+
|
|
1011
|
+
print-task-base)
|
|
1012
|
+
feature_slug="\${2:-}"
|
|
1013
|
+
depends_csv="\${3:-}"
|
|
1014
|
+
[ -n "$feature_slug" ] || fail "Usage: print-task-base <feature-slug> [depends-csv]"
|
|
1015
|
+
|
|
1016
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1017
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1018
|
+
|
|
1019
|
+
feature_branch="$(json_read_field "$manifest" "featureBranch")" || fail "Unable to read feature branch"
|
|
1020
|
+
base_start_point="$(json_read_field "$manifest" "baseStartPoint")" || fail "Unable to read base start point"
|
|
1021
|
+
|
|
1022
|
+
if [ -n "$depends_csv" ]; then
|
|
1023
|
+
DEPENDS_CSV="$depends_csv" MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
1024
|
+
const fs = require("fs")
|
|
1025
|
+
|
|
1026
|
+
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST_PATH, "utf8"))
|
|
1027
|
+
for (const dep of (process.env.DEPENDS_CSV || "").split(",").map((value) => value.trim()).filter(Boolean)) {
|
|
1028
|
+
const entry = manifest.tasks?.[dep]
|
|
1029
|
+
const status = entry?.integration?.status
|
|
1030
|
+
if (entry && (!status || status === "pending")) {
|
|
1031
|
+
throw new Error("Dependency " + dep + " is not integrated yet")
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
NODE
|
|
1035
|
+
printf '%s\n' "$feature_branch"
|
|
1036
|
+
exit 0
|
|
1037
|
+
fi
|
|
1038
|
+
|
|
1039
|
+
printf '%s\n' "$base_start_point"
|
|
1040
|
+
;;
|
|
1041
|
+
|
|
1042
|
+
prepare-task-branch)
|
|
1043
|
+
feature_slug="\${2:-}"
|
|
1044
|
+
task_id="\${3:-}"
|
|
1045
|
+
depends_csv="\${4:-}"
|
|
1046
|
+
worktree_directory="\${5:-}"
|
|
1047
|
+
|
|
1048
|
+
[ -n "$feature_slug" ] || fail "Usage: prepare-task-branch <feature-slug> <task-id> [depends-csv] [worktree-directory]"
|
|
1049
|
+
[ -n "$task_id" ] || fail "Missing task id"
|
|
1050
|
+
|
|
1051
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1052
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1053
|
+
|
|
1054
|
+
task_branch="$(task_branch_name "$feature_slug" "$task_id")"
|
|
1055
|
+
task_base="$(sh "$0" print-task-base "$feature_slug" "$depends_csv")"
|
|
1056
|
+
|
|
1057
|
+
if [ -n "$worktree_directory" ]; then
|
|
1058
|
+
if [ -d "$worktree_directory" ]; then
|
|
1059
|
+
printf '%s\n' "$task_branch"
|
|
1060
|
+
exit 0
|
|
1061
|
+
fi
|
|
1062
|
+
|
|
1063
|
+
parent_dir=$(dirname "$worktree_directory")
|
|
1064
|
+
[ -d "$parent_dir" ] || fail "Missing worktree parent directory: $parent_dir"
|
|
1065
|
+
|
|
1066
|
+
if git show-ref --verify --quiet "refs/heads/$task_branch"; then
|
|
1067
|
+
git worktree add "$worktree_directory" "$task_branch" >/dev/null
|
|
1068
|
+
else
|
|
1069
|
+
git worktree add -b "$task_branch" "$worktree_directory" "$task_base" >/dev/null
|
|
1070
|
+
fi
|
|
1071
|
+
printf '%s\n' "$task_branch"
|
|
1072
|
+
exit 0
|
|
1073
|
+
fi
|
|
1074
|
+
|
|
1075
|
+
if git show-ref --verify --quiet "refs/heads/$task_branch"; then
|
|
1076
|
+
git switch "$task_branch" >/dev/null
|
|
1077
|
+
else
|
|
1078
|
+
git switch -c "$task_branch" "$task_base" >/dev/null
|
|
1079
|
+
fi
|
|
1080
|
+
printf '%s\n' "$task_branch"
|
|
1081
|
+
;;
|
|
1082
|
+
|
|
1083
|
+
switch)
|
|
1084
|
+
feature_slug="\${2:-}"
|
|
1085
|
+
[ -n "$feature_slug" ] || fail "Usage: switch <feature-slug>"
|
|
1086
|
+
|
|
1087
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1088
|
+
if [ -f "$manifest" ]; then
|
|
1089
|
+
feature_branch="$(json_read_field "$manifest" "featureBranch")" || fail "Unable to read feature branch from $manifest"
|
|
1090
|
+
else
|
|
1091
|
+
feature_branch="$(feature_branch_name "$feature_slug")"
|
|
1092
|
+
fi
|
|
1093
|
+
|
|
1094
|
+
git switch "$feature_branch" >/dev/null
|
|
1095
|
+
printf '%s\n' "$feature_branch"
|
|
1096
|
+
;;
|
|
1097
|
+
|
|
1098
|
+
switch-active)
|
|
1099
|
+
feature_slug="$(parse_active_feature_slug)"
|
|
1100
|
+
[ -n "$feature_slug" ] || exit 0
|
|
1101
|
+
sh "$0" switch "$feature_slug"
|
|
1102
|
+
;;
|
|
1103
|
+
|
|
1104
|
+
record-task)
|
|
1105
|
+
feature_slug="\${2:-}"
|
|
1106
|
+
task_id="\${3:-}"
|
|
1107
|
+
task_branch="\${4:-}"
|
|
1108
|
+
validated_commit="\${5:-}"
|
|
1109
|
+
attempt="\${6:-}"
|
|
1110
|
+
worktree_directory="\${7:-}"
|
|
1111
|
+
task_label="\${8:-}"
|
|
1112
|
+
|
|
1113
|
+
[ -n "$feature_slug" ] || fail "Usage: record-task <feature-slug> <task-id> <task-branch> <validated-commit> <attempt> [worktree] [label]"
|
|
1114
|
+
[ -n "$task_id" ] || fail "Missing task id"
|
|
1115
|
+
[ -n "$task_branch" ] || fail "Missing task branch"
|
|
1116
|
+
[ -n "$validated_commit" ] || fail "Missing validated commit"
|
|
1117
|
+
[ -n "$attempt" ] || fail "Missing attempt"
|
|
1118
|
+
|
|
1119
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1120
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1121
|
+
|
|
1122
|
+
task_tip="$(git rev-parse "refs/heads/$task_branch" 2>/dev/null || printf '%s' "$validated_commit")"
|
|
1123
|
+
|
|
1124
|
+
FEATURE_SLUG="$feature_slug" TASK_ID="$task_id" TASK_BRANCH="$task_branch" VALIDATED_COMMIT="$validated_commit" TASK_TIP="$task_tip" TASK_ATTEMPT="$attempt" WORKTREE_DIRECTORY="$worktree_directory" TASK_LABEL="$task_label" MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
1125
|
+
const fs = require("fs")
|
|
1126
|
+
|
|
1127
|
+
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST_PATH, "utf8"))
|
|
1128
|
+
const existing = manifest.tasks?.[process.env.TASK_ID]
|
|
1129
|
+
|
|
1130
|
+
manifest.tasks = manifest.tasks || {}
|
|
1131
|
+
manifest.tasks[process.env.TASK_ID] = {
|
|
1132
|
+
...(existing || {}),
|
|
1133
|
+
taskID: process.env.TASK_ID,
|
|
1134
|
+
taskBranch: process.env.TASK_BRANCH,
|
|
1135
|
+
validatedCommit: process.env.VALIDATED_COMMIT,
|
|
1136
|
+
taskTip: process.env.TASK_TIP,
|
|
1137
|
+
attempt: Number(process.env.TASK_ATTEMPT),
|
|
1138
|
+
worktreeDirectory: process.env.WORKTREE_DIRECTORY || "",
|
|
1139
|
+
taskLabel: process.env.TASK_LABEL || "",
|
|
1140
|
+
recordedAt: new Date().toISOString(),
|
|
1141
|
+
integration: existing?.integration || { status: "pending" },
|
|
1142
|
+
}
|
|
1143
|
+
manifest.lastUpdatedAt = new Date().toISOString()
|
|
1144
|
+
fs.writeFileSync(process.env.MANIFEST_PATH, JSON.stringify(manifest, null, 2) + "\n", "utf8")
|
|
1145
|
+
NODE
|
|
1146
|
+
|
|
1147
|
+
printf '%s\n' "$validated_commit"
|
|
1148
|
+
;;
|
|
1149
|
+
|
|
1150
|
+
integrate-task)
|
|
1151
|
+
feature_slug="\${2:-}"
|
|
1152
|
+
task_id="\${3:-}"
|
|
1153
|
+
|
|
1154
|
+
[ -n "$feature_slug" ] || fail "Usage: integrate-task <feature-slug> <task-id>"
|
|
1155
|
+
[ -n "$task_id" ] || fail "Missing task id"
|
|
1156
|
+
|
|
1157
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1158
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1159
|
+
|
|
1160
|
+
feature_branch="$(json_read_field "$manifest" "featureBranch")" || fail "Unable to read feature branch"
|
|
1161
|
+
validated_commit="$(json_read_field "$manifest" "tasks.$task_id.validatedCommit")" || fail "Task $task_id has no validated commit"
|
|
1162
|
+
task_branch="$(json_read_field "$manifest" "tasks.$task_id.taskBranch")" || fail "Task $task_id has no task branch"
|
|
1163
|
+
|
|
1164
|
+
git switch "$feature_branch" >/dev/null
|
|
1165
|
+
|
|
1166
|
+
integration_status="already-contained"
|
|
1167
|
+
integration_method="ancestor"
|
|
1168
|
+
if git merge-base --is-ancestor "$validated_commit" HEAD; then
|
|
1169
|
+
integrated_commit="$validated_commit"
|
|
1170
|
+
elif git cherry "$feature_branch" "$validated_commit" 2>/dev/null | grep -q "^- $validated_commit$"; then
|
|
1171
|
+
integration_method="patch-equivalent"
|
|
1172
|
+
integrated_commit="$(find_existing_feature_commit "$feature_branch" "$validated_commit")"
|
|
1173
|
+
if [ -z "$integrated_commit" ]; then
|
|
1174
|
+
integrated_commit="$(git rev-parse HEAD)"
|
|
1175
|
+
fi
|
|
1176
|
+
elif git merge-base --is-ancestor HEAD "$validated_commit"; then
|
|
1177
|
+
git merge --ff-only "$validated_commit" >/dev/null
|
|
1178
|
+
integration_status="ff-only"
|
|
1179
|
+
integration_method="ff-only"
|
|
1180
|
+
integrated_commit="$(git rev-parse HEAD)"
|
|
1181
|
+
else
|
|
1182
|
+
git cherry-pick -x "$validated_commit" >/dev/null
|
|
1183
|
+
integration_status="cherry-picked"
|
|
1184
|
+
integration_method="cherry-pick"
|
|
1185
|
+
integrated_commit="$(git rev-parse HEAD)"
|
|
1186
|
+
fi
|
|
1187
|
+
|
|
1188
|
+
TASK_ID="$task_id" INTEGRATED_STATUS="$integration_status" INTEGRATION_METHOD="$integration_method" INTEGRATED_COMMIT="$integrated_commit" FEATURE_BRANCH="$feature_branch" TASK_BRANCH="$task_branch" MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
1189
|
+
const fs = require("fs")
|
|
1190
|
+
|
|
1191
|
+
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST_PATH, "utf8"))
|
|
1192
|
+
const task = manifest.tasks?.[process.env.TASK_ID]
|
|
1193
|
+
|
|
1194
|
+
if (!task) throw new Error("Task " + process.env.TASK_ID + " is missing from manifest")
|
|
1195
|
+
|
|
1196
|
+
task.integration = {
|
|
1197
|
+
status: process.env.INTEGRATED_STATUS,
|
|
1198
|
+
method: process.env.INTEGRATION_METHOD,
|
|
1199
|
+
featureBranch: process.env.FEATURE_BRANCH,
|
|
1200
|
+
taskBranch: process.env.TASK_BRANCH,
|
|
1201
|
+
integratedAt: new Date().toISOString(),
|
|
1202
|
+
integratedCommit: process.env.INTEGRATED_COMMIT,
|
|
1203
|
+
}
|
|
1204
|
+
manifest.lastUpdatedAt = new Date().toISOString()
|
|
1205
|
+
fs.writeFileSync(process.env.MANIFEST_PATH, JSON.stringify(manifest, null, 2) + "\n", "utf8")
|
|
1206
|
+
NODE
|
|
1207
|
+
|
|
1208
|
+
printf '%s\n' "$integrated_commit"
|
|
1209
|
+
;;
|
|
1210
|
+
|
|
1211
|
+
cleanup)
|
|
1212
|
+
feature_slug="\${2:-}"
|
|
1213
|
+
[ -n "$feature_slug" ] || fail "Usage: cleanup <feature-slug>"
|
|
1214
|
+
|
|
1215
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1216
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1217
|
+
|
|
1218
|
+
feature_branch="$(json_read_field "$manifest" "featureBranch")" || fail "Unable to read feature branch"
|
|
1219
|
+
git switch "$feature_branch" >/dev/null
|
|
1220
|
+
|
|
1221
|
+
cleanup_rows="$(MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
1222
|
+
const fs = require("fs")
|
|
1223
|
+
|
|
1224
|
+
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST_PATH, "utf8"))
|
|
1225
|
+
for (const [taskId, task] of Object.entries(manifest.tasks || {})) {
|
|
1226
|
+
if (!task?.integration?.status || task.integration.status === "pending") continue
|
|
1227
|
+
process.stdout.write(taskId + "\t" + (task.taskBranch || "") + "\t" + (task.worktreeDirectory || "") + "\n")
|
|
1228
|
+
}
|
|
1229
|
+
NODE
|
|
1230
|
+
)"
|
|
1231
|
+
|
|
1232
|
+
if [ -n "$cleanup_rows" ]; then
|
|
1233
|
+
printf '%s\n' "$cleanup_rows" | while IFS="$TAB" read -r task_id task_branch worktree_directory; do
|
|
1234
|
+
if [ -n "$worktree_directory" ] && [ -d "$worktree_directory" ]; then
|
|
1235
|
+
git worktree remove "$worktree_directory" >/dev/null
|
|
1236
|
+
fi
|
|
1237
|
+
|
|
1238
|
+
if [ -n "$task_branch" ] && [ "$task_branch" != "$feature_branch" ] && git show-ref --verify --quiet "refs/heads/$task_branch"; then
|
|
1239
|
+
git branch -d "$task_branch" >/dev/null
|
|
1240
|
+
fi
|
|
1241
|
+
done
|
|
1242
|
+
fi
|
|
1243
|
+
|
|
1244
|
+
MANIFEST_PATH="$manifest" "$JS_RUNTIME" - <<'NODE'
|
|
1245
|
+
const fs = require("fs")
|
|
1246
|
+
|
|
1247
|
+
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST_PATH, "utf8"))
|
|
1248
|
+
for (const task of Object.values(manifest.tasks || {})) {
|
|
1249
|
+
if (!task.integration?.status || task.integration.status === "pending") continue
|
|
1250
|
+
task.cleanup = {
|
|
1251
|
+
status: "done",
|
|
1252
|
+
cleanedAt: new Date().toISOString(),
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
manifest.lastUpdatedAt = new Date().toISOString()
|
|
1256
|
+
fs.writeFileSync(process.env.MANIFEST_PATH, JSON.stringify(manifest, null, 2) + "\n", "utf8")
|
|
1257
|
+
NODE
|
|
1258
|
+
|
|
1259
|
+
printf '%s\n' "$feature_branch"
|
|
1260
|
+
;;
|
|
1261
|
+
|
|
1262
|
+
print-feature-branch)
|
|
1263
|
+
feature_slug="\${2:-}"
|
|
1264
|
+
[ -n "$feature_slug" ] || fail "Usage: print-feature-branch <feature-slug>"
|
|
1265
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1266
|
+
if [ -f "$manifest" ]; then
|
|
1267
|
+
json_read_field "$manifest" "featureBranch"
|
|
1268
|
+
else
|
|
1269
|
+
feature_branch_name "$feature_slug"
|
|
1270
|
+
fi
|
|
1271
|
+
printf '\n'
|
|
1272
|
+
;;
|
|
1273
|
+
|
|
1274
|
+
print-base-branch)
|
|
1275
|
+
feature_slug="\${2:-}"
|
|
1276
|
+
[ -n "$feature_slug" ] || fail "Usage: print-base-branch <feature-slug>"
|
|
1277
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1278
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1279
|
+
json_read_field "$manifest" "baseBranch"
|
|
1280
|
+
printf '\n'
|
|
1281
|
+
;;
|
|
1282
|
+
|
|
1283
|
+
print-base-ref)
|
|
1284
|
+
feature_slug="\${2:-}"
|
|
1285
|
+
[ -n "$feature_slug" ] || fail "Usage: print-base-ref <feature-slug>"
|
|
1286
|
+
manifest="$(manifest_path "$feature_slug")"
|
|
1287
|
+
[ -f "$manifest" ] || fail "Missing integration manifest: $manifest"
|
|
1288
|
+
if json_read_field "$manifest" "baseRef" >/dev/null 2>&1; then
|
|
1289
|
+
json_read_field "$manifest" "baseRef"
|
|
1290
|
+
else
|
|
1291
|
+
json_read_field "$manifest" "baseBranch"
|
|
1292
|
+
fi
|
|
1293
|
+
printf '\n'
|
|
1294
|
+
;;
|
|
1295
|
+
|
|
1296
|
+
*)
|
|
1297
|
+
fail "Unknown command: \${cmd:-<empty>}"
|
|
1298
|
+
;;
|
|
1299
|
+
esac
|
|
1300
|
+
`;
|
|
1301
|
+
// ─── Build Verify ────────────────────────────────────────────────────────────
|
|
1302
|
+
function buildVerify(projectType, isKotlin) {
|
|
1303
|
+
if (projectType === "java") {
|
|
1304
|
+
return `#!/bin/sh
|
|
1305
|
+
set -e
|
|
1306
|
+
|
|
1307
|
+
ROOT_DIR="\${TARGET_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
1308
|
+
cd "$ROOT_DIR"
|
|
1309
|
+
|
|
1310
|
+
echo "[juninho:build-verify] Running build verification..."
|
|
1311
|
+
|
|
1312
|
+
if [ -x "./gradlew" ]; then
|
|
1313
|
+
${isKotlin ? './gradlew compileKotlin compileTestKotlin' : './gradlew compileJava compileTestJava'}
|
|
1314
|
+
exit 0
|
|
1315
|
+
fi
|
|
1316
|
+
|
|
1317
|
+
if [ -x "./mvnw" ]; then
|
|
1318
|
+
./mvnw -q -DskipTests compile test-compile
|
|
1319
|
+
exit 0
|
|
1320
|
+
fi
|
|
1321
|
+
|
|
1322
|
+
echo "[juninho:build-verify] No build tool found."
|
|
1323
|
+
exit 1
|
|
1324
|
+
`;
|
|
1325
|
+
}
|
|
1326
|
+
if (projectType === "go") {
|
|
1327
|
+
return `#!/bin/sh
|
|
1328
|
+
set -e
|
|
1329
|
+
|
|
1330
|
+
ROOT_DIR="\${TARGET_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
1331
|
+
cd "$ROOT_DIR"
|
|
1332
|
+
|
|
1333
|
+
echo "[juninho:build-verify] Running build verification..."
|
|
1334
|
+
go build ./...
|
|
1335
|
+
`;
|
|
1336
|
+
}
|
|
1337
|
+
// Node/Python/Generic — lightweight build check
|
|
1338
|
+
return `#!/bin/sh
|
|
1339
|
+
set -e
|
|
1340
|
+
|
|
1341
|
+
ROOT_DIR="\${TARGET_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
1342
|
+
cd "$ROOT_DIR"
|
|
1343
|
+
|
|
1344
|
+
echo "[juninho:build-verify] Running build verification..."
|
|
1345
|
+
|
|
1346
|
+
if [ -f "package.json" ]; then
|
|
1347
|
+
if npm run --silent build --if-present 2>/dev/null; then
|
|
1348
|
+
exit 0
|
|
1349
|
+
fi
|
|
1350
|
+
if npx tsc --noEmit 2>/dev/null; then
|
|
1351
|
+
exit 0
|
|
1352
|
+
fi
|
|
1353
|
+
fi
|
|
1354
|
+
|
|
1355
|
+
echo "[juninho:build-verify] No build verification available — skipping."
|
|
1356
|
+
exit 0
|
|
1357
|
+
`;
|
|
1358
|
+
}
|
|
1359
|
+
// ─── Install Git Hooks ───────────────────────────────────────────────────────
|
|
1360
|
+
const INSTALL_GIT_HOOKS = `#!/bin/sh
|
|
1361
|
+
set -e
|
|
1362
|
+
|
|
1363
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
1364
|
+
HOOKS_DIR="$ROOT_DIR/.git/hooks"
|
|
1365
|
+
SOURCE_HOOK="$ROOT_DIR/.opencode/hooks/pre-commit"
|
|
1366
|
+
TARGET_HOOK="$HOOKS_DIR/pre-commit"
|
|
1367
|
+
|
|
1368
|
+
if [ ! -d "$HOOKS_DIR" ]; then
|
|
1369
|
+
echo "[juninho:install-hooks] Missing git hooks directory: $HOOKS_DIR" >&2
|
|
1370
|
+
exit 1
|
|
1371
|
+
fi
|
|
1372
|
+
|
|
1373
|
+
if [ ! -f "$SOURCE_HOOK" ]; then
|
|
1374
|
+
echo "[juninho:install-hooks] Missing source hook: $SOURCE_HOOK" >&2
|
|
1375
|
+
exit 1
|
|
1376
|
+
fi
|
|
1377
|
+
|
|
1378
|
+
chmod +x "$SOURCE_HOOK"
|
|
1379
|
+
ln -sf ../../.opencode/hooks/pre-commit "$TARGET_HOOK"
|
|
1380
|
+
chmod +x "$TARGET_HOOK"
|
|
1381
|
+
|
|
1382
|
+
echo "[juninho:install-hooks] Installed pre-commit hook at $TARGET_HOOK"
|
|
1383
|
+
`;
|
|
619
1384
|
//# sourceMappingURL=support-scripts.js.map
|