@codyswann/lisa 2.292.0 → 2.293.1
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-work-item.mjs +76 -6
- package/dist/core/upstream-evidence-manifest.js +4 -4
- package/dist/opencode/plugin-templates/parity-safety-net.sh +41 -0
- 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-implement/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
- package/plugins/lisa/hooks/parity-safety-net.sh +41 -0
- package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +41 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
- 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/parity-safety-net.sh +41 -0
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +41 -0
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
- 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/parity-safety-net.sh +41 -0
- package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md +16 -11
|
@@ -76,6 +76,26 @@ function currentBranch() {
|
|
|
76
76
|
return git(["branch", "--show-current"]);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Branch an in-progress rebase is rewriting, or empty when no rebase is in
|
|
81
|
+
* progress. During a rebase HEAD is detached, but git records the branch being
|
|
82
|
+
* rebased in `<rebase-state-dir>/head-name` (issue #1956) — resolved via
|
|
83
|
+
* `git rev-parse --git-path` so linked worktrees find their private state dir.
|
|
84
|
+
*/
|
|
85
|
+
function rebaseBranch() {
|
|
86
|
+
for (const stateDir of ["rebase-merge", "rebase-apply"]) {
|
|
87
|
+
const file = resolve(
|
|
88
|
+
git(["rev-parse", "--git-path", `${stateDir}/head-name`])
|
|
89
|
+
);
|
|
90
|
+
if (!existsSync(file)) continue;
|
|
91
|
+
const headName = readFileSync(file, "utf8").trim();
|
|
92
|
+
if (headName.startsWith("refs/heads/")) {
|
|
93
|
+
return headName.slice("refs/heads/".length);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
|
|
79
99
|
function deepMerge(base, override) {
|
|
80
100
|
if (Array.isArray(base) || Array.isArray(override)) return override;
|
|
81
101
|
if (
|
|
@@ -301,7 +321,11 @@ function readState(optional = true) {
|
|
|
301
321
|
}
|
|
302
322
|
|
|
303
323
|
function assertStateBranch(state) {
|
|
304
|
-
|
|
324
|
+
// Mid-rebase HEAD is detached, but the binding must validate against the
|
|
325
|
+
// branch the rebase is rewriting (its head-name) so rebase picks and
|
|
326
|
+
// `git rebase --continue` commits are not wedged (issue #1956). A detached
|
|
327
|
+
// HEAD with NO rebase in progress still fails closed below.
|
|
328
|
+
const branch = currentBranch() || rebaseBranch();
|
|
305
329
|
if (!branch)
|
|
306
330
|
throw new TrackingError(
|
|
307
331
|
"Cannot use a work-item binding from detached HEAD"
|
|
@@ -402,15 +426,24 @@ function namesFrom(value) {
|
|
|
402
426
|
}
|
|
403
427
|
|
|
404
428
|
function assertRepoScope(ref, contract, labels, components = []) {
|
|
405
|
-
|
|
429
|
+
// #1957: mirror the intake-side scoping rule uniformly across trackers
|
|
430
|
+
// (plugins/lisa/rules/reference/config-resolution.md:949,:968): a work item
|
|
431
|
+
// is repo-scoped by the label `repo:<name>` OR the bare `<name>` label
|
|
432
|
+
// (Sentry-provenance items carry only the bare repo name). The match is the
|
|
433
|
+
// exact repo short name, case-insensitive via namesFrom's lowercasing —
|
|
434
|
+
// never substring or prefix. Jira additionally accepts a component equal to
|
|
435
|
+
// the bare name.
|
|
436
|
+
const bare = contract.identityRepo.toLowerCase();
|
|
437
|
+
const expected = `repo:${bare}`;
|
|
406
438
|
const labelNames = namesFrom(labels);
|
|
407
439
|
const componentNames = namesFrom(components);
|
|
408
440
|
if (
|
|
409
441
|
!labelNames.includes(expected) &&
|
|
410
|
-
!
|
|
442
|
+
!labelNames.includes(bare) &&
|
|
443
|
+
!componentNames.includes(bare)
|
|
411
444
|
) {
|
|
412
445
|
throw new TrackingError(
|
|
413
|
-
`Work item ${ref} is not scoped to repository ${contract.identityRepo}; require label ${expected}`
|
|
446
|
+
`Work item ${ref} is not scoped to repository ${contract.identityRepo}; require label ${expected} or bare label ${bare}`
|
|
414
447
|
);
|
|
415
448
|
}
|
|
416
449
|
}
|
|
@@ -888,15 +921,52 @@ function validateCommits(commits) {
|
|
|
888
921
|
};
|
|
889
922
|
}
|
|
890
923
|
|
|
924
|
+
/**
|
|
925
|
+
* Fully qualified remote default-branch ref (e.g. refs/remotes/origin/main),
|
|
926
|
+
* or undefined when it cannot be resolved offline. Reads the LOCAL
|
|
927
|
+
* `refs/remotes/<remote>/HEAD` symref only — never the network. Resolution
|
|
928
|
+
* failure means the exclusion is skipped (fail-safe strict, issue #1956).
|
|
929
|
+
* Security: only the remote DEFAULT branch is ever excluded — never
|
|
930
|
+
* `--remotes=<remote>`, whose ref set includes the branch being (force-)pushed
|
|
931
|
+
* and would let a pusher exempt arbitrary commits.
|
|
932
|
+
*/
|
|
933
|
+
function remoteDefaultRef(remote) {
|
|
934
|
+
const symref = run(
|
|
935
|
+
"git",
|
|
936
|
+
["symbolic-ref", "--quiet", `refs/remotes/${remote}/HEAD`],
|
|
937
|
+
{ allowFailure: true }
|
|
938
|
+
);
|
|
939
|
+
if (symref.status !== 0) return undefined;
|
|
940
|
+
const target = symref.stdout.trim();
|
|
941
|
+
if (!target.startsWith(`refs/remotes/${remote}/`)) return undefined;
|
|
942
|
+
const exists = run("git", ["rev-parse", "-q", "--verify", target], {
|
|
943
|
+
allowFailure: true,
|
|
944
|
+
});
|
|
945
|
+
return exists.status === 0 ? target : undefined;
|
|
946
|
+
}
|
|
947
|
+
|
|
891
948
|
function parsePushLines(input, remote) {
|
|
892
949
|
const commits = [];
|
|
950
|
+
// Commits already reachable from the remote default branch are the base's
|
|
951
|
+
// history (a merge-sync brings them along); excluding them keeps validation
|
|
952
|
+
// scoped to branch-authored commits (issue #1956).
|
|
953
|
+
const defaultRef = remoteDefaultRef(remote);
|
|
893
954
|
for (const line of input.trim().split(/\r?\n/).filter(Boolean)) {
|
|
894
955
|
const [, localOid, , remoteOid] = line.trim().split(/\s+/);
|
|
895
956
|
if (!localOid || ZERO_OID.test(localOid)) continue;
|
|
896
957
|
const args =
|
|
897
958
|
remoteOid && !ZERO_OID.test(remoteOid)
|
|
898
|
-
? [
|
|
899
|
-
|
|
959
|
+
? [
|
|
960
|
+
"rev-list",
|
|
961
|
+
`${remoteOid}..${localOid}`,
|
|
962
|
+
...(defaultRef ? ["--not", defaultRef] : []),
|
|
963
|
+
]
|
|
964
|
+
: // New-branch lane: `--remotes=<remote>` is safe HERE because the branch
|
|
965
|
+
// being pushed has no remote-tracking ref yet — the exclusion set can
|
|
966
|
+
// only contain refs that already passed validation on earlier pushes.
|
|
967
|
+
// The existing-branch lane above must NOT use it (its tracking ref is
|
|
968
|
+
// pusher-controlled); it excludes only the remote default branch.
|
|
969
|
+
["rev-list", localOid, "--not", `--remotes=${remote}`];
|
|
900
970
|
commits.push(...git(args).split("\n").filter(Boolean));
|
|
901
971
|
}
|
|
902
972
|
if (commits.length === 0 && input.trim() === "") {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** Generated by scripts/generate-upstream-evidence-manifest.mjs. Do not edit. */
|
|
3
3
|
export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
4
4
|
"all/copy-contents/gitignore": "06b56376465d5421db55d0b04958bbc79fdbf33b42f0559fe5ef6875ced1160f",
|
|
5
|
-
"all/copy-overwrite/scripts/lisa-work-item.mjs": "
|
|
5
|
+
"all/copy-overwrite/scripts/lisa-work-item.mjs": "5eb31c284e820a51312d4484f0669128007326af55ee515923fec33700d003a1",
|
|
6
6
|
"all/create-only/.claude/rules/PROJECT_RULES.md": "6e1c7923ad2a15356babc60c97e7f97be728d790af285b6a7cd7d1b4d39cd97f",
|
|
7
7
|
"all/create-only/.lisaignore": "3c9827e7e98daa46510b5357cd6dd9406da1821e64cc46d2eea9f311cd6d06b9",
|
|
8
8
|
"all/create-only/scripts/remote-agent-aws-setup.sh": "7677908a345891810d2104ba8b2fd1a8318bb29c408fb5ce0110d57beef9b556",
|
|
@@ -294,7 +294,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
294
294
|
"plugins/src/base/hooks/install-pkgs.sh": "e9a13faba849a277410dde911ab102ee6f88ef915b33571bb1d4eef904172db5",
|
|
295
295
|
"plugins/src/base/hooks/parity-safety-net-heredoc.py": "c5301958ed8ca29b0de7b8521d299cdbc8c1c402df8d8fada6b5f173b6cf5dbe",
|
|
296
296
|
"plugins/src/base/hooks/parity-safety-net.agy.sh": "ce9bd2b4566ad9147e4ace56434cffbbe87edb4ccbb57549ab3fd9f4c671ced4",
|
|
297
|
-
"plugins/src/base/hooks/parity-safety-net.sh": "
|
|
297
|
+
"plugins/src/base/hooks/parity-safety-net.sh": "b83c5043a0f29b2d8851258acfe6906aab7922cd80f486e25220e55f26a8c0a5",
|
|
298
298
|
"plugins/src/base/hooks/setup-jira-cli.sh": "a675f6b17ce7f0d6b9fb7daeba6d2bc59bcb48ef79b034076ea73b2b1e72ee09",
|
|
299
299
|
"plugins/src/base/hooks/shell-write-nudge.sh": "69839af423f8792b1e52c71097316c3264425553031627c0a2f9409a9d5becd0",
|
|
300
300
|
"plugins/src/base/hooks/threshold-ratchet-compare.mjs": "8bc4b379d7057b2fecf9cb35820c39efad0a65df7df49073786f996cc2a74810",
|
|
@@ -426,7 +426,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
426
426
|
"plugins/src/base/skills/lisa-github-write-issue/SKILL.md": "ee54b3d14cc5dd3a68a8c36590102dd02f49334c9493ad52fa8c7f35157073b1",
|
|
427
427
|
"plugins/src/base/skills/lisa-github-write-prd/SKILL.md": "bc919c52e2f42129d41a3f40279d2def99486ec58bed2dab51ca7166b427a947",
|
|
428
428
|
"plugins/src/base/skills/lisa-health/SKILL.md": "dfdb08a863e78bff42671793dcec29cfae0654db18ebf66a4aa77aeb56ddb775",
|
|
429
|
-
"plugins/src/base/skills/lisa-implement/SKILL.md": "
|
|
429
|
+
"plugins/src/base/skills/lisa-implement/SKILL.md": "cb3a0587907321301e6662480af0d333aee4a3e0d29e9bdc851c225be2c220d7",
|
|
430
430
|
"plugins/src/base/skills/lisa-improve-code-complexity/SKILL.md": "24ab5b193b409db6ee6bee981a1c0a48d08991782d7846116ad01658c8bc1ae8",
|
|
431
431
|
"plugins/src/base/skills/lisa-improve-harness/SKILL.md": "bafda5d2f9b86c48cd16bf0015528fdf5891675221d662fdc533aaad66aad669",
|
|
432
432
|
"plugins/src/base/skills/lisa-improve-max-lines-per-function/SKILL.md": "95c875950c9848fdaf520a18b9bbb264e33d7347be10170add768e01d9cd8e52",
|
|
@@ -480,7 +480,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
480
480
|
"plugins/src/base/skills/lisa-parity-code-review/SKILL.md": "5805254747c4ed9081fd514236f06e078be86d41694bf74b480bf7592cd58c1f",
|
|
481
481
|
"plugins/src/base/skills/lisa-parity-code-simplifier/SKILL.md": "c721ae772e73abff633c43d93267066afac188e911a77322fbb102db673849d7",
|
|
482
482
|
"plugins/src/base/skills/lisa-parity-coderabbit/SKILL.md": "ae882837d43741293d1b639c4516331fa6124fa1f5da234a74ecb31c5d7e52cd",
|
|
483
|
-
"plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md": "
|
|
483
|
+
"plugins/src/base/skills/lisa-parity-safety-net-rules/SKILL.md": "2515b016f3a1c80163aaf097c3f530e884e59e2070d072a6cb5c7b9d2658c946",
|
|
484
484
|
"plugins/src/base/skills/lisa-parity-sentry-sdk-setup/SKILL.md": "ed52b0273f6b535d26d561c80ae6c0bedb84c67fcf4321f688c05fe3ee3af575",
|
|
485
485
|
"plugins/src/base/skills/lisa-parity-sentry-seer/SKILL.md": "f44216fa7c8f0c3c3371c65ff73bbeadd1717579502a5603f16b3b33291cab7a",
|
|
486
486
|
"plugins/src/base/skills/lisa-parity-skill-creator/SKILL.md": "616e0493f75fe92c18137548d7c86a91a9b04d9844ca76fa3f14156e3e7814e5",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
# divergence: upstream blocks unconditionally; Lisa allows clean-tree resets.
|
|
25
25
|
# Residual risk (documented, accepted): the dirty check runs in the hook's
|
|
26
26
|
# cwd at hook time, so a `cd elsewhere && git reset --hard` evades it.
|
|
27
|
+
# - `git rebase --abort`/`--quit` while the in-progress rebase holds
|
|
28
|
+
# human-made conflict resolutions (AUTO_MERGE discriminator; issue #1956).
|
|
29
|
+
# Clean or untouched rebase state stays abortable; the apply backend and a
|
|
30
|
+
# missing AUTO_MERGE ref fail closed. Deliberate divergence: upstream
|
|
31
|
+
# blocks `rebase --abort` unconditionally, which strands agents mid-rebase.
|
|
27
32
|
# - `git checkout` discards (`--`, `-f/--force`, `--pathspec-from-file`,
|
|
28
33
|
# bare `.` — the bare-`.` block exceeds upstream)
|
|
29
34
|
# - `git switch --discard-changes` / `-f/--force`
|
|
@@ -312,6 +317,42 @@ if matches "${GIT_CMD}"'reset\b.*--(hard|merge)\b'; then
|
|
|
312
317
|
fi
|
|
313
318
|
fi
|
|
314
319
|
|
|
320
|
+
# 3b. `git rebase --abort` / `--quit` ONLY while human/agent conflict
|
|
321
|
+
# resolutions exist (issue #1956). `--abort` restores the pre-rebase branch
|
|
322
|
+
# and discards every resolution; `--quit` deletes the rebase bookkeeping
|
|
323
|
+
# (head-name, todo) while stranding a detached HEAD, making recovery
|
|
324
|
+
# ambiguous — both are treated the same, conditionally. A rebase state with
|
|
325
|
+
# nothing human-made in it (a clean-pick wedge, or a conflict stop nobody
|
|
326
|
+
# has touched) is agent-recoverable, so aborting it stays ALLOWED.
|
|
327
|
+
# Discriminator (empirical, git 2.53): diff the worktree/index against the
|
|
328
|
+
# AUTO_MERGE ref (the merge-ort recorded conflicted tree). Abort-safe ⇔
|
|
329
|
+
# worktree diff is quiet AND (the cached diff is quiet OR unmerged index
|
|
330
|
+
# entries exist — an untouched conflict stop has unmerged entries that make
|
|
331
|
+
# the cached diff non-quiet without any human edit). Fail CLOSED on the
|
|
332
|
+
# rebase-apply backend (no AUTO_MERGE contract) and on a missing AUTO_MERGE
|
|
333
|
+
# ref while rebase-merge state exists. Same accepted residual risk as
|
|
334
|
+
# guard 3: the probes run in the hook's cwd at hook time.
|
|
335
|
+
if matches "${GIT_CMD}"'rebase'"${GIT_TOKENS}"'--(abort|quit)([[:space:]]|$)'; then
|
|
336
|
+
rebase_apply_dir="$(git rev-parse --git-path rebase-apply 2>/dev/null || true)"
|
|
337
|
+
rebase_merge_dir="$(git rev-parse --git-path rebase-merge 2>/dev/null || true)"
|
|
338
|
+
if [ -n "$rebase_apply_dir" ] && [ -d "$rebase_apply_dir" ]; then
|
|
339
|
+
block "git rebase --abort/--quit on an apply-backend rebase cannot prove no conflict resolutions would be lost (fail closed; finish or continue the rebase instead)"
|
|
340
|
+
fi
|
|
341
|
+
if [ -n "$rebase_merge_dir" ] && [ -d "$rebase_merge_dir" ]; then
|
|
342
|
+
if ! git rev-parse -q --verify AUTO_MERGE >/dev/null 2>&1; then
|
|
343
|
+
block "git rebase --abort/--quit with an unresolvable AUTO_MERGE ref cannot prove no conflict resolutions would be lost (fail closed)"
|
|
344
|
+
fi
|
|
345
|
+
if ! git diff --quiet AUTO_MERGE 2>/dev/null; then
|
|
346
|
+
block "git rebase --abort/--quit would discard conflict resolutions in the working tree (finish resolving and run git rebase --continue instead)"
|
|
347
|
+
fi
|
|
348
|
+
unmerged_entries="$(git ls-files -u 2>/dev/null || true)"
|
|
349
|
+
if [ -z "$unmerged_entries" ] \
|
|
350
|
+
&& ! git diff --cached --quiet AUTO_MERGE 2>/dev/null; then
|
|
351
|
+
block "git rebase --abort/--quit would discard staged conflict resolutions (finish resolving and run git rebase --continue instead)"
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
|
|
315
356
|
# 4. `git checkout` worktree discards: the `--` pathspec form (with or without a
|
|
316
357
|
# ref), `-f/--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
317
358
|
# Blocking bare `.` exceeds upstream (which allows any single positional) —
|
package/package.json
CHANGED
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"brace-expansion": ">=5.0.6"
|
|
114
114
|
},
|
|
115
115
|
"name": "@codyswann/lisa",
|
|
116
|
-
"version": "2.
|
|
116
|
+
"version": "2.293.1",
|
|
117
117
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
118
118
|
"main": "dist/index.js",
|
|
119
119
|
"exports": {
|
|
@@ -95,7 +95,7 @@ Using the general-purpose agent in Team Lead session, **determine the base branc
|
|
|
95
95
|
- Already on a feature branch with an **open PR** → reuse it. If the PR's base ≠ the resolved base branch, surface the mismatch and re-target only with confirmation — the ticket's environment is the source of truth.
|
|
96
96
|
- Already on a feature branch with **no open PR** → reuse it; its PR base will be the resolved base branch (do not ask the human — the environment determines it).
|
|
97
97
|
- On an **environment / default branch** → check out a feature branch named for this plan (with the work-item ref prefix, per the linkage rules below) **from `origin/<base>`**.
|
|
98
|
-
- **
|
|
98
|
+
- **Sync the feature branch onto the latest `origin/<base>` and resolve any merge conflicts BEFORE starting work.** Both sync lanes are sanctioned in a bound worktree: **rebase** (`git rebase origin/<base>` — the commit hooks validate mid-rebase picks against the rebase head-name, so a work-item binding never wedges a rebase) and **merge** (`git merge origin/<base>` — push validation exempts commits already reachable from the remote default branch, while branch-authored commits stay strictly validated; the exemption needs the local `origin/HEAD` symref, so if a hand-added remote fails push validation on foreign commits, run `git remote set-head origin -a` first). Prefer rebase for a linear history; use merge when rewriting pushed history is undesirable. If a rebase goes wrong before anyone has resolved conflicts, `git rebase --abort` is a safe, allowed recovery; once conflict resolutions exist, the safety net blocks abort to protect them — finish resolving and `git rebase --continue` instead. If the conflicts cannot be resolved cleanly and safely, create a fix task for the agent team (with the conflicting file list and current merge state) and resolve it before implementation begins — never start work on stale or conflicted code.
|
|
99
99
|
4. **The PR targets the resolved base branch** — carry it as `target_branch=<base>` into `lisa-git-submit-pr` (Verify flow). `git-submit-pr` already chooses a closing keyword when the base is the production/default branch and a non-closing reference for a non-terminal environment branch. For a bug fixed on a non-integration environment branch, the current flow is not done until the fix is merged and verified there, then forward cherry-picked down to the integration branch via a linked follow-up.
|
|
100
100
|
|
|
101
101
|
Every Implement run now has a tracker work item. Preserve its canonical identifier for development linkage unconditionally:
|
|
@@ -69,22 +69,27 @@ RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-
|
|
|
69
69
|
(`git push origin +main`) is not blocked — upstream 1.0.6 allows it too.
|
|
70
70
|
4. `git reset --hard` / `git reset --merge` while the working tree is **dirty**
|
|
71
71
|
(would discard work). Clean-tree resets are intentionally allowed.
|
|
72
|
-
5. `git
|
|
72
|
+
5. `git rebase --abort` / `git rebase --quit` while the in-progress rebase
|
|
73
|
+
holds human-made conflict resolutions (they would be discarded). A clean or
|
|
74
|
+
untouched rebase state stays abortable — an agent-recoverable wedge; the
|
|
75
|
+
apply backend and a missing `AUTO_MERGE` ref fail closed. Deliberate
|
|
76
|
+
divergence: upstream blocks `rebase --abort` unconditionally.
|
|
77
|
+
6. `git checkout` discards: the `--` pathspec form (with or without a ref),
|
|
73
78
|
`-f`/`--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
74
79
|
Branch switching and `-b`/`-B` creation stay allowed.
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
7. `git switch --discard-changes` / `-f`/`--force`.
|
|
81
|
+
8. `git restore` touching the worktree — only `git restore --staged <path>`
|
|
77
82
|
without `--worktree` is allowed (unstaging is safe).
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
9. `git stash drop` / `git stash clear` (push/pop/list/apply stay allowed).
|
|
84
|
+
10. `git clean` with a force flag and no dry-run — `-n`/`--dry-run` anywhere
|
|
85
|
+
makes it a safe preview.
|
|
86
|
+
11. `git branch -D` (or `-d` combined with `-f`, clustered or split);
|
|
82
87
|
safe `-d` and rename `-m` stay allowed.
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
12. `git tag -d`, `git reflog delete`, `git worktree remove --force`.
|
|
89
|
+
13. Deletion via `find … -delete`, `find … -exec rm -rf`, and `xargs … rm -rf`
|
|
85
90
|
(plain non-recursive `rm` on find/xargs output stays allowed).
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
14. Disk destroyers: `dd of=/dev/…`, `mkfs … /dev/…`, `shred`.
|
|
92
|
+
15. Destructive SQL — `DROP DATABASE/SCHEMA/TABLE`, `TRUNCATE TABLE`.
|
|
88
93
|
|
|
89
94
|
Every git guard sees through leading git **global options** (`-C <path>`,
|
|
90
95
|
`-c <k>=<v>`, `--git-dir[=…]`, `--no-pager`, …), so `git -C /path <destructive>`
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
# divergence: upstream blocks unconditionally; Lisa allows clean-tree resets.
|
|
25
25
|
# Residual risk (documented, accepted): the dirty check runs in the hook's
|
|
26
26
|
# cwd at hook time, so a `cd elsewhere && git reset --hard` evades it.
|
|
27
|
+
# - `git rebase --abort`/`--quit` while the in-progress rebase holds
|
|
28
|
+
# human-made conflict resolutions (AUTO_MERGE discriminator; issue #1956).
|
|
29
|
+
# Clean or untouched rebase state stays abortable; the apply backend and a
|
|
30
|
+
# missing AUTO_MERGE ref fail closed. Deliberate divergence: upstream
|
|
31
|
+
# blocks `rebase --abort` unconditionally, which strands agents mid-rebase.
|
|
27
32
|
# - `git checkout` discards (`--`, `-f/--force`, `--pathspec-from-file`,
|
|
28
33
|
# bare `.` — the bare-`.` block exceeds upstream)
|
|
29
34
|
# - `git switch --discard-changes` / `-f/--force`
|
|
@@ -312,6 +317,42 @@ if matches "${GIT_CMD}"'reset\b.*--(hard|merge)\b'; then
|
|
|
312
317
|
fi
|
|
313
318
|
fi
|
|
314
319
|
|
|
320
|
+
# 3b. `git rebase --abort` / `--quit` ONLY while human/agent conflict
|
|
321
|
+
# resolutions exist (issue #1956). `--abort` restores the pre-rebase branch
|
|
322
|
+
# and discards every resolution; `--quit` deletes the rebase bookkeeping
|
|
323
|
+
# (head-name, todo) while stranding a detached HEAD, making recovery
|
|
324
|
+
# ambiguous — both are treated the same, conditionally. A rebase state with
|
|
325
|
+
# nothing human-made in it (a clean-pick wedge, or a conflict stop nobody
|
|
326
|
+
# has touched) is agent-recoverable, so aborting it stays ALLOWED.
|
|
327
|
+
# Discriminator (empirical, git 2.53): diff the worktree/index against the
|
|
328
|
+
# AUTO_MERGE ref (the merge-ort recorded conflicted tree). Abort-safe ⇔
|
|
329
|
+
# worktree diff is quiet AND (the cached diff is quiet OR unmerged index
|
|
330
|
+
# entries exist — an untouched conflict stop has unmerged entries that make
|
|
331
|
+
# the cached diff non-quiet without any human edit). Fail CLOSED on the
|
|
332
|
+
# rebase-apply backend (no AUTO_MERGE contract) and on a missing AUTO_MERGE
|
|
333
|
+
# ref while rebase-merge state exists. Same accepted residual risk as
|
|
334
|
+
# guard 3: the probes run in the hook's cwd at hook time.
|
|
335
|
+
if matches "${GIT_CMD}"'rebase'"${GIT_TOKENS}"'--(abort|quit)([[:space:]]|$)'; then
|
|
336
|
+
rebase_apply_dir="$(git rev-parse --git-path rebase-apply 2>/dev/null || true)"
|
|
337
|
+
rebase_merge_dir="$(git rev-parse --git-path rebase-merge 2>/dev/null || true)"
|
|
338
|
+
if [ -n "$rebase_apply_dir" ] && [ -d "$rebase_apply_dir" ]; then
|
|
339
|
+
block "git rebase --abort/--quit on an apply-backend rebase cannot prove no conflict resolutions would be lost (fail closed; finish or continue the rebase instead)"
|
|
340
|
+
fi
|
|
341
|
+
if [ -n "$rebase_merge_dir" ] && [ -d "$rebase_merge_dir" ]; then
|
|
342
|
+
if ! git rev-parse -q --verify AUTO_MERGE >/dev/null 2>&1; then
|
|
343
|
+
block "git rebase --abort/--quit with an unresolvable AUTO_MERGE ref cannot prove no conflict resolutions would be lost (fail closed)"
|
|
344
|
+
fi
|
|
345
|
+
if ! git diff --quiet AUTO_MERGE 2>/dev/null; then
|
|
346
|
+
block "git rebase --abort/--quit would discard conflict resolutions in the working tree (finish resolving and run git rebase --continue instead)"
|
|
347
|
+
fi
|
|
348
|
+
unmerged_entries="$(git ls-files -u 2>/dev/null || true)"
|
|
349
|
+
if [ -z "$unmerged_entries" ] \
|
|
350
|
+
&& ! git diff --cached --quiet AUTO_MERGE 2>/dev/null; then
|
|
351
|
+
block "git rebase --abort/--quit would discard staged conflict resolutions (finish resolving and run git rebase --continue instead)"
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
|
|
315
356
|
# 4. `git checkout` worktree discards: the `--` pathspec form (with or without a
|
|
316
357
|
# ref), `-f/--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
317
358
|
# Blocking bare `.` exceeds upstream (which allows any single positional) —
|
|
@@ -95,7 +95,7 @@ Using the general-purpose agent in Team Lead session, **determine the base branc
|
|
|
95
95
|
- Already on a feature branch with an **open PR** → reuse it. If the PR's base ≠ the resolved base branch, surface the mismatch and re-target only with confirmation — the ticket's environment is the source of truth.
|
|
96
96
|
- Already on a feature branch with **no open PR** → reuse it; its PR base will be the resolved base branch (do not ask the human — the environment determines it).
|
|
97
97
|
- On an **environment / default branch** → check out a feature branch named for this plan (with the work-item ref prefix, per the linkage rules below) **from `origin/<base>`**.
|
|
98
|
-
- **
|
|
98
|
+
- **Sync the feature branch onto the latest `origin/<base>` and resolve any merge conflicts BEFORE starting work.** Both sync lanes are sanctioned in a bound worktree: **rebase** (`git rebase origin/<base>` — the commit hooks validate mid-rebase picks against the rebase head-name, so a work-item binding never wedges a rebase) and **merge** (`git merge origin/<base>` — push validation exempts commits already reachable from the remote default branch, while branch-authored commits stay strictly validated; the exemption needs the local `origin/HEAD` symref, so if a hand-added remote fails push validation on foreign commits, run `git remote set-head origin -a` first). Prefer rebase for a linear history; use merge when rewriting pushed history is undesirable. If a rebase goes wrong before anyone has resolved conflicts, `git rebase --abort` is a safe, allowed recovery; once conflict resolutions exist, the safety net blocks abort to protect them — finish resolving and `git rebase --continue` instead. If the conflicts cannot be resolved cleanly and safely, create a fix task for the agent team (with the conflicting file list and current merge state) and resolve it before implementation begins — never start work on stale or conflicted code.
|
|
99
99
|
4. **The PR targets the resolved base branch** — carry it as `target_branch=<base>` into `lisa-git-submit-pr` (Verify flow). `git-submit-pr` already chooses a closing keyword when the base is the production/default branch and a non-closing reference for a non-terminal environment branch. For a bug fixed on a non-integration environment branch, the current flow is not done until the fix is merged and verified there, then forward cherry-picked down to the integration branch via a linked follow-up.
|
|
100
100
|
|
|
101
101
|
Every Implement run now has a tracker work item. Preserve its canonical identifier for development linkage unconditionally:
|
|
@@ -69,22 +69,27 @@ RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-
|
|
|
69
69
|
(`git push origin +main`) is not blocked — upstream 1.0.6 allows it too.
|
|
70
70
|
4. `git reset --hard` / `git reset --merge` while the working tree is **dirty**
|
|
71
71
|
(would discard work). Clean-tree resets are intentionally allowed.
|
|
72
|
-
5. `git
|
|
72
|
+
5. `git rebase --abort` / `git rebase --quit` while the in-progress rebase
|
|
73
|
+
holds human-made conflict resolutions (they would be discarded). A clean or
|
|
74
|
+
untouched rebase state stays abortable — an agent-recoverable wedge; the
|
|
75
|
+
apply backend and a missing `AUTO_MERGE` ref fail closed. Deliberate
|
|
76
|
+
divergence: upstream blocks `rebase --abort` unconditionally.
|
|
77
|
+
6. `git checkout` discards: the `--` pathspec form (with or without a ref),
|
|
73
78
|
`-f`/`--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
74
79
|
Branch switching and `-b`/`-B` creation stay allowed.
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
7. `git switch --discard-changes` / `-f`/`--force`.
|
|
81
|
+
8. `git restore` touching the worktree — only `git restore --staged <path>`
|
|
77
82
|
without `--worktree` is allowed (unstaging is safe).
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
9. `git stash drop` / `git stash clear` (push/pop/list/apply stay allowed).
|
|
84
|
+
10. `git clean` with a force flag and no dry-run — `-n`/`--dry-run` anywhere
|
|
85
|
+
makes it a safe preview.
|
|
86
|
+
11. `git branch -D` (or `-d` combined with `-f`, clustered or split);
|
|
82
87
|
safe `-d` and rename `-m` stay allowed.
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
12. `git tag -d`, `git reflog delete`, `git worktree remove --force`.
|
|
89
|
+
13. Deletion via `find … -delete`, `find … -exec rm -rf`, and `xargs … rm -rf`
|
|
85
90
|
(plain non-recursive `rm` on find/xargs output stays allowed).
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
14. Disk destroyers: `dd of=/dev/…`, `mkfs … /dev/…`, `shred`.
|
|
92
|
+
15. Destructive SQL — `DROP DATABASE/SCHEMA/TABLE`, `TRUNCATE TABLE`.
|
|
88
93
|
|
|
89
94
|
Every git guard sees through leading git **global options** (`-C <path>`,
|
|
90
95
|
`-c <k>=<v>`, `--git-dir[=…]`, `--no-pager`, …), so `git -C /path <destructive>`
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
# divergence: upstream blocks unconditionally; Lisa allows clean-tree resets.
|
|
25
25
|
# Residual risk (documented, accepted): the dirty check runs in the hook's
|
|
26
26
|
# cwd at hook time, so a `cd elsewhere && git reset --hard` evades it.
|
|
27
|
+
# - `git rebase --abort`/`--quit` while the in-progress rebase holds
|
|
28
|
+
# human-made conflict resolutions (AUTO_MERGE discriminator; issue #1956).
|
|
29
|
+
# Clean or untouched rebase state stays abortable; the apply backend and a
|
|
30
|
+
# missing AUTO_MERGE ref fail closed. Deliberate divergence: upstream
|
|
31
|
+
# blocks `rebase --abort` unconditionally, which strands agents mid-rebase.
|
|
27
32
|
# - `git checkout` discards (`--`, `-f/--force`, `--pathspec-from-file`,
|
|
28
33
|
# bare `.` — the bare-`.` block exceeds upstream)
|
|
29
34
|
# - `git switch --discard-changes` / `-f/--force`
|
|
@@ -312,6 +317,42 @@ if matches "${GIT_CMD}"'reset\b.*--(hard|merge)\b'; then
|
|
|
312
317
|
fi
|
|
313
318
|
fi
|
|
314
319
|
|
|
320
|
+
# 3b. `git rebase --abort` / `--quit` ONLY while human/agent conflict
|
|
321
|
+
# resolutions exist (issue #1956). `--abort` restores the pre-rebase branch
|
|
322
|
+
# and discards every resolution; `--quit` deletes the rebase bookkeeping
|
|
323
|
+
# (head-name, todo) while stranding a detached HEAD, making recovery
|
|
324
|
+
# ambiguous — both are treated the same, conditionally. A rebase state with
|
|
325
|
+
# nothing human-made in it (a clean-pick wedge, or a conflict stop nobody
|
|
326
|
+
# has touched) is agent-recoverable, so aborting it stays ALLOWED.
|
|
327
|
+
# Discriminator (empirical, git 2.53): diff the worktree/index against the
|
|
328
|
+
# AUTO_MERGE ref (the merge-ort recorded conflicted tree). Abort-safe ⇔
|
|
329
|
+
# worktree diff is quiet AND (the cached diff is quiet OR unmerged index
|
|
330
|
+
# entries exist — an untouched conflict stop has unmerged entries that make
|
|
331
|
+
# the cached diff non-quiet without any human edit). Fail CLOSED on the
|
|
332
|
+
# rebase-apply backend (no AUTO_MERGE contract) and on a missing AUTO_MERGE
|
|
333
|
+
# ref while rebase-merge state exists. Same accepted residual risk as
|
|
334
|
+
# guard 3: the probes run in the hook's cwd at hook time.
|
|
335
|
+
if matches "${GIT_CMD}"'rebase'"${GIT_TOKENS}"'--(abort|quit)([[:space:]]|$)'; then
|
|
336
|
+
rebase_apply_dir="$(git rev-parse --git-path rebase-apply 2>/dev/null || true)"
|
|
337
|
+
rebase_merge_dir="$(git rev-parse --git-path rebase-merge 2>/dev/null || true)"
|
|
338
|
+
if [ -n "$rebase_apply_dir" ] && [ -d "$rebase_apply_dir" ]; then
|
|
339
|
+
block "git rebase --abort/--quit on an apply-backend rebase cannot prove no conflict resolutions would be lost (fail closed; finish or continue the rebase instead)"
|
|
340
|
+
fi
|
|
341
|
+
if [ -n "$rebase_merge_dir" ] && [ -d "$rebase_merge_dir" ]; then
|
|
342
|
+
if ! git rev-parse -q --verify AUTO_MERGE >/dev/null 2>&1; then
|
|
343
|
+
block "git rebase --abort/--quit with an unresolvable AUTO_MERGE ref cannot prove no conflict resolutions would be lost (fail closed)"
|
|
344
|
+
fi
|
|
345
|
+
if ! git diff --quiet AUTO_MERGE 2>/dev/null; then
|
|
346
|
+
block "git rebase --abort/--quit would discard conflict resolutions in the working tree (finish resolving and run git rebase --continue instead)"
|
|
347
|
+
fi
|
|
348
|
+
unmerged_entries="$(git ls-files -u 2>/dev/null || true)"
|
|
349
|
+
if [ -z "$unmerged_entries" ] \
|
|
350
|
+
&& ! git diff --cached --quiet AUTO_MERGE 2>/dev/null; then
|
|
351
|
+
block "git rebase --abort/--quit would discard staged conflict resolutions (finish resolving and run git rebase --continue instead)"
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
|
|
315
356
|
# 4. `git checkout` worktree discards: the `--` pathspec form (with or without a
|
|
316
357
|
# ref), `-f/--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
317
358
|
# Blocking bare `.` exceeds upstream (which allows any single positional) —
|
|
@@ -95,7 +95,7 @@ Using the general-purpose agent in Team Lead session, **determine the base branc
|
|
|
95
95
|
- Already on a feature branch with an **open PR** → reuse it. If the PR's base ≠ the resolved base branch, surface the mismatch and re-target only with confirmation — the ticket's environment is the source of truth.
|
|
96
96
|
- Already on a feature branch with **no open PR** → reuse it; its PR base will be the resolved base branch (do not ask the human — the environment determines it).
|
|
97
97
|
- On an **environment / default branch** → check out a feature branch named for this plan (with the work-item ref prefix, per the linkage rules below) **from `origin/<base>`**.
|
|
98
|
-
- **
|
|
98
|
+
- **Sync the feature branch onto the latest `origin/<base>` and resolve any merge conflicts BEFORE starting work.** Both sync lanes are sanctioned in a bound worktree: **rebase** (`git rebase origin/<base>` — the commit hooks validate mid-rebase picks against the rebase head-name, so a work-item binding never wedges a rebase) and **merge** (`git merge origin/<base>` — push validation exempts commits already reachable from the remote default branch, while branch-authored commits stay strictly validated; the exemption needs the local `origin/HEAD` symref, so if a hand-added remote fails push validation on foreign commits, run `git remote set-head origin -a` first). Prefer rebase for a linear history; use merge when rewriting pushed history is undesirable. If a rebase goes wrong before anyone has resolved conflicts, `git rebase --abort` is a safe, allowed recovery; once conflict resolutions exist, the safety net blocks abort to protect them — finish resolving and `git rebase --continue` instead. If the conflicts cannot be resolved cleanly and safely, create a fix task for the agent team (with the conflicting file list and current merge state) and resolve it before implementation begins — never start work on stale or conflicted code.
|
|
99
99
|
4. **The PR targets the resolved base branch** — carry it as `target_branch=<base>` into `lisa-git-submit-pr` (Verify flow). `git-submit-pr` already chooses a closing keyword when the base is the production/default branch and a non-closing reference for a non-terminal environment branch. For a bug fixed on a non-integration environment branch, the current flow is not done until the fix is merged and verified there, then forward cherry-picked down to the integration branch via a linked follow-up.
|
|
100
100
|
|
|
101
101
|
Every Implement run now has a tracker work item. Preserve its canonical identifier for development linkage unconditionally:
|
|
@@ -69,22 +69,27 @@ RULES_FILE="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-
|
|
|
69
69
|
(`git push origin +main`) is not blocked — upstream 1.0.6 allows it too.
|
|
70
70
|
4. `git reset --hard` / `git reset --merge` while the working tree is **dirty**
|
|
71
71
|
(would discard work). Clean-tree resets are intentionally allowed.
|
|
72
|
-
5. `git
|
|
72
|
+
5. `git rebase --abort` / `git rebase --quit` while the in-progress rebase
|
|
73
|
+
holds human-made conflict resolutions (they would be discarded). A clean or
|
|
74
|
+
untouched rebase state stays abortable — an agent-recoverable wedge; the
|
|
75
|
+
apply backend and a missing `AUTO_MERGE` ref fail closed. Deliberate
|
|
76
|
+
divergence: upstream blocks `rebase --abort` unconditionally.
|
|
77
|
+
6. `git checkout` discards: the `--` pathspec form (with or without a ref),
|
|
73
78
|
`-f`/`--force`, `--pathspec-from-file`, and bare `git checkout .`.
|
|
74
79
|
Branch switching and `-b`/`-B` creation stay allowed.
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
7. `git switch --discard-changes` / `-f`/`--force`.
|
|
81
|
+
8. `git restore` touching the worktree — only `git restore --staged <path>`
|
|
77
82
|
without `--worktree` is allowed (unstaging is safe).
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
9. `git stash drop` / `git stash clear` (push/pop/list/apply stay allowed).
|
|
84
|
+
10. `git clean` with a force flag and no dry-run — `-n`/`--dry-run` anywhere
|
|
85
|
+
makes it a safe preview.
|
|
86
|
+
11. `git branch -D` (or `-d` combined with `-f`, clustered or split);
|
|
82
87
|
safe `-d` and rename `-m` stay allowed.
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
12. `git tag -d`, `git reflog delete`, `git worktree remove --force`.
|
|
89
|
+
13. Deletion via `find … -delete`, `find … -exec rm -rf`, and `xargs … rm -rf`
|
|
85
90
|
(plain non-recursive `rm` on find/xargs output stays allowed).
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
14. Disk destroyers: `dd of=/dev/…`, `mkfs … /dev/…`, `shred`.
|
|
92
|
+
15. Destructive SQL — `DROP DATABASE/SCHEMA/TABLE`, `TRUNCATE TABLE`.
|
|
88
93
|
|
|
89
94
|
Every git guard sees through leading git **global options** (`-C <path>`,
|
|
90
95
|
`-c <k>=<v>`, `--git-dir[=…]`, `--no-pager`, …), so `git -C /path <destructive>`
|