@hasna/hooks 0.7.10 → 0.9.0

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 (96) hide show
  1. package/README.md +32 -6
  2. package/bin/index.js +2640 -1820
  3. package/bin/serve.js +742 -551
  4. package/dist/config.d.ts +25 -19
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.js +1251 -717
  7. package/dist/lib/app-home.d.ts +9 -0
  8. package/dist/lib/installer.d.ts +40 -10
  9. package/dist/lib/local-opt-in.d.ts +80 -0
  10. package/dist/lib/profiles.d.ts +10 -0
  11. package/dist/lib/registration.d.ts +75 -0
  12. package/dist/lib/registry.d.ts +14 -0
  13. package/dist/lib/resolver-types.d.ts +45 -0
  14. package/dist/lib/sync.d.ts +13 -8
  15. package/dist/lib/transport.d.ts +87 -0
  16. package/dist/openapi.d.ts +1 -1
  17. package/dist/storage.js +54 -63
  18. package/hooks/hook-affected-tests/package.json +1 -1
  19. package/hooks/hook-agent-rules-version-check/README.md +1 -1
  20. package/hooks/hook-agent-rules-version-check/package.json +1 -1
  21. package/hooks/hook-agentmessages/package.json +1 -1
  22. package/hooks/hook-announce-start/package.json +1 -1
  23. package/hooks/hook-announce-stop/package.json +1 -1
  24. package/hooks/hook-autoformat/package.json +1 -1
  25. package/hooks/hook-branchprotect/package.json +1 -1
  26. package/hooks/hook-checkbugs/package.json +1 -1
  27. package/hooks/hook-checkdocs/package.json +1 -1
  28. package/hooks/hook-checkfiles/package.json +1 -1
  29. package/hooks/hook-checklint/package.json +1 -1
  30. package/hooks/hook-checkpoint/package.json +1 -1
  31. package/hooks/hook-checksecurity/package.json +1 -1
  32. package/hooks/hook-checktasks/package.json +1 -1
  33. package/hooks/hook-checktests/package.json +1 -1
  34. package/hooks/hook-conflict-detect/package.json +1 -1
  35. package/hooks/hook-contextrefresh/package.json +1 -1
  36. package/hooks/hook-desktopnotify/package.json +1 -1
  37. package/hooks/hook-dm-inject/package.json +1 -1
  38. package/hooks/hook-envsetup/package.json +1 -1
  39. package/hooks/hook-failure-to-task/package.json +1 -1
  40. package/hooks/hook-filelock/package.json +1 -1
  41. package/hooks/hook-fleet-blockers-gate/package.json +1 -1
  42. package/hooks/hook-fleet-catchup/package.json +1 -1
  43. package/hooks/hook-gitguard/package.json +1 -1
  44. package/hooks/hook-packageage/package.json +1 -1
  45. package/hooks/hook-permissionguard/package.json +1 -1
  46. package/hooks/hook-phonenotify/package.json +1 -1
  47. package/hooks/hook-precompact/package.json +1 -1
  48. package/hooks/hook-protectfiles/package.json +1 -1
  49. package/hooks/hook-stylescheck/package.json +1 -1
  50. package/hooks/hook-trash-guard/README.md +132 -0
  51. package/hooks/hook-trash-guard/package.json +12 -0
  52. package/hooks/hook-trash-guard/src/hook.ts +1114 -0
  53. package/hooks/hook-typecheck-gate/package.json +1 -1
  54. package/package.json +7 -10
  55. package/scripts/ensure-profiles-dir.mjs +52 -1
  56. package/hooks/codewith-native-common.test.ts +0 -1935
  57. package/hooks/hook-affected-tests/tsconfig.json +0 -25
  58. package/hooks/hook-agent-rules-version-check/src/hook.test.ts +0 -104
  59. package/hooks/hook-agent-rules-version-check/tsconfig.json +0 -25
  60. package/hooks/hook-announce-start/tsconfig.json +0 -25
  61. package/hooks/hook-announce-stop/tsconfig.json +0 -25
  62. package/hooks/hook-autoformat/tsconfig.json +0 -25
  63. package/hooks/hook-branchprotect/tsconfig.json +0 -25
  64. package/hooks/hook-checkbugs/tsconfig.json +0 -15
  65. package/hooks/hook-checkdocs/tsconfig.json +0 -15
  66. package/hooks/hook-checkfiles/tsconfig.json +0 -15
  67. package/hooks/hook-checklint/tsconfig.json +0 -15
  68. package/hooks/hook-checkpoint/tsconfig.json +0 -25
  69. package/hooks/hook-checksecurity/tsconfig.json +0 -15
  70. package/hooks/hook-checktasks/tsconfig.json +0 -20
  71. package/hooks/hook-checktests/tsconfig.json +0 -15
  72. package/hooks/hook-conflict-detect/tsconfig.json +0 -25
  73. package/hooks/hook-contextrefresh/tsconfig.json +0 -25
  74. package/hooks/hook-desktopnotify/tsconfig.json +0 -25
  75. package/hooks/hook-dm-inject/tsconfig.json +0 -25
  76. package/hooks/hook-envsetup/tsconfig.json +0 -25
  77. package/hooks/hook-failure-to-task/tsconfig.json +0 -25
  78. package/hooks/hook-filelock/tsconfig.json +0 -25
  79. package/hooks/hook-fleet-blockers-gate/src/hook.test.ts +0 -302
  80. package/hooks/hook-fleet-blockers-gate/tsconfig.json +0 -25
  81. package/hooks/hook-fleet-catchup/src/hook.test.ts +0 -156
  82. package/hooks/hook-fleet-catchup/tsconfig.json +0 -25
  83. package/hooks/hook-gitguard/tsconfig.json +0 -25
  84. package/hooks/hook-knowledge-context/src/hook.test.ts +0 -379
  85. package/hooks/hook-packageage/tsconfig.json +0 -25
  86. package/hooks/hook-permissionguard/tsconfig.json +0 -25
  87. package/hooks/hook-phonenotify/tsconfig.json +0 -25
  88. package/hooks/hook-precompact/tsconfig.json +0 -25
  89. package/hooks/hook-protectfiles/tsconfig.json +0 -25
  90. package/hooks/hook-scanoutput/src/hook.test.ts +0 -217
  91. package/hooks/hook-spiral-detector/src/hook.test.ts +0 -72
  92. package/hooks/hook-stylescheck/tsconfig.json +0 -25
  93. package/hooks/hook-typecheck-gate/tsconfig.json +0 -25
  94. package/hooks/hook-workspace-repos-guard/src/hook.test.ts +0 -466
  95. package/hooks/hook-workspace-repos-guard/tsconfig.json +0 -21
  96. package/hooks/mention-context/src/hook.test.ts +0 -68
