@codyswann/lisa 3.48.4 → 3.48.6
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/all/copy-overwrite/scripts/lisa-commit-msg-gates.mjs +251 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +9 -4
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/scripts/lisa-commit-msg-gates.mjs +12 -0
- package/scripts/lisa-github-rulesets.sh +97 -14
- package/typescript/copy-contents/.husky/commit-msg +28 -1
- package/typescript/copy-overwrite/.prettierignore +1 -0
|
@@ -39,6 +39,11 @@ YES_MODE=false
|
|
|
39
39
|
VERBOSE=false
|
|
40
40
|
PROJECT_PATH=""
|
|
41
41
|
|
|
42
|
+
# Rulesets that already matched and were therefore not sent, and the payload
|
|
43
|
+
# GitHub last accepted (set by apply_with_integration_fallback).
|
|
44
|
+
UNCHANGED_COUNT=0
|
|
45
|
+
APPLIED_PAYLOAD=""
|
|
46
|
+
|
|
42
47
|
# Project type hierarchy (child -> parent)
|
|
43
48
|
# Using a function to avoid associative array issues with set -u
|
|
44
49
|
get_parent_type() {
|
|
@@ -428,22 +433,14 @@ add_config_required_checks() {
|
|
|
428
433
|
}
|
|
429
434
|
|
|
430
435
|
preserve_live_required_checks() {
|
|
431
|
-
local
|
|
432
|
-
local
|
|
433
|
-
local json="$3"
|
|
434
|
-
local ruleset_name="$4"
|
|
436
|
+
local live="$1"
|
|
437
|
+
local json="$2"
|
|
435
438
|
|
|
436
|
-
if [[ -z "$
|
|
439
|
+
if [[ -z "$live" ]]; then
|
|
437
440
|
echo "$json"
|
|
438
441
|
return 0
|
|
439
442
|
fi
|
|
440
443
|
|
|
441
|
-
local live
|
|
442
|
-
if ! live=$(gh api -X GET "repos/$repo/rulesets/$existing_id" 2>/dev/null); then
|
|
443
|
-
log_warning "Could not read existing ruleset '$ruleset_name' details — refusing to silently replace required checks" >&2
|
|
444
|
-
return 1
|
|
445
|
-
fi
|
|
446
|
-
|
|
447
444
|
echo "$json" | jq --argjson live "$live" '
|
|
448
445
|
def required_checks:
|
|
449
446
|
(.rules // [])
|
|
@@ -475,6 +472,73 @@ preserve_live_required_checks() {
|
|
|
475
472
|
end'
|
|
476
473
|
}
|
|
477
474
|
|
|
475
|
+
# The contexts the outgoing payload requires that the live ruleset does not.
|
|
476
|
+
# This is the drift a template change opens: a context added to a template never
|
|
477
|
+
# reaches an already-configured repository on its own (#2641), so the operator
|
|
478
|
+
# needs the reconciliation to SAY which checks it just made blocking rather than
|
|
479
|
+
# to report an opaque "applied".
|
|
480
|
+
ruleset_added_contexts() {
|
|
481
|
+
local live="$1"
|
|
482
|
+
local payload="$2"
|
|
483
|
+
|
|
484
|
+
jq -r -n --argjson live "${live:-null}" --argjson want "$payload" '
|
|
485
|
+
def contexts:
|
|
486
|
+
[ (.rules // [])[]
|
|
487
|
+
| select(.type == "required_status_checks")
|
|
488
|
+
| (.parameters.required_status_checks // [])[]
|
|
489
|
+
| .context ];
|
|
490
|
+
|
|
491
|
+
(if $live == null then [] else ($live | contexts) end) as $have
|
|
492
|
+
| ($want | contexts)
|
|
493
|
+
| map(select(. as $context | ($have | index($context)) | not))
|
|
494
|
+
| .[]'
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
# True when everything the payload asks for is ALREADY true of the live ruleset.
|
|
498
|
+
#
|
|
499
|
+
# Deliberately a subset test rather than an equality test: GitHub echoes back
|
|
500
|
+
# parameters it filled in with its own defaults (`required_reviewers`,
|
|
501
|
+
# `require_extra_approval_for_unattributed_changes`, ...) which no template
|
|
502
|
+
# names. Comparing whole documents would report drift on every run and make
|
|
503
|
+
# "nothing to do" unreachable — the AC's second scenario would then never hold
|
|
504
|
+
# even on a repository that needs no change.
|
|
505
|
+
ruleset_is_current() {
|
|
506
|
+
local live="$1"
|
|
507
|
+
local payload="$2"
|
|
508
|
+
|
|
509
|
+
[[ -n "$live" ]] || return 1
|
|
510
|
+
|
|
511
|
+
jq -e -n \
|
|
512
|
+
--argjson live "$(strip_readonly_fields "$live")" \
|
|
513
|
+
--argjson want "$payload" '
|
|
514
|
+
def covers($have; $need):
|
|
515
|
+
if ($need | type) == "object" then
|
|
516
|
+
($have | type) == "object"
|
|
517
|
+
and ([$need | keys_unsorted[]]
|
|
518
|
+
| all(. as $key | ($have | has($key)) and covers($have[$key]; $need[$key])))
|
|
519
|
+
elif ($need | type) == "array" then
|
|
520
|
+
($have | type) == "array"
|
|
521
|
+
and ($have | length) == ($need | length)
|
|
522
|
+
and ([range(0; $need | length)]
|
|
523
|
+
| all(. as $index | covers($have[$index]; $need[$index])))
|
|
524
|
+
else $have == $need
|
|
525
|
+
end;
|
|
526
|
+
covers($live; $want)' > /dev/null
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Print one line per context this run makes blocking. Reads the payload that
|
|
530
|
+
# GitHub actually ACCEPTED, so a context dropped by the integration fallback is
|
|
531
|
+
# never reported as added.
|
|
532
|
+
report_added_contexts() {
|
|
533
|
+
local live="$1"
|
|
534
|
+
local payload="$2"
|
|
535
|
+
|
|
536
|
+
local context
|
|
537
|
+
while IFS= read -r context; do
|
|
538
|
+
[[ -n "$context" ]] && log_info " + now required: $context"
|
|
539
|
+
done < <(ruleset_added_contexts "$live" "$payload")
|
|
540
|
+
}
|
|
541
|
+
|
|
478
542
|
apply_ruleset() {
|
|
479
543
|
local repo="$1"
|
|
480
544
|
local template_file="$2"
|
|
@@ -499,10 +563,22 @@ apply_ruleset() {
|
|
|
499
563
|
return 0
|
|
500
564
|
fi
|
|
501
565
|
|
|
502
|
-
local existing_id
|
|
566
|
+
local existing_id live=""
|
|
503
567
|
existing_id=$(find_ruleset_by_name "$existing_rulesets" "$ruleset_name")
|
|
504
568
|
if [[ -n "$existing_id" ]]; then
|
|
505
|
-
|
|
569
|
+
if ! live=$(gh api -X GET "repos/$repo/rulesets/$existing_id" 2>/dev/null); then
|
|
570
|
+
log_warning "Could not read existing ruleset '$ruleset_name' details — refusing to silently replace required checks"
|
|
571
|
+
return 1
|
|
572
|
+
fi
|
|
573
|
+
clean_template=$(preserve_live_required_checks "$live" "$clean_template")
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
# Idempotence: a run that would change nothing must say so and send nothing,
|
|
577
|
+
# so a second run is visibly a no-op rather than an indistinguishable write.
|
|
578
|
+
if ruleset_is_current "$live" "$clean_template"; then
|
|
579
|
+
log_success "Ruleset '$ruleset_name' already matches the template — nothing to do"
|
|
580
|
+
UNCHANGED_COUNT=$((UNCHANGED_COUNT + 1))
|
|
581
|
+
return 0
|
|
506
582
|
fi
|
|
507
583
|
|
|
508
584
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
@@ -511,6 +587,7 @@ apply_ruleset() {
|
|
|
511
587
|
else
|
|
512
588
|
log_info "[DRY RUN] Would create ruleset '$ruleset_name'"
|
|
513
589
|
fi
|
|
590
|
+
report_added_contexts "$live" "$clean_template"
|
|
514
591
|
log_verbose "Template: $template_file"
|
|
515
592
|
return 0
|
|
516
593
|
fi
|
|
@@ -521,7 +598,9 @@ apply_ruleset() {
|
|
|
521
598
|
log_info "Creating ruleset '$ruleset_name'..."
|
|
522
599
|
fi
|
|
523
600
|
|
|
601
|
+
APPLIED_PAYLOAD=""
|
|
524
602
|
if apply_with_integration_fallback "$repo" "$ruleset_name" "$clean_template" "$existing_id"; then
|
|
603
|
+
report_added_contexts "$live" "${APPLIED_PAYLOAD:-$clean_template}"
|
|
525
604
|
return 0
|
|
526
605
|
fi
|
|
527
606
|
log_error "Failed to apply ruleset '$ruleset_name'"
|
|
@@ -546,6 +625,7 @@ apply_with_integration_fallback() {
|
|
|
546
625
|
# Capture stdout AND stderr — gh prints the API error body (which carries
|
|
547
626
|
# the "Invalid integration ids" detail) on stdout, not stderr.
|
|
548
627
|
send_ruleset() {
|
|
628
|
+
APPLIED_PAYLOAD="$1"
|
|
549
629
|
echo "$1" > "$temp_file"
|
|
550
630
|
if [[ -n "$existing_id" ]]; then
|
|
551
631
|
error_output=$(gh api -X PUT "repos/$repo/rulesets/$existing_id" --input "$temp_file" 2>&1)
|
|
@@ -772,7 +852,10 @@ main() {
|
|
|
772
852
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
773
853
|
log_info "Dry run complete. ${#templates[@]} ruleset(s) would be applied."
|
|
774
854
|
else
|
|
775
|
-
log_success "Applied $success_count ruleset(s)"
|
|
855
|
+
log_success "Applied $((success_count - UNCHANGED_COUNT)) ruleset(s), $UNCHANGED_COUNT already current"
|
|
856
|
+
if [[ $UNCHANGED_COUNT -eq $success_count ]]; then
|
|
857
|
+
log_info "Nothing to do — every ruleset already matches its template"
|
|
858
|
+
fi
|
|
776
859
|
if [[ $fail_count -gt 0 ]]; then
|
|
777
860
|
log_warning "$fail_count ruleset(s) failed"
|
|
778
861
|
exit 1
|
|
@@ -27,11 +27,36 @@ fi
|
|
|
27
27
|
if [ ! -f "$WORK_ITEM_SCRIPT" ]; then
|
|
28
28
|
WORK_ITEM_SCRIPT="all/copy-overwrite/scripts/lisa-work-item.mjs"
|
|
29
29
|
fi
|
|
30
|
+
|
|
31
|
+
# Several INDEPENDENT gate families bite at this one moment, and each of the
|
|
32
|
+
# checks below knows only its own. Whichever refuses first, the operator is told
|
|
33
|
+
# about ALL of them here — otherwise satisfying every requirement the refusal
|
|
34
|
+
# names still says nothing about whether the commit will be accepted, which is
|
|
35
|
+
# exactly the property those checklists exist to provide. Owned by neither
|
|
36
|
+
# check, because neither can know what the other installs.
|
|
37
|
+
GATE_FAMILIES_SCRIPT="node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-commit-msg-gates.mjs"
|
|
38
|
+
if [ ! -f "$GATE_FAMILIES_SCRIPT" ]; then
|
|
39
|
+
GATE_FAMILIES_SCRIPT="scripts/lisa-commit-msg-gates.mjs"
|
|
40
|
+
fi
|
|
41
|
+
if [ ! -f "$GATE_FAMILIES_SCRIPT" ]; then
|
|
42
|
+
GATE_FAMILIES_SCRIPT="all/copy-overwrite/scripts/lisa-commit-msg-gates.mjs"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Never the reason a commit fails: the caller has already decided to refuse.
|
|
46
|
+
lisa_commit_gate_families() {
|
|
47
|
+
[ -f "$GATE_FAMILIES_SCRIPT" ] || return 0
|
|
48
|
+
command -v node >/dev/null 2>&1 || return 0
|
|
49
|
+
node "$GATE_FAMILIES_SCRIPT" list --hook "$0" --refused "$1" 2>/dev/null || true
|
|
50
|
+
}
|
|
51
|
+
|
|
30
52
|
if ! command -v node >/dev/null 2>&1; then
|
|
31
53
|
echo "❌ Commit blocked: Node.js is required to validate tracker work items."
|
|
32
54
|
exit 1
|
|
33
55
|
fi
|
|
34
|
-
node "$WORK_ITEM_SCRIPT" validate-commit "$COMMIT_MSG_FILE"
|
|
56
|
+
if ! node "$WORK_ITEM_SCRIPT" validate-commit "$COMMIT_MSG_FILE"; then
|
|
57
|
+
lisa_commit_gate_families traceability
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
35
60
|
|
|
36
61
|
echo "📝 Validating commit message with commitlint..."
|
|
37
62
|
$EXECUTOR commitlint --edit $1
|
|
@@ -52,6 +77,7 @@ if [ $? -ne 0 ]; then
|
|
|
52
77
|
echo "Format: <type>(<optional scope>): <subject>"
|
|
53
78
|
echo ""
|
|
54
79
|
echo "For more info, see: https://www.conventionalcommits.org/"
|
|
80
|
+
lisa_commit_gate_families commit-conformance
|
|
55
81
|
exit 1
|
|
56
82
|
fi
|
|
57
83
|
|
|
@@ -91,6 +117,7 @@ if ! printf '%s\n' "$COMMIT_MSG" | grep -Eiq "Co-authored-by:.*(Claude|Codex)";
|
|
|
91
117
|
echo "If you're using Claude Code, use the /git:commit command"
|
|
92
118
|
echo "If you're using Codex, enable commit_attribution in .codex/config.toml"
|
|
93
119
|
echo ""
|
|
120
|
+
lisa_commit_gate_families ai-coauthorship
|
|
94
121
|
exit 1
|
|
95
122
|
fi
|
|
96
123
|
|
|
@@ -72,6 +72,7 @@ scripts/check-threshold-ratchet.mjs
|
|
|
72
72
|
scripts/check-verification-coverage.mjs
|
|
73
73
|
scripts/classify-maestro-failures.mjs
|
|
74
74
|
scripts/lisa-command-envelope.mjs
|
|
75
|
+
scripts/lisa-commit-msg-gates.mjs
|
|
75
76
|
scripts/lisa-destructive-guard.mjs
|
|
76
77
|
scripts/lisa-environment-prepare.mjs
|
|
77
78
|
scripts/lisa-floor-collisions.mjs
|