@codyswann/lisa 3.33.5 → 3.33.7

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 (145) hide show
  1. package/all/copy-overwrite/scripts/check-state-classification.mjs +18 -4
  2. package/all/copy-overwrite/scripts/lisa-gates.mjs +47 -46
  3. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +3 -2
  4. package/all/copy-overwrite/scripts/lisa-work-item.mjs +284 -2
  5. package/dist/configs/eslint/base.d.ts +21 -0
  6. package/dist/configs/eslint/base.d.ts.map +1 -1
  7. package/dist/configs/eslint/base.js +78 -2
  8. package/dist/configs/eslint/base.js.map +1 -1
  9. package/dist/configs/eslint/cdk.d.ts.map +1 -1
  10. package/dist/configs/eslint/cdk.js +5 -1
  11. package/dist/configs/eslint/cdk.js.map +1 -1
  12. package/dist/configs/eslint/expo.d.ts.map +1 -1
  13. package/dist/configs/eslint/expo.js +5 -1
  14. package/dist/configs/eslint/expo.js.map +1 -1
  15. package/dist/configs/eslint/harper-fabric.d.ts.map +1 -1
  16. package/dist/configs/eslint/harper-fabric.js +6 -1
  17. package/dist/configs/eslint/harper-fabric.js.map +1 -1
  18. package/dist/configs/eslint/nestjs.d.ts.map +1 -1
  19. package/dist/configs/eslint/nestjs.js +5 -1
  20. package/dist/configs/eslint/nestjs.js.map +1 -1
  21. package/dist/configs/eslint/phaser.d.ts.map +1 -1
  22. package/dist/configs/eslint/phaser.js +6 -1
  23. package/dist/configs/eslint/phaser.js.map +1 -1
  24. package/dist/configs/eslint/typescript.d.ts +2 -2
  25. package/dist/configs/eslint/typescript.d.ts.map +1 -1
  26. package/dist/configs/eslint/typescript.js +6 -2
  27. package/dist/configs/eslint/typescript.js.map +1 -1
  28. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  29. package/dist/core/lisa-owned-hash-ledger.js +12 -0
  30. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  31. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  32. package/dist/core/upstream-evidence-manifest.js +36 -35
  33. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  34. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +11 -4
  35. package/expo/copy-overwrite/scripts/bdd/discover.mjs +12 -6
  36. package/expo/copy-overwrite/scripts/bdd/waivers.mjs +7 -4
  37. package/expo/copy-overwrite/scripts/bdd-matrix.mjs +1 -0
  38. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +26 -17
  39. package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +12 -8
  40. package/package.json +1 -1
  41. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +11 -4
  44. package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +7 -2
  45. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +1 -1
  46. package/plugins/lisa/.codex-plugin/skills/lisa-jira-sync/SKILL.md +7 -2
  47. package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +7 -2
  48. package/plugins/lisa/.codex-plugin/skills/lisa-tracker-sync/SKILL.md +7 -2
  49. package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +11 -4
  50. package/plugins/lisa/skills/lisa-github-sync/SKILL.md +7 -2
  51. package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -1
  52. package/plugins/lisa/skills/lisa-jira-sync/SKILL.md +7 -2
  53. package/plugins/lisa/skills/lisa-linear-sync/SKILL.md +7 -2
  54. package/plugins/lisa/skills/lisa-tracker-sync/SKILL.md +7 -2
  55. package/plugins/lisa-agy/plugin.json +1 -1
  56. package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +11 -4
  57. package/plugins/lisa-agy/skills/lisa-github-sync/SKILL.md +7 -2
  58. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -1
  59. package/plugins/lisa-agy/skills/lisa-jira-sync/SKILL.md +7 -2
  60. package/plugins/lisa-agy/skills/lisa-linear-sync/SKILL.md +7 -2
  61. package/plugins/lisa-agy/skills/lisa-tracker-sync/SKILL.md +7 -2
  62. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  65. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +11 -4
  69. package/plugins/lisa-copilot/skills/lisa-github-sync/SKILL.md +7 -2
  70. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -1
  71. package/plugins/lisa-copilot/skills/lisa-jira-sync/SKILL.md +7 -2
  72. package/plugins/lisa-copilot/skills/lisa-linear-sync/SKILL.md +7 -2
  73. package/plugins/lisa-copilot/skills/lisa-tracker-sync/SKILL.md +7 -2
  74. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +11 -4
  76. package/plugins/lisa-cursor/skills/lisa-github-sync/SKILL.md +7 -2
  77. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -1
  78. package/plugins/lisa-cursor/skills/lisa-jira-sync/SKILL.md +7 -2
  79. package/plugins/lisa-cursor/skills/lisa-linear-sync/SKILL.md +7 -2
  80. package/plugins/lisa-cursor/skills/lisa-tracker-sync/SKILL.md +7 -2
  81. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-expo-agy/plugin.json +1 -1
  84. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  89. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  93. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  94. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  99. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  103. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  104. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  108. package/plugins/lisa-rails-agy/plugin.json +1 -1
  109. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  113. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  114. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  118. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  119. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  121. package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +11 -4
  122. package/plugins/src/base/skills/lisa-github-sync/SKILL.md +7 -2
  123. package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -1
  124. package/plugins/src/base/skills/lisa-jira-sync/SKILL.md +7 -2
  125. package/plugins/src/base/skills/lisa-linear-sync/SKILL.md +7 -2
  126. package/plugins/src/base/skills/lisa-tracker-sync/SKILL.md +7 -2
  127. package/scripts/check-conflict-markers.mjs +1 -1
  128. package/scripts/check-required-check-promotions.mjs +8 -4
  129. package/scripts/detect-stale-workflow-inputs.mjs +1 -3
  130. package/scripts/fix-namespace-test-assertions.mjs +1 -1
  131. package/scripts/generate-agy-plugin-artifacts.mjs +12 -7
  132. package/scripts/generate-codex-plugin-artifacts.mjs +14 -9
  133. package/scripts/generate-copilot-plugin-artifacts.mjs +1 -1
  134. package/scripts/generate-cursor-plugin-artifacts.mjs +2 -2
  135. package/scripts/generate-upstream-evidence-manifest.mjs +1 -1
  136. package/scripts/lisa-assert-eas-profile.mjs +3 -3
  137. package/scripts/plugin-parity-drift.mjs +29 -13
  138. package/scripts/plugin-routing-validate.mjs +8 -2
  139. package/scripts/update-node-version.ts +7 -3
  140. package/scripts/verify-health-agentic-built.mjs +27 -20
  141. package/scripts/verify-health-consumer-built.mjs +7 -3
  142. package/scripts/verify-health-deterministic-built.mjs +24 -19
  143. package/typescript/copy-overwrite/eslint.ignore.config.json +0 -2
  144. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +9 -4
  145. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +1 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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": "3.33.5",
