@codyswann/lisa 4.22.12 → 4.22.13
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/lib/gate-failure-diagnosis.mjs +4 -2
- package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +62 -11
- package/all/copy-overwrite/scripts/lisa-environment-prepare.mjs +10 -1
- package/all/copy-overwrite/scripts/lisa-floor-collisions.mjs +1 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +43 -2
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +19 -5
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +13 -1
- package/all/copy-overwrite/scripts/lisa-schema-validate.mjs +2 -2
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +24 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts +5 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.d.ts.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +8 -2
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +33 -32
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-no-verify.sh +43 -2
- package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +4 -3
- package/dist/opencode/plugin-templates/lisa-block-no-verify.ts +11 -2
- package/dist/opencode/plugin-templates/parity-safety-net.sh +19 -5
- package/expo/create-only/.github/workflows/nightly-e2e-report.yml +1 -0
- package/package.json +3 -3
- 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-github-build-intake/SKILL.md +7 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-sync/SKILL.md +2 -2
- package/plugins/lisa/hooks/block-no-verify.sh +43 -2
- package/plugins/lisa/hooks/parity-safety-net.sh +19 -5
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +10 -1
- package/plugins/lisa/rules/reference/config-resolution.md +0 -4
- package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/lisa/scripts/resolve-lifecycle-role.mjs +15 -3
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +7 -8
- package/plugins/lisa/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/lisa/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/lisa/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/lisa/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/lisa/skills/lisa-tracker-sync/SKILL.md +2 -2
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +19 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/resolve-lifecycle-role.mjs +15 -3
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +7 -8
- package/plugins/lisa-agy/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/lisa-agy/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/lisa-agy/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/lisa-agy/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/lisa-agy/skills/lisa-tracker-sync/SKILL.md +2 -2
- 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/hooks/block-no-verify.sh +43 -2
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +19 -5
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +10 -1
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +0 -4
- package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/lisa-copilot/scripts/resolve-lifecycle-role.mjs +15 -3
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +7 -8
- package/plugins/lisa-copilot/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/lisa-copilot/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/lisa-copilot/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/lisa-copilot/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/lisa-copilot/skills/lisa-tracker-sync/SKILL.md +2 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +43 -2
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +19 -5
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +10 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +0 -4
- package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -1
- package/plugins/lisa-cursor/scripts/resolve-lifecycle-role.mjs +15 -3
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +7 -8
- package/plugins/lisa-cursor/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/lisa-cursor/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/lisa-cursor/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/lisa-cursor/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/lisa-cursor/skills/lisa-tracker-sync/SKILL.md +2 -2
- 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/hooks/block-no-verify.sh +43 -2
- package/plugins/src/base/hooks/parity-safety-net.sh +19 -5
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +10 -1
- package/plugins/src/base/rules/reference/config-resolution.md +0 -4
- package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -1
- package/plugins/src/base/scripts/resolve-lifecycle-role.mjs +15 -3
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +7 -8
- package/plugins/src/base/skills/lisa-github-prd-intake/SKILL.md +10 -11
- package/plugins/src/base/skills/lisa-github-sync/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-github-write-prd/SKILL.md +14 -12
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +7 -8
- package/plugins/src/base/skills/lisa-jira-journey/scripts/parse-plan.py +54 -12
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +9 -10
- package/plugins/src/base/skills/lisa-linear-evidence/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +2 -1
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +4 -3
- package/plugins/src/base/skills/lisa-tracker-sync/SKILL.md +2 -2
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +10 -1
- package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +10 -13
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +34 -3
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +17 -1
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +10 -1
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
# Add a name here in the same commit that closes a vector. A hardening that
|
|
24
24
|
# forgets to is invisible to refresh, and shows up as an unexplained diff at
|
|
25
25
|
# review time instead of a named capability.
|
|
26
|
-
# lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, git-config-key, git-config-parameters, git-config-parameters-append, git-config-parameters-expansion, heredoc-shell-word, herestring-aware, no-verify-short
|
|
26
|
+
# lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, git-config-key, git-config-parameters, git-config-parameters-append, git-config-parameters-expansion, heredoc-shell-word, herestring-aware, no-verify-short, nested-shell-no-verify
|
|
27
27
|
#
|
|
28
28
|
# Shell-token matching avoids false positives from issue bodies, heredocs, and
|
|
29
29
|
# commit-message prose while still catching quoted real argv values such as
|
|
@@ -216,6 +216,9 @@ COMMAND_SEPARATORS = {
|
|
|
216
216
|
";", "|", "||", "&", "&&", "(", ")", "<", ">", ">>", "<<", "&|",
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
SHELL_PROGRAMS = {"bash", "dash", "ksh", "sh", "zsh"}
|
|
220
|
+
MAX_SHELL_NESTING = 8
|
|
221
|
+
|
|
219
222
|
# git's own options that take a SEPARATE value token, which therefore must be
|
|
220
223
|
# skipped when looking for the subcommand: `git -c core.hooksPath=x commit` and
|
|
221
224
|
# `git -C /repo commit` both reach `commit`.
|
|
@@ -333,6 +336,8 @@ def commit_bypasses_verification(argv):
|
|
|
333
336
|
if token.startswith("--"):
|
|
334
337
|
if token in COMMIT_LONG_SEPARATE_VALUE:
|
|
335
338
|
index += 1
|
|
339
|
+
elif disables_verification(token):
|
|
340
|
+
return True
|
|
336
341
|
continue
|
|
337
342
|
# A bare `-` is git's stdin placeholder, not an option cluster.
|
|
338
343
|
if not token.startswith("-") or token == "-":
|
|
@@ -366,11 +371,25 @@ def subcommand_after_git(tokens, start):
|
|
|
366
371
|
return None
|
|
367
372
|
|
|
368
373
|
|
|
369
|
-
def
|
|
374
|
+
def shell_starts_command(tokens, index):
|
|
375
|
+
"""Whether a recognized shell token occupies command position."""
|
|
376
|
+
start = index - 1
|
|
377
|
+
while start >= 0 and tokens[start] not in COMMAND_SEPARATORS:
|
|
378
|
+
start -= 1
|
|
379
|
+
prefix = tokens[start + 1:index]
|
|
380
|
+
if not prefix:
|
|
381
|
+
return True
|
|
382
|
+
if prefix[0] == "env":
|
|
383
|
+
prefix = prefix[1:]
|
|
384
|
+
return all("=" in token and not token.startswith("=") for token in prefix)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def git_commit_skips_verification(text, depth=0):
|
|
370
388
|
"""Whether the command runs `git commit` with the short `-n` bypass.
|
|
371
389
|
|
|
372
390
|
Args:
|
|
373
391
|
text: The command line, heredoc payloads already stripped.
|
|
392
|
+
depth: Number of recognized shell payloads already traversed.
|
|
374
393
|
|
|
375
394
|
Returns:
|
|
376
395
|
True if any `git commit` invocation on the line skips verification.
|
|
@@ -387,6 +406,28 @@ def git_commit_skips_verification(text):
|
|
|
387
406
|
found = subcommand_after_git(scoped_tokens, index + 1)
|
|
388
407
|
if found and found[0] == "commit" and commit_bypasses_verification(found[1]):
|
|
389
408
|
return True
|
|
409
|
+
if depth >= MAX_SHELL_NESTING:
|
|
410
|
+
return False
|
|
411
|
+
for index, token in enumerate(scoped_tokens):
|
|
412
|
+
program = token.rsplit("/", 1)[-1]
|
|
413
|
+
if program not in SHELL_PROGRAMS or not shell_starts_command(
|
|
414
|
+
scoped_tokens, index
|
|
415
|
+
):
|
|
416
|
+
continue
|
|
417
|
+
cursor = index + 1
|
|
418
|
+
while cursor < len(scoped_tokens):
|
|
419
|
+
option = scoped_tokens[cursor]
|
|
420
|
+
if option in COMMAND_SEPARATORS or not option.startswith("-"):
|
|
421
|
+
break
|
|
422
|
+
cursor += 1
|
|
423
|
+
if "c" not in option[1:]:
|
|
424
|
+
continue
|
|
425
|
+
if cursor >= len(scoped_tokens):
|
|
426
|
+
break
|
|
427
|
+
payload = scoped_tokens[cursor]
|
|
428
|
+
if git_commit_skips_verification(payload, depth + 1):
|
|
429
|
+
return True
|
|
430
|
+
break
|
|
390
431
|
return False
|
|
391
432
|
|
|
392
433
|
|
|
@@ -724,6 +724,10 @@ classify_rm_target() {
|
|
|
724
724
|
set +f
|
|
725
725
|
block "recursive forced delete of the current directory (rm -rf .)"
|
|
726
726
|
;;
|
|
727
|
+
.git | .git/* | */.git | */.git/*)
|
|
728
|
+
set +f
|
|
729
|
+
block "recursive forced delete of the git control plane (.git holds every commit, branch and stash not already pushed; nothing in the working tree can rebuild it)"
|
|
730
|
+
;;
|
|
727
731
|
.. | ../* | */.. | */../*)
|
|
728
732
|
set +f
|
|
729
733
|
block "recursive forced delete of a path outside the project (.. traversal)"
|
|
@@ -1031,11 +1035,16 @@ fi
|
|
|
1031
1035
|
# purpose, and blocking those would be the collision that gets a guard switched
|
|
1032
1036
|
# off. The trailing `([/[:space:]'"$qc"']|$)` is what draws that line.
|
|
1033
1037
|
readonly GIT_CONTROL_PLANE='(^|[[:space:]='"$qc"'./])\.git([/[:space:]'"$qc"']|$)'
|
|
1034
|
-
|
|
1035
|
-
|
|
1038
|
+
while IFS= read -r git_stmt; do
|
|
1039
|
+
[ -n "$git_stmt" ] || continue
|
|
1040
|
+
if ! scan -E "$git_stmt" "$RM_RF_CLUSTER" \
|
|
1041
|
+
&& ! scan -E "$git_stmt" "$RM_RF_SPLIT"; then
|
|
1042
|
+
continue
|
|
1043
|
+
fi
|
|
1044
|
+
if scan -E "$git_stmt" "$GIT_CONTROL_PLANE"; then
|
|
1036
1045
|
block "recursive forced delete of the git control plane (.git holds every commit, branch and stash not already pushed; nothing in the working tree can rebuild it). Delete a specific ignored artifact instead, or re-clone if the checkout is genuinely to be discarded."
|
|
1037
1046
|
fi
|
|
1038
|
-
|
|
1047
|
+
done <<< "$(printf '%s' "$normalized_command_str" | tr '&|;' '\n')"
|
|
1039
1048
|
|
|
1040
1049
|
# 15. Credential stores. Reading one into an agent transcript is disclosure even
|
|
1041
1050
|
# when nothing is copied anywhere: the value lands in a log, a context window,
|
|
@@ -1077,14 +1086,19 @@ has_private_dotenv_path() {
|
|
|
1077
1086
|
| grep -Eio -- '(^|[[:space:]='"$qc"'./])\.env(\.[[:alnum:]_-]+)?')" \
|
|
1078
1087
|
|| dotenv_scan_status=$?
|
|
1079
1088
|
if [ "$dotenv_scan_status" -gt 1 ]; then
|
|
1080
|
-
|
|
1089
|
+
scan_failed "$dotenv_scan_status"
|
|
1081
1090
|
fi
|
|
1082
1091
|
|
|
1083
1092
|
while IFS= read -r candidate; do
|
|
1084
1093
|
[ -n "$candidate" ] || continue
|
|
1094
|
+
dotenv_normalize_status=0
|
|
1085
1095
|
candidate="$(printf '%s' "$candidate" \
|
|
1086
1096
|
| tr '[:upper:]' '[:lower:]' \
|
|
1087
|
-
| grep -Eo -- '\.env(\.[[:alnum:]_-]+)?')"
|
|
1097
|
+
| grep -Eo -- '\.env(\.[[:alnum:]_-]+)?')" \
|
|
1098
|
+
|| dotenv_normalize_status=$?
|
|
1099
|
+
if [ "$dotenv_normalize_status" -gt 1 ] || [ -z "$candidate" ]; then
|
|
1100
|
+
scan_failed "$dotenv_normalize_status"
|
|
1101
|
+
fi
|
|
1088
1102
|
case "$candidate" in
|
|
1089
1103
|
.env.example | .env.sample | .env.template | .env.dist | .env.schema | .env.defaults) ;;
|
|
1090
1104
|
*) return 0 ;;
|
|
@@ -79,7 +79,7 @@ export const FAMILIES = [
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
id: "lighthouse",
|
|
82
|
-
match: /(^|\/)lighthouserc-config\.json$/,
|
|
82
|
+
match: /(^|\/)(?:lighthouserc-config|lighthouserc|\.lighthouserc)\.json$/,
|
|
83
83
|
kind: "lighthouse",
|
|
84
84
|
},
|
|
85
85
|
];
|
|
@@ -98,6 +98,8 @@ export const LIGHTHOUSE_ASSERTION_DIRECTIONS = Object.freeze({
|
|
|
98
98
|
maxLength: "max",
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
const LIGHTHOUSE_LEVELS = Object.freeze({ off: 0, warn: 1, error: 2 });
|
|
102
|
+
|
|
101
103
|
/**
|
|
102
104
|
* Extract numeric Lighthouse assertions with a direction chosen per key.
|
|
103
105
|
*
|
|
@@ -133,6 +135,13 @@ export function extractLighthouseAssertions(conf) {
|
|
|
133
135
|
return out;
|
|
134
136
|
}
|
|
135
137
|
for (const [audit, spec] of Object.entries(assertions)) {
|
|
138
|
+
const level = Array.isArray(spec) ? spec[0] : spec;
|
|
139
|
+
if (typeof level === "string" && Object.hasOwn(LIGHTHOUSE_LEVELS, level)) {
|
|
140
|
+
out.set(`${audit}.$level`, {
|
|
141
|
+
value: LIGHTHOUSE_LEVELS[level],
|
|
142
|
+
direction: "min",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
136
145
|
const options =
|
|
137
146
|
Array.isArray(spec) &&
|
|
138
147
|
spec.length >= 2 &&
|
|
@@ -909,10 +909,6 @@ read_notion_token() {
|
|
|
909
909
|
if [ -n "${PLUGIN_ROOT:-}" ]; then
|
|
910
910
|
candidates+=("$PLUGIN_ROOT/skills/lisa-secrets-access/scripts/resolve-secret.mjs")
|
|
911
911
|
fi
|
|
912
|
-
# Last rung deliberately needs no environment variable: an agent that was
|
|
913
|
-
# never handed a plugin root still has the installed package to fall back on.
|
|
914
|
-
candidates+=(node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs)
|
|
915
|
-
|
|
916
912
|
local resolver
|
|
917
913
|
local tried=()
|
|
918
914
|
for resolver in "${candidates[@]}"; do
|
|
@@ -83,7 +83,7 @@ Notes:
|
|
|
83
83
|
|
|
84
84
|
### Classifying a hold
|
|
85
85
|
|
|
86
|
-
`rollup-blocker-classification` (`
|
|
86
|
+
`rollup-blocker-classification` (`scripts/rollup-blocker-classification.mjs` under the active Lisa plugin root) is the single installed implementation. It takes the child graph the vendor reader already produced and returns, per held leaf, a class and the actor who must clear it:
|
|
87
87
|
|
|
88
88
|
| Class | Recorded signal on the leaf | Who must act | Clears on its own? |
|
|
89
89
|
|---|---|---|---|
|
|
@@ -94,6 +94,9 @@ export const VENDOR_ROOTS = Object.freeze({
|
|
|
94
94
|
github: "github.labels.build",
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
/** GitHub's PRD lifecycle is a separate required role map from build work. */
|
|
98
|
+
const GITHUB_PRD_PREFIX = "prd.";
|
|
99
|
+
|
|
97
100
|
/** Resolution outcomes, reported so a caller never has to guess what happened. */
|
|
98
101
|
export const OUTCOMES = Object.freeze({
|
|
99
102
|
CONFIGURED: "configured",
|
|
@@ -170,7 +173,12 @@ export const resolveRole = ({
|
|
|
170
173
|
global: globalConfig,
|
|
171
174
|
fallback,
|
|
172
175
|
}) => {
|
|
173
|
-
|
|
176
|
+
let root = VENDOR_ROOTS[vendor];
|
|
177
|
+
let configuredRole = role;
|
|
178
|
+
if (vendor === "github" && role.startsWith(GITHUB_PRD_PREFIX)) {
|
|
179
|
+
root = "github.labels.prd";
|
|
180
|
+
configuredRole = role.slice(GITHUB_PRD_PREFIX.length);
|
|
181
|
+
}
|
|
174
182
|
if (!root) {
|
|
175
183
|
return {
|
|
176
184
|
value: "",
|
|
@@ -182,7 +190,9 @@ export const resolveRole = ({
|
|
|
182
190
|
|
|
183
191
|
// `done` is env-keyed on every vendor; an env selects one rung of the map.
|
|
184
192
|
const path =
|
|
185
|
-
env &&
|
|
193
|
+
env && configuredRole === "done"
|
|
194
|
+
? `${root}.done.${env}`
|
|
195
|
+
: `${root}.${configuredRole}`;
|
|
186
196
|
|
|
187
197
|
for (const [label, source] of [
|
|
188
198
|
["local", local],
|
|
@@ -199,7 +209,9 @@ export const resolveRole = ({
|
|
|
199
209
|
}
|
|
200
210
|
}
|
|
201
211
|
|
|
202
|
-
const optional =
|
|
212
|
+
const optional =
|
|
213
|
+
!role.startsWith(GITHUB_PRD_PREFIX) &&
|
|
214
|
+
OPTIONAL_ROLES.includes(configuredRole);
|
|
203
215
|
|
|
204
216
|
// R2 — a fallback may inform a read, never supply a write target.
|
|
205
217
|
if (fallback && intent === "write") {
|
|
@@ -36,18 +36,17 @@ selection-only: never assign or reassign issues as part of build intake.
|
|
|
36
36
|
Build-queue label names are read from `.lisa.config.json` `github.labels.build.*`, falling back to defaults documented in the `config-resolution` rule. Bash pattern:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
# Read role with default fallback. Local overrides global per-key.
|
|
40
39
|
read_role() {
|
|
41
40
|
# Single resolver — see config-resolution "The single resolver".
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
local value
|
|
42
|
+
value=$(node "${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-plugins/lisa}}/scripts/resolve-lifecycle-role.mjs" \
|
|
43
|
+
--role "$1" --vendor github --intent "${2:-read}") || return $?
|
|
44
|
+
[ -n "$value" ] || { echo "Error: required GitHub role '$1' resolved empty." >&2; return 2; }
|
|
45
|
+
printf '%s\n' "$value"
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
READY=$(read_role ready
|
|
50
|
-
CLAIMED=$(read_role claimed
|
|
48
|
+
READY=$(read_role ready read) || exit $?
|
|
49
|
+
CLAIMED=$(read_role claimed write) || exit $?
|
|
51
50
|
|
|
52
51
|
read_intake_assignee() {
|
|
53
52
|
local cli_value local_v
|
|
@@ -19,21 +19,20 @@ Run one intake cycle against that repo. The first eligible issue with the `ready
|
|
|
19
19
|
PRD label names are read from `.lisa.config.json` `github.labels.prd.*`, falling back to defaults documented in the `config-resolution` rule. Bash pattern:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
# Read role with default fallback. Local overrides global per-key.
|
|
23
22
|
read_role() {
|
|
24
23
|
# Single resolver — see config-resolution "The single resolver".
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
local value
|
|
25
|
+
value=$(node "${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-plugins/lisa}}/scripts/resolve-lifecycle-role.mjs" \
|
|
26
|
+
--role "$1" --vendor github --intent "${2:-write}") || return $?
|
|
27
|
+
[ -n "$value" ] || { echo "Error: required GitHub PRD role '$1' resolved empty." >&2; return 2; }
|
|
28
|
+
printf '%s\n' "$value"
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
READY=$(read_role ready
|
|
33
|
-
IN_REVIEW=$(read_role in_review
|
|
34
|
-
BLOCKED=$(read_role blocked
|
|
35
|
-
TICKETED=$(read_role ticketed
|
|
36
|
-
SHIPPED=$(read_role shipped
|
|
31
|
+
READY=$(read_role prd.ready write) || exit $?
|
|
32
|
+
IN_REVIEW=$(read_role prd.in_review write) || exit $?
|
|
33
|
+
BLOCKED=$(read_role prd.blocked write) || exit $?
|
|
34
|
+
TICKETED=$(read_role prd.ticketed write) || exit $?
|
|
35
|
+
SHIPPED=$(read_role prd.shipped write) || exit $?
|
|
37
36
|
```
|
|
38
37
|
|
|
39
38
|
In prose below, the role names refer to the resolved labels: e.g. "the `ready` label" means whatever `github.labels.prd.ready` resolves to (default: `prd-ready`).
|
|
@@ -129,7 +129,7 @@ classifier absent.
|
|
|
129
129
|
|
|
130
130
|
**Single-environment collapse (this repo).** `.lisa.config.json` `deploy.branches` declares only `production: main`, so the env-keyed `done` resolves to the single label `status:done` — there is no `status:on-dev` / `status:on-stg` and **no dev → staging → prod promotion chain**. Resolve the env rungs via the env-keyed `done` logic in `config-resolution`, but in the single-environment case the only rung is production and it collapses to the one `status:done` value; the rollup never attempts to resolve a dev or staging `done`. Projects that DO have multiple environments keep the env-keyed map and roll the parent up to whichever `done` (including intermediate `status:on-dev`/`status:on-stg`) its leaves have collectively reached.
|
|
131
131
|
|
|
132
|
-
**Apply the derived label** (only when it differs from the parent's current `status:*`): remove the parent's existing `status:*` label and add the derived one, keeping exactly one `status:*` label so the build-queue invariant holds. Post an idempotent `[claude-sync] rollup` comment naming the derived state and the child tally (e.g. `3/4 leaves terminal, 1 blocked → status:blocked`); when the derived state is `status:blocked` the comment also carries the classifier's per-class section, so the blocking child and its actor are one read rather than a descent.
|
|
132
|
+
**Apply the derived label** (only when it differs from the parent's current `status:*`): remove the parent's existing `status:*` label and add the derived one, keeping exactly one `status:*` label so the build-queue invariant holds. Re-read the labels immediately after the edit and require exactly one `status:*`; if the count differs, surface an error and stop without trying to normalize the ambiguous set automatically. Post an idempotent `[claude-sync] rollup` comment naming the derived state and the child tally (e.g. `3/4 leaves terminal, 1 blocked → status:blocked`); when the derived state is `status:blocked` the comment also carries the classifier's per-class section, so the blocking child and its actor are one read rather than a descent. Use the classifier's `change.summary` as the dedupe key and skip the comment when that summary is unchanged.
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
135
|
gh issue edit <parent-number> --repo <org>/<repo> \
|
|
@@ -22,21 +22,23 @@ REPO=$(jq -r '.github.repo // empty' .lisa.config.local.json 2>/dev/null); REPO=
|
|
|
22
22
|
|
|
23
23
|
read_role() {
|
|
24
24
|
# Single resolver — see config-resolution "The single resolver".
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
node "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-lifecycle-role.mjs" \
|
|
29
|
-
--role "$1" --vendor github --intent "${
|
|
25
|
+
# Every PRD role used here is required. Resolver errors remain visible, and
|
|
26
|
+
# an empty result is refused rather than passed to `gh --label`.
|
|
27
|
+
local value
|
|
28
|
+
value=$(node "${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-plugins/lisa}}/scripts/resolve-lifecycle-role.mjs" \
|
|
29
|
+
--role "$1" --vendor github --intent "${2:-write}") || return $?
|
|
30
|
+
[ -n "$value" ] || { echo "Error: required GitHub role '$1' resolved empty." >&2; return 2; }
|
|
31
|
+
printf '%s\n' "$value"
|
|
30
32
|
}
|
|
31
33
|
# Resolve the FULL PRD lifecycle vocabulary from config (never hard-code names) — needed so the
|
|
32
34
|
# "exactly one role" reconcile and the past-ready check work for projects that renamed any label.
|
|
33
|
-
PRD_DRAFT=$(read_role
|
|
34
|
-
PRD_READY=$(read_role
|
|
35
|
-
PRD_IN_REVIEW=$(read_role
|
|
36
|
-
PRD_BLOCKED=$(read_role
|
|
37
|
-
PRD_TICKETED=$(read_role
|
|
38
|
-
PRD_SHIPPED=$(read_role
|
|
39
|
-
PRD_VERIFIED=$(read_role
|
|
35
|
+
PRD_DRAFT=$(read_role prd.draft write) || exit $?
|
|
36
|
+
PRD_READY=$(read_role prd.ready write) || exit $?
|
|
37
|
+
PRD_IN_REVIEW=$(read_role prd.in_review write) || exit $?
|
|
38
|
+
PRD_BLOCKED=$(read_role prd.blocked write) || exit $?
|
|
39
|
+
PRD_TICKETED=$(read_role prd.ticketed write) || exit $?
|
|
40
|
+
PRD_SHIPPED=$(read_role prd.shipped write) || exit $?
|
|
41
|
+
PRD_VERIFIED=$(read_role prd.verified write) || exit $?
|
|
40
42
|
# All lifecycle labels (for one-of reconcile) and the "progressed past ready" set (never down-rank):
|
|
41
43
|
ALL_PRD_LABELS=("$PRD_DRAFT" "$PRD_READY" "$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
|
|
42
44
|
PROGRESSED=("$PRD_IN_REVIEW" "$PRD_BLOCKED" "$PRD_TICKETED" "$PRD_SHIPPED" "$PRD_VERIFIED")
|
|
@@ -20,18 +20,17 @@ Run one build-intake cycle. The first eligible ready ticket is claimed, built vi
|
|
|
20
20
|
Status names are read from `.lisa.config.json` `jira.workflow.*`, falling back to defaults documented in the `config-resolution` rule. Bash pattern:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
# Read role with default fallback. Local overrides global per-key.
|
|
24
23
|
read_role() {
|
|
25
24
|
# Single resolver — see config-resolution "The single resolver".
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
local value
|
|
26
|
+
value=$(node "${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-plugins/lisa}}/scripts/resolve-lifecycle-role.mjs" \
|
|
27
|
+
--role "$1" --vendor jira --intent "${2:-read}") || return $?
|
|
28
|
+
[ -n "$value" ] || { echo "Error: required JIRA role '$1' resolved empty." >&2; return 2; }
|
|
29
|
+
printf '%s\n' "$value"
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
READY=$(read_role ready
|
|
34
|
-
CLAIMED=$(read_role claimed
|
|
32
|
+
READY=$(read_role ready read) || exit $?
|
|
33
|
+
CLAIMED=$(read_role claimed write) || exit $?
|
|
35
34
|
```
|
|
36
35
|
|
|
37
36
|
For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
@@ -33,6 +33,7 @@ import sys
|
|
|
33
33
|
import urllib.request
|
|
34
34
|
from base64 import b64encode
|
|
35
35
|
from pathlib import Path
|
|
36
|
+
from urllib.parse import urlsplit
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
# Intentionally matches the exact local-claim prefixes only. EVIDENCE-REF is a
|
|
@@ -66,13 +67,50 @@ def jira_config_candidates():
|
|
|
66
67
|
return candidates
|
|
67
68
|
|
|
68
69
|
|
|
70
|
+
def read_jira_config(config_path):
|
|
71
|
+
"""Read the server and login fields from one jira-cli config."""
|
|
72
|
+
server = ""
|
|
73
|
+
login = ""
|
|
74
|
+
with open(config_path) as config:
|
|
75
|
+
for line in config:
|
|
76
|
+
if line.startswith("server:"):
|
|
77
|
+
server = line.split(":", 1)[1].strip()
|
|
78
|
+
elif line.startswith("login:"):
|
|
79
|
+
login = line.split(":", 1)[1].strip()
|
|
80
|
+
return server, login
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def server_origin(server):
|
|
84
|
+
"""Return the normalized HTTPS origin used as the credential trust key."""
|
|
85
|
+
try:
|
|
86
|
+
parsed = urlsplit(server)
|
|
87
|
+
port = parsed.port
|
|
88
|
+
except ValueError:
|
|
89
|
+
return ""
|
|
90
|
+
if parsed.scheme.lower() != "https" or not parsed.hostname:
|
|
91
|
+
return ""
|
|
92
|
+
authority = parsed.hostname.lower()
|
|
93
|
+
if port and port != 443:
|
|
94
|
+
authority = f"{authority}:{port}"
|
|
95
|
+
return f"https://{authority}"
|
|
96
|
+
|
|
97
|
+
|
|
69
98
|
def resolve_jira_config_path():
|
|
70
|
-
"""Resolve
|
|
99
|
+
"""Resolve a project config only when its host matches a user trust root."""
|
|
71
100
|
candidates = jira_config_candidates()
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
home_config = candidates[-1]
|
|
102
|
+
trusted_server = os.environ.get("JIRA_SERVER", "").strip()
|
|
103
|
+
if not trusted_server and home_config.exists():
|
|
104
|
+
trusted_server, _ = read_jira_config(home_config)
|
|
105
|
+
trusted_origin = server_origin(trusted_server)
|
|
106
|
+
|
|
107
|
+
for candidate in candidates[:-1]:
|
|
108
|
+
if not candidate.exists():
|
|
109
|
+
continue
|
|
110
|
+
candidate_server, _ = read_jira_config(candidate)
|
|
111
|
+
if trusted_origin and server_origin(candidate_server) == trusted_origin:
|
|
74
112
|
return candidate
|
|
75
|
-
return
|
|
113
|
+
return home_config
|
|
76
114
|
|
|
77
115
|
|
|
78
116
|
def get_jira_config():
|
|
@@ -82,14 +120,18 @@ def get_jira_config():
|
|
|
82
120
|
print(f"ERROR: jira-cli config not found at {config_path}", file=sys.stderr)
|
|
83
121
|
sys.exit(1)
|
|
84
122
|
|
|
85
|
-
server =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
server, login = read_jira_config(config_path)
|
|
124
|
+
|
|
125
|
+
trusted_server = os.environ.get("JIRA_SERVER", "").strip()
|
|
126
|
+
trusted_origin = server_origin(trusted_server)
|
|
127
|
+
if trusted_origin:
|
|
128
|
+
if server_origin(server) != trusted_origin:
|
|
129
|
+
print("ERROR: jira-cli config server does not match trusted JIRA_SERVER", file=sys.stderr)
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
server = trusted_server.rstrip("/")
|
|
132
|
+
if not server_origin(server):
|
|
133
|
+
print("ERROR: JIRA server must be an HTTPS URL from a trusted config", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
93
135
|
|
|
94
136
|
token = os.environ.get("JIRA_API_TOKEN", "")
|
|
95
137
|
if not token:
|