@mmerterden/multi-agent-pipeline 12.7.0 → 12.8.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/CHANGELOG.md +126 -0
- package/install/_common.mjs +48 -0
- package/install/_dev-only-files.mjs +125 -5
- package/install/claude.mjs +14 -8
- package/install/copilot.mjs +5 -8
- package/package.json +17 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
- package/pipeline/lib/credential-store.sh +20 -0
- package/pipeline/multi-agent-refs/_account-picker.md +1 -1
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
- package/pipeline/multi-agent-refs/phases/operations.md +28 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -0
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +49 -4
- package/pipeline/schemas/prefs.schema.json +6 -0
- package/pipeline/scripts/_smoke-root.sh +61 -0
- package/pipeline/scripts/audit-log.sh +25 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
- package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
- package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
- package/pipeline/eval/golden-tasks/README.md +0 -65
- package/pipeline/eval/intent-cases.json +0 -40
- package/pipeline/eval/run-metrics-fixture.json +0 -93
- package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
- package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
- package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
- package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
- package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
- package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
- package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
- package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
- package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
- package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
- package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
- package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
- package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
- package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
- package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
- package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
- package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
- package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
- package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
- package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
- package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
- package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
- package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
- package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
- package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
- package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
- package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
- package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
- package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
- package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
- package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
- package/pipeline/eval/triage/README.md +0 -54
- package/pipeline/scripts/benchmark-phase-0.sh +0 -128
- package/pipeline/scripts/check-md-links.mjs +0 -88
- package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
- package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
- package/pipeline/scripts/eval-intent.mjs +0 -107
- package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
- package/pipeline/scripts/eval-triage.mjs +0 -171
- package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
- package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
- package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
- package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
- package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
- package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
- package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
- package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
- package/pipeline/scripts/lint-skills.mjs +0 -154
- package/pipeline/scripts/run-smokes.mjs +0 -130
- package/pipeline/scripts/scorecard.mjs +0 -258
- package/pipeline/scripts/smoke-add-detail.sh +0 -137
- package/pipeline/scripts/smoke-agent-guard.sh +0 -74
- package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
- package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
- package/pipeline/scripts/smoke-ask-choice.sh +0 -42
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
- package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
- package/pipeline/scripts/smoke-changelog-version.sh +0 -47
- package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
- package/pipeline/scripts/smoke-channels-flow.sh +0 -130
- package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
- package/pipeline/scripts/smoke-clarify.sh +0 -148
- package/pipeline/scripts/smoke-command-inventory.sh +0 -81
- package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
- package/pipeline/scripts/smoke-community-gates.sh +0 -75
- package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
- package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
- package/pipeline/scripts/smoke-context-budget.sh +0 -72
- package/pipeline/scripts/smoke-cost-budget.sh +0 -70
- package/pipeline/scripts/smoke-cost-summary.sh +0 -139
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
- package/pipeline/scripts/smoke-description-tr.sh +0 -82
- package/pipeline/scripts/smoke-dev-critic.sh +0 -144
- package/pipeline/scripts/smoke-diff-explain.sh +0 -147
- package/pipeline/scripts/smoke-diff-risk.sh +0 -190
- package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
- package/pipeline/scripts/smoke-eval-live.sh +0 -136
- package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
- package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
- package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
- package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
- package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
- package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
- package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
- package/pipeline/scripts/smoke-generate-issue.sh +0 -120
- package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
- package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
- package/pipeline/scripts/smoke-install-layout.sh +0 -248
- package/pipeline/scripts/smoke-intent-guard.sh +0 -86
- package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
- package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
- package/pipeline/scripts/smoke-keychain.sh +0 -158
- package/pipeline/scripts/smoke-language-axis.sh +0 -109
- package/pipeline/scripts/smoke-learning-curve.sh +0 -61
- package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
- package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
- package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
- package/pipeline/scripts/smoke-md-links.sh +0 -8
- package/pipeline/scripts/smoke-md2confluence.sh +0 -126
- package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
- package/pipeline/scripts/smoke-migrate-state.sh +0 -102
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
- package/pipeline/scripts/smoke-model-fallback.sh +0 -89
- package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
- package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
- package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
- package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
- package/pipeline/scripts/smoke-pack-contents.sh +0 -140
- package/pipeline/scripts/smoke-pat-audit.sh +0 -128
- package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
- package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
- package/pipeline/scripts/smoke-phase-banner.sh +0 -101
- package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
- package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
- package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
- package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
- package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
- package/pipeline/scripts/smoke-plan-safety.sh +0 -139
- package/pipeline/scripts/smoke-plan-todos.sh +0 -196
- package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
- package/pipeline/scripts/smoke-pre-commit.sh +0 -170
- package/pipeline/scripts/smoke-pref-migration.sh +0 -226
- package/pipeline/scripts/smoke-prefs-language.sh +0 -134
- package/pipeline/scripts/smoke-progress-contract.sh +0 -127
- package/pipeline/scripts/smoke-prune-logs.sh +0 -137
- package/pipeline/scripts/smoke-purge.sh +0 -138
- package/pipeline/scripts/smoke-push-retry.sh +0 -75
- package/pipeline/scripts/smoke-repo-map.sh +0 -300
- package/pipeline/scripts/smoke-review-readiness.sh +0 -92
- package/pipeline/scripts/smoke-review-watch.sh +0 -146
- package/pipeline/scripts/smoke-routines.sh +0 -84
- package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
- package/pipeline/scripts/smoke-run-metrics.sh +0 -50
- package/pipeline/scripts/smoke-search.sh +0 -187
- package/pipeline/scripts/smoke-shadow-git.sh +0 -224
- package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
- package/pipeline/scripts/smoke-skill-language.sh +0 -83
- package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
- package/pipeline/scripts/smoke-skill-scan.sh +0 -198
- package/pipeline/scripts/smoke-source-parity.sh +0 -85
- package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
- package/pipeline/scripts/smoke-sync-parity.sh +0 -92
- package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
- package/pipeline/scripts/smoke-telemetry.sh +0 -147
- package/pipeline/scripts/smoke-test-gap.sh +0 -183
- package/pipeline/scripts/smoke-token-budget.sh +0 -67
- package/pipeline/scripts/smoke-token-preflight.sh +0 -82
- package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
- package/pipeline/scripts/smoke-triage-memory.sh +0 -174
- package/pipeline/scripts/smoke-update-check.sh +0 -135
- package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
- package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
- package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
- package/pipeline/scripts/smoke-validator-gates.sh +0 -164
- package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
- package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
- package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
- package/pipeline/scripts/smoke-work-summary.sh +0 -163
- package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
- package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
- package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
- package/pipeline/scripts/smoke-write-state.sh +0 -159
- package/pipeline/scripts/sync-parity-check.sh +0 -135
- package/pipeline/scripts/test-gap-rules/android.json +0 -25
- package/pipeline/scripts/test-gap-rules/ios.json +0 -34
- package/pipeline/scripts/test-gap-rules/node.json +0 -29
- package/pipeline/scripts/test-gap-rules/python.json +0 -25
- package/pipeline/scripts/validate-schemas.mjs +0 -88
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-lib-scripts.sh - sanity checks for picker library scripts.
|
|
3
|
-
# Verifies each lib emits the expected JSON shape so the picker pipeline can
|
|
4
|
-
# rely on it. Network-dependent paths (real Jira/GH fetch) are skipped; only
|
|
5
|
-
# offline-detectable behavior is asserted.
|
|
6
|
-
#
|
|
7
|
-
# Covered:
|
|
8
|
-
# credential-store.sh doctor, platform detection, get/set/delete cycle (macOS only)
|
|
9
|
-
# account-resolver.sh returns JSON array with required fields
|
|
10
|
-
# issue-fetcher.sh input-kind detection + maturity object shape
|
|
11
|
-
# multi-repo-pipeline.sh bridge → schema-valid agent-state.json v2.1.0
|
|
12
|
-
|
|
13
|
-
set -uo pipefail
|
|
14
|
-
|
|
15
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
17
|
-
LIB_DIR="$REPO_ROOT/pipeline/lib"
|
|
18
|
-
|
|
19
|
-
PASS=0
|
|
20
|
-
FAIL=0
|
|
21
|
-
|
|
22
|
-
ok() { echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
23
|
-
fail() { echo " ✗ $1" >&2; FAIL=$((FAIL+1)); }
|
|
24
|
-
|
|
25
|
-
# --- credential-store.sh -----------------------------------------------------
|
|
26
|
-
echo "→ credential-store.sh"
|
|
27
|
-
CRED="$LIB_DIR/credential-store.sh"
|
|
28
|
-
if [ ! -x "$CRED" ]; then
|
|
29
|
-
fail "credential-store.sh missing or not executable"
|
|
30
|
-
else
|
|
31
|
-
PLATFORM=$("$CRED" platform 2>/dev/null || echo "")
|
|
32
|
-
case "$PLATFORM" in
|
|
33
|
-
macos|windows|linux|unknown) ok "platform detected: $PLATFORM" ;;
|
|
34
|
-
*) fail "unexpected platform output: $PLATFORM" ;;
|
|
35
|
-
esac
|
|
36
|
-
|
|
37
|
-
if "$CRED" doctor 2>&1 | grep -qE '^Backend:'; then
|
|
38
|
-
ok "doctor reports backend"
|
|
39
|
-
else
|
|
40
|
-
fail "doctor missing Backend: line"
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
# macOS only: full get/set/delete round-trip with throwaway key
|
|
44
|
-
if [ "$PLATFORM" = "macos" ]; then
|
|
45
|
-
KEY="multi-agent-smoke-test-$$"
|
|
46
|
-
VAL="smoke-value-$(date +%s)"
|
|
47
|
-
if "$CRED" set "$KEY" "$VAL" 2>/dev/null; then
|
|
48
|
-
readback=$("$CRED" get "$KEY" 2>/dev/null || echo "")
|
|
49
|
-
if [ "$readback" = "$VAL" ]; then
|
|
50
|
-
ok "set/get round-trip works"
|
|
51
|
-
else
|
|
52
|
-
fail "get returned '$readback' instead of '$VAL'"
|
|
53
|
-
fi
|
|
54
|
-
"$CRED" delete "$KEY" 2>/dev/null
|
|
55
|
-
empty=$("$CRED" get "$KEY" 2>/dev/null || echo "")
|
|
56
|
-
if [ -z "$empty" ]; then
|
|
57
|
-
ok "delete clears the entry"
|
|
58
|
-
else
|
|
59
|
-
fail "entry still present after delete"
|
|
60
|
-
fi
|
|
61
|
-
else
|
|
62
|
-
fail "set failed on macOS"
|
|
63
|
-
fi
|
|
64
|
-
fi
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# --- account-resolver.sh -----------------------------------------------------
|
|
68
|
-
echo "→ account-resolver.sh"
|
|
69
|
-
RESOLVER="$LIB_DIR/account-resolver.sh"
|
|
70
|
-
if [ ! -x "$RESOLVER" ]; then
|
|
71
|
-
fail "account-resolver.sh missing or not executable"
|
|
72
|
-
else
|
|
73
|
-
out=$("$RESOLVER" 2>/dev/null || echo "[]")
|
|
74
|
-
if printf '%s' "$out" | python3 -c 'import json,sys; json.loads(sys.stdin.read())' 2>/dev/null; then
|
|
75
|
-
ok "emits valid JSON"
|
|
76
|
-
else
|
|
77
|
-
fail "JSON output invalid: $(printf '%s' "$out" | head -c 200)"
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
# When non-empty, every account must have the required fields
|
|
81
|
-
python3 - <<PY
|
|
82
|
-
import json, sys
|
|
83
|
-
try:
|
|
84
|
-
data = json.loads('''$out''')
|
|
85
|
-
except Exception as e:
|
|
86
|
-
print(f"PARSE_FAIL:{e}")
|
|
87
|
-
sys.exit(0)
|
|
88
|
-
|
|
89
|
-
if not data:
|
|
90
|
-
print("EMPTY")
|
|
91
|
-
sys.exit(0)
|
|
92
|
-
|
|
93
|
-
required = {"id","label","prefix","providers","tokens","userKeys","users","jiraHost","bitbucketHost","isWork"}
|
|
94
|
-
missing = []
|
|
95
|
-
for i, acc in enumerate(data):
|
|
96
|
-
miss = required - set(acc.keys())
|
|
97
|
-
if miss:
|
|
98
|
-
missing.append((i, sorted(miss)))
|
|
99
|
-
if missing:
|
|
100
|
-
print(f"MISSING_FIELDS:{missing}")
|
|
101
|
-
else:
|
|
102
|
-
print(f"OK:{len(data)} accounts, all required fields present")
|
|
103
|
-
PY
|
|
104
|
-
status=$(python3 - <<PY
|
|
105
|
-
import json
|
|
106
|
-
data = json.loads('''$out''')
|
|
107
|
-
print("ok" if data and all({"id","label","prefix","providers","tokens","userKeys","users","jiraHost","bitbucketHost","isWork"} <= set(a.keys()) for a in data) else ("empty" if not data else "fields-missing"))
|
|
108
|
-
PY
|
|
109
|
-
)
|
|
110
|
-
case "$status" in
|
|
111
|
-
ok) ok "all accounts have required schema fields" ;;
|
|
112
|
-
empty) ok "no accounts on this machine (skipping schema check)" ;;
|
|
113
|
-
fields-missing) fail "some accounts missing required fields" ;;
|
|
114
|
-
esac
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
# --- issue-fetcher.sh: input-kind detection (no network) ---------------------
|
|
118
|
-
echo "→ issue-fetcher.sh"
|
|
119
|
-
FETCHER="$LIB_DIR/issue-fetcher.sh"
|
|
120
|
-
if [ ! -x "$FETCHER" ]; then
|
|
121
|
-
fail "issue-fetcher.sh missing or not executable"
|
|
122
|
-
else
|
|
123
|
-
# detect_kind is internal; we exercise via freetext + jira-id paths that
|
|
124
|
-
# don't hit the network. Freetext is the safest offline assertion.
|
|
125
|
-
out=$("$FETCHER" "Login crash on iOS 17" 2>/dev/null || echo "")
|
|
126
|
-
if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="freetext", d["kind"]; assert d["maturity"]["score"] is None, "freetext score should be None"' 2>/dev/null; then
|
|
127
|
-
ok "freetext path emits kind=freetext + maturity.score=None"
|
|
128
|
-
else
|
|
129
|
-
fail "freetext path output unexpected: $(printf '%s' "$out" | head -c 200)"
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
# Jira-id detection without token (fetch will fail, but kind detection should still work)
|
|
133
|
-
out=$(ACCOUNT_JIRA_TOKEN_KEY="" ACCOUNT_JIRA_HOST="" "$FETCHER" "PROJ-1" 2>/dev/null || echo "")
|
|
134
|
-
if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="jira", d["kind"]; assert d["key"]=="PROJ-1", d["key"]' 2>/dev/null; then
|
|
135
|
-
ok "jira-id path emits kind=jira + key parsed"
|
|
136
|
-
else
|
|
137
|
-
fail "jira-id path output unexpected"
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
# Bare "#316" and "316" must land in the gh-num branch (needsRepoPicker
|
|
141
|
-
# descriptor when no default owner/repo), NOT gh-short with an empty repo
|
|
142
|
-
# prefix that builds https://github.com/<owner>//issues/316.
|
|
143
|
-
for bare in "#316" "316"; do
|
|
144
|
-
out=$(ACCOUNT_DEFAULT_OWNER="" ACCOUNT_DEFAULT_REPO="" "$FETCHER" "$bare" 2>/dev/null || echo "")
|
|
145
|
-
if printf '%s' "$out" | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); assert d["kind"]=="github", d["kind"]; assert d["key"]=="316", d["key"]; assert d.get("needsRepoPicker") is True, d' 2>/dev/null; then
|
|
146
|
-
ok "bare '$bare' classified as gh-num (key=316, needsRepoPicker)"
|
|
147
|
-
else
|
|
148
|
-
fail "bare '$bare' misclassified: $(printf '%s' "$out" | head -c 200)"
|
|
149
|
-
fi
|
|
150
|
-
done
|
|
151
|
-
fi
|
|
152
|
-
|
|
153
|
-
# --- multi-repo-pipeline.sh: bridge --------------------------------------
|
|
154
|
-
echo "→ multi-repo-pipeline.sh bridge"
|
|
155
|
-
MRP="$LIB_DIR/multi-repo-pipeline.sh"
|
|
156
|
-
if [ ! -x "$MRP" ]; then
|
|
157
|
-
fail "multi-repo-pipeline.sh missing or not executable"
|
|
158
|
-
else
|
|
159
|
-
TMP=$(mktemp -d)
|
|
160
|
-
cat > "$TMP/state.json" <<EOF
|
|
161
|
-
{
|
|
162
|
-
"taskId": "PROJ-1-smoke-$$",
|
|
163
|
-
"shortId": 1,
|
|
164
|
-
"branch": "feature/PROJ-1",
|
|
165
|
-
"baseBranch": "develop",
|
|
166
|
-
"issueRef": {"kind":"jira","key":"PROJ-1","title":"Smoke test"},
|
|
167
|
-
"primary": {"name":"my-app","cloneUrl":"git@github.com:org/my-app.git","provider":"github"},
|
|
168
|
-
"extras": [{"name":"common","cloneUrl":"git@github.com:org/common.git","provider":"github"}],
|
|
169
|
-
"worktreeRoot": "$TMP/worktree",
|
|
170
|
-
"commitType": "feat",
|
|
171
|
-
"identity": {"name":"Smoke","email":"smoke@example.com"}
|
|
172
|
-
}
|
|
173
|
-
EOF
|
|
174
|
-
bridged=$("$MRP" bridge "$TMP/state.json" 2>/dev/null || echo "")
|
|
175
|
-
if [ -z "$bridged" ] || [ ! -f "$bridged" ]; then
|
|
176
|
-
fail "bridge did not write agent-state.json"
|
|
177
|
-
else
|
|
178
|
-
if python3 - <<PY 2>/dev/null
|
|
179
|
-
import json
|
|
180
|
-
import jsonschema # may not be installed in CI; fall back to shape check
|
|
181
|
-
PY
|
|
182
|
-
then
|
|
183
|
-
python3 - "$bridged" "$REPO_ROOT/pipeline/schemas/agent-state.schema.json" <<'PY' && ok "bridge output schema-valid (jsonschema)" || fail "bridge output schema-invalid"
|
|
184
|
-
import json, sys
|
|
185
|
-
from jsonschema import Draft202012Validator
|
|
186
|
-
data = json.load(open(sys.argv[1]))
|
|
187
|
-
schema = json.load(open(sys.argv[2]))
|
|
188
|
-
errs = list(Draft202012Validator(schema).iter_errors(data))
|
|
189
|
-
if errs:
|
|
190
|
-
for e in errs: print("✗", "/".join(str(p) for p in e.absolute_path), e.message)
|
|
191
|
-
sys.exit(1)
|
|
192
|
-
PY
|
|
193
|
-
else
|
|
194
|
-
# Fallback: shape-only check
|
|
195
|
-
if python3 - "$bridged" <<'PY' 2>/dev/null
|
|
196
|
-
import json, sys
|
|
197
|
-
d = json.load(open(sys.argv[1]))
|
|
198
|
-
required = {"schemaVersion","taskId","shortId","project","branch","baseBranch","currentPhase","status","startedAt","phases","identity","projects"}
|
|
199
|
-
missing = required - set(d.keys())
|
|
200
|
-
assert not missing, f"missing: {missing}"
|
|
201
|
-
assert d["schemaVersion"] == "2.1.0"
|
|
202
|
-
assert len(d["projects"]) == 2
|
|
203
|
-
PY
|
|
204
|
-
then
|
|
205
|
-
ok "bridge output has required fields (jsonschema unavailable; shape check)"
|
|
206
|
-
else
|
|
207
|
-
fail "bridge output missing required fields"
|
|
208
|
-
fi
|
|
209
|
-
fi
|
|
210
|
-
rm -rf "$(dirname "$bridged")"
|
|
211
|
-
fi
|
|
212
|
-
rm -rf "$TMP"
|
|
213
|
-
fi
|
|
214
|
-
|
|
215
|
-
# --- multi-repo-pipeline.sh: prepare + commit + teardown (offline) -----------
|
|
216
|
-
# Uses local bare-repo fixtures so we can exercise prepare/commit/teardown
|
|
217
|
-
# without network access. push and pr remain unverified (network-bound).
|
|
218
|
-
echo "→ multi-repo-pipeline.sh prepare/commit/teardown (offline fixtures)"
|
|
219
|
-
MRP="$LIB_DIR/multi-repo-pipeline.sh"
|
|
220
|
-
if [ ! -x "$MRP" ]; then
|
|
221
|
-
fail "multi-repo-pipeline.sh not executable (already reported above)"
|
|
222
|
-
else
|
|
223
|
-
FIXTURE_ROOT=$(mktemp -d)
|
|
224
|
-
REPO_A_BARE="$FIXTURE_ROOT/repo-a.git"
|
|
225
|
-
REPO_B_BARE="$FIXTURE_ROOT/repo-b.git"
|
|
226
|
-
WORKTREE_ROOT="$FIXTURE_ROOT/wt"
|
|
227
|
-
|
|
228
|
-
# Build two bare-repo fixtures with one initial commit each.
|
|
229
|
-
for bare in "$REPO_A_BARE" "$REPO_B_BARE"; do
|
|
230
|
-
git init --quiet --bare -b main "$bare"
|
|
231
|
-
seed=$(mktemp -d)
|
|
232
|
-
git -C "$seed" init --quiet -b main
|
|
233
|
-
echo "init" > "$seed/README.md"
|
|
234
|
-
git -C "$seed" add README.md
|
|
235
|
-
git -C "$seed" -c user.email=t@e.com -c user.name=T commit --quiet -m "init"
|
|
236
|
-
git -C "$seed" remote add origin "$bare"
|
|
237
|
-
git -C "$seed" push --quiet origin main
|
|
238
|
-
rm -rf "$seed"
|
|
239
|
-
done
|
|
240
|
-
|
|
241
|
-
cat > "$FIXTURE_ROOT/state.json" <<EOF
|
|
242
|
-
{
|
|
243
|
-
"taskId":"OFFLINE-1",
|
|
244
|
-
"shortId":1,
|
|
245
|
-
"branch":"feature/OFFLINE-1",
|
|
246
|
-
"baseBranch":"main",
|
|
247
|
-
"issueRef":{"kind":"jira","key":"OFFLINE-1","title":"smoke fixture"},
|
|
248
|
-
"primary":{"name":"repo-a","cloneUrl":"$REPO_A_BARE","provider":"github"},
|
|
249
|
-
"extras":[{"name":"repo-b","cloneUrl":"$REPO_B_BARE","provider":"github"}],
|
|
250
|
-
"worktreeRoot":"$WORKTREE_ROOT",
|
|
251
|
-
"commitType":"feat",
|
|
252
|
-
"identity":{"name":"T","email":"t@e.com"}
|
|
253
|
-
}
|
|
254
|
-
EOF
|
|
255
|
-
|
|
256
|
-
# prepare
|
|
257
|
-
if "$MRP" prepare "$FIXTURE_ROOT/state.json" >/dev/null 2>&1; then
|
|
258
|
-
if [ -d "$WORKTREE_ROOT/repo-a/.git" ] && [ -d "$WORKTREE_ROOT/repo-b/.git" ]; then
|
|
259
|
-
ok "prepare clones every repo into worktreeRoot"
|
|
260
|
-
else
|
|
261
|
-
fail "prepare did not create both worktrees"
|
|
262
|
-
fi
|
|
263
|
-
branch_a=$(git -C "$WORKTREE_ROOT/repo-a" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
264
|
-
branch_b=$(git -C "$WORKTREE_ROOT/repo-b" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
265
|
-
if [ "$branch_a" = "feature/OFFLINE-1" ] && [ "$branch_b" = "feature/OFFLINE-1" ]; then
|
|
266
|
-
ok "prepare checks out the task branch in every repo"
|
|
267
|
-
else
|
|
268
|
-
fail "prepare branch mismatch: a=$branch_a b=$branch_b"
|
|
269
|
-
fi
|
|
270
|
-
else
|
|
271
|
-
fail "prepare exited non-zero on offline fixture"
|
|
272
|
-
fi
|
|
273
|
-
|
|
274
|
-
# commit
|
|
275
|
-
echo "change-a" > "$WORKTREE_ROOT/repo-a/feature.txt"
|
|
276
|
-
echo "change-b" > "$WORKTREE_ROOT/repo-b/feature.txt"
|
|
277
|
-
git -C "$WORKTREE_ROOT/repo-a" config user.email t@e.com >/dev/null 2>&1
|
|
278
|
-
git -C "$WORKTREE_ROOT/repo-a" config user.name T >/dev/null 2>&1
|
|
279
|
-
git -C "$WORKTREE_ROOT/repo-b" config user.email t@e.com >/dev/null 2>&1
|
|
280
|
-
git -C "$WORKTREE_ROOT/repo-b" config user.name T >/dev/null 2>&1
|
|
281
|
-
|
|
282
|
-
if "$MRP" commit "$FIXTURE_ROOT/state.json" >/dev/null 2>&1; then
|
|
283
|
-
msg_a=$(git -C "$WORKTREE_ROOT/repo-a" log -1 --pretty=%s 2>/dev/null || echo "")
|
|
284
|
-
msg_b=$(git -C "$WORKTREE_ROOT/repo-b" log -1 --pretty=%s 2>/dev/null || echo "")
|
|
285
|
-
if printf '%s' "$msg_a" | grep -q '\[OFFLINE-1\]' && printf '%s' "$msg_b" | grep -q '\[OFFLINE-1\]'; then
|
|
286
|
-
ok "commit creates one commit per repo with [JIRA-KEY] suffix"
|
|
287
|
-
else
|
|
288
|
-
fail "commit messages missing key suffix: a=$msg_a b=$msg_b"
|
|
289
|
-
fi
|
|
290
|
-
else
|
|
291
|
-
fail "commit exited non-zero"
|
|
292
|
-
fi
|
|
293
|
-
|
|
294
|
-
# bridge (re-verify schema-valid output uses the repo names)
|
|
295
|
-
bridged=$("$MRP" bridge "$FIXTURE_ROOT/state.json" 2>/dev/null || echo "")
|
|
296
|
-
if [ -n "$bridged" ] && python3 -c '
|
|
297
|
-
import json, sys
|
|
298
|
-
d = json.load(open(sys.argv[1]))
|
|
299
|
-
assert len(d["projects"]) == 2
|
|
300
|
-
assert d["projects"][0]["project"] == "repo-a"
|
|
301
|
-
assert d["projects"][1]["project"] == "repo-b"
|
|
302
|
-
' "$bridged" 2>/dev/null; then
|
|
303
|
-
ok "bridge populates projects[] from primary + extras"
|
|
304
|
-
rm -rf "$(dirname "$bridged")"
|
|
305
|
-
else
|
|
306
|
-
fail "bridge output missing projects[] entries"
|
|
307
|
-
fi
|
|
308
|
-
|
|
309
|
-
# teardown guard: a worktreeRoot of "." (the current project root) must be
|
|
310
|
-
# refused loudly, never rm -rf'd.
|
|
311
|
-
UNSAFE_STATE="$FIXTURE_ROOT/unsafe-state.json"
|
|
312
|
-
sed 's|"worktreeRoot":"[^"]*"|"worktreeRoot":"."|' "$FIXTURE_ROOT/state.json" > "$UNSAFE_STATE"
|
|
313
|
-
GUARD_DIR=$(mktemp -d)
|
|
314
|
-
( cd "$GUARD_DIR" && "$MRP" teardown "$UNSAFE_STATE" ) >/dev/null 2>&1
|
|
315
|
-
guard_rc=$?
|
|
316
|
-
if [ "$guard_rc" -ne 0 ] && [ -d "$GUARD_DIR" ]; then
|
|
317
|
-
ok "teardown refuses worktreeRoot='.' (exit=$guard_rc, dir intact)"
|
|
318
|
-
else
|
|
319
|
-
fail "teardown did not refuse unsafe worktreeRoot='.' (exit=$guard_rc)"
|
|
320
|
-
fi
|
|
321
|
-
rm -rf "$GUARD_DIR"
|
|
322
|
-
|
|
323
|
-
# teardown
|
|
324
|
-
"$MRP" teardown "$FIXTURE_ROOT/state.json" >/dev/null 2>&1
|
|
325
|
-
if [ ! -d "$WORKTREE_ROOT" ]; then
|
|
326
|
-
ok "teardown removes worktreeRoot"
|
|
327
|
-
else
|
|
328
|
-
fail "teardown left $WORKTREE_ROOT in place"
|
|
329
|
-
fi
|
|
330
|
-
|
|
331
|
-
rm -rf "$FIXTURE_ROOT"
|
|
332
|
-
fi
|
|
333
|
-
|
|
334
|
-
# --- channels-multi-repo.sh: render fan-out (offline) ------------------------
|
|
335
|
-
echo "→ channels-multi-repo.sh renderers"
|
|
336
|
-
CMR="$LIB_DIR/channels-multi-repo.sh"
|
|
337
|
-
if [ ! -x "$CMR" ]; then
|
|
338
|
-
fail "channels-multi-repo.sh missing or not executable"
|
|
339
|
-
else
|
|
340
|
-
TMP=$(mktemp -d)
|
|
341
|
-
cat > "$TMP/multi.json" <<'EOF'
|
|
342
|
-
{
|
|
343
|
-
"schemaVersion":"2.1.0","taskId":"X","shortId":1,"project":"my-app",
|
|
344
|
-
"branch":"feature/X","baseBranch":"develop","currentPhase":6,"status":"in_progress",
|
|
345
|
-
"startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
|
|
346
|
-
"projects":[
|
|
347
|
-
{"project":"my-app","projectRoot":"/tmp/a","branch":"feature/X","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":{"number":42,"url":"https://github.com/org/my-app/pull/42"}},
|
|
348
|
-
{"project":"common","projectRoot":"/tmp/b","branch":"feature/X","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":{"number":17,"url":"https://github.com/org/common/pull/17"}}
|
|
349
|
-
]
|
|
350
|
-
}
|
|
351
|
-
EOF
|
|
352
|
-
cat > "$TMP/single.json" <<'EOF'
|
|
353
|
-
{
|
|
354
|
-
"schemaVersion":"2.1.0","taskId":"Y","shortId":1,"project":"my-app",
|
|
355
|
-
"branch":"feature/Y","baseBranch":"develop","currentPhase":6,"status":"in_progress",
|
|
356
|
-
"startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
|
|
357
|
-
"pr":{"number":1,"url":"https://github.com/org/my-app/pull/1"}
|
|
358
|
-
}
|
|
359
|
-
EOF
|
|
360
|
-
echo "body content" > "$TMP/body.md"
|
|
361
|
-
|
|
362
|
-
# targets: multi-repo gives 2 entries with cross-links populated
|
|
363
|
-
count=$("$CMR" targets "$TMP/multi.json" 2>/dev/null | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d))')
|
|
364
|
-
if [ "$count" = "2" ]; then
|
|
365
|
-
ok "targets emits one entry per project (multi-repo)"
|
|
366
|
-
else
|
|
367
|
-
fail "targets count $count != 2"
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
# render-pr: extras body must mention 'Part of:' primary URL
|
|
371
|
-
rendered=$("$CMR" render-pr "$TMP/multi.json" "$TMP/body.md" "common" 2>/dev/null)
|
|
372
|
-
if printf '%s' "$rendered" | grep -q "Part of: https://github.com/org/my-app"; then
|
|
373
|
-
ok "render-pr injects 'Part of:' link on extras"
|
|
374
|
-
else
|
|
375
|
-
fail "render-pr missing 'Part of:' on extras"
|
|
376
|
-
fi
|
|
377
|
-
rendered=$("$CMR" render-pr "$TMP/multi.json" "$TMP/body.md" "my-app" 2>/dev/null)
|
|
378
|
-
if printf '%s' "$rendered" | grep -q "Related: https://github.com/org/common"; then
|
|
379
|
-
ok "render-pr injects 'Related:' link on primary"
|
|
380
|
-
else
|
|
381
|
-
fail "render-pr missing 'Related:' on primary"
|
|
382
|
-
fi
|
|
383
|
-
|
|
384
|
-
# render-jira: bulleted PR list at top
|
|
385
|
-
rendered=$("$CMR" render-jira "$TMP/multi.json" "$TMP/body.md" 2>/dev/null)
|
|
386
|
-
bullet_count=$(printf '%s' "$rendered" | grep -c '^\* PR:' || true)
|
|
387
|
-
if [ "$bullet_count" = "2" ]; then
|
|
388
|
-
ok "render-jira prepends bulleted PR list (2 entries)"
|
|
389
|
-
else
|
|
390
|
-
fail "render-jira bullet count $bullet_count != 2"
|
|
391
|
-
fi
|
|
392
|
-
|
|
393
|
-
# render-jira/-conf with ZERO PR urls: must pass the body through unchanged
|
|
394
|
-
# (the old `grep -c || echo 0` idiom emitted "0\n0" here and prepended
|
|
395
|
-
# bogus "* PR:" bullets to the posted body).
|
|
396
|
-
cat > "$TMP/noprs.json" <<'EOF'
|
|
397
|
-
{
|
|
398
|
-
"schemaVersion":"2.1.0","taskId":"Z","shortId":1,"project":"my-app",
|
|
399
|
-
"branch":"feature/Z","baseBranch":"develop","currentPhase":6,"status":"in_progress",
|
|
400
|
-
"startedAt":"2026-04-27T12:00:00Z","phases":{},"identity":{"name":"T","email":"t@e.com"},
|
|
401
|
-
"projects":[
|
|
402
|
-
{"project":"my-app","projectRoot":"/tmp/a","branch":"feature/Z","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":null},
|
|
403
|
-
{"project":"common","projectRoot":"/tmp/b","branch":"feature/Z","baseBranch":"develop","identity":{"name":"T","email":"t@e.com"},"pr":null}
|
|
404
|
-
]
|
|
405
|
-
}
|
|
406
|
-
EOF
|
|
407
|
-
rendered=$("$CMR" render-jira "$TMP/noprs.json" "$TMP/body.md" 2>/dev/null)
|
|
408
|
-
if [ "$rendered" = "body content" ]; then
|
|
409
|
-
ok "render-jira with zero PR urls passes body through unchanged"
|
|
410
|
-
else
|
|
411
|
-
fail "render-jira with zero PR urls mutated body: $rendered"
|
|
412
|
-
fi
|
|
413
|
-
rendered=$("$CMR" render-conf "$TMP/noprs.json" "$TMP/body.md" 2>/dev/null)
|
|
414
|
-
if [ "$rendered" = "body content" ]; then
|
|
415
|
-
ok "render-conf with zero PR urls passes body through unchanged"
|
|
416
|
-
else
|
|
417
|
-
fail "render-conf with zero PR urls mutated body: $rendered"
|
|
418
|
-
fi
|
|
419
|
-
|
|
420
|
-
# render-conf: ## Related PRs heading
|
|
421
|
-
rendered=$("$CMR" render-conf "$TMP/multi.json" "$TMP/body.md" 2>/dev/null)
|
|
422
|
-
if printf '%s' "$rendered" | grep -q "## Related PRs"; then
|
|
423
|
-
ok "render-conf prepends 'Related PRs' heading"
|
|
424
|
-
else
|
|
425
|
-
fail "render-conf missing heading block"
|
|
426
|
-
fi
|
|
427
|
-
|
|
428
|
-
# single-repo passthrough: render-pr on single state must return body unchanged
|
|
429
|
-
rendered=$("$CMR" render-pr "$TMP/single.json" "$TMP/body.md" "my-app" 2>/dev/null)
|
|
430
|
-
if [ "$rendered" = "body content" ]; then
|
|
431
|
-
ok "single-repo passthrough leaves body unchanged"
|
|
432
|
-
else
|
|
433
|
-
fail "single-repo passthrough mutated body: $rendered"
|
|
434
|
-
fi
|
|
435
|
-
|
|
436
|
-
rm -rf "$TMP"
|
|
437
|
-
fi
|
|
438
|
-
|
|
439
|
-
# --- Summary -----------------------------------------------------------------
|
|
440
|
-
echo ""
|
|
441
|
-
TOTAL=$((PASS+FAIL))
|
|
442
|
-
if [ $FAIL -eq 0 ]; then
|
|
443
|
-
echo "══ lib-scripts smoke: $PASS passed, 0 failed ══"
|
|
444
|
-
exit 0
|
|
445
|
-
else
|
|
446
|
-
echo "══ lib-scripts smoke: $PASS passed, $FAIL failed (of $TOTAL) ══" >&2
|
|
447
|
-
exit 1
|
|
448
|
-
fi
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-mcp-gate.sh - fixture-based test harness for smoke-no-mcp-in-dev-phases.sh.
|
|
3
|
-
#
|
|
4
|
-
# The gate script doubles as the production check (run against a live
|
|
5
|
-
# state.telemetry.mcpCalls[] after a pipeline run) and is also picked up by the
|
|
6
|
-
# smoke-*.sh glob - but bare, with no live run, it only WARNs. This harness
|
|
7
|
-
# drives the gate with crafted state files so the actual decision logic
|
|
8
|
-
# (PASS / FAIL / WARN / parse-error) is exercised in CI on every run.
|
|
9
|
-
|
|
10
|
-
set -uo pipefail
|
|
11
|
-
|
|
12
|
-
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
13
|
-
GATE="$REPO_ROOT/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh"
|
|
14
|
-
|
|
15
|
-
PASS=0
|
|
16
|
-
FAIL=0
|
|
17
|
-
pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
|
|
18
|
-
fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
|
|
19
|
-
|
|
20
|
-
cleanup() { [ -n "${TMP:-}" ] && rm -rf "$TMP"; }
|
|
21
|
-
trap cleanup EXIT
|
|
22
|
-
TMP=$(mktemp -d)
|
|
23
|
-
|
|
24
|
-
run_gate() { bash "$GATE" --state-file "$1" >/dev/null 2>&1; echo $?; }
|
|
25
|
-
|
|
26
|
-
echo "→ 1. Clean run (all MCP calls in Phase 1) PASSES"
|
|
27
|
-
cat > "$TMP/clean.json" <<'EOF'
|
|
28
|
-
{"telemetry":{"mcpCalls":[
|
|
29
|
-
{"tool":"mcp__claude_ai_Figma__get_design_context","phase":1,"timestamp":"2026-05-29T10:00:00Z"},
|
|
30
|
-
{"tool":"mcp__claude_ai_Figma__get_screenshot","phase":1,"timestamp":"2026-05-29T10:00:05Z"}
|
|
31
|
-
]}}
|
|
32
|
-
EOF
|
|
33
|
-
[ "$(run_gate "$TMP/clean.json")" = "0" ] && pass "phase-1-only telemetry exits 0" || fail "phase-1-only telemetry should exit 0"
|
|
34
|
-
|
|
35
|
-
echo "→ 2. Violation (MCP call in Phase 3) FAILS"
|
|
36
|
-
cat > "$TMP/viol.json" <<'EOF'
|
|
37
|
-
{"telemetry":{"mcpCalls":[
|
|
38
|
-
{"tool":"mcp__claude_ai_Figma__get_design_context","phase":1,"timestamp":"2026-05-29T10:00:00Z"},
|
|
39
|
-
{"tool":"mcp__claude_ai_Figma__get_screenshot","phase":3,"timestamp":"2026-05-29T11:00:00Z"}
|
|
40
|
-
]}}
|
|
41
|
-
EOF
|
|
42
|
-
[ "$(run_gate "$TMP/viol.json")" = "1" ] && pass "phase-3 MCP call exits 1" || fail "phase-3 MCP call should exit 1 (gate is a no-op?)"
|
|
43
|
-
|
|
44
|
-
echo "→ 3. Phase 2+ boundary (phase 2 is a violation)"
|
|
45
|
-
cat > "$TMP/viol2.json" <<'EOF'
|
|
46
|
-
{"telemetry":{"mcpCalls":[{"tool":"mcp__claude_ai_Figma__get_metadata","phase":2,"timestamp":"2026-05-29T10:30:00Z"}]}}
|
|
47
|
-
EOF
|
|
48
|
-
[ "$(run_gate "$TMP/viol2.json")" = "1" ] && pass "phase-2 MCP call exits 1" || fail "phase-2 MCP call should exit 1"
|
|
49
|
-
|
|
50
|
-
echo "→ 4. Empty mcpCalls array PASSES"
|
|
51
|
-
cat > "$TMP/empty.json" <<'EOF'
|
|
52
|
-
{"telemetry":{"mcpCalls":[]}}
|
|
53
|
-
EOF
|
|
54
|
-
[ "$(run_gate "$TMP/empty.json")" = "0" ] && pass "empty mcpCalls exits 0" || fail "empty mcpCalls should exit 0"
|
|
55
|
-
|
|
56
|
-
echo "→ 5. Absent telemetry field PASSES (backward compat with pre-v9 runs)"
|
|
57
|
-
cat > "$TMP/absent.json" <<'EOF'
|
|
58
|
-
{"taskId":"#1","status":"complete"}
|
|
59
|
-
EOF
|
|
60
|
-
[ "$(run_gate "$TMP/absent.json")" = "0" ] && pass "absent telemetry exits 0" || fail "absent telemetry should exit 0"
|
|
61
|
-
|
|
62
|
-
echo "→ 6. Corrupt JSON state is a hard error (not silently passed)"
|
|
63
|
-
echo '{not valid json' > "$TMP/corrupt.json"
|
|
64
|
-
[ "$(run_gate "$TMP/corrupt.json")" = "2" ] && pass "corrupt state exits 2" || fail "corrupt state should exit 2"
|
|
65
|
-
|
|
66
|
-
echo ""
|
|
67
|
-
echo "══ mcp-gate smoke: $PASS passed, $FAIL failed ══"
|
|
68
|
-
[ "$FAIL" -eq 0 ]
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-md-links.sh - thin wrapper so the internal markdown link gate runs
|
|
3
|
-
# inside the standard smoke discovery (run-smokes.mjs picks up smoke-*.sh).
|
|
4
|
-
# Logic lives in check-md-links.mjs.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
8
|
-
node "$ROOT/pipeline/scripts/check-md-links.mjs"
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-md2confluence.sh - offline sanity checks for md2confluence-v3.py.
|
|
3
|
-
# Imports the module from its file path and exercises the pure conversion
|
|
4
|
-
# layer: front-matter parsing, the TR/EN punctuation gate (fancy punctuation
|
|
5
|
-
# stripped, diacritics preserved), and markdown -> storage-XML rendering for
|
|
6
|
-
# headings, tables, and code blocks. No network, no credentials, no Confluence.
|
|
7
|
-
|
|
8
|
-
set -uo pipefail
|
|
9
|
-
|
|
10
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
12
|
-
MOD="$REPO_ROOT/pipeline/lib/md2confluence-v3.py"
|
|
13
|
-
|
|
14
|
-
if ! command -v python3 >/dev/null 2>&1; then
|
|
15
|
-
echo "smoke-md2confluence: python3 not found; skipping" >&2
|
|
16
|
-
exit 0
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
if [ ! -f "$MOD" ]; then
|
|
20
|
-
echo " ✗ md2confluence-v3.py missing at $MOD" >&2
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
MOD_PATH="$MOD" python3 - <<'PYEOF'
|
|
25
|
-
import importlib.util
|
|
26
|
-
import os
|
|
27
|
-
import sys
|
|
28
|
-
|
|
29
|
-
spec = importlib.util.spec_from_file_location("md2c", os.environ["MOD_PATH"])
|
|
30
|
-
mod = importlib.util.module_from_spec(spec)
|
|
31
|
-
sys.modules["md2c"] = mod # dataclass introspection needs the module registered
|
|
32
|
-
spec.loader.exec_module(mod)
|
|
33
|
-
|
|
34
|
-
passed = 0
|
|
35
|
-
failed = 0
|
|
36
|
-
|
|
37
|
-
def ok(msg):
|
|
38
|
-
global passed
|
|
39
|
-
print(f" ✓ {msg}")
|
|
40
|
-
passed += 1
|
|
41
|
-
|
|
42
|
-
def fail(msg):
|
|
43
|
-
global failed
|
|
44
|
-
print(f" ✗ {msg}", file=sys.stderr)
|
|
45
|
-
failed += 1
|
|
46
|
-
|
|
47
|
-
# --- parse_front_matter ------------------------------------------------------
|
|
48
|
-
print("→ parse_front_matter")
|
|
49
|
-
fm, body = mod.parse_front_matter("---\nfeature: Sample\nlanguage: tr\n---\n# Heading\nbody text\n")
|
|
50
|
-
if fm.get("feature") == "Sample" and fm.get("language") == "tr":
|
|
51
|
-
ok("front-matter keys parsed")
|
|
52
|
-
else:
|
|
53
|
-
fail(f"front-matter parse wrong: {fm}")
|
|
54
|
-
if body.startswith("# Heading"):
|
|
55
|
-
ok("body separated from front-matter")
|
|
56
|
-
else:
|
|
57
|
-
fail(f"body not separated: {body[:40]!r}")
|
|
58
|
-
|
|
59
|
-
fm2, body2 = mod.parse_front_matter("# No front matter\n")
|
|
60
|
-
if fm2 == {} and body2.startswith("# No"):
|
|
61
|
-
ok("no-front-matter input passes through")
|
|
62
|
-
else:
|
|
63
|
-
fail("no-front-matter input mangled")
|
|
64
|
-
|
|
65
|
-
# --- apply_punctuation_gate ----------------------------------------------------
|
|
66
|
-
print("→ apply_punctuation_gate")
|
|
67
|
-
dirty = "Geliştirme Özeti — kullanıcı için “detay” …"
|
|
68
|
-
clean, warnings = mod.apply_punctuation_gate(dirty, "tr")
|
|
69
|
-
forbidden = ["—", "–", "…", "“", "”", "‘", "’"]
|
|
70
|
-
if not any(ch in clean for ch in forbidden):
|
|
71
|
-
ok("fancy punctuation stripped")
|
|
72
|
-
else:
|
|
73
|
-
fail(f"fancy punctuation survived: {clean!r}")
|
|
74
|
-
for ch in ("ş", "Ö", "ı", "ç"):
|
|
75
|
-
if ch not in clean:
|
|
76
|
-
fail(f"diacritic {ch!r} was lost - gate must not ASCII-fold")
|
|
77
|
-
break
|
|
78
|
-
else:
|
|
79
|
-
ok("Turkish diacritics preserved")
|
|
80
|
-
|
|
81
|
-
# --- markdown_to_storage ---------------------------------------------------------
|
|
82
|
-
print("→ markdown_to_storage")
|
|
83
|
-
md = "\n".join([
|
|
84
|
-
"# Title",
|
|
85
|
-
"",
|
|
86
|
-
"Intro paragraph.",
|
|
87
|
-
"",
|
|
88
|
-
"| A | B |",
|
|
89
|
-
"|---|---|",
|
|
90
|
-
"| 1 | 2 |",
|
|
91
|
-
"",
|
|
92
|
-
"```swift",
|
|
93
|
-
"let x = 1",
|
|
94
|
-
"```",
|
|
95
|
-
])
|
|
96
|
-
res = mod.markdown_to_storage(
|
|
97
|
-
md,
|
|
98
|
-
attachments_available=set(),
|
|
99
|
-
mermaid_fallback=True,
|
|
100
|
-
tooltip_macro_supported=False,
|
|
101
|
-
)
|
|
102
|
-
xml = res.storage_xml
|
|
103
|
-
checks = [
|
|
104
|
-
("<h1>", "h1 heading rendered"),
|
|
105
|
-
("<table>", "table rendered"),
|
|
106
|
-
("ac:structured-macro", "code block rendered as macro"),
|
|
107
|
-
]
|
|
108
|
-
for needle, label in checks:
|
|
109
|
-
if needle in xml:
|
|
110
|
-
ok(label)
|
|
111
|
-
else:
|
|
112
|
-
fail(f"{label}: {needle!r} not found in storage XML")
|
|
113
|
-
|
|
114
|
-
if res.images_referenced == []:
|
|
115
|
-
ok("no phantom image references")
|
|
116
|
-
else:
|
|
117
|
-
fail(f"unexpected images referenced: {res.images_referenced}")
|
|
118
|
-
|
|
119
|
-
# --- summary -------------------------------------------------------------------
|
|
120
|
-
print()
|
|
121
|
-
if failed == 0:
|
|
122
|
-
print(f"══ md2confluence smoke: {passed} passed, 0 failed ══")
|
|
123
|
-
sys.exit(0)
|
|
124
|
-
print(f"══ md2confluence smoke: {passed} passed, {failed} failed ══", file=sys.stderr)
|
|
125
|
-
sys.exit(1)
|
|
126
|
-
PYEOF
|