3
+ "version": "3.33.7",
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"
@@ -66,10 +66,17 @@ When updating an existing PR, preserve any existing linkage line unless the new
66
66
  After creating or updating the PR, always make the reverse link durable on the source work item when `work_item_ref` is available:
67
67
 
68
68
  1. Resolve the live PR URL with `gh pr view <pr-number> --json url --jq .url`.
69
- 2. Invoke `lisa-tracker-sync` with the original work item ref, milestone `pr-ready`, `pr_url=<url>`, and `tracker_provider=<provider>` when known.
70
- 3. The vendor sync skill must prefer the provider's native development-link primitive where one is available and verifiable.
71
- 4. If native linkage is unavailable, unconfigured, or cannot be verified, the vendor sync skill must create or update a single managed `[lisa-pr-link]` comment on the work item containing the PR URL. The fallback comment is not optional; it is the ticket-side half of the two-way link.
72
- 5. When the PR later merges, invoke `lisa-tracker-sync` again with milestone `pr-merged`, the same `pr_url`, and the merge SHA when available, so the managed backlink reflects the final state.
69
+ 2. Run the backlink command. It is the executable form of this requirement — it writes the managed `[lisa-pr-link]` comment on the work item, or updates the one already there, for every tracker Lisa supports:
70
+
71
+ ```bash
72
+ node scripts/lisa-work-item.mjs backlink --ref <work_item_ref> --pr-url <url>
73
+ ```
74
+
75
+ It is idempotent, so run it on every push rather than deciding whether it is needed. It refuses loudly for a tracker it cannot write, and never silently no-ops. Do not hand-post the comment, and do not describe the posting procedure anywhere: the same file that writes it is the file that checks it, which is what keeps producer and consumer from drifting.
76
+ 3. Invoke `lisa-tracker-sync` with the original work item ref, milestone `pr-ready`, `pr_url=<url>`, and `tracker_provider=<provider>` when known. That is the progress-note and status side; it is not what satisfies the traceability check.
77
+ 4. When the PR later merges, invoke `lisa-tracker-sync` again with milestone `pr-merged`, the same `pr_url`, and the merge SHA when available.
78
+
79
+ **Why step 2 exists — do not "simplify" it away as redundant with the `Refs` line.** Under the non-closing rule above, GitHub never creates a native development link at all: that surface *is* the closing-reference mechanism, so no non-closing form can populate it. A PR carrying a perfectly correct `Refs` line still fails the required Work-Item Traceability check without the comment. The managed comment is the ticket-side half of the link, not a fallback for when native linkage happens to be absent.
73
80
 
