@codyswann/lisa 2.259.4 → 2.260.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 (78) hide show
  1. package/dist/cli/file-upstream-cmd.d.ts +28 -0
  2. package/dist/cli/file-upstream-cmd.d.ts.map +1 -0
  3. package/dist/cli/file-upstream-cmd.js +231 -0
  4. package/dist/cli/file-upstream-cmd.js.map +1 -0
  5. package/dist/cli/gate-commands.d.ts +17 -0
  6. package/dist/cli/gate-commands.d.ts.map +1 -0
  7. package/dist/cli/gate-commands.js +47 -0
  8. package/dist/cli/gate-commands.js.map +1 -0
  9. package/dist/cli/index.d.ts +3 -0
  10. package/dist/cli/index.d.ts.map +1 -1
  11. package/dist/cli/index.js +6 -27
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/core/upstream-attribution-body.d.ts +38 -25
  14. package/dist/core/upstream-attribution-body.d.ts.map +1 -1
  15. package/dist/core/upstream-attribution-body.js +515 -155
  16. package/dist/core/upstream-attribution-body.js.map +1 -1
  17. package/dist/core/upstream-evidence-manifest.d.ts +7 -0
  18. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -0
  19. package/dist/core/upstream-evidence-manifest.js +11623 -0
  20. package/dist/core/upstream-evidence-manifest.js.map +1 -0
  21. package/package.json +4 -1
  22. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +32 -8
  25. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +32 -8
  26. package/plugins/lisa-agy/plugin.json +1 -1
  27. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +32 -8
  28. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  31. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +32 -8
  35. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +32 -8
  37. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-agy/plugin.json +1 -1
  40. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  45. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  50. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  55. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  60. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-agy/plugin.json +1 -1
  65. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  70. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  75. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +32 -8
  78. package/scripts/generate-upstream-evidence-manifest.mjs +157 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.259.4",
