@codyswann/lisa 4.4.9 → 4.4.11

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 (67) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +78 -1
  2. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  3. package/dist/core/lisa-owned-hash-ledger.js +1 -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 +6 -4
  7. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  8. package/dist/opencode/plugin-templates/parity-safety-net.sh +78 -1
  9. package/package.json +1 -1
  10. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  12. package/plugins/lisa/hooks/parity-safety-net.sh +78 -1
  13. package/plugins/lisa-agy/hooks/parity-safety-net.sh +78 -1
  14. package/plugins/lisa-agy/plugin.json +1 -1
  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/hooks/parity-safety-net.sh +78 -1
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +78 -1
  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/materialized-artifacts.json +22 -0
  65. package/plugins/src/base/hooks/parity-safety-net.sh +78 -1
  66. package/scripts/build-plugins.sh +45 -1
  67. package/scripts/check-plugins-sync.sh +166 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.9",
3
+ "version": "4.4.11",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,22 @@
1
+ [
2
+ "all/copy-overwrite/scripts/lib/bounded-spawn.mjs",
3
+ "all/copy-overwrite/scripts/lib/invoked-as-script.mjs",
4
+ "all/copy-overwrite/scripts/lisa-enforcement-fallback.sh",
5
+ "all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh",
6
+ "all/copy-overwrite/scripts/lisa-hooks/block-instruction-file-edits.sh",
7
+ "all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh",
8
+ "all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh",
9
+ "all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh",
10
+ "all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh",
11
+ "all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh",
12
+ "expo/copy-overwrite/scripts/lib/bounded-spawn.mjs",
13
+ "expo/copy-overwrite/scripts/lib/invoked-as-script.mjs",
14
+ "rails/copy-overwrite/scripts/check-threshold-ratchet.mjs",
15
+ "rails/copy-overwrite/scripts/threshold-ratchet-compare.mjs",
16
+ "rails/copy-overwrite/scripts/threshold-ratchet-families.mjs",
17
+ "typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs",
18
+ "typescript/copy-overwrite/scripts/lib/bounded-spawn.mjs",
19
+ "typescript/copy-overwrite/scripts/lib/invoked-as-script.mjs",
20
+ "typescript/copy-overwrite/scripts/threshold-ratchet-compare.mjs",
21
+ "typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs"
22
+ ]
@@ -558,7 +558,84 @@ if matches '\b(drop[[:space:]]+(database|schema|table)|truncate[[:space:]]+(tabl
558
558
  block "destructive SQL (DROP/TRUNCATE) detected"
559
559
  fi
560
560
 