74
81
  Do not report PR submission as fully synced while the PR body references the ticket but the ticket has neither a verified native PR link nor the managed backlink comment.
75
82
 
@@ -57,8 +57,13 @@ Optional arguments include `pr_url=<url>` for the live pull request and `merge_s
57
57
  When `$ARGUMENTS` includes `pr_url=<url>` for `PR ready` or `PR merged`, ensure the GitHub Issue has a durable ticket -> PR link:
58
58
 
59
59
  1. Make sure the PR body contains `Refs #<n>` (or the fully qualified cross-repo form) — never a closing keyword, per the GitHub rule in `lisa-git-submit-pr`. Read the issue side with `gh api graphql` against `issue.timelineItems`, or `gh issue view <number> --json closedByPullRequestsReferences`. **Not** `gh issue view --json timelineItems`: `timelineItems` is not a supported field for that command, so the check silently returns nothing useful rather than failing loudly.
60
- 2. Post or update a single managed issue comment starting with `[lisa-pr-link]`. Include the PR URL, milestone (`pr-ready` or `pr-merged`), and merge SHA when available. This is **unconditional**, not contingent on step 1 failing: under the non-closing rule GitHub never creates a native development link (that surface is the closing-reference mechanism), so this comment is the only ticket-side backlink there will be.
61
- 3. Keep the fallback idempotent: search existing comments for `[lisa-pr-link]` and the PR URL; update/replace that managed comment where the provider allows updates, otherwise skip when the current body already matches. Do not append duplicate backlink comments on reruns.
60
+ 2. Establish the managed backlink comment by running the command that owns it never by hand, and never by describing the procedure here:
61
+
62
+ ```bash
63
+ node scripts/lisa-work-item.mjs backlink --ref <work-item> --pr-url <url>
64
+ ```
65
+
66
+ It creates the `[lisa-pr-link]` comment or updates the one already present, instead of appending duplicates, so it is safe to run on every milestone. This is **unconditional**, not contingent on step 1 failing: under the non-closing rule GitHub never creates a native development link (that surface is the closing-reference mechanism), so this comment is the only ticket-side backlink there will be — run it whether or not native linkage exists or cannot be verified, because the required Work-Item Traceability check reads this comment and nothing else guarantees one. The comment carries the marker and the PR URL only; the milestone (`pr-ready` / `pr-merged`) and merge SHA belong in the milestone progress note, so that a rerun at a new milestone still converges on one backlink comment.
62
67
 
63
68
  Native GitHub linkage cannot be verified under the non-closing rule because it is never created in the first place, so the managed comment is not a contingency here — it is the mechanism. The issue must show the PR from at least one ticket-side surface, and this is the only one available.
64
69
 
@@ -341,7 +341,7 @@ Before shutting down the team, execute the Verify flow:
341
341
  5. Commit ALL outstanding changes in logical batches on the branch (minus sensitive data/information) — not just changes made by the agent team. This includes pre-existing uncommitted changes that were on the branch before the plan started. Do NOT filter commits to only "task-related" files. If it shows up in git status, it gets committed (unless it contains secrets).
342
342
  6. Push the changes - if any pre-push hook blocks you, create a task for the agent team to fix the error/problem whether it was pre-existing or not
343
343
  7. Open a pull request with auto-merge on via `lisa-git-submit-pr`, targeting the **base branch resolved from the ticket's environment** (`target_branch=<base>`, per the branch step above), and including the mandatory work-item ref so the PR can be linked natively to the source issue.