@@ -0,0 +1,132 @@
1
+ # trash-guard
2
+
3
+ Codewith-native hook installed as `hooks run trash-guard`.
4
+
5
+ PreToolUse guard for `rm` issued through the Bash tool. It rewrites the verb
6
+ into `@hasna/trash`'s guard subcommand — `<abs>/trash guard <same args>` — so
7
+ the delete lands in a trash store and stays recoverable. When there is nothing
8
+ to redirect to, it **refuses** the command.
9
+
10
+ ## The decision, and why it fails the way it does
11
+
12
+ The block decision is **self-contained**: it never waits on the trash store, a
13
+ network, a credential or the `@hasna/trash` package. The binary is consulted
14
+ only as an opportunistic upgrade:
15
+
16
+ | `trash` on PATH | verdict |
17
+ |---|---|
18
+ | present | `permissionDecision: "allow"` + `updatedInput` rewriting `rm` to `<abs>/trash guard` |
19
+ | absent | `permissionDecision: "deny"` + a reason telling the agent to install `@hasna/trash` |
20
+
21
+ There is no third row. It degrades **redirect → block**, never
22
+ **redirect → allow**: a delete this hook cannot redirect is never silently run.
23
+
24
+ The rewrite emits a **complete** `tool_input` — every key the model supplied,
25
+ plus `command`, `description`, `timeout` and `run_in_background`, plus
26
+ `dangerouslyDisableSandbox` when it was present. That care is deliberate: the
27
+ harness falls back to the **original** tool input when `updatedInput` is
28
+ missing or empty, so a partial rewrite would run the raw `rm`. The hook
29
+ re-scans the rewritten text before allowing it, and if the re-scan still finds
30
+ a live delete verb it denies instead. A command containing both an owned `rm`
31
+ and one handed to another guard is refused rather than partially rewritten.
32
+
33
+ ## What it rewrites
34
+
35
+ Only `rm`'s own grammar, and only in **command position**: the verb must be the
36
+ command, after any assignment prefixes (`FOO=1 rm …`), shell keywords, and
37
+ recognized wrappers (`sudo`, `doas`, `env`, `nice`, `ionice`, `stdbuf`, `time`,
38
+ `timeout`, `nohup`, `setsid`, `command`, `builtin`, `exec`). Quoting, `~`,
39
+ `$HOME` and `${HOME}` spellings, `--`, redirections and every other byte of the
40
+ command are preserved.
41
+
42
+ Wrappers that take positional arguments are handled (`timeout 5 rm -rf x`
43
+ rewrites the `rm`, not the duration).
44
+
45
+ ## What it refuses
46
+
47
+ - `rmdir`, `unlink`, `shred` — their flags and semantics are not `rm`'s.
48
+ - `git rm` (without `--cached`), `git clean` — they delete outside `rm`.
49
+ - `find -delete`, `find -exec rm …`, `xargs rm` — the delete does not run
50
+ through a verb this hook can swap.
51
+ - A delete inside a command substitution (`$(rm …)`, backticks).
52
+ - `busybox rm` / `toybox rm` — a different applet, not GNU `rm`.
53
+ - Shells and opaque command strings (`sh -c 'rm …'`, `eval`, `su -c …`).
54
+ - An unparseable command (unterminated quote, substitution or here-document)
55
+ that mentions a delete verb.
56
+ - The protected class, below.
57
+
58
+ Each refusal carries a reason and a way forward: re-run the delete as
59
+ `rm -- <path>`, which this hook intercepts and redirects.
60
+
61
+ ## The protected class
62
+
63
+ Refused outright, never redirected (plan §15 decision 11.3):
64
+
65
+ - the filesystem root and the system roots `pre-bash`'s protected-path rules
66
+ already name (`/etc`, `/usr`, `/bin`, `/home`, `/var`, …) — the root itself,
67
+ or any ancestor of it;
68
+ - the home directory itself (`~`, `$HOME`, `${HOME}`);
69
+ - `~/.hasna`, `~/.ssh`, `~/.aws` — the state and credential stores, including
70
+ everything under them.
71
+
72
+ These are the catastrophic cases where "move it to trash" is not an acceptable
73
+ answer. Everything else this hook owns is rewritten, not refused.
74
+
75
+ ## Scope: what it deliberately does NOT own
76
+
77
+ Deletes under the protected repo-checkout roots `$HOME/.hasna/repos/clones` and
78
+ `$HOME/workspace/repos` belong to **`workspace-repos-guard`**, which blocks
79
+ every delete under them at any depth. This hook does not restate that policy:
80
+ it recognizes the boundary and abstains, so the other hook's decision stands.
81
+ A command that mixes a delete inside those roots with one outside is refused,
82
+ because a partial rewrite would leave one of them unredirected.
83
+
84
+ ## Conflict discipline
85
+
86
+ `trash-guard` declares `rewritesInput: true` in the registry. Two PreToolUse
87
+ hooks on overlapping matchers that both rewrite the tool input are a silent
88
+ data-loss hazard: the harness keeps one rewrite (last writer wins), so the
89
+ losing hook's guard disappears with no error. Installing a second such hook is
90
+ therefore **refused**, and `hooks doctor` reports more than one input-rewriting
91
+ hook on an overlapping matcher. Other overlaps still install with the usual
92
+ advisory warning.
93
+
94
+ The registration is written with `timeout: 5`. The harness's documented
95
+ default is 600s, and a hook that times out **does not block** — only a verdict
96
+ already on stdout does. Five seconds is several orders of magnitude above this
97
+ hook's measured cost (a single-pass lexer, no process spawns, no I/O beyond
98
+ one `statSync` per PATH entry).
99
+
100
+ ## Known limitations
101
+
102
+ The guard is a best-effort **text** classifier, not an execution sandbox.
103
+
104
+ - **Variable indirection is undetectable.** `R=...; rm -rf $R`, a loop over
105
+ computed paths, or a script downloaded and run at runtime cannot be
106
+ classified before the shell expands it. This is the same limitation
107
+ `workspace-repos-guard` documents, and it is inherent to inspecting the
108
+ command string rather than the syscall. An operand that is a variable or a
109
+ glob is still **rewritten** (the `trash` binary classifies what the shell
110
+ actually expands to), but it cannot be checked against the protected class
111
+ here.
112
+ - **A hook only ever sees the agent's own tool calls.** It cannot stop a file
113
+ being deleted by another process, by a build tool, by a script the agent
114
+ runs, or by `unlink(2)` called directly. Coverage is the Bash tool, wave 1,
115
+ nothing else — `Write`/`Edit` pre-image capture is wave 2.
116
+ - **Nested and generated commands escape it.** `bash -c`, `eval`, `make`,
117
+ `npm run`, a `Dockerfile`, a heredoc-fed interpreter: the hook can only see
118
+ that a shell string mentions a delete verb and refuse it, never redirect
119
+ inside it.
120
+ - **Threat model: accident, not adversary.** A hostile same-user agent can
121
+ remove the hook, edit the store, or call `unlink(2)` directly; nothing here
122
+ holds against that.
123
+ - **Fail-closed only for deletes.** On an internal error the hook denies a
124
+ command that mentions a delete verb and stays silent otherwise, so a guard
125
+ defect cannot wedge unrelated work. Fail-closed cannot be guaranteed where
126
+ the harness itself never delivers the hook input.
127
+
128
+ ## Configuration
129
+
130
+ None. The home directory comes from `os.homedir()`; the trash binary is
131
+ resolved by scanning `PATH` for an executable `trash` and rewriting to the
132
+ absolute path found, so the rewritten command does not depend on `PATH` again.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "trash-guard",
3
+ "version": "0.1.0",
4
+ "description": "Codewith-native Trash Guard hook for @hasna/hooks",
5
+ "type": "module",
6
+ "main": "./src/hook.ts",
7
+ "scripts": {
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "author": "Hasna",
11
+ "license": "Apache-2.0"
12
+ }