561
- # 14. Project-local custom rules. Each non-comment line is an ERE; a match blocks.
561
+ # 14. The git control plane. A recursive forced delete of `.git` destroys every
562
+ # commit, branch and stash that is not already pushed — the one directory whose
563
+ # loss is not recoverable from the working tree.
564
+ #
565
+ # This is not a new principle, it is a hole in one the guard already holds. Rule
566
+ # 4 above already refuses `rm -rf "$SOMEDIR"` and paths outside the project;
567
+ # `rm -rf .git` was permitted only because nothing named it. Measured before
568
+ # this rule existed: `rm -rf .git`, `rm -rf ./.git` and `rm -rf .git/objects`
569
+ # were all ALLOWED while the variable-expanded case was correctly blocked.
570
+ #
571
+ # `.git` must be a whole path component. `.gitignore`, `.gitattributes`,
572
+ # `.github/` and a `.git-old` backup are ordinary files a project deletes on
573
+ # purpose, and blocking those would be the collision that gets a guard switched
574
+ # off. The trailing `([/[:space:]'"$qc"']|$)` is what draws that line.
575
+ readonly GIT_CONTROL_PLANE='(^|[[:space:]='"$qc"'./])\.git([/[:space:]'"$qc"']|$)'
576
+ if matches_cs "$RM_RF_CLUSTER" || matches_cs "$RM_RF_SPLIT"; then
577
+ if matches_cs "$GIT_CONTROL_PLANE"; then
578
+ 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."
579
+ fi
580
+ fi
581
+
582
+ # 15. Credential stores. Reading one into an agent transcript is disclosure even
583
+ # when nothing is copied anywhere: the value lands in a log, a context window,
584
+ # and whatever retains either.
585
+ #
586
+ # Scoped deliberately rather than by the obvious pattern. Each clause below is a
587
+ # separate judgement about a family where an agent has no legitimate read, and
588
+ # each carries the exclusions that keep ordinary work moving — public keys,
589
+ # known_hosts, ssh config, and `.env.example` are all still readable, because a
590
+ # guard that blocks routine setup is a guard somebody disables.
591
+
592
+ # The verbs that disclose: read it, duplicate it, or send it. Writing is NOT
593
+ # here — `echo FOO=1 > .env` during setup creates a secret rather than leaking
594
+ # one, and refusing it would block the very workflow that produces the file.
595
+ readonly SECRET_READ_VERB='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(cat|bat|less|more|head|tail|strings|xxd|od|base64|cp|mv|rsync|scp|sftp|tar|zip|curl|wget|http|nc)([[:space:]]|$)'
596
+
597
+ # 15a. SSH PRIVATE keys. `id_rsa.pub` and friends are public by construction and
598
+ # stay readable; so do `known_hosts`, `config` and `authorized_keys`, which
599
+ # agents legitimately inspect when diagnosing a remote.
600
+ readonly SSH_PRIVATE_KEY='\.ssh/(id_[[:alnum:]_-]+|[[:alnum:]_.-]*_(rsa|dsa|ecdsa|ed25519))([[:space:]'"$qc"']|$)'
601
+ # 15b. Cloud provider credential files.
602
+ readonly CLOUD_CREDENTIAL='(\.aws/(credentials|config)|\.config/gcloud/[[:alnum:]_./-]*credentials[[:alnum:]_.-]*|\.azure/(accessTokens|msal_token_cache)[[:alnum:]_.-]*|\.kube/config)([[:space:]'"$qc"']|$)'
603
+ # 15c. Coding-agent credential stores — the tokens that impersonate the operator
604
+ # to every service their agent can reach.
605
+ readonly AGENT_CREDENTIAL='(\.(claude|codex|cursor|copilot|gemini|antigravity)/[[:alnum:]_./-]*(credentials|auth|token)[[:alnum:]_.-]*|\.netrc|\.npmrc|\.pypirc|\.docker/config\.json)([[:space:]'"$qc"']|$)'
606
+ # 15d. Dotenv files. `.env.example`, `.sample`, `.template`, `.dist` and
607
+ # `.schema` are checked-in documentation of which keys exist, never the values,
608
+ # so they are excluded — that collision is the one this rule most had to avoid.
609
+ readonly DOTENV_SECRET='(^|[[:space:]='"$qc"'./])\.env(\.[[:alnum:]_-]+)?([[:space:]'"$qc"']|$)'
610
+ readonly DOTENV_PUBLIC='\.env\.(example|sample|template|dist|schema|defaults)([[:space:]'"$qc"']|$)'
611
+
612
+ if matches "$SECRET_READ_VERB"; then
613
+ if matches "$SSH_PRIVATE_KEY"; then
614
+ block "reading, copying or transmitting an SSH PRIVATE key. Public keys (.pub), known_hosts and ssh config are unaffected; if you need the fingerprint use \`ssh-keygen -lf <key>.pub\`."
615
+ fi
616
+ if matches "$CLOUD_CREDENTIAL"; then
617
+ block "reading, copying or transmitting a cloud provider credential file. Use the provider CLI's own identity command (\`aws sts get-caller-identity\`, \`gcloud auth list\`) instead of reading the file."
618
+ fi
619
+ if matches "$AGENT_CREDENTIAL"; then
620
+ block "reading, copying or transmitting a coding-agent or package-registry credential store. These tokens impersonate the operator to every service the agent can reach."
621
+ fi
622
+ # `.env` only when the path is NOT one of the public example forms. Ordered as
623
+ # an exclusion rather than a narrower pattern so a new example suffix is one
624
+ # word to add, and so the reason a file is exempt stays readable.
625
+ if matches "$DOTENV_SECRET" && ! matches "$DOTENV_PUBLIC"; then
626
+ block "reading, copying or transmitting a dotenv file holding real values. \`.env.example\`, \`.env.sample\`, \`.env.template\` and \`.env.dist\` are readable, and WRITING a .env is unaffected — only reading one back is refused."
627
+ fi
628
+ # Fail-closed, narrowly. A read whose DIRECTORY is a known credential store
629
+ # but whose leaf is variable-expanded cannot be classified, and #2980's lesson
630
+ # is that a guard which cannot classify must refuse rather than permit. Scoped
631
+ # to credential directories on purpose: refusing every \`cat "\$FILE"\` would be
632
+ # the over-reach that gets the whole hook switched off.
633
+ if matches_cs '(\.ssh|\.aws|\.config/gcloud|\.azure|\.kube)/[^[:space:]'"$qc"']*\$'; then
634
+ block "reading a path inside a credential directory whose filename is variable-expanded, so the guard cannot tell which file it is. Name the file explicitly if it is genuinely not a secret."
635
+ fi
636
+ fi
637
+
638
+ # 16. Project-local custom rules. Each non-comment line is an ERE; a match blocks.
562
639
  rules_file="${SAFETY_NET_RULES_FILE:-${CLAUDE_PROJECT_DIR:-$PWD}/.claude/safety-net-rules.txt}"