344
- 7a. Confirm two-way linkage before treating PR submission as complete: the PR body/title/branch must reference the work item, and the work item must have either a verified native PR link or a single managed `[lisa-pr-link]` fallback comment from `lisa-tracker-sync`.
344
+ 7a. Confirm two-way linkage before treating PR submission as complete: the PR body/title/branch must reference the work item, and the work item must have either a verified native PR link or the single managed `[lisa-pr-link]` comment. Establish that comment with `node scripts/lisa-work-item.mjs backlink --ref <work_item_ref> --pr-url <url>` rather than posting it by hand — it is idempotent, and it is the same file that enforces the check, so producer and consumer cannot drift.
345
345
  8. PR Watch Loop: Drive the PR to merge via the `drive-pr-to-merge` skill — the single source of truth for clearing every blocker (auto-merge with direct-merge fallback, `BEHIND` re-sync, conflict resolution, failing-check fixes, human + bot review-comment handling with thread resolution, stale `CHANGES_REQUESTED` dismissal, and post-merge ancestry verification). `git-submit-pr` already invokes it; if you reach this step with a PR already open, invoke `drive-pr-to-merge` directly with the PR number. For a large review backlog you may fan the code-fix work out to the agent team, but `drive-pr-to-merge` owns the loop and the terminal conditions — do not re-implement them.
346
346
  9. Merge the PR, then refresh the ticket-side backlink with `lisa-tracker-sync <work_item_ref> pr-merged pr_url=<url> merge_sha=<sha> tracker_provider=<provider>`.
347
347
  10. Monitor the deploy action that triggers automatically from the successful merge
@@ -55,8 +55,13 @@ Before adding a comment, check for an existing milestone comment to avoid duplic
55
55
  When `$ARGUMENTS` includes `pr_url=<url>` for `PR ready` or `PR merged`, ensure the JIRA ticket has a durable ticket -> PR link:
56
56
 
57
57
  1. Prefer the JIRA development-link surface when the site's GitHub/JIRA integration or remote-link API is available through `lisa-atlassian-access`; verify by re-reading the ticket's remote links / development metadata.
58
- 2. If native linkage is unavailable, unconfigured, cross-system, or cannot be verified, create or update a single managed JIRA comment containing the PR URL. The comment must start with `[lisa-pr-link]` and include the milestone (`pr-ready` or `pr-merged`) and merge SHA when available.
59
- 3. Keep the fallback idempotent: read existing comments, find the `[lisa-pr-link]` comment for the same PR URL, and update/skip it instead of appending duplicates. If the current access layer cannot update comments in place, skip when an identical managed comment already exists and otherwise add exactly one replacement comment with the stable marker.
58
+ 2. Establish the managed backlink comment by running the command that owns it never by hand, and never by describing the procedure here:
59
+
60
+ ```bash
61
+ node scripts/lisa-work-item.mjs backlink --ref <work-item> --pr-url <url>
62
+ ```
63
+
64
+ It creates the `[lisa-pr-link]` comment or updates the one already present, instead of appending duplicates, so it is safe to run on every milestone. This is **unconditional** — run it whether or not native linkage exists or cannot be verified, because the required Work-Item Traceability check reads this comment and nothing else guarantees one. The comment carries the marker and the PR URL only; the milestone (`pr-ready` / `pr-merged`) and merge SHA belong in the milestone progress note, so that a rerun at a new milestone still converges on one backlink comment.
60
65
 
61
66
  The PR body/branch issue key is the PR -> ticket side. This step is the required ticket -> PR side.
62
67
 
