@stream44.studio/dco 0.3.0-rc.2

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/commit.sh ADDED
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # DCO-enabled Git Commit Wrapper
4
+ # ===============================
5
+ # This script wraps `git commit` to automatically add --signoff
6
+ # and shows the DCO terms on first use.
7
+ #
8
+
9
+ set -e
10
+
11
+ # Color codes for better UX
12
+ readonly RED='\033[0;31m'
13
+ readonly GREEN='\033[0;32m'
14
+ readonly YELLOW='\033[1;33m'
15
+ readonly BLUE='\033[0;34m'
16
+ readonly CYAN='\033[0;36m'
17
+ readonly MAGENTA='\033[0;35m'
18
+ readonly NC='\033[0m' # No Color
19
+ readonly BOLD='\033[1m'
20
+
21
+ # Verbose logging
22
+ VERBOSE=false
23
+
24
+ # SSH signing key path (optional, set via --signing-key)
25
+ SIGNING_KEY=""
26
+
27
+ # Build git commit args with optional SSH signing
28
+ git_commit_with_sign() {
29
+ local sign_args=()
30
+ if [[ -n "$SIGNING_KEY" ]]; then
31
+ sign_args+=(-c 'gpg.format=ssh' -c "user.signingkey=$SIGNING_KEY")
32
+ fi
33
+ git "${sign_args[@]}" commit "$@"
34
+ }
35
+ verbose_log() {
36
+ if [[ "$VERBOSE" == "true" ]]; then
37
+ echo -e "${BLUE}[verbose]${NC} $*" >&2
38
+ fi
39
+ }
40
+
41
+ # Resolve package version from package.json next to this script
42
+ get_package_version() {
43
+ local script_dir
44
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
45
+ if [[ -f "$script_dir/package.json" ]]; then
46
+ sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$script_dir/package.json" | head -1
47
+ else
48
+ echo "unknown"
49
+ fi
50
+ }
51
+ readonly DCO_VERSION=$(get_package_version)
52
+
53
+ # Find the git repository root
54
+ find_git_root() {
55
+ local dir
56
+ dir=$(git rev-parse --show-toplevel 2>/dev/null) || {
57
+ echo -e "${RED}Error: Not in a git repository${NC}" >&2
58
+ exit 1
59
+ }
60
+ echo "$dir"
61
+ }
62
+
63
+ # Record DCO signature in git tree for permanent record
64
+ record_dco_signature() {
65
+ local git_root="$1"
66
+ local name="$2"
67
+ local email="$3"
68
+ local date="$4"
69
+ local agreement_commit="$5"
70
+ local agreement_change_date="$6"
71
+ local sig_file="$git_root/.dco-signatures"
72
+
73
+ # Create or append to the signatures file
74
+ if [[ ! -f "$sig_file" ]]; then
75
+ cat > "$sig_file" <<EOF
76
+ # DCO Signatures
77
+
78
+ This file contains a permanent record of all Developer Certificate of Origin (DCO) agreements.
79
+ Each contributor who has agreed to the DCO.md is listed below with their signing date.
80
+ Format: name <email> | signed: <date> | agreement: <commit> (<agreement_change_date>) [| signature: <key_fingerprint>]
81
+
82
+ ---
83
+
84
+ EOF
85
+ fi
86
+
87
+ # Append the new signature (single line with all info)
88
+ local sig_line="$name <$email> | signed: $date | agreement: $agreement_commit ($agreement_change_date)"
89
+ if [[ -n "$SIGNING_KEY" ]]; then
90
+ local fingerprint
91
+ fingerprint=$(ssh-keygen -lf "$SIGNING_KEY" 2>/dev/null | awk '{print $2}')
92
+ if [[ -n "$fingerprint" ]]; then
93
+ sig_line="$sig_line | signature: $fingerprint"
94
+ fi
95
+ fi
96
+ echo "$sig_line" >> "$sig_file"
97
+
98
+ # Commit the signature to git
99
+ # Save list of currently staged files (excluding .dco-signatures)
100
+ local staged_files
101
+ staged_files=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | grep -v "^\.dco-signatures$" || true)
102
+ verbose_log "record_dco_signature: staged files to preserve: $(echo "$staged_files" | tr '\n' ' ')"
103
+
104
+ # Unstage everything
105
+ git reset >/dev/null 2>&1 || true
106
+
107
+ # Stage ONLY the signature file
108
+ git add "$sig_file" 2>/dev/null || true
109
+
110
+ # Create a commit for the DCO signature (should only contain .dco-signatures)
111
+ local sign_args=()
112
+ if [[ -n "$SIGNING_KEY" ]]; then
113
+ sign_args+=(--gpg-sign)
114
+ fi
115
+ if git_commit_with_sign --signoff --no-verify "${sign_args[@]}" -m "[DCO] DCO.md signed by $name" -m "Developer Certificates of Origin established using https://github.com/Stream44/dco" >/dev/null 2>&1; then
116
+ echo -e "${GREEN}✓ Signature recorded in repository history${NC}"
117
+ else
118
+ echo -e "${YELLOW}Note: Signature file already up to date${NC}"
119
+ fi
120
+
121
+ # Restore the originally staged files
122
+ if [[ -n "$staged_files" ]]; then
123
+ echo "$staged_files" | while IFS= read -r file; do
124
+ git add "$file" 2>/dev/null || true
125
+ done
126
+ fi
127
+ }
128
+
129
+ # Show the DCO and get user agreement
130
+ show_dco_first_time() {
131
+ local git_root="$1"
132
+ local auto_agree="$2"
133
+ local dco_file="$git_root/DCO.md"
134
+ local marker_file="$git_root/.git/.dco-agreed"
135
+
136
+ # Check if user has already agreed
137
+ if [[ -f "$marker_file" ]]; then
138
+ # Verify the current git user matches the one who signed
139
+ local current_name current_email
140
+ current_name=$(git config user.name 2>/dev/null || echo "")
141
+ current_email=$(git config user.email 2>/dev/null || echo "")
142
+
143
+ local stored_name stored_email stored_date stored_agreement_commit stored_agreement_change_date
144
+ stored_name=$(grep "^name=" "$marker_file" | cut -d'=' -f2-)
145
+ stored_email=$(grep "^email=" "$marker_file" | cut -d'=' -f2-)
146
+ stored_date=$(grep "^date=" "$marker_file" | cut -d'=' -f2-)
147
+ stored_agreement_commit=$(grep "^agreement_commit=" "$marker_file" | cut -d'=' -f2-)
148
+ stored_agreement_change_date=$(grep "^agreement_change_date=" "$marker_file" | cut -d'=' -f2-)
149
+
150
+ if [[ "$current_name" != "$stored_name" ]] || [[ "$current_email" != "$stored_email" ]]; then
151
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
152
+ echo -e "${RED}${BOLD} DCO Identity Mismatch${NC}" >&2
153
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
154
+ echo >&2
155
+ echo -e "${YELLOW}The DCO was originally signed by:${NC}" >&2
156
+ echo -e " Name: ${CYAN}$stored_name${NC}" >&2
157
+ echo -e " Email: ${CYAN}$stored_email${NC}" >&2
158
+ echo >&2
159
+ echo -e "${YELLOW}But you are currently configured as:${NC}" >&2
160
+ echo -e " Name: ${CYAN}$current_name${NC}" >&2
161
+ echo -e " Email: ${CYAN}$current_email${NC}" >&2
162
+ echo >&2
163
+ echo -e "${BLUE}To sign with a different identity, please remove the DCO agreement file:${NC}" >&2
164
+ echo -e " ${BOLD}rm $marker_file${NC}" >&2
165
+ echo >&2
166
+ echo -e "${BLUE}Then run this script again to sign the DCO with your current identity.${NC}" >&2
167
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" >&2
168
+ exit 1
169
+ fi
170
+
171
+ # Verify the signature exists in .dco-signatures file
172
+ local sig_file="$git_root/.dco-signatures"
173
+ local sig_valid="false"
174
+ if [[ -f "$sig_file" ]]; then
175
+ local sig_line
176
+ sig_line=$(grep "$stored_name.*<$stored_email>" "$sig_file" || true)
177
+ if [[ -n "$sig_line" ]]; then
178
+ sig_valid="true"
179
+ # Re-create marker from signature line if marker data is stale/empty
180
+ if [[ -z "$stored_agreement_commit" ]]; then
181
+ verbose_log "Marker missing agreement data, restoring from .dco-signatures"
182
+ local restored_agreement_commit restored_agreement_change_date restored_signed_date
183
+ restored_agreement_commit=$(echo "$sig_line" | sed -n 's/.*| agreement: \([a-f0-9]*\).*/\1/p')
184
+ restored_agreement_change_date=$(echo "$sig_line" | sed -n 's/.*| agreement: [a-f0-9]* (\(.*\))/\1/p')
185
+ restored_signed_date=$(echo "$sig_line" | sed -n 's/.*| signed: \([^|]*\) |.*/\1/p')
186
+ cat > "$marker_file" <<EOF
187
+ name=$stored_name
188
+ email=$stored_email
189
+ date=$restored_signed_date
190
+ agreement_commit=$restored_agreement_commit
191
+ agreement_change_date=$restored_agreement_change_date
192
+ EOF
193
+ stored_date="$restored_signed_date"
194
+ stored_agreement_commit="$restored_agreement_commit"
195
+ stored_agreement_change_date="$restored_agreement_change_date"
196
+ echo -e "${GREEN}✓ DCO marker restored from existing signature${NC}"
197
+ fi
198
+ else
199
+ verbose_log "Signature not found in .dco-signatures, will re-sign"
200
+ fi
201
+ else
202
+ verbose_log ".dco-signatures missing, will re-sign"
203
+ fi
204
+
205
+ if [[ "$sig_valid" == "false" ]]; then
206
+ # Auto-recover: remove stale marker and fall through to re-sign
207
+ echo -e "${YELLOW}DCO signature record missing or out of sync. Re-signing...${NC}"
208
+ rm -f "$marker_file"
209
+ # Fall through to the signing flow below
210
+ else
211
+
212
+ # Already signed - display details
213
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
214
+ echo -e "${GREEN}${BOLD} DCO Already Signed${NC}"
215
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
216
+ echo
217
+ echo -e " ${BOLD}Signer:${NC} $stored_name <$stored_email>"
218
+ echo -e " ${BOLD}Signed:${NC} $stored_date"
219
+ if [[ -n "$stored_agreement_commit" ]]; then
220
+ echo -e " ${BOLD}Agreement commit:${NC} $(git rev-parse --short "$stored_agreement_commit" 2>/dev/null || echo "$stored_agreement_commit")"
221
+ fi
222
+ if [[ -n "$stored_agreement_change_date" ]]; then
223
+ echo -e " ${BOLD}Agreement date:${NC} $stored_agreement_change_date"
224
+ fi
225
+ echo
226
+ return 0
227
+ fi
228
+ fi
229
+
230
+ # No marker file — check if .dco-signatures already has this user's signature
231
+ local sig_file="$git_root/.dco-signatures"
232
+ if [[ -f "$sig_file" ]]; then
233
+ local current_name current_email
234
+ current_name=$(git config user.name 2>/dev/null || echo "")
235
+ current_email=$(git config user.email 2>/dev/null || echo "")
236
+ local existing_sig
237
+ existing_sig=$(grep "$current_name.*<$current_email>" "$sig_file" || true)
238
+ if [[ -n "$existing_sig" ]]; then
239
+ verbose_log "Found existing signature in .dco-signatures, restoring marker"
240
+ local restored_agreement_commit restored_agreement_change_date restored_signed_date
241
+ restored_agreement_commit=$(echo "$existing_sig" | sed -n 's/.*| agreement: \([a-f0-9]*\).*/\1/p')
242
+ restored_agreement_change_date=$(echo "$existing_sig" | sed -n 's/.*| agreement: [a-f0-9]* (\(.*\))/\1/p')
243
+ restored_signed_date=$(echo "$existing_sig" | sed -n 's/.*| signed: \([^|]*\) |.*/\1/p')
244
+ cat > "$marker_file" <<EOF
245
+ name=$current_name
246
+ email=$current_email
247
+ date=$restored_signed_date
248
+ agreement_commit=$restored_agreement_commit
249
+ agreement_change_date=$restored_agreement_change_date
250
+ EOF
251
+ echo -e "${GREEN}✓ DCO marker restored from existing signature${NC}"
252
+ return 0
253
+ fi
254
+ fi
255
+
256
+ # Check if DCO.md exists on disk
257
+ if [[ ! -f "$dco_file" ]]; then
258
+ echo -e "${RED}Error: DCO.md not found in repository root${NC}" >&2
259
+ echo -e "${RED}Cannot proceed without DCO file${NC}" >&2
260
+ exit 1
261
+ fi
262
+
263
+ # Check if DCO.md is committed to git; if not, commit it automatically
264
+ local has_dco_commits="false"
265
+ # git log fails on repos with no commits at all, so handle that
266
+ if git rev-parse HEAD >/dev/null 2>&1; then
267
+ # Repo has at least one commit - check if DCO.md is tracked
268
+ if [[ -n "$(git log --oneline -1 -- "$dco_file" 2>/dev/null)" ]]; then
269
+ has_dco_commits="true"
270
+ fi
271
+ fi
272
+ verbose_log "DCO.md has commits: $has_dco_commits"
273
+
274
+ if [[ "$has_dco_commits" == "false" ]]; then
275
+ echo -e "${YELLOW}DCO.md is not yet committed to git. Committing it now...${NC}"
276
+
277
+ # Save currently staged files (excluding DCO.md)
278
+ local staged_files
279
+ staged_files=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | grep -v "^DCO\.md$" || true)
280
+ verbose_log "Staged files to preserve: $(echo "$staged_files" | tr '\n' ' ')"
281
+
282
+ # Unstage everything (may fail on empty repos, that's ok)
283
+ if git rev-parse HEAD >/dev/null 2>&1; then
284
+ git reset >/dev/null 2>&1 || true
285
+ else
286
+ # Empty repo: unstage individually
287
+ verbose_log "Empty repo detected, unstaging files individually"
288
+ if [[ -n "$staged_files" ]]; then
289
+ echo "$staged_files" | while IFS= read -r file; do
290
+ git rm --cached "$file" >/dev/null 2>&1 || true
291
+ done
292
+ fi
293
+ fi
294
+
295
+ # Stage and commit only DCO.md
296
+ git add "$dco_file"
297
+ verbose_log "Staging DCO.md for commit"
298
+ local sign_args=()
299
+ if [[ -n "$SIGNING_KEY" ]]; then
300
+ sign_args+=(--gpg-sign)
301
+ fi
302
+ if git_commit_with_sign --signoff --no-verify "${sign_args[@]}" -m "[DCO] Set DCO.md Policy by $(git config user.name)" -m "Developer Certificates of Origin established using https://github.com/Stream44/dco" >/dev/null 2>&1; then
303
+ echo -e "${GREEN}✓ DCO.md committed to repository${NC}"
304
+ else
305
+ echo -e "${RED}Error: Failed to commit DCO.md${NC}" >&2
306
+ exit 1
307
+ fi
308
+
309
+ # Restore originally staged files
310
+ if [[ -n "$staged_files" ]]; then
311
+ verbose_log "Restoring staged files"
312
+ echo "$staged_files" | while IFS= read -r file; do
313
+ git add "$file" 2>/dev/null || true
314
+ done
315
+ fi
316
+ fi
317
+
318
+ # Display the DCO
319
+ echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
320
+ echo -e "${BOLD}${CYAN} DEVELOPER CERTIFICATE OF ORIGIN (DCO)${NC} - tools version ${CYAN}${DCO_VERSION}${NC}"
321
+ echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
322
+ echo
323
+ echo -e "${BLUE}This is your first commit to this repository.${NC}"
324
+ echo -e "${BLUE}Please read and agree to the Developer Certificate of Origin below.${NC}"
325
+ echo
326
+
327
+ # Ask if ready to review (unless auto-agreeing)
328
+ if [[ "$auto_agree" != "true" ]]; then
329
+ while true; do
330
+ echo -e -n "${YELLOW}Are you ready to review the DCO? (yes/no): ${NC}"
331
+ read -r ready_response
332
+ case "$ready_response" in
333
+ [Yy]es|[Yy])
334
+ echo
335
+ break
336
+ ;;
337
+ [Nn]o|[Nn])
338
+ echo -e "${RED}✗ DCO review required to commit${NC}" >&2
339
+ exit 1
340
+ ;;
341
+ *)
342
+ echo -e "${RED}Please answer 'yes' or 'no'${NC}"
343
+ ;;
344
+ esac
345
+ done
346
+ else
347
+ echo -e "${GREEN}Auto-agreeing to DCO (--yes-signoff)${NC}"
348
+ echo
349
+ fi
350
+
351
+ cat "$dco_file"
352
+ echo
353
+ echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
354
+ echo
355
+ echo -e "${MAGENTA}NOTE: You will only be asked to agree once and all future commits will be signed off automatically.${NC}"
356
+ echo
357
+
358
+ # Show who is signing
359
+ local git_name git_email
360
+ git_name=$(git config user.name 2>/dev/null || echo "")
361
+ git_email=$(git config user.email 2>/dev/null || echo "")
362
+
363
+ if [[ -n "$git_name" ]] && [[ -n "$git_email" ]]; then
364
+ echo -e "${BLUE}You are signing as:${NC}"
365
+ echo -e " ${BOLD}$git_name <$git_email>${NC}"
366
+ if [[ -n "$SIGNING_KEY" ]]; then
367
+ local key_fingerprint
368
+ key_fingerprint=$(ssh-keygen -lf "$SIGNING_KEY" 2>/dev/null | awk '{print $2}')
369
+ echo -e " ${BOLD}Signing key:${NC} ${CYAN}${key_fingerprint}${NC}"
370
+ echo -e " ${BOLD}Key path:${NC} ${CYAN}${SIGNING_KEY}${NC}"
371
+ fi
372
+ echo
373
+ fi
374
+
375
+ # Ask for agreement (unless auto-agreeing)
376
+ if [[ "$auto_agree" != "true" ]]; then
377
+ while true; do
378
+ echo -e -n "${YELLOW}Do you agree to the DCO terms above? (yes/no): ${NC}"
379
+ read -r response
380
+ case "$response" in
381
+ [Yy]es|[Yy])
382
+ break
383
+ ;;
384
+ [Nn]o|[Nn])
385
+ echo -e "${RED}✗ DCO agreement required to commit${NC}" >&2
386
+ exit 1
387
+ ;;
388
+ *)
389
+ echo -e "${RED}Please answer 'yes' or 'no'${NC}"
390
+ ;;
391
+ esac
392
+ done
393
+ fi
394
+
395
+ # Record the agreement
396
+ echo -e "${GREEN}✓ DCO agreement accepted${NC}"
397
+ local git_name git_email sign_date agreement_commit agreement_change_date
398
+ git_name=$(git config user.name 2>/dev/null || echo "")
399
+ git_email=$(git config user.email 2>/dev/null || echo "")
400
+ sign_date=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
401
+
402
+ # Get the last commit that changed DCO.md and its date
403
+ agreement_commit=$(git log -1 --format='%H' -- "$dco_file")
404
+ agreement_change_date=$(git log -1 --format='%ai' -- "$dco_file")
405
+
406
+ cat > "$marker_file" <<EOF
407
+ name=$git_name
408
+ email=$git_email
409
+ date=$sign_date
410
+ agreement_commit=$agreement_commit
411
+ agreement_change_date=$agreement_change_date
412
+ EOF
413
+
414
+ # Record the DCO signature in the git tree for permanent record
415
+ record_dco_signature "$git_root" "$git_name" "$git_email" "$sign_date" "$agreement_commit" "$agreement_change_date"
416
+
417
+ echo
418
+ return 0
419
+ }
420
+
421
+ # Main function
422
+ main() {
423
+ local git_root
424
+ git_root=$(find_git_root)
425
+
426
+ # Parse flags; collect remaining args for git commit
427
+ local auto_agree="false"
428
+ local git_args=()
429
+
430
+ while [[ $# -gt 0 ]]; do
431
+ case "$1" in
432
+ --yes-signoff)
433
+ auto_agree="true"
434
+ ;;
435
+ --verbose)
436
+ VERBOSE=true
437
+ ;;
438
+ --signing-key)
439
+ shift
440
+ SIGNING_KEY="$1"
441
+ ;;
442
+ *)
443
+ git_args+=("$1")
444
+ ;;
445
+ esac
446
+ shift
447
+ done
448
+
449
+ verbose_log "git_root: $git_root"
450
+ verbose_log "auto_agree: $auto_agree"
451
+ verbose_log "git_args: ${git_args[*]}"
452
+
453
+ # Sign the DCO (sign-only, does not commit user code)
454
+ show_dco_first_time "$git_root" "$auto_agree"
455
+
456
+ # If git arguments were provided, run git commit with --signoff
457
+ if [[ ${#git_args[@]} -gt 0 ]]; then
458
+ verbose_log "Running git commit with signoff and args: ${git_args[*]}"
459
+ local sign_args=()
460
+ if [[ -n "$SIGNING_KEY" ]]; then
461
+ sign_args+=(-c 'gpg.format=ssh' -c "user.signingkey=$SIGNING_KEY" --gpg-sign)
462
+ fi
463
+ git_commit_with_sign --signoff "${sign_args[@]}" "${git_args[@]}"
464
+ fi
465
+ }
466
+
467
+ # Run main function
468
+ main "$@"
package/dco.sh ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # DCO CLI Entry Point
4
+ # ====================
5
+ # Routes subcommands to the appropriate script.
6
+ #
7
+ # dco commit [--signing-key <path>] [--yes] <git commit arguments>
8
+ # dco validate [--verbose] [--enforce-signature-fingerprints]
9
+ #
10
+
11
+ set -e
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+
15
+ # Default subcommand is 'commit'
16
+ SUBCOMMAND="${1:-commit}"
17
+
18
+ case "$SUBCOMMAND" in
19
+ commit)
20
+ shift 2>/dev/null || true
21
+ exec "$SCRIPT_DIR/commit.sh" "$@"
22
+ ;;
23
+ validate)
24
+ shift
25
+ exec "$SCRIPT_DIR/validate.sh" "$@"
26
+ ;;
27
+ --help|-h)
28
+ echo "Usage: dco <command> [options]"
29
+ echo ""
30
+ echo "Commands:"
31
+ echo " commit Sign the DCO and commit (default)"
32
+ echo " validate Validate DCO signatures on commits"
33
+ echo ""
34
+ echo "Commit options:"
35
+ echo " --signing-key <path> SSH key for cryptographic signing"
36
+ echo " --yes-signoff Auto-agree to DCO terms"
37
+ echo " <git arguments> Passed through to git commit"
38
+ echo ""
39
+ echo "Validate options:"
40
+ echo " --verbose Show detailed output"
41
+ echo " --enforce-signature-fingerprints Require SSH signature fingerprints"
42
+ exit 0
43
+ ;;
44
+ *)
45
+ echo "Unknown command: $SUBCOMMAND" >&2
46
+ echo "Usage: dco <commit|validate> [options]" >&2
47
+ exit 1
48
+ ;;
49
+ esac