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