@@ -72,8 +72,13 @@ Call `lisa-linear-access operation: save-comment({issueId: <id>, body: <comment>
72
72
  When `$ARGUMENTS` includes `pr_url=<url>` for `pr-ready` or `pr-merged`, ensure the Linear Issue has a durable ticket -> PR link:
73
73
 
74
74
  1. Prefer Linear's native GitHub attachment / pull request link when the integration has attached the PR through the branch name, PR title, or PR body issue identifier. Verify by re-reading the Issue and its attachments / relations where the Linear access layer exposes them.
75
- 2. If native linkage is unavailable, unconfigured, cross-system, or cannot be verified, create or update a single managed Linear comment containing the PR URL. The comment must start with `[lisa-pr-link]` and include the milestone (`pr-ready` or `pr-merged`) and merge SHA when available.
76
- 3. Keep the fallback idempotent: read existing comments where the access layer exposes them, find the `[lisa-pr-link]` comment for the same PR URL, and update/skip it instead of appending duplicates. If comment update is unavailable, skip when an identical managed comment already exists and otherwise add exactly one replacement comment with the stable marker.
75
+ 2. Establish the managed backlink comment by running the command that owns it never by hand, and never by describing the procedure here:
76
+
77
+ ```bash
78
+ node scripts/lisa-work-item.mjs backlink --ref <work-item> --pr-url <url>
79
+ ```
80
+
81
+ It creates the `[lisa-pr-link]` comment or updates the one already present, instead of appending duplicates, so it is safe to run on every milestone. This is **unconditional** — run it whether or not native linkage exists or cannot be verified, because the required Work-Item Traceability check reads this comment and nothing else guarantees one. The comment carries the marker and the PR URL only; the milestone (`pr-ready` / `pr-merged`) and merge SHA belong in the milestone progress note, so that a rerun at a new milestone still converges on one backlink comment.
77
82
 
78
83
  The PR branch/title/body identifier is the PR -> Linear side. This phase is the required Linear -> PR side.
79
84
 
@@ -52,8 +52,13 @@ When `$ARGUMENTS` includes `pr_url=<url>` with milestone `pr-ready` or `pr-merge
52
52
 
53
53
  1. Prefer the provider's native development-link primitive when Lisa can write and verify it for that provider.
54
54
  2. Verify the native link using the provider read surface when available.
55
- 3. If the native link is unavailable, unconfigured, cross-system, or cannot be verified, create or update one managed backlink comment on the work item containing the PR URL and current milestone.
56
- 4. Keep the comment idempotent by using a stable marker such as `[lisa-pr-link]`; reruns update or skip the existing managed comment rather than appending duplicates.
55
+ 3. Whether or not the native link exists or cannot be verified, establish the managed backlink comment with the one command that owns it:
56
+
57
+ ```bash
58
+ node scripts/lisa-work-item.mjs backlink --ref <work-item> --pr-url <url>
59
+ ```
60
+
61
+ 4. That command is idempotent by construction — it updates the existing `[lisa-pr-link]` comment rather than appending duplicates — and it refuses loudly for a tracker it cannot write. Do not restate its procedure in a vendor skill; the file that writes the comment is the file that checks it, and that is what stops the two from drifting.
57
62
 
58
63
  This is the reverse half of `lisa-git-submit-pr`'s PR body linkage. A PR that mentions a ticket is not considered fully synced until the ticket also has either a verified native PR link or the managed fallback comment.
59
64
 
@@ -299,7 +299,7 @@ export function main(argv, io = {}) {
299
299
  scanned: files.length,
300
300
  });
301
301
  out.write(
302
- (opts.json ? JSON.stringify(report, null, 2) : humanReport(report)) + "\n"
302
+ `${opts.json ? JSON.stringify(report, null, 2) : humanReport(report)}\n`
303
303
  );
304
304
  return results.length === 0 ? 0 : 1;
305
305
  }
@@ -140,6 +140,10 @@ import path from "node:path";
140
140
  import { fileURLToPath } from "node:url";
141
141
  import yaml from "js-yaml";
142
142
 
143
+ // Literals named once — each was repeated enough times that a typo in one
144
+ // copy would diverge silently.
145
+ const HEADROOM_EVIDENCE_MISSING = "headroom-evidence-missing";
146
+
143
147
  /** Integration id GitHub Actions reports status checks under. */
144
148
  export const ACTIONS_INTEGRATION_ID = 15_368;
145
149
 
