@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.
Files changed (68) hide show
  1. package/all/copy-overwrite/scripts/lisa-commit-msg-gates.mjs +251 -0
  2. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  3. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  4. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  5. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  6. package/dist/core/upstream-evidence-manifest.js +9 -4
  7. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  8. package/package.json +1 -1
  9. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +1 -0
  12. package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +1 -0
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +1 -0
  15. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  18. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +1 -0
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +1 -0
  24. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-agy/plugin.json +1 -1
  27. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  47. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-agy/plugin.json +1 -1
  52. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  57. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  62. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +1 -0
  65. package/scripts/lisa-commit-msg-gates.mjs +12 -0
  66. package/scripts/lisa-github-rulesets.sh +97 -14
  67. package/typescript/copy-contents/.husky/commit-msg +28 -1
  68. 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 repo="$1"
432
- local existing_id="$2"
433
- local json="$3"
434
- local ruleset_name="$4"
436
+ local live="$1"
437
+ local json="$2"
435
438
 
436
- if [[ -z "$existing_id" ]]; then
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
- clean_template=$(preserve_live_required_checks "$repo" "$existing_id" "$clean_template" "$ruleset_name")
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" || exit 1
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