563
640
  if [ -f "$rules_file" ]; then
564
641
  while IFS= read -r rule || [ -n "$rule" ]; do
@@ -5,7 +5,11 @@
5
5
  set -euo pipefail
6
6
 
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
- ROOT_DIR="$SCRIPT_DIR/.."
8
+ # Resolved rather than left as `$SCRIPT_DIR/..`, so every destination this
9
+ # script writes begins with a literal prefix the manifest below can strip to
10
+ # get a repository-relative path. With the `..` left in, that strip produces
11
+ # paths that no `git status` pathspec matches.
12
+ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
9
13
  PLUGINS_DIR="$ROOT_DIR/plugins"
10
14
  SRC_DIR="$PLUGINS_DIR/src"
11
15
 
@@ -22,10 +26,50 @@ VERSION=$(node -e "console.log(require('$ROOT_DIR/package.json').version)")
22
26
  # here, as the file is generated: the authored source stays honest about being
23
27
  # editable, the shipped copy states that it is replaced, and the two cannot
24
28
  # disagree because one produces the other.
29
+ #
30
+ # Every call is RECORDED, into plugins/materialized-artifacts.json. That file is
31
+ # how `check-plugins-sync.sh` knows which paths outside plugins/ this build
32
+ # owns. It is a record of what this run actually wrote, not a list anyone
33
+ # maintains — a second list is precisely how those destinations went unchecked
34
+ # for as long as they did (#3064), and a fix that restated them would be the
35
+ # same defect with fresh paint.
25
36
  materialize() {
26
37
  node "$ROOT_DIR/scripts/materialize-copy-overwrite.mjs" "$1" "$2"
38
+ MATERIALIZED_PATHS="${MATERIALIZED_PATHS}${2#"$ROOT_DIR"/}
39
+ "
27
40
  }
28
41
 
42
+ MATERIALIZED_PATHS=""
43
+ MATERIALIZED_MANIFEST="$PLUGINS_DIR/materialized-artifacts.json"
44
+
45
+ # Write the manifest of everything `materialize` wrote this run.
46
+ #
47
+ # On EXIT rather than at the end of the script, so the record covers every call
48
+ # no matter where the last one is added. A materialize call appended below the
49
+ # final line — the natural place to add one — would otherwise write a file the
50
+ # manifest never mentions, which is the reintroduction of the gap.
51
+ #
52
+ # Only on success: a build that died halfway wrote only some of its
53
+ # destinations, and recording that partial set as the truth would turn one
54
+ # failed build into a spurious sync failure on the next run.
55
+ write_materialized_manifest() {
56
+ local status="$1"
57
+ [ "$status" -eq 0 ] || return 0
58
+ mkdir -p "$PLUGINS_DIR"
59
+ printf '%s' "$MATERIALIZED_PATHS" | node -e '
60
+ const fs = require("node:fs");
61
+ let raw = "";
62
+ process.stdin.setEncoding("utf8");
63
+ process.stdin.on("data", chunk => { raw += chunk; });
64
+ process.stdin.on("end", () => {
65
+ const paths = [...new Set(raw.split("\n").filter(Boolean))].sort();
66
+ fs.writeFileSync(process.argv[1], `${JSON.stringify(paths, null, 2)}\n`);
67
+ });
68
+ ' "$MATERIALIZED_MANIFEST"
69
+ }
70
+
71
+ trap 'write_materialized_manifest $?' EXIT
72
+
29
73
  inject_version() {
30
74
  local manifest="$1"
31
75
  if [ -f "$manifest" ]; then