@codyswann/lisa 4.66.4 → 4.67.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.
- package/all/copy-contents/gitignore +5 -0
- package/all/copy-overwrite/scripts/lisa-self-update.mjs +636 -0
- package/all/create-only/.github/workflows/lisa-update.yml +93 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +10 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +38 -24
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +2 -1
- package/plugins/lisa/agents/architecture-specialist.md +4 -0
- package/plugins/lisa/agents/bug-fixer.md +4 -0
- package/plugins/lisa/agents/builder.md +4 -0
- package/plugins/lisa/agents/debug-specialist.md +4 -0
- package/plugins/lisa/agents/product-specialist.md +4 -0
- package/plugins/lisa/agents/quality-specialist.md +4 -0
- package/plugins/lisa/agents/spec-conformance-specialist.md +4 -0
- package/plugins/lisa/agents/test-specialist.md +4 -0
- package/plugins/lisa/agents/verification-specialist.md +4 -0
- package/plugins/lisa/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa/hooks/enforcement-vintage.mjs +22 -3
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/lisa/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/lisa/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/lisa/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/lisa/skills/lisa-track/SKILL.md +2 -1
- package/plugins/lisa-agy/agents/architecture-specialist.md +4 -0
- package/plugins/lisa-agy/agents/bug-fixer.md +4 -0
- package/plugins/lisa-agy/agents/builder.md +4 -0
- package/plugins/lisa-agy/agents/debug-specialist.md +4 -0
- package/plugins/lisa-agy/agents/product-specialist.md +4 -0
- package/plugins/lisa-agy/agents/quality-specialist.md +4 -0
- package/plugins/lisa-agy/agents/spec-conformance-specialist.md +4 -0
- package/plugins/lisa-agy/agents/test-specialist.md +4 -0
- package/plugins/lisa-agy/agents/verification-specialist.md +4 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/lisa-agy/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/lisa-agy/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/lisa-agy/skills/lisa-track/SKILL.md +2 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/agents/architecture-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/bug-fixer.agent.md +4 -0
- package/plugins/lisa-copilot/agents/builder.agent.md +4 -0
- package/plugins/lisa-copilot/agents/debug-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/product-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/quality-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/spec-conformance-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/test-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/agents/verification-specialist.agent.md +4 -0
- package/plugins/lisa-copilot/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa-copilot/hooks/enforcement-vintage.mjs +22 -3
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/lisa-copilot/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/lisa-copilot/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +2 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/agents/architecture-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/bug-fixer.md +4 -0
- package/plugins/lisa-cursor/agents/builder.md +4 -0
- package/plugins/lisa-cursor/agents/debug-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/product-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/quality-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/spec-conformance-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/test-specialist.md +4 -0
- package/plugins/lisa-cursor/agents/verification-specialist.md +4 -0
- package/plugins/lisa-cursor/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/lisa-cursor/hooks/enforcement-vintage.mjs +22 -3
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/lisa-cursor/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/lisa-cursor/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +2 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/agents/architecture-specialist.md +4 -0
- package/plugins/src/base/agents/bug-fixer.md +4 -0
- package/plugins/src/base/agents/builder.md +4 -0
- package/plugins/src/base/agents/debug-specialist.md +4 -0
- package/plugins/src/base/agents/product-specialist.md +4 -0
- package/plugins/src/base/agents/quality-specialist.md +4 -0
- package/plugins/src/base/agents/spec-conformance-specialist.md +4 -0
- package/plugins/src/base/agents/test-specialist.md +4 -0
- package/plugins/src/base/agents/verification-specialist.md +4 -0
- package/plugins/src/base/hooks/enforcement-vintage-npm.mjs +320 -0
- package/plugins/src/base/hooks/enforcement-vintage.mjs +22 -3
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +12 -0
- package/plugins/src/base/skills/lisa-git-commit/SKILL.md +7 -7
- package/plugins/src/base/skills/lisa-git-submit-pr/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-github-read-issue/SKILL.md +4 -3
- package/plugins/src/base/skills/lisa-implement/SKILL.md +11 -4
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-jira-read-ticket/SKILL.md +4 -3
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-linear-read-issue/SKILL.md +3 -2
- package/plugins/src/base/skills/lisa-track/SKILL.md +2 -1
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-overwrite/.prettierignore +1 -0
|
@@ -56,7 +56,7 @@ After download, branch on `mimeType`:
|
|
|
56
56
|
|
|
57
57
|
### Comments
|
|
58
58
|
|
|
59
|
-
Fetch ALL comments in chronological order. Do not truncate. For each:
|
|
59
|
+
Fetch ALL comments in chronological order via `lisa-atlassian-access` `operation: comments key: <TICKET-KEY>`, which pages until every comment is read — the comments embedded in `read-ticket` are only the first page. Do not truncate. If the result reports `comments_complete: false` (a failed page, or an MCP-only read that could not page), say so at the top of the bundle's Comments section with the fetched-versus-total counts, so no downstream agent mistakes a partial set for the whole. For each:
|
|
60
60
|
- Author, timestamp, body
|
|
61
61
|
- Flag comments that contain: credentials, reproduction steps, status updates from stakeholders, decisions, or triage headers like `[repo-name]`
|
|
62
62
|
|
|
@@ -94,7 +94,7 @@ For each linked ticket, invoke `lisa-atlassian-access` with `operation: read-tic
|
|
|
94
94
|
|
|
95
95
|
If the primary ticket has an epic parent (or IS an epic):
|
|
96
96
|
|
|
97
|
-
1. Fetch the epic itself via `lisa-atlassian-access` `operation: read-ticket key: <EPIC-KEY>` — full description, acceptance criteria,
|
|
97
|
+
1. Fetch the epic itself via `lisa-atlassian-access` `operation: read-ticket key: <EPIC-KEY>` — full description, acceptance criteria, Validation Journey — and its comments, all of them, via `lisa-atlassian-access` `operation: comments key: <EPIC-KEY>`.
|
|
98
98
|
2. Find epic siblings via JQL:
|
|
99
99
|
```jql
|
|
100
100
|
"Epic Link" = <EPIC-KEY> AND key != <TICKET-KEY>
|
|
@@ -135,7 +135,8 @@ Produce a single structured output that the caller can pass verbatim to downstre
|
|
|
135
135
|
### Validation Journey
|
|
136
136
|
<section or "None">
|
|
137
137
|
|
|
138
|
-
### Comments (<
|
|
138
|
+
### Comments (<fetched> of <total>; comments_complete: <true|false>)
|
|
139
|
+
<when incomplete: "INCOMPLETE — <fetched> of <total> comments read via <substrate>">
|
|
139
140
|
<chronological comments, flagged items called out>
|
|
140
141
|
|
|
141
142
|
### Attachments
|
|
@@ -386,7 +386,7 @@ After the claim succeeds, run the per-Issue lifecycle defined by the `linear-age
|
|
|
386
386
|
- `lisa-linear-verify` — pre-flight quality gate, including the draft-then-block procedure on FAIL
|
|
387
387
|
- `lisa-ticket-triage` — analytical triage gate (a `BLOCKED` verdict stops the cycle with findings posted)
|
|
388
388
|
- Intent determination from the type label
|
|
389
|
-
2. **Dispatch the flow in-session:** when the gates pass, invoke the lifecycle skill via the Skill tool — `lisa-implement <ISSUE-ID>` for Build / Fix / Improve / Investigate-Only (or `lisa-plan` for an Epic-equivalent) — passing the full context bundle from the read step. **When 3b classified this Issue `rejection-reclaim`, the context bundle passed to `lisa-implement` MUST include the rejection evidence summary** (what was rejected, the defect the QA comment named, the approach named as wrong) — reuse the evidence already read in 3b, do not fetch it twice — so the plan can address it per `rejection-detection`; absence of evidence never blocks. `lisa-implement`'s own orchestration preamble then creates the per-item agent team (input-resolver, Roster Decision, specialist fanout) exactly as a direct invocation would.
|
|
389
|
+
2. **Dispatch the flow in-session:** when the gates pass, invoke the lifecycle skill via the Skill tool — `lisa-implement <ISSUE-ID>` for Build / Fix / Improve / Investigate-Only (or `lisa-plan` for an Epic-equivalent) — passing the full context bundle from the read step. Hand that bundle over by file, not by text: before invoking `lisa-implement`, run the ignore guard below, then write the bundle verbatim to persist it at the git-ignored `.lisa/work-item-context.md` of this worktree — always overwriting the file with this cycle's bundle, never reusing one an earlier item left behind — and pass only `caller_bundle_path=<absolute path>` in the invocation. `lisa-implement`'s input-resolver keeps that file as the base instead of re-summarizing it, so every comment reaches the agents doing the work. Bundle text, and any credential value in it, never appears in a spawn prompt, task description, or Skill argument — only its path. **Ignore guard before any write.** A host can receive these skills before its shipped `.gitignore` entry, so prove the path is ignored first, from the worktree root: `git check-ignore -q .lisa/work-item-context.md || printf '%s\n' '.lisa/work-item-context.md' >> "$(git rev-parse --git-common-dir)/info/exclude"`, then re-run `git check-ignore -q .lisa/work-item-context.md`. If it is still not ignored, stop and report — do not write the bundle. If an `.easignore` exists and `grep -qxF '.lisa/work-item-context.md' .easignore` fails, stop and report instead of writing: EAS uploads read `.easignore`, which is derived from `.gitignore`, not from `info/exclude`. **When 3b classified this Issue `rejection-reclaim`, the context bundle passed to `lisa-implement` MUST include the rejection evidence summary** (what was rejected, the defect the QA comment named, the approach named as wrong) — reuse the evidence already read in 3b, do not fetch it twice — so the plan can address it per `rejection-detection`; absence of evidence never blocks. `lisa-implement`'s own orchestration preamble then creates the per-item agent team (input-resolver, Roster Decision, specialist fanout) exactly as a direct invocation would.
|
|
390
390
|
3. **Milestone sync and evidence** (`lisa-linear-sync`, `lisa-linear-evidence`) happen at the milestones the `linear-agent` workflow defines, within the dispatched flow.
|
|
391
391
|
|
|
392
392
|
If you are somehow running this skill as a spawned teammate inside an existing team (nested misrouting — Intake keeps this chain in the lead session), do NOT run the lifecycle inline and do NOT spawn named peers. Return this payload to the lead so the lead session can run this Phase 3c in-session:
|
|
@@ -43,7 +43,7 @@ Call `lisa-linear-access operation: get-issue`. Extract and preserve:
|
|
|
43
43
|
- Attachment URLs (capture, do not download unless needed)
|
|
44
44
|
|
|
45
45
|
**Comments**
|
|
46
|
-
Fetch ALL comments via `lisa-linear-access operation: list-comments({issueId: <id>})` in chronological order. Walk thread parents/children — Linear comments are threaded via `parentId`. Do not truncate. For each comment:
|
|
46
|
+
Fetch ALL comments via `lisa-linear-access operation: list-comments({issueId: <id>})` in chronological order. Page to exhaustion: request the next page while `pageInfo.hasNextPage` is true, passing `endCursor` as `after`, and never stop at the first page. Walk thread parents/children — Linear comments are threaded via `parentId`. Do not truncate. Linear reports no comment total, so `<total>` is the fetched count once `hasNextPage` is false, and `unknown` when a page fails — in which case set `comments_complete: false` and say so at the top of the Comments section. For each comment:
|
|
47
47
|
- Author, timestamp, body
|
|
48
48
|
- Flag comments that contain: credentials, reproduction steps, status updates from stakeholders, decisions, or triage headers.
|
|
49
49
|
|
|
@@ -137,7 +137,8 @@ Produce a single structured output the caller can pass verbatim to downstream ag
|
|
|
137
137
|
### Validation Journey
|
|
138
138
|
<section or "None">
|
|
139
139
|
|
|
140
|
-
### Comments (<
|
|
140
|
+
### Comments (<fetched> of <total>; comments_complete: <true|false>)
|
|
141
|
+
<when incomplete: "INCOMPLETE — <fetched> of <total> comments read via <substrate>">
|
|
141
142
|
<chronological comments, flagged items called out>
|
|
142
143
|
|
|
143
144
|
### Attachments
|
|
@@ -105,7 +105,7 @@ Otherwise:
|
|
|
105
105
|
|
|
106
106
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
107
107
|
- A detached-HEAD worktree is valid at this stage: the binding records `branch: null` as a pending state. Create the feature branch only after the gate succeeds, then run `node scripts/lisa-work-item.mjs attach-branch`. Commit preparation and validation fail closed until that attachment succeeds.
|
|
108
|
-
4.
|
|
108
|
+
4. **Ignore guard before any write.** A host can receive these skills before its shipped `.gitignore` entry, so prove the path is ignored first, from the worktree root: `git check-ignore -q .lisa/work-item-context.md || printf '%s\n' '.lisa/work-item-context.md' >> "$(git rev-parse --git-common-dir)/info/exclude"`, then re-run `git check-ignore -q .lisa/work-item-context.md`. If it is still not ignored, stop and report — do not write the bundle. If an `.easignore` exists and `grep -qxF '.lisa/work-item-context.md' .easignore` fails, stop and report instead of writing: EAS uploads read `.easignore`, which is derived from `.gitignore`, not from `info/exclude`. Then write the full resolved work-item context — the live vendor read bundle, unedited, every comment included — to the worktree-local, git-ignored `<worktree root>/.lisa/work-item-context.md`, then return this structured result (the file is the context; a summary in the reply never substitutes for it). When a `caller_bundle_path` was forwarded for this work item and the file already exists, do not overwrite it — it holds the caller's bundle (or a previous run's) and is left for the resolver step. Otherwise this is the first write; when `lisa-implement` forwarded a `caller_bundle_path`, its resolver step then keeps the bundle at that path as the base and appends only what this live read adds, and in every case finishes it with the `## Comment inventory` section:
|
|
109
109
|
|
|
110
110
|
```text
|
|
111
111
|
tracker_provider: jira|github|linear
|
|
@@ -116,6 +116,7 @@ Otherwise:
|
|
|
116
116
|
gate_reason: <why a human must judge this first>|null
|
|
117
117
|
claim_outcome: claimed|reused|held-by-gate
|
|
118
118
|
binding_outcome: verified|skipped-human-gate
|
|
119
|
+
work_item_context: <absolute path to .lisa/work-item-context.md>
|
|
119
120
|
```
|
|
120
121
|
|
|
121
122
|
## Lifecycle
|
|
@@ -13,6 +13,10 @@ You work out how this change should be built before anyone writes it, and you sa
|
|
|
13
13
|
|
|
14
14
|
`codebase-research` carries the investigation method, `task-decomposition` the breakdown, `epic-triage` the larger-than-one-change case, and each carries its own output contract. Follow them; nothing is restated here.
|
|
15
15
|
|
|
16
|
+
## Work-item context
|
|
17
|
+
|
|
18
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
19
|
+
|
|
16
20
|
## What you decide
|
|
17
21
|
|
|
18
22
|
- **What already exists.** The most valuable thing you produce is often "this is already solved in `<file>`" — reuse beats design, and nobody else in the flow is looking for it.
|
|
@@ -11,6 +11,10 @@ skills:
|
|
|
11
11
|
|
|
12
12
|
You are a bug fix specialist. Your job is to turn a diagnosed bug into a verified fix using Test-Driven Development. The reproduction scenario becomes your failing test.
|
|
13
13
|
|
|
14
|
+
## Work-item context
|
|
15
|
+
|
|
16
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
17
|
+
|
|
14
18
|
## Prerequisites
|
|
15
19
|
|
|
16
20
|
You receive a diagnosed bug from the **Implement** flow (Fix work type) with:
|
|
@@ -11,6 +11,10 @@ skills:
|
|
|
11
11
|
|
|
12
12
|
You are a feature build specialist. Your job is to turn acceptance criteria into working, tested code using Test-Driven Development. Each acceptance criterion becomes a test.
|
|
13
13
|
|
|
14
|
+
## Work-item context
|
|
15
|
+
|
|
16
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
17
|
+
|
|
14
18
|
## Prerequisites
|
|
15
19
|
|
|
16
20
|
You receive a task from the **Implement** flow (Build or Improve work type) with:
|
|
@@ -12,6 +12,10 @@ You prove causes. A conclusion you have not executed against is a hypothesis, ho
|
|
|
12
12
|
|
|
13
13
|
Both procedures live in your skills — `reproduce-bug` for establishing the failure, `root-cause-analysis` for proving its cause, including the verdict vocabulary, the stopping rule, and both output contracts. Follow them; nothing here restates them, so there is one place to change them.
|
|
14
14
|
|
|
15
|
+
## Work-item context
|
|
16
|
+
|
|
17
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
18
|
+
|
|
15
19
|
## What you route
|
|
16
20
|
|
|
17
21
|
- **Which skill the work is in.** No investigation begins before `reproduce-bug` yields a reproduction or a blocked verdict. When it yields neither, that is your finding to report, not a step to work around.
|
|
@@ -11,6 +11,10 @@ You represent the person who will use this, and you write down what "working" me
|
|
|
11
11
|
|
|
12
12
|
`acceptance-criteria` carries the Gherkin conventions and the output contract. Follow it; nothing is restated here.
|
|
13
13
|
|
|
14
|
+
## Work-item context
|
|
15
|
+
|
|
16
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
17
|
+
|
|
14
18
|
## What you decide
|
|
15
19
|
|
|
16
20
|
- **What the user is actually trying to achieve**, as distinct from what the ticket asks for. Those differ often enough that naming the goal is most of your value.
|
|
@@ -11,6 +11,10 @@ You read the change the way the next person to touch it will, and you say plainl
|
|
|
11
11
|
|
|
12
12
|
`quality-review` carries the checklist, the severity bands, and the finding format. Follow it; nothing is restated here.
|
|
13
13
|
|
|
14
|
+
## Work-item context
|
|
15
|
+
|
|
16
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
17
|
+
|
|
14
18
|
## What you decide
|
|
15
19
|
|
|
16
20
|
- **Severity, honestly.** Everything marked critical means nothing is. Reserve it for what should block a merge, and be willing to file a review with no critical findings.
|
|
@@ -10,6 +10,10 @@ skills:
|
|
|
10
10
|
|
|
11
11
|
You are a spec conformance specialist. Your job is to prove that the shipped work matches its spec **exactly** — nothing more, nothing less.
|
|
12
12
|
|
|
13
|
+
## Work-item context
|
|
14
|
+
|
|
15
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
16
|
+
|
|
13
17
|
## Scope
|
|
14
18
|
|
|
15
19
|
You answer one question: **Does what shipped match what was asked?**
|
|
@@ -11,6 +11,10 @@ You decide what has to be true for this change to be trusted, and design the tes
|
|
|
11
11
|
|
|
12
12
|
`test-strategy` carries the matrix format, the coverage discipline, and the output contract. Follow it; nothing is restated here.
|
|
13
13
|
|
|
14
|
+
## Work-item context
|
|
15
|
+
|
|
16
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
17
|
+
|
|
14
18
|
## What you decide
|
|
15
19
|
|
|
16
20
|
- **What could break that nobody has asked about.** The acceptance criteria are the floor. Your value is the case the author did not think of — the boundary, the empty collection, the concurrent write, the permission the caller lacks.
|
|
@@ -14,6 +14,10 @@ You are a verification specialist. Your job is to **prove empirically** that wor
|
|
|
14
14
|
|
|
15
15
|
Read [`rules/verification-reference.mdc`](../rules/verification-reference.mdc) at the start of every investigation for the full verification framework, types, and lifecycle. Read [`rules/falsifiable-checks-reference.mdc`](../rules/falsifiable-checks-reference.mdc) alongside it: every check YOU author — probe, script, codified spec, sweep — is subject to it, and a check that has not been shown capable of failing is reported as *unvalidated*, never as passing. Read [`rules/claim-evidence-mapping-reference.mdc`](../rules/claim-evidence-mapping-reference.mdc) too: it binds every claim to the **boundary** it asserts and every boundary to the evidence **kinds** that reach it. The verdict you write is what `spec-conformance-specialist` cross-checks — record each claim's `boundary`, its `required_evidence_kinds`, its `evidence_refs`, and its `not_established` list so a boundary mismatch is catchable rather than invisible.
|
|
16
16
|
|
|
17
|
+
## Work-item context
|
|
18
|
+
|
|
19
|
+
When your task belongs to a tracked work item — it names `work_item_context` or a work-item ref — read the work-item context file in full before you plan, build, review, or verify anything. Use the absolute path your task gives as `work_item_context`; if it gives only the work-item ref, use `.lisa/work-item-context.md` at the root of the bound worktree. If the task concerns a tracker work item but names neither, check for `.lisa/work-item-context.md` at the bound worktree root, or for a bound work item via `node scripts/lisa-work-item.mjs current`; read the file if it is present. It is the verbatim tracker bundle for this work item — description, every comment, related items — saved by the input-resolver; a summary in your prompt indexes it but never stands in for it. Its trailing `## Comment inventory` section lists each comment with its flags. Treat each flagged comment as an obligation: a decision, constraint, credential or access note, or reproduction step that your work must honour and your report must account for. A secret quoted in a credential-flagged comment never leaves that file: cite it by what it is and which comment holds it, and keep the value or identifier out of code, commits, task notes, prompts, plan or roster files, tracker comments, and PR text. If a work item is named or bound and its context file is missing or unreadable, report that to the team lead and stop — never proceed from memory or a summary. Only a task whose source is a plan file, a PRD, a raw error, or a log, with no work item bound to the worktree, proceeds from the source the task supplies; no context file is expected there.
|
|
20
|
+
|
|
17
21
|
## Core Philosophy
|
|
18
22
|
|
|
19
23
|
**"If you didn't run it, you didn't verify it."** Code review is not verification. Reading a test file is not verification. **Running tests, typecheck, and lint is not verification either — those are quality gates (prerequisites).** Only executing the actual system and observing output counts as proof. Verification means making HTTP requests, clicking through the UI, running CLI commands, querying the database, or otherwise interacting with the running software as an end user would.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two rows `enforcement-vintage` could not show before: the Lisa version the
|
|
3
|
+
* PROJECT installed, and the newest Lisa PUBLISHED (CodySwannGT/lisa#4325).
|
|
4
|
+
*
|
|
5
|
+
* Why these two. The session-copy rows answer "is this session running the
|
|
6
|
+
* newest Lisa on this disk?" — and a whole host can be current by that measure
|
|
7
|
+
* while sitting many releases behind npm, because nothing on the disk is newer
|
|
8
|
+
* than the version the project last installed. A host only moves when something
|
|
9
|
+
* opens an update PR for it. The agent standing in that host is the party most
|
|
10
|
+
* able to notice and least likely to be told, so the comparison goes where the
|
|
11
|
+
* agent reads: the injected context.
|
|
12
|
+
*
|
|
13
|
+
* NO NETWORK ON THE STARTUP PATH. A session start must never wait on npm. The
|
|
14
|
+
* row is read from the cache the `lisa` CLI's own update check already writes
|
|
15
|
+
* (`node_modules/.cache/@codyswann/lisa/update-check.json`, same shape, same
|
|
16
|
+
* 6-hour freshness), so the two surfaces cannot disagree about "latest". When
|
|
17
|
+
* that cache is missing or stale, a DETACHED child refreshes it for the next
|
|
18
|
+
* session and this session reports what it has — a stale reading named as
|
|
19
|
+
* stale, never a guess.
|
|
20
|
+
*
|
|
21
|
+
* FAIL SOFT, ALWAYS. Every function here returns a neutral value on any error.
|
|
22
|
+
* @module plugins/src/base/hooks/enforcement-vintage-npm
|
|
23
|
+
*/
|
|
24
|
+
import { spawn } from "child_process";
|
|
25
|
+
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
26
|
+
import path from "path";
|
|
27
|
+
import { fileURLToPath } from "url";
|
|
28
|
+
|
|
29
|
+
/** The package whose versions are compared. */
|
|
30
|
+
export const LISA_PACKAGE = "@codyswann/lisa";
|
|
31
|
+
|
|
32
|
+
/** Same freshness window as the CLI update check (`src/cli/update-check.ts`). */
|
|
33
|
+
export const NPM_LATEST_TTL_MS = 6 * 60 * 60 * 1000;
|
|
34
|
+
|
|
35
|
+
/** Budget for the detached refresh; it runs after the session has started. */
|
|
36
|
+
const REFRESH_TIMEOUT_MS = 5000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Registry endpoint for the latest dist-tag.
|
|
40
|
+
*
|
|
41
|
+
* The cached mutable pointer lags a publish by minutes (CodySwannGT/lisa#3685).
|
|
42
|
+
* That is fine here for the same reason it is fine in the CLI nag: nothing
|
|
43
|
+
* downstream of this value chooses, publishes or gates — it is advice.
|
|
44
|
+
*/
|
|
45
|
+
const NPM_LATEST_URL = "https://registry.npmjs.org/@codyswann/lisa/latest";
|
|
46
|
+
|
|
47
|
+
/** A plain `x.y.z` release, optionally with a prerelease or build suffix. */
|
|
48
|
+
const VERSION_SHAPE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]+)?$/u;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compare two prerelease identifier lists by semver precedence.
|
|
52
|
+
* @param {string[]} a Identifiers of the first prerelease.
|
|
53
|
+
* @param {string[]} b Identifiers of the second prerelease.
|
|
54
|
+
* @returns {number} Negative when `a` precedes `b`, positive when it follows.
|
|
55
|
+
*/
|
|
56
|
+
function comparePrerelease(a, b) {
|
|
57
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
58
|
+
if (a[index] === undefined) return -1;
|
|
59
|
+
if (b[index] === undefined) return 1;
|
|
60
|
+
if (a[index] === b[index]) continue;
|
|
61
|
+
const numeric = /^\d+$/u;
|
|
62
|
+
if (numeric.test(a[index]) && numeric.test(b[index])) {
|
|
63
|
+
return Number(a[index]) - Number(b[index]);
|
|
64
|
+
}
|
|
65
|
+
if (numeric.test(a[index])) return -1;
|
|
66
|
+
if (numeric.test(b[index])) return 1;
|
|
67
|
+
return a[index] < b[index] ? -1 : 1;
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether version `a` has lower semver precedence than `b`.
|
|
74
|
+
*
|
|
75
|
+
* Prerelease-aware on purpose: a project pinned to `4.66.5-rc.1` is BEHIND a
|
|
76
|
+
* published `4.66.5`, and comparing release fields alone would call it current.
|
|
77
|
+
* Build metadata is ignored, as semver requires.
|
|
78
|
+
* @param {string} a Candidate older version.
|
|
79
|
+
* @param {string} b Candidate newer version.
|
|
80
|
+
* @returns {boolean} True when `a` precedes `b`.
|
|
81
|
+
*/
|
|
82
|
+
export function precedes(a, b) {
|
|
83
|
+
const split = version => {
|
|
84
|
+
const [core, pre] = String(version).split("+")[0].split(/-(.*)/su);
|
|
85
|
+
const fields = core.split(".").map(field => Number(field) || 0);
|
|
86
|
+
return { fields, pre: pre ? pre.split(".") : [] };
|
|
87
|
+
};
|
|
88
|
+
const left = split(a);
|
|
89
|
+
const right = split(b);
|
|
90
|
+
for (let index = 0; index < 3; index += 1) {
|
|
91
|
+
const l = left.fields[index] ?? 0;
|
|
92
|
+
const r = right.fields[index] ?? 0;
|
|
93
|
+
if (l !== r) return l < r;
|
|
94
|
+
}
|
|
95
|
+
if (left.pre.length === 0) return false;
|
|
96
|
+
if (right.pre.length === 0) return true;
|
|
97
|
+
return comparePrerelease(left.pre, right.pre) < 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse a JSON file, or return null.
|
|
102
|
+
* @param {string} file Absolute path.
|
|
103
|
+
* @returns {any} Parsed JSON, or null when unreadable or malformed.
|
|
104
|
+
*/
|
|
105
|
+
function readJson(file) {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The update-check cache path for a project — the CLI's own path.
|
|
115
|
+
* @param {string} projectDir Project root.
|
|
116
|
+
* @returns {string} Absolute cache path.
|
|
117
|
+
*/
|
|
118
|
+
export function npmLatestCachePath(projectDir) {
|
|
119
|
+
return path.join(
|
|
120
|
+
projectDir,
|
|
121
|
+
"node_modules",
|
|
122
|
+
".cache",
|
|
123
|
+
"@codyswann",
|
|
124
|
+
"lisa",
|
|
125
|
+
"update-check.json"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The Lisa version this project installed, or null when it has none to show.
|
|
131
|
+
*
|
|
132
|
+
* Null inside the Lisa monorepo itself: there `node_modules/@codyswann/lisa` is
|
|
133
|
+
* a fixture pinned majors behind the repository, and reporting it as the
|
|
134
|
+
* project's pin would tell every Lisa session its project is stale.
|
|
135
|
+
* @param {string} projectDir Project root.
|
|
136
|
+
* @returns {{version: string, source: string} | null} The installed copy.
|
|
137
|
+
*/
|
|
138
|
+
export function projectPin(projectDir) {
|
|
139
|
+
const own = readJson(path.join(projectDir, "package.json"));
|
|
140
|
+
if (own?.name === LISA_PACKAGE) return null;
|
|
141
|
+
const source = path.join(
|
|
142
|
+
projectDir,
|
|
143
|
+
"node_modules",
|
|
144
|
+
"@codyswann",
|
|
145
|
+
"lisa",
|
|
146
|
+
"package.json"
|
|
147
|
+
);
|
|
148
|
+
const version = readJson(source)?.version;
|
|
149
|
+
return typeof version === "string" && VERSION_SHAPE.test(version)
|
|
150
|
+
? { version, source }
|
|
151
|
+
: null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Read the cached npm latest.
|
|
156
|
+
* @param {string} cachePath Absolute cache path.
|
|
157
|
+
* @param {number} nowMs Current time in ms.
|
|
158
|
+
* @param {number} [ttlMs] Freshness window.
|
|
159
|
+
* @returns {{version: string, fetchedAt: string, fresh: boolean} | null} Cached value.
|
|
160
|
+
*/
|
|
161
|
+
export function cachedNpmLatest(cachePath, nowMs, ttlMs = NPM_LATEST_TTL_MS) {
|
|
162
|
+
const cache = readJson(cachePath);
|
|
163
|
+
const version = cache?.latest;
|
|
164
|
+
const fetchedAt = cache?.fetchedAt;
|
|
165
|
+
if (typeof version !== "string" || !VERSION_SHAPE.test(version)) return null;
|
|
166
|
+
if (typeof fetchedAt !== "string") return null;
|
|
167
|
+
const fetchedMs = Date.parse(fetchedAt);
|
|
168
|
+
if (!Number.isFinite(fetchedMs)) return null;
|
|
169
|
+
return { version, fetchedAt, fresh: nowMs - fetchedMs <= ttlMs };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Whether a refresh should be started for this session.
|
|
174
|
+
* @param {{version: string, fresh: boolean} | null} cached Cached value.
|
|
175
|
+
* @param {NodeJS.ProcessEnv} env Environment.
|
|
176
|
+
* @returns {boolean} True when the cache is missing or stale and checks are on.
|
|
177
|
+
*/
|
|
178
|
+
export function needsRefresh(cached, env) {
|
|
179
|
+
if (env.LISA_SKIP_UPDATE_CHECK === "1") return false;
|
|
180
|
+
return cached === null || !cached.fresh;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Start a detached refresh of the cache and return immediately.
|
|
185
|
+
* @param {string} script Absolute path of this module.
|
|
186
|
+
* @param {string} cachePath Absolute cache path.
|
|
187
|
+
* @param {typeof spawn} [spawnImpl] Injected for tests.
|
|
188
|
+
* @returns {boolean} Whether a child was started.
|
|
189
|
+
*/
|
|
190
|
+
export function startDetachedRefresh(script, cachePath, spawnImpl = spawn) {
|
|
191
|
+
try {
|
|
192
|
+
const child = spawnImpl(
|
|
193
|
+
process.execPath,
|
|
194
|
+
[script, "--refresh-npm-latest", cachePath],
|
|
195
|
+
{ detached: true, stdio: "ignore" }
|
|
196
|
+
);
|
|
197
|
+
child.on?.("error", () => {});
|
|
198
|
+
child.unref?.();
|
|
199
|
+
return true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Fetch npm latest and write it to the cache in the CLI's shape.
|
|
207
|
+
*
|
|
208
|
+
* probe-direction: neutral — a failed fetch writes nothing, so the next
|
|
209
|
+
* session reports "npm latest: unknown" (or the older cached value, labelled
|
|
210
|
+
* stale); no gate reads this value, it only shapes advisory context.
|
|
211
|
+
* @param {string} cachePath Absolute cache path.
|
|
212
|
+
* @param {{fetchImpl?: typeof fetch, now?: () => Date}} [deps] Injected for tests.
|
|
213
|
+
* @returns {Promise<string | null>} The version written, or null.
|
|
214
|
+
*/
|
|
215
|
+
export async function refreshNpmLatest(cachePath, deps = {}) {
|
|
216
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
217
|
+
const now = deps.now ?? (() => new Date());
|
|
218
|
+
try {
|
|
219
|
+
const response = await fetchImpl(NPM_LATEST_URL, {
|
|
220
|
+
signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS),
|
|
221
|
+
});
|
|
222
|
+
if (!response.ok) return null;
|
|
223
|
+
const body = await response.json();
|
|
224
|
+
const version = body?.version;
|
|
225
|
+
if (typeof version !== "string" || !VERSION_SHAPE.test(version)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
229
|
+
writeFileSync(
|
|
230
|
+
cachePath,
|
|
231
|
+
`${JSON.stringify({ latest: version, fetchedAt: now().toISOString() }, null, 2)}\n`,
|
|
232
|
+
"utf8"
|
|
233
|
+
);
|
|
234
|
+
return version;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Render the project and npm rows, plus the guidance when the project is behind.
|
|
242
|
+
* @param {{pin: {version: string, source: string} | null, latest: {version: string, fetchedAt: string, fresh: boolean} | null, refreshing: boolean, projectBehind: boolean}} state Resolved rows.
|
|
243
|
+
* @returns {{rows: string[], guidance: string[]}} Lines to splice into the block.
|
|
244
|
+
*/
|
|
245
|
+
export function renderNpmRows(state) {
|
|
246
|
+
const rows = [];
|
|
247
|
+
if (state.pin) {
|
|
248
|
+
rows.push(`project pin: lisa ${state.pin.version} at ${state.pin.source}`);
|
|
249
|
+
}
|
|
250
|
+
if (state.latest) {
|
|
251
|
+
rows.push(
|
|
252
|
+
`npm latest: lisa ${state.latest.version} (checked ${state.latest.fetchedAt}${state.latest.fresh ? "" : "; stale"}${state.refreshing ? ", refreshing for the next session" : ""})`
|
|
253
|
+
);
|
|
254
|
+
} else if (state.pin) {
|
|
255
|
+
rows.push(
|
|
256
|
+
`npm latest: unknown${state.refreshing ? " (checking in the background for the next session)" : ""}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const guidance = state.projectBehind
|
|
260
|
+
? [
|
|
261
|
+
`PROJECT BEHIND — this project installs lisa ${state.pin?.version} while lisa ${state.latest?.version} is published.`,
|
|
262
|
+
"- Do NOT upgrade Lisa inside the current task: a version bump plus its template apply belongs in its own pull request, never mixed into feature work.",
|
|
263
|
+
"- Check for an open pull request on a `lisa/update-*` branch. If none is open, the project's Lisa Update workflow (`.github/workflows/lisa-update.yml`) opens one on its schedule; say so in your report, and if that workflow is missing, recommend running `lisa apply` once so the project receives it.",
|
|
264
|
+
]
|
|
265
|
+
: [];
|
|
266
|
+
return { rows, guidance };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Resolve everything the npm rows need, starting a refresh when warranted.
|
|
271
|
+
* @param {{projectDir: string, script?: string, env: NodeJS.ProcessEnv, nowMs: number, spawnImpl?: typeof spawn}} input Inputs.
|
|
272
|
+
* @returns {{pin: {version: string, source: string} | null, latest: {version: string, fetchedAt: string, fresh: boolean} | null, refreshing: boolean, projectBehind: boolean}} Resolved rows.
|
|
273
|
+
*/
|
|
274
|
+
export function resolveNpmState(input) {
|
|
275
|
+
try {
|
|
276
|
+
const pin = projectPin(input.projectDir);
|
|
277
|
+
if (pin === null) {
|
|
278
|
+
return { pin, latest: null, refreshing: false, projectBehind: false };
|
|
279
|
+
}
|
|
280
|
+
const cachePath = npmLatestCachePath(input.projectDir);
|
|
281
|
+
const latest = cachedNpmLatest(cachePath, input.nowMs);
|
|
282
|
+
const refreshing =
|
|
283
|
+
needsRefresh(latest, input.env) &&
|
|
284
|
+
startDetachedRefresh(
|
|
285
|
+
input.script ?? fileURLToPath(import.meta.url),
|
|
286
|
+
cachePath,
|
|
287
|
+
input.spawnImpl
|
|
288
|
+
);
|
|
289
|
+
const projectBehind = Boolean(
|
|
290
|
+
latest && precedes(pin.version, latest.version)
|
|
291
|
+
);
|
|
292
|
+
return { pin, latest, refreshing, projectBehind };
|
|
293
|
+
} catch {
|
|
294
|
+
return { pin: null, latest: null, refreshing: false, projectBehind: false };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Whether this module is the process entry point (the detached refresh child).
|
|
300
|
+
* Realpaths both sides for the reason `enforcement-vintage.mjs` gives.
|
|
301
|
+
* @param {string} moduleUrl This module's `import.meta.url`.
|
|
302
|
+
* @param {string | undefined} [argv1] Entry path.
|
|
303
|
+
* @returns {boolean} True when run directly.
|
|
304
|
+
*/
|
|
305
|
+
function invokedDirectly(moduleUrl, argv1 = process.argv[1]) {
|
|
306
|
+
if (!argv1) return false;
|
|
307
|
+
try {
|
|
308
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(moduleUrl));
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (
|
|
315
|
+
invokedDirectly(import.meta.url) &&
|
|
316
|
+
process.argv[2] === "--refresh-npm-latest" &&
|
|
317
|
+
process.argv[3]
|
|
318
|
+
) {
|
|
319
|
+
refreshNpmLatest(process.argv[3]).catch(() => {});
|
|
320
|
+
}
|
|
@@ -52,6 +52,7 @@ import { readFileSync, existsSync } from "fs";
|
|
|
52
52
|
import path from "path";
|
|
53
53
|
import { fileURLToPath } from "url";
|
|
54
54
|
import { realpathSync } from "fs";
|
|
55
|
+
import { renderNpmRows, resolveNpmState } from "./enforcement-vintage-npm.mjs";
|
|
55
56
|
|
|
56
57
|
/** Marker the block is wrapped in, so an agent can cite it by name. */
|
|
57
58
|
const BLOCK_TAG = "lisa-enforcement-vintage";
|
|
@@ -336,6 +337,15 @@ export function renderBlock(state) {
|
|
|
336
337
|
`newest on this disk: lisa ${state.newest.version} at ${state.newest.source}`
|
|
337
338
|
);
|
|
338
339
|
}
|
|
340
|
+
const npm = renderNpmRows(
|
|
341
|
+
state.npm ?? {
|
|
342
|
+
pin: null,
|
|
343
|
+
latest: null,
|
|
344
|
+
refreshing: false,
|
|
345
|
+
projectBehind: false,
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
npm.rows.forEach(row => lines.push(row));
|
|
339
349
|
lines.push("");
|
|
340
350
|
if (state.behind || !state.running.version || state.treeBehind) {
|
|
341
351
|
lines.push(
|
|
@@ -363,6 +373,10 @@ export function renderBlock(state) {
|
|
|
363
373
|
"when you report on guard or contract behaviour; it is only current until the next release."
|
|
364
374
|
);
|
|
365
375
|
}
|
|
376
|
+
if (npm.guidance.length > 0) {
|
|
377
|
+
lines.push("");
|
|
378
|
+
npm.guidance.forEach(line => lines.push(line));
|
|
379
|
+
}
|
|
366
380
|
lines.push(`</${BLOCK_TAG}>`);
|
|
367
381
|
return lines.join("\n");
|
|
368
382
|
}
|
|
@@ -405,9 +419,14 @@ export function run(argv, stdin, env = process.env) {
|
|
|
405
419
|
return JSON.stringify({
|
|
406
420
|
hookSpecificOutput: {
|
|
407
421
|
hookEventName: eventName(stdin),
|
|
408
|
-
additionalContext: renderBlock(
|
|
409
|
-
resolveState({ hooksDir, projectDir, configDir })
|
|
410
|
-
|
|
422
|
+
additionalContext: renderBlock({
|
|
423
|
+
...resolveState({ hooksDir, projectDir, configDir }),
|
|
424
|
+
npm: resolveNpmState({
|
|
425
|
+
projectDir,
|
|
426
|
+
env,
|
|
427
|
+
nowMs: Date.now(),
|
|
428
|
+
}),
|
|
429
|
+
}),
|
|
411
430
|
},
|
|
412
431
|
});
|
|
413
432
|
}
|
|
@@ -367,6 +367,7 @@ Substrate column meanings (ordering per `credential-substrate-precedence`):
|
|
|
367
367
|
| `transition key:<K> to:<S>` | guarded fallback only: `acli jira workitem transition --key <K> --status "<S>" --yes` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__transitionJiraIssue` | resolve transition id then `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/transitions` |
|
|
368
368
|
| `transitions key:<K>` — **false friend:** available transitions from current status, **NOT** past history; for history use `changelog` | (not exposed) | `mcp__plugin_atlassian_atlassian__getTransitionsForJiraIssue` | `GET https://<SITE>/rest/api/3/issue/<K>/transitions` |
|
|
369
369
|
| `changelog key:<K>` (read; ordered past status transitions) | (not exposed) | (not exposed) | `GET https://<SITE>/rest/api/3/issue/<K>?expand=changelog` |
|
|
370
|
+
| `comments key:<K>` (read; all comments, paginated) — **not** `comment`, which is the write | (not exposed) | `mcp__plugin_atlassian_atlassian__getJiraIssue` (comment field only; may be capped — page via curl when `total` exceeds what it returned) | `GET https://<SITE>/rest/api/3/issue/<K>/comment?startAt=<n>&maxResults=100&orderBy=created` |
|
|
370
371
|
| `comment key:<K> body:<B>` | guarded fallback only: `acli jira workitem comment add --key <K> --body "<B>"` + post-read tenant assertion | `mcp__plugin_atlassian_atlassian__addCommentToJiraIssue` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issue/<K>/comment` |
|
|
371
372
|
| `link from:<K> to:<K2> type:<T>` | guarded fallback only: `acli jira workitem link create --in <K> --out <K2> --type "<T>" --yes` + direction and tenant assertion (see direction note) | `mcp__plugin_atlassian_atlassian__createJiraIssueLink` | `POST https://api.atlassian.com/ex/jira/<CLOUDID>/rest/api/3/issueLink` |
|
|
372
373
|
| `remote-links key:<K>` | (not exposed) | `mcp__plugin_atlassian_atlassian__getJiraIssueRemoteIssueLinks` | `GET https://<SITE>/rest/api/3/issue/<K>/remotelink` |
|
|
@@ -423,6 +424,17 @@ Operations not in this table are unsupported — add an adapter row before using
|
|
|
423
424
|
- **Pagination / truncation.** The issue-resource changelog (`?expand=changelog`) truncates busy issues (`changelog.maxResults`/`total`/`startAt`). When `total` exceeds what the issue resource returned, page the dedicated endpoint `GET https://<SITE>/rest/api/3/issue/<K>/changelog?startAt=<n>` until `startAt + maxResults >= total`, preserving order across pages. A silently truncated history is a correctness bug for detection.
|
|
424
425
|
- **Graceful degrade — never block the build.** A failed changelog fetch (network, auth, missing substrate) returns the substrate contract's `Error:` result. Callers MUST treat that as **unknown** history and proceed — a history read failure never blocks the build.
|
|
425
426
|
|
|
427
|
+
### `comments` — every comment on one issue
|
|
428
|
+
|
|
429
|
+
`comments key:<K>` returns every comment on a JIRA issue, oldest first. `read-ticket` embeds only the first page of comments in the issue resource (`fields.comment` carries its own `startAt`/`maxResults`/`total`), so a busy ticket's later comments — often the decisions and constraints — are missing from it.
|
|
430
|
+
|
|
431
|
+
- **Substrate.** JIRA REST `GET https://<SITE>/rest/api/3/issue/<K>/comment?startAt=<n>&maxResults=100&orderBy=created` (a read, so the `<SITE>` gateway is allowed after the token account check). acli exposes no paginated comment list; the MCP `getJiraIssue` comment field is used only when no token is available and is subject to the same first-page cap.
|
|
432
|
+
- **Shape.** For each entry in `comments[]` emit `{ id, author, created, body }` — `author.displayName`/`accountId`, `created` (ISO timestamp), and `body` converted from ADF to plain text with headings, lists, code, and links preserved.
|
|
433
|
+
- **Pagination.** Start at `startAt=0` and request the next page until `startAt + maxResults >= total`, preserving order across pages. Never stop at the first page.
|
|
434
|
+
- **Empty is valid.** An issue with no comments returns `total: 0`; that is a result, not an error.
|
|
435
|
+
- **Completeness fields.** Every result carries `comments_complete`, `comments_fetched`, and `comments_total`; `comments_complete: true` only when `comments_fetched == comments_total`. When only the MCP substrate is available and it cannot page past its first batch, return what it gave with `comments_complete: false` and the fetched-versus-total counts — never present a capped set as the whole.
|
|
436
|
+
- **Graceful degrade.** A failed page (network, auth, missing substrate) returns the substrate contract's `Error:` result for that page. Callers record the issue's comments as **incomplete** — naming how many of `total` were read — so a partial set is never silently truncated into looking complete.
|
|
437
|
+
|
|
426
438
|
### Step 4 — Return result
|
|
427
439
|
|
|
428
440
|
Emit either:
|