@@ -343,7 +347,7 @@ function budgetProblems(budget) {
343
347
  if (typeof subject !== "string" || typeof measuredOn !== "string") {
344
348
  return [
345
349
  {
346
- rule: "headroom-evidence-missing",
350
+ rule: HEADROOM_EVIDENCE_MISSING,
347
351
  detail:
348
352
  "every headroom.budgets[] entry needs a subject and a measured_on_subject",
349
353
  },
@@ -391,7 +395,7 @@ function provenanceProblems(observedOn) {
391
395
  }
392
396
  return [
393
397
  {
394
- rule: "headroom-evidence-missing",
398
+ rule: HEADROOM_EVIDENCE_MISSING,
395
399
  detail:
396
400
  'headroom.observed_on must be "pass": the run that produced observed_worst_ms must have COMPLETED within its budget, because a duration reported alongside a timeout measures contention, not cost',
397
401
  },
@@ -414,7 +418,7 @@ function ratioProblems(budgetMs, observedMs) {
414
418
  ) {
415
419
  return [
416
420
  {
417
- rule: "headroom-evidence-missing",
421
+ rule: HEADROOM_EVIDENCE_MISSING,
418
422
  detail:
419
423
  "headroom needs a positive budget_ms and a positive observed_worst_ms",
420
424
  },
@@ -481,7 +485,7 @@ export function headroomProblems(headroom) {
481
485
  for (const [field, detail] of prose) {
482
486
  const value = headroom[field];
483
487
  if (typeof value !== "string" || value.trim() === "") {
484
- problems.push({ rule: "headroom-evidence-missing", detail });
488
+ problems.push({ rule: HEADROOM_EVIDENCE_MISSING, detail });
485
489
  }
486
490
  }
487
491
  if (problems.length > 0) return problems;
@@ -231,9 +231,7 @@ function humanTable(report) {
231
231
  * @returns {void}
232
232
  */
233
233
  function emitReport(out, report, json) {
234
- out.write(
235
- (json ? JSON.stringify(report, null, 2) : humanTable(report)) + "\n"
236
- );
234
+ out.write(`${json ? JSON.stringify(report, null, 2) : humanTable(report)}\n`);
237
235
  }
238
236
 
239
237
  /**
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Fix test assertions and a few skill references after lisa-* skill / lisa: command namespace migration.
4
4
  */
5
- import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
5
+ import { readFileSync, writeFileSync } from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
@@ -44,6 +44,11 @@ import { fileURLToPath } from "node:url";
44
44
  import { nestCommandsUnderLisa } from "./lib/nest-plugin-commands.mjs";
45
45
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
46
46
 
47
+ // Literals named once — each was repeated enough times that a typo in one
48
+ // copy would diverge silently.
49
+ const CLAUDE_PLUGIN_DIR = ".claude-plugin";
50
+ const PLUGIN_MANIFEST = "plugin.json";
51
+
47
52
  const REPO_ROOT = path.resolve(
48
53
  path.dirname(fileURLToPath(import.meta.url)),
49
54
  ".."
@@ -115,7 +120,7 @@ function copyDir(src, dst, keep = () => true) {
115
120
  * @param {string} version Version string for the manifest.
116
121
  */
117
122
  export function generateAgyVariant(srcDir, outDir, version) {
118
- const claudeManifest = path.join(srcDir, ".claude-plugin", "plugin.json");
123
+ const claudeManifest = path.join(srcDir, CLAUDE_PLUGIN_DIR, PLUGIN_MANIFEST);
119
124
  if (!fs.existsSync(claudeManifest)) return;
120
125
 
121
126
  const policy = readPolicy();
@@ -148,8 +153,8 @@ export function generateAgyVariant(srcDir, outDir, version) {
148
153
  }
149
154
  // Skip the .claude-plugin manifest — we'll write a bare plugin.json instead.
150
155
  if (
151
- relPath === path.join(".claude-plugin", "plugin.json") ||
152
- relPath === ".claude-plugin"
156
+ relPath === path.join(CLAUDE_PLUGIN_DIR, PLUGIN_MANIFEST) ||
157
+ relPath === CLAUDE_PLUGIN_DIR
153
158
  ) {
154
159
  return false;
155
160
  }
@@ -181,11 +186,11 @@ export function generateAgyVariant(srcDir, outDir, version) {
181
186
  delete manifest.hooks;
182
187
  delete manifest.mcpServers;
183
188
 
184
- const bareManifestPath = path.join(outDir, "plugin.json");
185
- fs.writeFileSync(bareManifestPath, JSON.stringify(manifest, null, 2) + "\n");
189
+ const bareManifestPath = path.join(outDir, PLUGIN_MANIFEST);
190
+ fs.writeFileSync(bareManifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
186
191
 
187
192
  // 3. Ensure no .claude-plugin/ directory survives.
188
- const ghostDir = path.join(outDir, ".claude-plugin");
193
+ const ghostDir = path.join(outDir, CLAUDE_PLUGIN_DIR);
189
194
  if (fs.existsSync(ghostDir)) {
190
195
  fs.rmSync(ghostDir, { recursive: true, force: true });
191
196
  }
@@ -328,7 +333,7 @@ function emitAgyPluginHooks(srcDir, outDir, sourceHooks, installDirName) {
328
333
  );
329
334
  fs.writeFileSync(
330
335
  path.join(outDir, "hooks.json"),
331
- JSON.stringify(hooksConfig, null, 2) + "\n"
336
+ `${JSON.stringify(hooksConfig, null, 2)}\n`
332
337
  );
333
338
 
334
339
  // Copy the agy-protocol scripts into the variant's hooks/ subdir.
@@ -38,6 +38,11 @@ import fs from "node:fs";
38
38
  import path from "node:path";
39
39
  import { fileURLToPath } from "node:url";
40
40
 
41
+ // Literals named once — each was repeated enough times that a typo in one
42
+ // copy would diverge silently.
43
+ const CODEX_PLUGIN_DIR = ".codex-plugin";
44
+ const HOOKS_MANIFEST = "hooks.json";
45
+
41
46
  const REPO_ROOT = path.resolve(
42
47
  path.dirname(fileURLToPath(import.meta.url)),
43
48
  ".."
@@ -469,7 +474,7 @@ function writeSkillAgentsInRoot(skillsDir) {
469
474
  function codexSkillRoots(pluginDir) {
470
475
  return [
471
476
  path.join(pluginDir, "skills"),
472
- path.join(pluginDir, ".codex-plugin", "skills"),
477
+ path.join(pluginDir, CODEX_PLUGIN_DIR, "skills"),
473
478
  ].filter(skillRoot => fs.existsSync(skillRoot));
474
479
  }
475
480
 
@@ -482,7 +487,7 @@ function codexSkillRoots(pluginDir) {
482
487
  * @returns {readonly string[]} Generated skill names.
483
488
  */
484
489
  export function emitCommandSkills(pluginDir) {
485
- const generatedRoot = path.join(pluginDir, ".codex-plugin", "skills");
490
+ const generatedRoot = path.join(pluginDir, CODEX_PLUGIN_DIR, "skills");
486
491
  const commandsRoot = path.join(pluginDir, "commands");
487
492
  if (!fs.existsSync(commandsRoot)) {
488
493
  return [];
@@ -536,7 +541,7 @@ export function emitCommandSkills(pluginDir) {
536
541
  */
537
542
  export function emitCodexSkillVariants(pluginDir) {
538
543
  const authoredRoot = path.join(pluginDir, "skills");
539
- const codexRoot = path.join(pluginDir, ".codex-plugin", "skills");
544
+ const codexRoot = path.join(pluginDir, CODEX_PLUGIN_DIR, "skills");
540
545
  fs.rmSync(codexRoot, { force: true, recursive: true });
541
546
  if (!fs.existsSync(authoredRoot)) return [];
542
547
 
@@ -799,14 +804,14 @@ function main() {
799
804
  * @param {object} claudeManifest Parsed contents of .claude-plugin/plugin.json.
800
805
  */
801
806
  export function emitCodexHooks(pluginDir, claudeManifest) {
802
- const codexPluginDir = path.join(pluginDir, ".codex-plugin");
803
- const hooksJsonPath = path.join(codexPluginDir, "hooks.json");
807
+ const codexPluginDir = path.join(pluginDir, CODEX_PLUGIN_DIR);
808
+ const hooksJsonPath = path.join(codexPluginDir, HOOKS_MANIFEST);
804
809
  // Purge the older layouts so a rebuilt plugin never ships a stale/duplicate
805
810
  // file:
806
811
  // - <plugin-root>/hooks/hooks.json (2.121–2.124; broke Claude startup)
807
812
  // - <plugin-root>/.codex-plugin/hooks/ (pre-2.121 copied-scripts dir)
808
813
  // The shared .sh scripts at <plugin-root>/hooks/ are left untouched.
809
- fs.rmSync(path.join(pluginDir, "hooks", "hooks.json"), { force: true });
814
+ fs.rmSync(path.join(pluginDir, "hooks", HOOKS_MANIFEST), { force: true });
810
815
  fs.rmSync(path.join(codexPluginDir, "hooks"), {
811
816
  force: true,
812
817
  recursive: true,
@@ -861,7 +866,7 @@ function writeCodexManifest(pluginDir, claudeManifest, pluginName, version) {
861
866
  },
862
867
  };
863
868
 
864
- const manifestDir = path.join(pluginDir, ".codex-plugin");
869
+ const manifestDir = path.join(pluginDir, CODEX_PLUGIN_DIR);
865
870
  fs.mkdirSync(manifestDir, { recursive: true });
866
871
  fs.writeFileSync(
867
872
  path.join(manifestDir, "plugin.json"),
@@ -871,7 +876,7 @@ function writeCodexManifest(pluginDir, claudeManifest, pluginName, version) {
871
876
 
872
877
  export function componentPointers(pluginDir) {
873
878
  return {
874
- ...(fs.existsSync(path.join(pluginDir, ".codex-plugin", "skills"))
879
+ ...(fs.existsSync(path.join(pluginDir, CODEX_PLUGIN_DIR, "skills"))
875
880
  ? { skills: "./.codex-plugin/skills/" }
876
881
  : fs.existsSync(path.join(pluginDir, "skills"))
877
882
  ? { skills: "./skills/" }
@@ -879,7 +884,7 @@ export function componentPointers(pluginDir) {
879
884
  ...(fs.existsSync(path.join(pluginDir, ".mcp.json"))
880
885
  ? { mcpServers: "./.mcp.json" }
881
886
  : {}),
882
- ...(fs.existsSync(path.join(pluginDir, ".codex-plugin", "hooks.json"))
887
+ ...(fs.existsSync(path.join(pluginDir, CODEX_PLUGIN_DIR, HOOKS_MANIFEST))
883
888
  ? { hooks: "./.codex-plugin/hooks.json" }
884
889
  : {}),
885
890
  };
@@ -247,7 +247,7 @@ export function generateCopilotVariant(srcDir, outDir, version) {
247
247
  }
248
248
  }
249
249
 
250
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
250
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
251
251
 
252
252
  // 4. Filter the hooks/ directory.
253
253
  const hooksDir = path.join(outDir, "hooks");
@@ -336,7 +336,7 @@ export function generateCursorVariant(srcDir, outDir, version) {
336
336
  manifest.version = version;
337
337
  const cursorHooks = buildCursorHooksJson(manifest.hooks ?? {});
338
338
  delete manifest.hooks;
339
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
339
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
340
340
 
341
341
  // 2a. Emit hooks/hooks.json in Cursor's native shape when any hooks survive.
342
342
  if (cursorHooks) {
@@ -344,7 +344,7 @@ export function generateCursorVariant(srcDir, outDir, version) {
344
344
  fs.mkdirSync(cursorHooksDir, { recursive: true });
345
345
  fs.writeFileSync(
346
346
  path.join(cursorHooksDir, "hooks.json"),
347
- JSON.stringify(cursorHooks, null, 2) + "\n"
347
+ `${JSON.stringify(cursorHooks, null, 2)}\n`
348
348
  );
349
349
  }
350
350
 
@@ -123,7 +123,7 @@ const existingOutput = existsSync(outputPath)
123
123
  : "";
124
124
  const existingPublicCommits = [
125
125
  ...existingOutput.matchAll(
126
- /^\s+(?:"([0-9][a-f0-9]{39})"|([a-f][a-f0-9]{39})):\s+true,$/gmu
126
+ /^\s+(?:"(\d[a-f0-9]{39})"|([a-f][a-f0-9]{39})):\s+true,$/gmu
127
127
  ),
128
128
  ]
129
129
  .map(match => match[1] ?? match[2] ?? "")
@@ -88,9 +88,9 @@ export function resolveProfile(profiles, name) {
88
88
  if (!profile) {
89
89
  const known = Object.keys(profiles ?? {});
90
90
  throw new Error(
91
- `eas.json has no build profile called "${current}"` +
92
- (current === name ? "" : ` (reached by extending from "${name}")`) +
93
- `. It defines: ${known.length ? known.join(", ") : "none"}.`
91
+ `eas.json has no build profile called "${current}"${
92
+ current === name ? "" : ` (reached by extending from "${name}")`
93
+ }. It defines: ${known.length ? known.join(", ") : "none"}.`
94
94
  );
95
95
  }
96
96
  seen.add(current);