3
+ "version": "2.260.0",
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-harper-fabric",
3
- "version": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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": "2.259.4",
3
+ "version": "2.260.0",
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"
@@ -90,19 +90,19 @@ This disposition completes the SLL-5 loop (#1583): on a Lisa-attributed failure
90
90
  root-cause-key = <lisa-surface>#<failure-class>
91
91
  ```
92
92
 
93
- - `<lisa-surface>` — the Lisa-relative path of the surface at fault (e.g. `plugins/src/base/skills/lisa-doctor/SKILL.md`, `typescript/copy-overwrite/.github/workflows/quality.yml`), or the canonical rule/skill/hook name when no single file applies (e.g. `lisa-doctor`, `block-no-verify`).
94
- - `<failure-class>` — a short lowercase hyphen-slug for the class of failure (e.g. `pagination-truncation`, `stale-artifact-overwrite`).
93
+ - `<lisa-surface>` — the exact public Lisa-relative path of the surface at fault (e.g. `plugins/src/base/skills/lisa-doctor/SKILL.md`, `typescript/copy-overwrite/.github/workflows/quality.yml`). Canonical names, aliases, host paths, and unmanifested paths are prohibited.
94
+ - `<failure-class>` — exactly one exported closed value: `access-control-failure`, `agent-parity-regression`, `configuration-regression`, `data-integrity-failure`, `dependency-regression`, `generated-artifact-regression`, `installation-regression`, `observability-gap`, `pagination-truncation`, `performance-regression`, `public-data-exposure`, `release-regression`, `runtime-regression`, `stale-artifact-overwrite`, `test-coverage-gap`, `validation-gap`, or `workflow-contract-violation`.
95
95
  - Normalize: lowercase, trim, collapse every whitespace run to a single `-`. The key must contain no host-project name, no local issue number, and no fingerprint — those vary per project and would defeat fleet-wide dedupe.
96
96
 
97
97
  4. **Enforce the per-run cap.** Resolve `hardening.maxUpstreamFilingsPerRun` from `.lisa.config.json` (default `5` — a conservative bound modeled on `lisa-repair-intake`'s `max_candidates` precedent). Count every upstream create **and** update this run performs; once the cap is reached, drop the remaining candidates and **note each dropped candidate visibly** (in the run summary, naming its root-cause key) — never queue a spam burst and never drop silently. A later run picks the dropped candidates up idempotently.
98
98
 
99
- 5. **Evidence redaction (binding).** The upstream repo is PUBLIC by default (`hardening.upstreamRepo` → `CodySwannGT/lisa`) and this filing runs headless on crons — treat every drafted upstream body and comment as world-readable:
99
+ 5. **Evidence redaction (binding).** The upstream repo is PUBLIC by default (`hardening.upstreamRepo` → `CodySwannGT/lisa`) and this filing runs headless on crons — treat every drafted upstream body and comment as world-readable. The PRIMARY control is the **allowlist projection** in step 6: `bunx @codyswann/lisa file-upstream` calls the sole `buildUpstreamAttributionIssueBody` export from `@codyswann/lisa/learnings`, so content outside its enumerated fields is structurally incapable of reaching the public document. The rules below bind the FIELDS supplied to that command; the closing scan is a defense-in-depth backstop, never the primary control:
100
100
 
101
101
  - Quote ONLY Lisa-owned surface text: template/rule/skill/hook excerpts and upstream commit references. The reproduction must be REDACTED — generic placeholders, never the host project's real values.
102
102
  - Never paste host environment values, tokens/credentials, connection strings, API keys, PII (names, emails, customer data), or proprietary host code/payloads.
103
- - The evidence chain names the Lisa surface and the failure class — never project payloads. When host context is essential to understand the failure, LINK the host-project issue instead of quoting it.
104
- - Before filing or commenting, scan the drafted body for common secret shapes — `key=value` pairs with high-entropy values, token prefixes (`AKIA`, `ghp_`, `xox`), email addresses — and strip on match. When in doubt, leave it out: a thinner upstream ticket is recoverable; a leaked secret is not.
105
- - Compose the public upstream issue body through the executable allowlist builder, never by free-form prose assembly. Import `buildUpstreamAttributionIssueBody` from `@codyswann/lisa/learnings` and pass only its typed fields: marker key, Lisa surface, failure class, operator impact, harness fault, requested Lisa change, affected project name, host issue link, Lisa-owned excerpts, attribution evidence, and upstream refs. Non-allowlisted fields must reject before filing; do not "drop" unknown host context silently. The deny-list scan above remains a backstop after the builder returns.
103
+ - The evidence chain names the Lisa surface and the failure class — never project payloads. Keep any host-project issue link only in the private local trace; it is never supplied to or emitted by the public builder.
104
+ - Use only these exact generic placeholders: `<host-project>`, `<env-value>`, `<credential>`, `<connection-string>`, `<pii>`, `<host-payload>`, `<path>`, `<identifier>`. A project-specific placeholder is host prose and must be rejected.
105
+ - **Backstop (defense in depth, never the primary control).** Before filing or commenting, scan the projected document for common secret shapes — `key=value` pairs with high-entropy values, token prefixes (`AKIA`, `ghp_`, `xox`), email addresses and reject on a match. To strip on match, remove the offending INPUT field and re-run `file-upstream`; never strip or edit the generated output itself. Non-allowlisted fields must reject before filing rather than be silently dropped. When in doubt, leave it out: a thinner upstream document is recoverable; a leaked secret is not.
106
106
 
107
107
  6. **Dedupe by marker, then file or update.** The upstream marker is:
108
108
 
@@ -112,8 +112,32 @@ This disposition completes the SLL-5 loop (#1583): on a Lisa-attributed failure
112
112
 
113
113
  Resolve the upstream repo from `.lisa.config.json` `hardening.upstreamRepo` (default `CodySwannGT/lisa`). Search **all issue states** for an existing issue carrying the marker — a closed marker-bearing ticket still owns this root cause, and searching only open issues would mint a duplicate the moment the original closes. Match on the **MARKER, never the title** — with the same eventual-consistency guard as above (`gh issue list -R <upstream> --state all --search '"<marker>" in:body' --json number,state,url`, and when the search index returns nothing, also `gh issue list -R <upstream> --state all --json number,state,body` and grep the bodies for the marker before concluding no ticket exists).
114
114
 
115
- - **No existing ticket** → file via `lisa-github-write-issue` targeting the upstream repo, following the `lisa-rework-triage` "Filing upstream" discipline. The body must be the exact output of `buildUpstreamAttributionIssueBody`, which provides the three-audience description (what failed for the operator, what the harness did wrong, what to change), the redacted evidence chain (Lisa-owned text only, per step 5: defect → Lisa surface → attribution evidence → redacted reproduction), the affected project name/link, and **exactly one** dedupe marker. Apply the `self-hardening` label. **Never write a markerless body** — it permanently breaks all future dedupe.
116
- - **Existing ticket (open or closed)** → this is a repeat encounter: comment the new occurrence on the existing issue with this project's evidence, marker-deduped per occurrence via `<!-- [lisa-upstream-attribution-occurrence] key=<fingerprint> -->` so re-runs never duplicate the occurrence comment. Never open a second issue, and never match on the title — evidence compounds on one ticket. When the match is **CLOSED**, still comment the occurrence there and reference it in the local trace instead of filing a duplicate; do not reopen it yourself — recurrence evidence on a closed ticket signals the shipped fix may not cover this case, and reopening is the upstream maintainer's call.
115
+ - **No existing ticket** → compose the body EXCLUSIVELY through the executable builder, then file its stdout verbatim via `lisa-github-write-issue` targeting the upstream repo with the `self-hardening` label:
116
+
117
+ ```bash
118
+ bunx @codyswann/lisa file-upstream --input filing-event.json # or pipe the JSON on stdin
119
+ ```
120
+
121
+ **Never assemble the public body as free-form prose.** Public composition happens through the builder, never by free-form prose assembly. The builder owns the three-audience description and the redacted evidence chain (Lisa-owned text only). Do not hand-write either section, name an affected project in prose, or edit the builder's output after generation. Supply only this JSON shape; host-project issue URLs remain private local-trace data and are never accepted by the public builder:
122
+
123
+ ```json
124
+ {
125
+ "documentKind": "issue",
126
+ "lisaSurface": "plugins/src/base/skills/lisa-doctor/SKILL.md",
127
+ "failureClass": "stale-artifact-overwrite",
128
+ "lisaOwnedExcerpts": [
129
+ {
130
+ "file": "plugins/src/base/skills/lisa-doctor/SKILL.md",
131
+ "text": "<verbatim Lisa-owned text>"
132
+ }
133
+ ],
134
+ "upstreamCommitRefs": ["<full 40-character public-origin SHA>"],
135
+ "redactedPlaceholders": ["<host-project>", "<env-value>"]
136
+ }
137
+ ```
138
+
139
+ Every excerpt is verified against the installed package's Lisa-owned source and every commit against the public origin. Callers cannot supply a verifier or Lisa root. Anything outside the allowlist is REJECTED by field name with empty stdout and a non-zero exit; fix or remove the offending input field and re-run the command, never route around it. The builder emits **exactly one** dedupe marker; **never write a markerless body** — it permanently breaks all future dedupe.
140
+ - **Existing ticket (open or closed)** → this is a repeat encounter: comment the new occurrence on the existing issue with this project's evidence. The comment is projected through the same builder: run `bunx @codyswann/lisa file-upstream` with the same allowlisted fields plus exactly `"documentKind": "occurrence"` and `"occurrenceFingerprint": "<fingerprint>"`. The supplied fingerprint is only a validated seed: the builder hashes it and emits a different deterministic `sll4-<12 lowercase hex>` marker. Use the marker in the builder's stdout — never the supplied literal — to search existing issue comments for a duplicate, then post stdout verbatim only when that emitted marker is absent. Free-form occurrence prose is prohibited, and post-generation edits are prohibited. Never open a second issue, and never match on the title — evidence compounds on one ticket. When the match is **CLOSED**, still comment the occurrence there and reference it in the local trace instead of filing a duplicate; do not reopen it yourself — recurrence evidence on a closed ticket signals the shipped fix may not cover this case, and reopening is the upstream maintainer's call.
117
141
 
118
142
  7. **Leave the local trace — a note, never a rule.** Post one follow-up comment on the triggering issue linking the upstream ticket:
119
143
 
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /** Generate the exact hash-pinned allowlist for public upstream evidence. */
3
+ import { execFileSync } from "node:child_process";
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import path from "node:path";
7
+
8
+ const repoRoot = path.resolve(import.meta.dirname, "..");
9
+ const outputPath = path.join(
10
+ repoRoot,
11
+ "src/core/upstream-evidence-manifest.ts"
12
+ );
13
+ const packagedEvidencePrefixes = [
14
+ "plugins/src/",
15
+ "all/",
16
+ "cdk/",
17
+ "expo/",
18
+ "eslint-plugin-code-organization/",
19
+ "eslint-plugin-component-structure/",
20
+ "eslint-plugin-phaser/",
21
+ "eslint-plugin-ui-standards/",
22
+ "harper-fabric/",
23
+ "nestjs/",
24
+ "npm-package/",
25
+ "oxlint/",
26
+ "phaser/",
27
+ "rails/",
28
+ "scripts/",
29
+ "tsconfig/",
30
+ "typescript/",
31
+ "ui/",
32
+ ];
33
+ const forbiddenSegments = new Set([
34
+ ".claude-plugin",
35
+ ".git",
36
+ ".lisa",
37
+ ".mcp",
38
+ "node_modules",
39
+ ]);
40
+ const forbiddenBasenames = new Set([
41
+ ".lisa.config.json",
42
+ ".mcp.json",
43
+ "audit.local",
44
+ ]);
45
+ const canonicalOriginUrls = new Set([
46
+ "https://github.com/CodySwannGT/lisa.git",
47
+ "https://github.com/CodySwannGT/lisa",
48
+ "git@github.com:CodySwannGT/lisa.git",
49
+ ]);
50
+
51
+ const originUrl = execFileSync("git", ["remote", "get-url", "origin"], {
52
+ cwd: repoRoot,
53
+ encoding: "utf8",
54
+ }).trim();
55
+ if (!canonicalOriginUrls.has(originUrl)) {
56
+ throw new Error(`Refusing non-canonical Lisa origin: ${originUrl}`);
57
+ }
58
+
59
+ const tracked = execFileSync("git", ["ls-files", "-z"], {
60
+ cwd: repoRoot,
61
+ encoding: "utf8",
62
+ })
63
+ .split("\0")
64
+ .filter(Boolean);
65
+ const explicitlyIncluded = ["scripts/generate-upstream-evidence-manifest.mjs"];
66
+ const publicSurfaceMembers = [...new Set([...tracked, ...explicitlyIncluded])]
67
+ .filter(file => file !== "src/core/upstream-evidence-manifest.ts")
68
+ .filter(file => {
69
+ const segments = file.split("/");
70
+ const basename = segments.at(-1) ?? "";
71
+ return (
72
+ !segments.some(segment => forbiddenSegments.has(segment)) &&
73
+ !forbiddenBasenames.has(basename) &&
74
+ !basename.endsWith(".local")
75
+ );
76
+ })
77
+ .sort();
78
+ const evidenceMembers = publicSurfaceMembers.filter(file =>
79
+ packagedEvidencePrefixes.some(prefix => file.startsWith(prefix))
80
+ );
81
+ const existingOutput = existsSync(outputPath)
82
+ ? readFileSync(outputPath, "utf8")
83
+ : "";
84
+ const existingPublicCommits = [
85
+ ...existingOutput.matchAll(
86
+ /^\s+(?:"([0-9][a-f0-9]{39})"|([a-f][a-f0-9]{39})):\s+true,$/gmu
87
+ ),
88
+ ]
89
+ .map(match => match[1] ?? match[2] ?? "")
90
+ .filter(Boolean);
91
+ const refreshPublicCommits = process.argv.includes("--refresh-public-commits");
92
+ const discoveredPublicCommits = refreshPublicCommits
93
+ ? execFileSync("git", ["rev-list", "--remotes=origin"], {
94
+ cwd: repoRoot,
95
+ encoding: "utf8",
96
+ })
97
+ .split("\n")
98
+ .filter(commit => /^[a-f0-9]{40}$/.test(commit))
99
+ : [];
100
+ const publicCommits = [
101
+ ...new Set([...existingPublicCommits, ...discoveredPublicCommits]),
102
+ ].sort();
103
+
104
+ if (publicSurfaceMembers.length === 0 || evidenceMembers.length === 0) {
105
+ throw new Error("Refusing to generate an empty upstream evidence manifest");
106
+ }
107
+ if (publicCommits.length === 0) {
108
+ throw new Error(
109
+ "Refusing to generate an empty public origin commit manifest; rerun with --refresh-public-commits from a full canonical-origin clone"
110
+ );
111
+ }
112
+
113
+ const formatObjectKey = value =>
114
+ /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
115
+ const surfaceEntries = publicSurfaceMembers.map(
116
+ file => ` ${formatObjectKey(file)}: true,`
117
+ );
118
+ const evidenceEntries = evidenceMembers.map(file => {
119
+ const digest = createHash("sha256")
120
+ .update(readFileSync(path.join(repoRoot, file)))
121
+ .digest("hex");
122
+ return ` ${JSON.stringify(file)}:\n ${JSON.stringify(digest)},`;
123
+ });
124
+ const commitEntries = publicCommits.map(commit => {
125
+ return ` ${formatObjectKey(commit)}: true,`;
126
+ });
127
+ const output = `/* eslint-disable max-lines -- generated exact public-evidence allowlist */
128
+ /** Generated by scripts/generate-upstream-evidence-manifest.mjs. Do not edit. */
129
+ export const UPSTREAM_EVIDENCE_MANIFEST: Readonly<Record<string, string>> =
130
+ Object.freeze({
131
+ ${evidenceEntries.join("\n")}
132
+ });
133
+
134
+ /** Exact paths tracked by the public Lisa repository at generation time. */
135
+ export const UPSTREAM_SURFACE_MANIFEST: Readonly<Record<string, true>> =
136
+ Object.freeze({
137
+ ${surfaceEntries.join("\n")}
138
+ });
139
+
140
+ /** Full commit SHAs reachable from public origin refs when this package was built. */
141
+ export const UPSTREAM_PUBLIC_COMMITS: Readonly<Record<string, true>> =
142
+ Object.freeze({
143
+ ${commitEntries.join("\n")}
144
+ });
145
+ /* eslint-enable max-lines -- end generated exact public-evidence allowlist */
146
+ `;
147
+
148
+ if (process.argv.includes("--check")) {
149
+ const current = readFileSync(outputPath, "utf8");
150
+ if (current !== output) {
151
+ throw new Error(
152
+ "src/core/upstream-evidence-manifest.ts is stale; run bun run build:upstream-evidence-manifest"
153
+ );
154
+ }
155
+ } else {
156
+ writeFileSync(outputPath, output);
157
+ }