@mmerterden/multi-agent-pipeline 13.1.0 → 13.2.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.
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bash
2
+ # Local-only lint runner for the iOS coding standard.
3
+ #
4
+ # Nothing is written inside the repository. The config, the resolved config, the baseline and
5
+ # every report live under $WORK_ROOT, outside any git working tree. Adding SwiftLint to the
6
+ # project (a committed .swiftlint.yml, a build phase, a CI job) is a separate decision and this
7
+ # script deliberately does not take it.
8
+ #
9
+ # ./lint-local.sh <module-path> [--write-baseline] [--strict] [--report json|xcode|emoji]
10
+ #
11
+ # First run for a module:
12
+ # ./lint-local.sh Domains/<Module> --write-baseline # grandfather what exists today
13
+ # Then:
14
+ # ./lint-local.sh Domains/<Module> --strict # only new violations surface
15
+
16
+ set -euo pipefail
17
+
18
+ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ WORK_ROOT="${IOS_STANDARD_WORK_ROOT:-$HOME/.claude/local/ios-coding-standard}"
20
+ DRAFT_CONFIG="$SKILL_DIR/swiftlint.draft.yml"
21
+
22
+ MODULE_PATH="${1:-}"
23
+ shift || true
24
+
25
+ STRICT=""
26
+ WRITE_BASELINE=""
27
+ REPORTER="xcode"
28
+ while [ $# -gt 0 ]; do
29
+ case "$1" in
30
+ --strict) STRICT="--strict" ;;
31
+ --write-baseline) WRITE_BASELINE="1" ;;
32
+ --report) shift; REPORTER="${1:-xcode}" ;;
33
+ *) echo "unknown option: $1" >&2; exit 2 ;;
34
+ esac
35
+ shift
36
+ done
37
+
38
+ if [ -z "$MODULE_PATH" ] || [ ! -d "$MODULE_PATH" ]; then
39
+ echo "usage: $(basename "$0") <module-path> [--write-baseline] [--strict] [--report <kind>]" >&2
40
+ exit 2
41
+ fi
42
+
43
+ if ! command -v swiftlint >/dev/null 2>&1; then
44
+ cat >&2 <<'EOF'
45
+ swiftlint is not installed.
46
+
47
+ Install it for your user only — this does not touch the project:
48
+
49
+ brew install swiftlint
50
+
51
+ If Homebrew is unavailable, a pinned local copy also works:
52
+
53
+ mkdir -p ~/.local/bin && cd ~/.local/bin
54
+ curl -sL https://github.com/realm/SwiftLint/releases/latest/download/portable_swiftlint.zip -o sl.zip
55
+ unzip -o sl.zip && rm sl.zip && chmod +x swiftlint
56
+ # then add ~/.local/bin to PATH
57
+
58
+ EOF
59
+ exit 127
60
+ fi
61
+
62
+ REPO_ROOT="$(cd "$MODULE_PATH" && git rev-parse --show-toplevel)"
63
+ MODULE_ABS="$(cd "$MODULE_PATH" && pwd)"
64
+ MODULE_NAME="$(basename "$MODULE_ABS")"
65
+ MODULE_WORK="$WORK_ROOT/$MODULE_NAME"
66
+ mkdir -p "$MODULE_WORK"
67
+
68
+ case "$MODULE_WORK" in
69
+ "$REPO_ROOT"/*) echo "refusing to write inside the repository: $MODULE_WORK" >&2; exit 1 ;;
70
+ esac
71
+
72
+ # ── Placeholder resolution ────────────────────────────────────────────────
73
+ # Per-module inventory, produced by the audit's Phase 2d. Optional: without it the
74
+ # sensitive-data rules fall back to a conservative generic pattern.
75
+ INVENTORY="$MODULE_WORK/inventory.env"
76
+ if [ -f "$INVENTORY" ]; then
77
+ # shellcheck disable=SC1090
78
+ . "$INVENTORY"
79
+ fi
80
+
81
+ # SENSITIVE / TRANSIENT: alternations of the module's own symbol names.
82
+ : "${SENSITIVE:=token|password|passcode|secret|credential|passport|nationalId|identityNumber|pnr|reservationCode|ticketNumber|membershipNumber|cardNumber|cvv|dateOfBirth}"
83
+ : "${TRANSIENT:=oneTimeCode|otp|verificationCode|draft}"
84
+
85
+ # SIBLINGS: every other module that lives beside this one — the MOD-01 import guard.
86
+ if [ -z "${SIBLINGS:-}" ]; then
87
+ PARENT="$(dirname "$MODULE_ABS")"
88
+ SIBLINGS="$(find "$PARENT" -mindepth 1 -maxdepth 1 -type d ! -name "$MODULE_NAME" -exec basename {} \; \
89
+ | sort | paste -sd'|' -)"
90
+ fi
91
+ [ -n "$SIBLINGS" ] || SIBLINGS="__none__"
92
+
93
+ # LOGIC: the paths where TEST-01/03/05 and CONC-04 apply.
94
+ # Logic paths only. A Scene is NOT logic: `Task { await viewModel.action() }` in a button
95
+ # closure is the idiomatic sync->async bridge, and flagging it buries the real finding.
96
+ : "${LOGIC:=.*(ViewModel|UseCase|Repository|Mapper)[^/]*\\.swift}"
97
+
98
+ RESOLVED="$MODULE_WORK/swiftlint.resolved.yml"
99
+ # Substituted with python rather than sed: every replacement is a regex alternation full of
100
+ # delimiter characters, and sed has no delimiter that is safe against all of them.
101
+ SENSITIVE="$SENSITIVE" TRANSIENT="$TRANSIENT" SIBLINGS="$SIBLINGS" LOGIC="$LOGIC" \
102
+ python3 -c '
103
+ import os, sys
104
+ src, dst = sys.argv[1], sys.argv[2]
105
+ text = open(src).read()
106
+ for key in ("SENSITIVE", "TRANSIENT", "SIBLINGS"):
107
+ # Wrapped in a non-capturing group: these values are alternations, and "|" binds looser than
108
+ # concatenation. Substituting them bare turns "prefix\bA|B|C" into "(prefix\bA) or (B) or (C)",
109
+ # which matches every line containing any bare term — the whole pattern silently collapses.
110
+ text = text.replace("((%s))" % key, "(?:%s)" % os.environ[key])
111
+ text = text.replace("((LOGIC))", os.environ["LOGIC"])
112
+ open(dst, "w").write(text)
113
+ ' "$DRAFT_CONFIG" "$RESOLVED"
114
+
115
+ BASELINE="$MODULE_WORK/baseline.json"
116
+
117
+ echo "module : $MODULE_ABS"
118
+ echo "config : $RESOLVED"
119
+ echo "workdir : $MODULE_WORK (outside the repo)"
120
+ echo "siblings : $SIBLINGS"
121
+ [ -f "$INVENTORY" ] && echo "inventory : $INVENTORY" || echo "inventory : (none — using the generic fallback pattern)"
122
+ echo
123
+
124
+ cd "$MODULE_ABS"
125
+
126
+ if [ -n "$WRITE_BASELINE" ]; then
127
+ echo "writing baseline -> $BASELINE"
128
+ BL_PATHS=()
129
+ for d in Sources Tests; do [ -d "$d" ] && BL_PATHS+=("$d"); done
130
+ [ ${#BL_PATHS[@]} -gt 0 ] || BL_PATHS=(.)
131
+ swiftlint lint --config "$RESOLVED" --write-baseline "$BASELINE" --quiet "${BL_PATHS[@]}" || true
132
+ echo
133
+ echo "baseline written. from now on run without --write-baseline; only new violations surface."
134
+ exit 0
135
+ fi
136
+
137
+ LINT_PATHS=()
138
+ for d in Sources Tests; do [ -d "$d" ] && LINT_PATHS+=("$d"); done
139
+ [ ${#LINT_PATHS[@]} -gt 0 ] || LINT_PATHS=(.)
140
+
141
+ ARGS=(lint --config "$RESOLVED" --reporter "$REPORTER" --quiet)
142
+ [ -f "$BASELINE" ] && ARGS+=(--baseline "$BASELINE")
143
+ [ -n "$STRICT" ] && ARGS+=("$STRICT")
144
+
145
+ set +e
146
+ # stdout is the report, stderr is progress + config diagnostics — never merge them, or a
147
+ # machine-readable reporter comes back with progress lines spliced into it.
148
+ swiftlint "${ARGS[@]}" "${LINT_PATHS[@]}" 2>"$MODULE_WORK/last-run.err" | tee "$MODULE_WORK/last-run.txt"
149
+ STATUS=${PIPESTATUS[0]}
150
+ if [ -s "$MODULE_WORK/last-run.err" ]; then
151
+ echo
152
+ echo "swiftlint diagnostics -> $MODULE_WORK/last-run.err"
153
+ grep -E '^(warning|error):' "$MODULE_WORK/last-run.err" | sort -u | head
154
+ fi
155
+ set -e
156
+
157
+ echo
158
+ echo "report: $MODULE_WORK/last-run.txt"
159
+ [ -f "$BASELINE" ] && echo "baseline in effect: $BASELINE (delete it to see every existing violation)"
160
+ exit $STATUS