@codyswann/lisa 2.71.0 → 2.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/skills/confluence-write-prd/SKILL.md +8 -2
  5. package/plugins/lisa/skills/github-write-issue/SKILL.md +3 -1
  6. package/plugins/lisa/skills/github-write-prd/SKILL.md +10 -5
  7. package/plugins/lisa/skills/jira-write-ticket/SKILL.md +3 -1
  8. package/plugins/lisa/skills/linear-write-issue/SKILL.md +3 -1
  9. package/plugins/lisa/skills/linear-write-prd/SKILL.md +11 -5
  10. package/plugins/lisa/skills/notion-write-prd/SKILL.md +5 -3
  11. package/plugins/lisa/skills/prd-source-write/SKILL.md +5 -1
  12. package/plugins/lisa/skills/tracker-write/SKILL.md +9 -1
  13. package/plugins/lisa/skills/usage-accounting/SKILL.md +170 -0
  14. package/plugins/lisa/skills/usage-accounting/agents/openai.yaml +4 -0
  15. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  23. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  31. package/plugins/src/base/skills/confluence-write-prd/SKILL.md +8 -2
  32. package/plugins/src/base/skills/github-write-issue/SKILL.md +3 -1
  33. package/plugins/src/base/skills/github-write-prd/SKILL.md +10 -5
  34. package/plugins/src/base/skills/jira-write-ticket/SKILL.md +3 -1
  35. package/plugins/src/base/skills/linear-write-issue/SKILL.md +3 -1
  36. package/plugins/src/base/skills/linear-write-prd/SKILL.md +11 -5
  37. package/plugins/src/base/skills/notion-write-prd/SKILL.md +5 -3
  38. package/plugins/src/base/skills/prd-source-write/SKILL.md +5 -1
  39. package/plugins/src/base/skills/tracker-write/SKILL.md +9 -1
  40. package/plugins/src/base/skills/usage-accounting/SKILL.md +170 -0
package/package.json CHANGED
@@ -82,7 +82,7 @@
82
82
  "lodash": ">=4.18.1"
83
83
  },
84
84
  "name": "@codyswann/lisa",
85
- "version": "2.71.0",
85
+ "version": "2.73.0",
86
86
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
87
87
  "main": "dist/index.js",
88
88
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -65,7 +65,10 @@ else → **create**.
65
65
  format** (XHTML): `#`/`##`/`###` → `<h1>/<h2>/<h3>`, paragraphs → `<p>`, lists → `<ul>/<ol><li>`,
66
66
  fenced code → `<ac:structured-macro ac:name="code">`. Embed the marker as a storage comment
67
67
  (`<!-- $MARKER -->`) or a small `<p>` so future CQL dedupe finds it. The body must always contain
68
- **exactly one** marker; never write a markerless page (CREATE or UPDATE).
68
+ **exactly one** marker; never write a markerless page (CREATE or UPDATE). If the live page body
69
+ already contains the canonical managed `## Lisa Usage` section, preserve it verbatim unless the
70
+ caller intentionally supplied an updated canonical section; use the shared `usage-accounting`
71
+ serializer/merge path rather than hand-editing ledger rows in storage XHTML.
69
72
 
70
73
  **CREATE:** `lisa:atlassian-access` `operation: write-page` (create form) with a payload that sets:
71
74
  - `title`: `$TITLE`
@@ -77,7 +80,8 @@ fenced code → `<ac:structured-macro ac:name="code">`. Embed the marker as a st
77
80
  1. **GET-then-PUT** (load-bearing, as `confluence-prd-intake` documents): first `operation: read-page
78
81
  id: <page-id>` to read the current `version.number`, then `operation: write-page` (edit form) with
79
82
  the marker-normalized storage body and `version.number` bumped to current+1. A PUT without the
80
- current version is rejected; never drop the marker on the edit.
83
+ current version is rejected; never drop the marker or an existing managed `## Lisa Usage` section
84
+ on the edit.
81
85
  2. Re-parent to the target lifecycle parent if the role changed — **unless** the page's current
82
86
  parent is in the resolved `${PROGRESSED_PARENTS[@]}` set (already past `ready`). If so, leave it
83
87
  and report `reused (already past ready)`. Reverse-lookup the current parent in
@@ -99,5 +103,7 @@ outcome: created | reused
99
103
  - State is the parent page, not a label — never attempt Confluence label writes (they 401 on scoped
100
104
  tokens; see `config-resolution`).
101
105
  - Match dedupe by marker, never by title.
106
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
107
+ section or silently drop ledger rows.
102
108
  - Never re-parent a PRD already past `ready` down to draft/ready.
103
109
  - Resolve parents from config (`confluence.parents.{draft,ready}`) — never hardcode page ids.
@@ -276,7 +276,7 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
276
276
 
277
277
  ### UPDATE
278
278
 
279
- 1. Re-read the current body via `gh issue view <number> --repo <org>/<repo> --json body --jq '.body'`. Edit only the sections being changed; preserve everything else verbatim.
279
+ 1. Re-read the current body via `gh issue view <number> --repo <org>/<repo> --json body --jq '.body'`. Edit only the sections being changed; preserve everything else verbatim, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
280
280
  2. Apply the edit:
281
281
  ```bash
282
282
  gh issue edit <number> --repo <org>/<repo> --body-file /tmp/updated-body.md
@@ -331,6 +331,8 @@ The mapping below is the single source of truth for how JIRA concepts translate
331
331
  - Never create a Bug, Task, or Sub-task whose AC references work in a different repo. GitHub Issues already live in one repo; reject AC bullets that span others — split into per-repo issues under a shared Epic.
332
332
  - Never include a runtime-behavior issue without a target backend environment, and never include an authenticated-surface issue without sign-in credentials.
333
333
  - Never overwrite an issue body without reading the current version first.
334
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
335
+ section or silently drop ledger rows.
334
336
  - All writes go through this skill (or the `tracker-write` shim). Other vendor-neutral skills must NEVER call `gh issue create` directly.
335
337
  - The gate logic lives in `lisa:github-validate-issue`, NOT here. This skill calls the validator at Phase 5.5 and Phase 7. When a gate needs to change, change it in `lisa:github-validate-issue`.
336
338
  - Never bypass the sub-issue mutation by encoding the parent only in the body. The native sub-issue link is what `lisa:github-read-issue` and the GitHub UI use to render the hierarchy.
@@ -55,10 +55,13 @@ EXISTING=$(gh issue list --repo "$ORG/$REPO" --state open --search "\"$MARKER\"
55
55
 
56
56
  ## Phase 3 — Create or update
57
57
 
58
- **Marker normalization (both paths).** Before writing any body, ensure it contains **exactly one**
59
- marker line — inject `<!-- $MARKER -->` if the caller's synthesized body doesn't already carry it.
60
- **Never write a markerless body** (including on UPDATE or when `source_ref` is passed): a body without
61
- the marker breaks future dedupe. If the body already has the marker, leave the single instance.
58
+ **Marker + usage-ledger preservation (both paths).** Before writing any body, ensure it contains
59
+ **exactly one** marker line — inject `<!-- $MARKER -->` if the caller's synthesized body doesn't
60
+ already carry it. **Never write a markerless body** (including on UPDATE or when `source_ref` is
61
+ passed): a body without the marker breaks future dedupe. If the body already has the marker, leave
62
+ the single instance. If the live issue body already contains the canonical managed `## Lisa Usage`
63
+ section, preserve it verbatim unless the caller intentionally supplied an updated canonical section;
64
+ use the shared `usage-accounting` serializer/merge path rather than hand-editing ledger rows.
62
65
 
63
66
  **CREATE** (no existing issue):
64
67
 
@@ -77,7 +80,7 @@ the marker breaks future dedupe. If the body already has the marker, leave the s
77
80
  **UPDATE** (existing issue or `source_ref`):
78
81
 
79
82
  1. `gh issue edit <n> --repo "$ORG/$REPO" --body-file /tmp/prd-body.md` with the **marker-normalized**
80
- body (regenerate in place; never drop the marker).
83
+ body (regenerate in place; never drop the marker or an existing managed `## Lisa Usage` section).
81
84
  2. Reconcile the lifecycle label to **exactly one**: add `$ROLE_LABEL`, remove every other label in
82
85
  the resolved `${ALL_PRD_LABELS[@]}` set (the config-resolved names — not a hard-coded list) via
83
86
  `gh issue edit <n> --add-label / --remove-label`. Never leave a PRD carrying two lifecycle labels.
@@ -104,6 +107,8 @@ outcome: created | reused
104
107
 
105
108
  - Exactly one PRD lifecycle label at all times (leaf-only does not apply — PRDs are not build leaves).
106
109
  - Match dedupe by marker, never by title.
110
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
111
+ section or silently drop ledger rows.
107
112
  - Never down-rank a PRD already past `ready`.
108
113
  - A *closed* prior PRD does not suppress a new one — a recurrence after closure is a genuine new PRD.
109
114
  - This is a source-side writer (`prd-*` labels). It never touches build labels (`status:*`) — that is
@@ -235,7 +235,7 @@ If the validator reports `PASS`, continue to Phase 6.
235
235
 
236
236
  1. Invoke `lisa:atlassian-access` with `operation: write-ticket payload: {key: <K>, ...fields-being-changed}`. Do NOT resend fields that weren't in the change set — it blows away history.
237
237
  2. Add new relationships via `lisa:atlassian-access` `operation: link from: <K1> to: <K2> type: "<link-type>"`. Existing links are not touched unless explicitly removed.
238
- 3. If description changes, preserve sections you are not editing. Re-read via `/jira-read-ticket` first.
238
+ 3. If description changes, preserve sections you are not editing. Re-read via `/jira-read-ticket` first, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
239
239
 
240
240
  ## Phase 7 — Verify
241
241
 
@@ -259,5 +259,7 @@ Skip this step only on UPDATE when no material change was made.
259
259
  - Never include a runtime-behavior ticket without a target backend environment, and never include an authenticated-surface ticket without sign-in credentials in the description.
260
260
  - Never invent custom field values. If the project requires a field you don't have, stop and ask.
261
261
  - Never overwrite a description without reading the current version first.
262
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
263
+ section or silently drop ledger rows.
262
264
  - All writes go through this skill so best practices are enforced uniformly. Downstream skills (e.g. `lisa:jira-create`) should delegate here rather than invoking `lisa:atlassian-access` write operations directly.
263
265
  - The gate logic (what makes a valid ticket) lives in `lisa:jira-validate-ticket`, NOT in this skill. This skill calls the validator at Phase 5.5 (pre-write) and Phase 7 (via `lisa:jira-verify` post-write). When a gate needs to change, change it in `lisa:jira-validate-ticket` — every caller (write path, dry-run path, post-write verify) picks it up automatically.
@@ -260,7 +260,7 @@ If the validator reports `PASS`, continue to Phase 6.
260
260
  ### UPDATE
261
261
 
262
262
  1. Call `mcp__linear-server__save_project` or `mcp__linear-server__save_issue` with **only the fields being changed**. Do NOT resend fields that weren't in the change set — Linear treats the call as a full overwrite of the listed fields.
263
- 2. Preserve description sections you are not editing — re-read via `/linear-read-issue` first.
263
+ 2. Preserve description sections you are not editing — re-read via `/linear-read-issue` first, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
264
264
 
265
265
  ## Phase 7 — Verify
266
266
 
@@ -285,6 +285,8 @@ Skip this step only on UPDATE when no material change was made.
285
285
  - Never include a runtime-behavior item without a target backend environment, and never include an authenticated-surface item without sign-in credentials in the description.
286
286
  - Never invent custom field values. If the team requires a field you don't have, stop and ask.
287
287
  - Never overwrite a description without reading the current version first.
288
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
289
+ section or silently drop ledger rows.
288
290
  - All Linear writes go through this skill so best practices are enforced uniformly. Downstream skills (e.g. `lisa:linear-create`) should delegate here rather than calling the MCP write tools directly.
289
291
  - The gate logic (what makes a valid item) lives in `lisa:linear-validate-issue`, NOT in this skill. This skill calls the validator at Phase 5.5 (pre-write) and Phase 7 (via `lisa:linear-verify` post-write). When a gate needs to change, change it in `lisa:linear-validate-issue` — every caller picks it up automatically.
290
292
  - This skill is the destination of the `lisa:tracker-write` shim when `tracker = "linear"`. Vendor-neutral callers (`notion-to-tracker`, `confluence-to-tracker`, `linear-to-tracker`, `github-to-tracker`) MUST go through `lisa:tracker-write`, not call this skill directly.
@@ -52,9 +52,12 @@ marker, **never** the project name:
52
52
 
53
53
  ## Phase 3 — Create or update
54
54
 
55
- **Marker normalization (both paths).** Before writing the `description`, ensure it contains
56
- **exactly one** marker line — inject the marker if the synthesized description lacks it. **Never write
57
- a markerless description** (including UPDATE / `source_ref`): that breaks future dedupe.
55
+ **Marker + usage-ledger preservation (both paths).** Before writing the `description`, ensure it
56
+ contains **exactly one** marker line — inject the marker if the synthesized description lacks it.
57
+ **Never write a markerless description** (including UPDATE / `source_ref`): that breaks future
58
+ dedupe. If the live Project description already contains the canonical managed `## Lisa Usage`
59
+ section, preserve it verbatim unless the caller intentionally supplied an updated canonical section;
60
+ use the shared `usage-accounting` serializer/merge path rather than hand-editing ledger rows.
58
61
 
59
62
  **CREATE:** `mcp__linear-server__save_project` with:
60
63
  - `name`: `$TITLE`
@@ -64,8 +67,9 @@ a markerless description** (including UPDATE / `source_ref`): that breaks future
64
67
  - `state`: Linear Project default (e.g. `backlog`)
65
68
 
66
69
  **UPDATE** (existing project or `source_ref`): `save_project` with the project id and **only** the
67
- changed fields — regenerate the marker-normalized `description`, and reconcile labels to **exactly
68
- one** PRD lifecycle label: add the role label, remove every other label in the resolved
70
+ changed fields — regenerate the marker-normalized `description` without dropping the managed
71
+ `## Lisa Usage` section, and reconcile labels to **exactly one** PRD lifecycle label: add the role
72
+ label, remove every other label in the resolved
69
73
  `${ALL_PRD_LABELS[@]}` set (config-resolved names, not a hard-coded list). Do **not** down-rank a
70
74
  Project whose current label is in the resolved `${PROGRESSED[@]}` set (already past `ready`) — leave
71
75
  it and report `reused (already past ready)`.
@@ -84,6 +88,8 @@ outcome: created | reused
84
88
 
85
89
  - Exactly one PRD lifecycle project-label at all times.
86
90
  - Match dedupe by marker, never by project name.
91
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
92
+ section or silently drop ledger rows.
87
93
  - Never down-rank a Project already past `ready`.
88
94
  - Source-side writer (`prd-*` project labels) — never touches issue-level build labels (`status:*`),
89
95
  which are `lisa:linear-write-issue`'s lane (see `config-resolution` self-host separation).
@@ -60,8 +60,8 @@ exceeds 100 blocks, create the page with the first ≤100 blocks then add the re
60
60
  accept the markdown content directly (it performs this conversion) — prefer that; the explicit block
61
61
  conversion is the curl-substrate path.
62
62
 
63
- **Marker normalization (both paths).** The page must always carry **exactly one** marker. On CREATE
64
- the marker is the first body block; on UPDATE never remove it. Never write a markerless page.
63
+ **Marker + usage-ledger preservation (both paths).** The page must always carry **exactly one**
64
+ marker. On CREATE the marker is the first body block; on UPDATE never remove it. Never write a markerless body. Never write a markerless page. If the existing page content already contains the canonical managed `## Lisa Usage` section, preserve that section when regenerating the page body unless the caller intentionally supplied an updated canonical section; use the shared `usage-accounting` serializer/merge path rather than freehand block edits to ledger rows.
65
65
 
66
66
  **CREATE:**
67
67
 
@@ -86,7 +86,7 @@ the marker is the first body block; on UPDATE never remove it. Never write a mar
86
86
  (`operation: archive-page` is page-level, so for blocks delete via the blocks API through
87
87
  `notion-access` or, where block deletion isn't available, replace their text in place) and
88
88
  `operation: append-blocks` the regenerated blocks. Do not duplicate the whole spec as a dated
89
- note, and never drop the marker.
89
+ note, and never drop the marker or an existing managed `## Lisa Usage` section.
90
90
 
91
91
  ## Phase 4 — Return
92
92
 
@@ -102,6 +102,8 @@ outcome: created | reused
102
102
 
103
103
  - All access via `lisa:notion-access`; never touch the Notion API/MCP directly.
104
104
  - Match dedupe by marker, never by title.
105
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
106
+ section or silently drop ledger rows.
105
107
  - Never down-rank a PRD whose Status is already past `ready`.
106
108
  - Resolve the Status vocabulary from config (`notion.statusProperty`, `notion.values.*`) — never
107
109
  hardcode value names.
@@ -8,7 +8,9 @@ allowed-tools: ["Skill", "Bash", "Read"]
8
8
 
9
9
  Thin dispatcher. Resolves the configured PRD `source` and delegates to the matching vendor PRD
10
10
  writer, which owns the concrete create/update, the lifecycle-role application, and the marker-based
11
- dedupe. This skill only routes it never talks to a source API itself.
11
+ dedupe. When the supplied PRD body already contains the canonical `## Lisa Usage` ledger, the
12
+ vendor writer must preserve that managed section on update instead of dropping it or reformatting it
13
+ ad hoc. This skill only routes — it never talks to a source API itself.
12
14
 
13
15
  See the `config-resolution` rule for the full configuration schema and the PRD lifecycle roles.
14
16
 
@@ -74,6 +76,8 @@ prior PRD-source-write behavior to preserve, so omitted means `draft`.
74
76
 
75
77
  - Never bypass dispatch — a vendor-neutral caller calling a `*-write-prd` skill directly defeats the
76
78
  per-project source switch (exactly the `tracker-write` discipline, mirrored).
79
+ - Never drop or duplicate an existing managed `## Lisa Usage` section. Writer-specific preservation
80
+ and fallback behavior belongs in the vendor writers and follows the `usage-accounting` contract.
77
81
  - Never accept a source outside `{notion, confluence, github, linear}`. `jira` and `file` fail loudly.
78
82
  - Never mutate the spec between layers. The vendor writers define their own create/dedupe contract.
79
83
  - Never invent a PRD lifecycle role string — resolve every role from `config-resolution` per vendor.
@@ -6,7 +6,12 @@ allowed-tools: ["Skill", "Bash", "Read"]
6
6
 
7
7
  # Tracker Write: $ARGUMENTS
8
8
 
9
- Thin dispatcher. Resolves the configured destination tracker and delegates to the matching vendor write skill.
9
+ Thin dispatcher. Resolves the configured destination tracker and delegates to the matching vendor
10
+ write skill.
11
+
12
+ When the incoming description/body already contains the canonical `## Lisa Usage` ledger, the vendor
13
+ writer must preserve that managed section on update and use the `usage-accounting` contract for any
14
+ body-vs-comment fallback. This shim only routes — it never edits tracker artifacts itself.
10
15
 
11
16
  See the `config-resolution` rule for the full configuration schema and skill-mapping table.
12
17
 
@@ -40,6 +45,9 @@ See the `config-resolution` rule for the full configuration schema and skill-map
40
45
  ## Rules
41
46
 
42
47
  - Never bypass dispatch — calling the vendor skill directly from a vendor-neutral caller defeats the per-project switch.
48
+ - Never drop, duplicate, or hand-rewrite an existing managed `## Lisa Usage` section in this shim.
49
+ Writer-specific preservation logic belongs in the vendor writers and follows the
50
+ `usage-accounting` contract.
43
51
  - Never accept a tracker value outside `{jira, github, linear}`.
44
52
  - Never mutate `$ARGUMENTS` between layers. The vendor skills define their own input contract.
45
53
  - Never inline gate logic here. All validation rules live in the vendor skills (`lisa:jira-validate-ticket` / `lisa:github-validate-issue` / `lisa:linear-validate-issue`); this skill only routes.
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: usage-accounting
3
+ description: "Shared usage-ledger utility for Lisa lifecycle flows and artifact writers. Delegates all direct-entry recording and rollup refreshes through one vendor-neutral contract so PRDs, tickets, evidence comments, PRs, and markdown artifacts preserve the canonical `## Lisa Usage` section instead of inventing per-flow formats."
4
+ allowed-tools: ["Skill", "Read", "Bash"]
5
+ ---
6
+
7
+ # Usage Accounting: $ARGUMENTS
8
+
9
+ Single chokepoint for Lisa usage-ledger writes. Caller skills (`research`, `plan`,
10
+ `implement`, `verify`, `debrief`, `intake`, `prd-source-write`, `tracker-write`,
11
+ `tracker-evidence`, later rollup/monitor flows) MUST delegate usage-entry writes and
12
+ rollup refreshes through this skill rather than hand-editing artifact bodies or comments.
13
+
14
+ This skill does not define the ledger format itself. The durable schema, token order,
15
+ rewrite invariants, and rollup semantics live in the `usage-accounting` rule. This skill
16
+ applies that contract to real artifacts and chooses the safest writable surface.
17
+
18
+ ## Invocation contract
19
+
20
+ The caller passes exactly one operation plus its arguments:
21
+
22
+ ```text
23
+ operation: record
24
+ artifact_ref: github:CodySwannGT/lisa#729
25
+ artifact_kind: github-issue
26
+ entry: { ...LisaUsageEntry fields... }
27
+
28
+ operation: rollup
29
+ artifact_ref: github:CodySwannGT/lisa#724
30
+ artifact_kind: github-issue
31
+ child_refs:
32
+ - github:CodySwannGT/lisa#729
33
+ - github:CodySwannGT/lisa#730
34
+
35
+ operation: record_and_rollup
36
+ artifact_ref: github:CodySwannGT/lisa#724
37
+ artifact_kind: github-issue
38
+ entry: { ...LisaUsageEntry fields... }
39
+ child_refs:
40
+ - github:CodySwannGT/lisa#729
41
+ ```
42
+
43
+ Required arguments:
44
+
45
+ - `operation`: one of `record`, `rollup`, or `record_and_rollup`.
46
+ - `artifact_ref`: canonical artifact identifier for the target being updated.
47
+ - `artifact_kind`: the writable host surface (`github-issue`, `github-pr-comment`, `jira-ticket`,
48
+ `linear-issue`, `notion-page`, `confluence-page`, `markdown-file`, or another caller-defined
49
+ host string with a matching read/write adapter).
50
+
51
+ Operation-specific arguments:
52
+
53
+ - `record` requires `entry`.
54
+ - `rollup` requires `child_refs` (empty is allowed when the caller wants a direct-only refresh).
55
+ - `record_and_rollup` requires `entry` and may also pass `child_refs`.
56
+
57
+ Optional arguments:
58
+
59
+ - `attachment_preference`: `body-first` (default) or `comment-only`.
60
+ - `comment_ref`: existing managed comment identifier when the ledger already lives in a comment.
61
+ - `parent_artifact_ref`: explicit parent for callers that need to anchor descendant rollups.
62
+ - `existing_body`: caller-supplied body snapshot when it already owns a fresh read.
63
+
64
+ The `entry` payload MUST satisfy the canonical usage-entry contract from the rule: stable
65
+ `entry_id`, `flow`, `run_id`, provider/model, source semantics, token fields, pricing fields,
66
+ and artifact refs. Callers do not get to omit the "unavailable" case; when trustworthy usage is
67
+ missing they still pass an explicit entry with `source: unavailable` and nullable token/cost
68
+ fields.
69
+
70
+ ## Return shape
71
+
72
+ Return structured output so callers can persist or log what happened without reparsing prose:
73
+
74
+ ```yaml
75
+ outcome: updated | comment-fallback | no-op | blocked
76
+ artifact_ref: "github:CodySwannGT/lisa#729"
77
+ surface:
78
+ kind: body | comment
79
+ ref: "<artifact or comment identifier>"
80
+ entry_ids:
81
+ direct:
82
+ - "<entry-id>"
83
+ child:
84
+ - "<rolled-up-child-entry-id>"
85
+ rollup:
86
+ direct_tokens: 1200
87
+ child_tokens: 450
88
+ total_tokens: 1650
89
+ direct_cost: 0.42
90
+ child_cost: 0.17
91
+ total_cost: 0.59
92
+ warnings:
93
+ - "<warning text>"
94
+ error:
95
+ code: "<stable-code>"
96
+ message: "<exact failure text>"
97
+ remediation: "<next step>"
98
+ ```
99
+
100
+ - `updated` means the preferred writable surface was updated successfully.
101
+ - `comment-fallback` means body/description edits were unsafe, so the canonical ledger landed in a
102
+ managed comment instead.
103
+ - `no-op` means the requested operation produced byte-identical ledger output.
104
+ - `blocked` means the caller asked for a write that could not be completed; preserve the exact host
105
+ failure text.
106
+
107
+ ## Workflow
108
+
109
+ ### Step 1 — Read the current managed surface
110
+
111
+ 1. Resolve the target artifact from `artifact_ref` / `artifact_kind`.
112
+ 2. Prefer a caller-supplied `existing_body` when present and trustworthy for this write.
113
+ 3. Otherwise read the current body/description from the host's native adapter.
114
+ 4. If the caller passed `comment_ref`, read that managed comment body too.
115
+
116
+ Never guess the prior ledger state. Always start from the current managed body/comment so rewrite
117
+ idempotency is preserved.
118
+
119
+ ### Step 2 — Choose the writable surface
120
+
121
+ Default policy is **body first**:
122
+
123
+ - If the host body/description is writable by the caller's normal writer path, update the body in
124
+ place and keep the canonical `## Lisa Usage` section there.
125
+ - If direct body edits are unsafe, unavailable, or likely to clobber other writer-owned content,
126
+ fall back to a **single managed comment** containing the same canonical section.
127
+ - If the caller explicitly passes `attachment_preference: comment-only`, skip the body attempt and
128
+ write the managed comment directly.
129
+
130
+ The fallback is part of the contract, not an error. Writers should prefer the artifact body they
131
+ already own, but evidence comments, immutable PR descriptions, or size-limited hosts may require
132
+ the managed comment path.
133
+
134
+ ### Step 3 — Apply the requested operation
135
+
136
+ All three operations use the shared utilities and rule contract; they differ only in which inputs
137
+ they require and whether they recompute child totals:
138
+
139
+ - `record`: upsert exactly one direct usage entry on the target artifact. Rewrite the entire
140
+ `## Lisa Usage` section in place using the canonical serializer; never append ad hoc rows.
141
+ - `rollup`: recompute the rollup token and visible totals from the target's current direct entries
142
+ plus usage discovered from `child_refs`. Dedupe strictly by stable `entry_id`.
143
+ - `record_and_rollup`: first upsert the direct entry, then refresh totals against `child_refs` in
144
+ the same managed write so callers do not produce split-brain ledger states.
145
+
146
+ The implementation path should use the shared utility layer (`parseLisaUsageSection`,
147
+ `mergeLisaUsageEntries`, `createLisaUsageRollup`, `upsertLisaUsageSection`) rather than duplicating
148
+ token parsing or markdown rendering in each caller.
149
+
150
+ ### Step 4 — Persist and report
151
+
152
+ 1. If the rendered ledger body is byte-identical to the current managed surface, return `outcome:
153
+ no-op`.
154
+ 2. Otherwise write the body or managed comment through the host adapter.
155
+ 3. Return the exact writable surface used, direct entry ids, rolled-up child entry ids, totals, and
156
+ any fallback warning.
157
+
158
+ If the host write fails, preserve the exact error text in `error.message`. Do not collapse write
159
+ failures into a generic "usage update failed."
160
+
161
+ ## Rules
162
+
163
+ - Never invent a per-flow usage format. All usage-ledger writes go through this skill and the
164
+ canonical `usage-accounting` rule.
165
+ - Never append a second `## Lisa Usage` section or a second managed usage comment.
166
+ - Never treat missing usage as zero. Callers must record explicit `source: unavailable` entries.
167
+ - Never skip rollup dedupe. Child totals are keyed by stable `entry_id`, not by child ref count.
168
+ - Never silently drop to comments. Return `outcome: comment-fallback` so the caller can surface the
169
+ writable surface that actually holds the ledger.
170
+ - Never overwrite unrelated artifact body content. Rewrite only the managed usage section/comment.
@@ -0,0 +1,4 @@
1
+ display_name: "Usage Accounting"
2
+ short_description: "Shared usage-ledger utility for Lisa lifecycle flows and artifact writers"
3
+ default_prompt:
4
+ - "Use $usage-accounting: Shared usage-ledger utility for Lisa lifecycle flows and artifact writers."
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -65,7 +65,10 @@ else → **create**.
65
65
  format** (XHTML): `#`/`##`/`###` → `<h1>/<h2>/<h3>`, paragraphs → `<p>`, lists → `<ul>/<ol><li>`,
66
66
  fenced code → `<ac:structured-macro ac:name="code">`. Embed the marker as a storage comment
67
67
  (`<!-- $MARKER -->`) or a small `<p>` so future CQL dedupe finds it. The body must always contain
68
- **exactly one** marker; never write a markerless page (CREATE or UPDATE).
68
+ **exactly one** marker; never write a markerless page (CREATE or UPDATE). If the live page body
69
+ already contains the canonical managed `## Lisa Usage` section, preserve it verbatim unless the
70
+ caller intentionally supplied an updated canonical section; use the shared `usage-accounting`
71
+ serializer/merge path rather than hand-editing ledger rows in storage XHTML.
69
72
 
70
73
  **CREATE:** `lisa:atlassian-access` `operation: write-page` (create form) with a payload that sets:
71
74
  - `title`: `$TITLE`
@@ -77,7 +80,8 @@ fenced code → `<ac:structured-macro ac:name="code">`. Embed the marker as a st
77
80
  1. **GET-then-PUT** (load-bearing, as `confluence-prd-intake` documents): first `operation: read-page
78
81
  id: <page-id>` to read the current `version.number`, then `operation: write-page` (edit form) with
79
82
  the marker-normalized storage body and `version.number` bumped to current+1. A PUT without the
80
- current version is rejected; never drop the marker on the edit.
83
+ current version is rejected; never drop the marker or an existing managed `## Lisa Usage` section
84
+ on the edit.
81
85
  2. Re-parent to the target lifecycle parent if the role changed — **unless** the page's current
82
86
  parent is in the resolved `${PROGRESSED_PARENTS[@]}` set (already past `ready`). If so, leave it
83
87
  and report `reused (already past ready)`. Reverse-lookup the current parent in
@@ -99,5 +103,7 @@ outcome: created | reused
99
103
  - State is the parent page, not a label — never attempt Confluence label writes (they 401 on scoped
100
104
  tokens; see `config-resolution`).
101
105
  - Match dedupe by marker, never by title.
106
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
107
+ section or silently drop ledger rows.
102
108
  - Never re-parent a PRD already past `ready` down to draft/ready.
103
109
  - Resolve parents from config (`confluence.parents.{draft,ready}`) — never hardcode page ids.
@@ -276,7 +276,7 @@ If the validator reports `FAIL`, do NOT proceed to Phase 6. Fix the spec and re-
276
276
 
277
277
  ### UPDATE
278
278
 
279
- 1. Re-read the current body via `gh issue view <number> --repo <org>/<repo> --json body --jq '.body'`. Edit only the sections being changed; preserve everything else verbatim.
279
+ 1. Re-read the current body via `gh issue view <number> --repo <org>/<repo> --json body --jq '.body'`. Edit only the sections being changed; preserve everything else verbatim, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
280
280
  2. Apply the edit:
281
281
  ```bash
282
282
  gh issue edit <number> --repo <org>/<repo> --body-file /tmp/updated-body.md
@@ -331,6 +331,8 @@ The mapping below is the single source of truth for how JIRA concepts translate
331
331
  - Never create a Bug, Task, or Sub-task whose AC references work in a different repo. GitHub Issues already live in one repo; reject AC bullets that span others — split into per-repo issues under a shared Epic.
332
332
  - Never include a runtime-behavior issue without a target backend environment, and never include an authenticated-surface issue without sign-in credentials.
333
333
  - Never overwrite an issue body without reading the current version first.
334
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
335
+ section or silently drop ledger rows.
334
336
  - All writes go through this skill (or the `tracker-write` shim). Other vendor-neutral skills must NEVER call `gh issue create` directly.
335
337
  - The gate logic lives in `lisa:github-validate-issue`, NOT here. This skill calls the validator at Phase 5.5 and Phase 7. When a gate needs to change, change it in `lisa:github-validate-issue`.
336
338
  - Never bypass the sub-issue mutation by encoding the parent only in the body. The native sub-issue link is what `lisa:github-read-issue` and the GitHub UI use to render the hierarchy.
@@ -55,10 +55,13 @@ EXISTING=$(gh issue list --repo "$ORG/$REPO" --state open --search "\"$MARKER\"
55
55
 
56
56
  ## Phase 3 — Create or update
57
57
 
58
- **Marker normalization (both paths).** Before writing any body, ensure it contains **exactly one**
59
- marker line — inject `<!-- $MARKER -->` if the caller's synthesized body doesn't already carry it.
60
- **Never write a markerless body** (including on UPDATE or when `source_ref` is passed): a body without
61
- the marker breaks future dedupe. If the body already has the marker, leave the single instance.
58
+ **Marker + usage-ledger preservation (both paths).** Before writing any body, ensure it contains
59
+ **exactly one** marker line — inject `<!-- $MARKER -->` if the caller's synthesized body doesn't
60
+ already carry it. **Never write a markerless body** (including on UPDATE or when `source_ref` is
61
+ passed): a body without the marker breaks future dedupe. If the body already has the marker, leave
62
+ the single instance. If the live issue body already contains the canonical managed `## Lisa Usage`
63
+ section, preserve it verbatim unless the caller intentionally supplied an updated canonical section;
64
+ use the shared `usage-accounting` serializer/merge path rather than hand-editing ledger rows.
62
65
 
63
66
  **CREATE** (no existing issue):
64
67
 
@@ -77,7 +80,7 @@ the marker breaks future dedupe. If the body already has the marker, leave the s
77
80
  **UPDATE** (existing issue or `source_ref`):
78
81
 
79
82
  1. `gh issue edit <n> --repo "$ORG/$REPO" --body-file /tmp/prd-body.md` with the **marker-normalized**
80
- body (regenerate in place; never drop the marker).
83
+ body (regenerate in place; never drop the marker or an existing managed `## Lisa Usage` section).
81
84
  2. Reconcile the lifecycle label to **exactly one**: add `$ROLE_LABEL`, remove every other label in
82
85
  the resolved `${ALL_PRD_LABELS[@]}` set (the config-resolved names — not a hard-coded list) via
83
86
  `gh issue edit <n> --add-label / --remove-label`. Never leave a PRD carrying two lifecycle labels.
@@ -104,6 +107,8 @@ outcome: created | reused
104
107
 
105
108
  - Exactly one PRD lifecycle label at all times (leaf-only does not apply — PRDs are not build leaves).
106
109
  - Match dedupe by marker, never by title.
110
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
111
+ section or silently drop ledger rows.
107
112
  - Never down-rank a PRD already past `ready`.
108
113
  - A *closed* prior PRD does not suppress a new one — a recurrence after closure is a genuine new PRD.
109
114
  - This is a source-side writer (`prd-*` labels). It never touches build labels (`status:*`) — that is
@@ -235,7 +235,7 @@ If the validator reports `PASS`, continue to Phase 6.
235
235
 
236
236
  1. Invoke `lisa:atlassian-access` with `operation: write-ticket payload: {key: <K>, ...fields-being-changed}`. Do NOT resend fields that weren't in the change set — it blows away history.
237
237
  2. Add new relationships via `lisa:atlassian-access` `operation: link from: <K1> to: <K2> type: "<link-type>"`. Existing links are not touched unless explicitly removed.
238
- 3. If description changes, preserve sections you are not editing. Re-read via `/jira-read-ticket` first.
238
+ 3. If description changes, preserve sections you are not editing. Re-read via `/jira-read-ticket` first, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
239
239
 
240
240
  ## Phase 7 — Verify
241
241
 
@@ -259,5 +259,7 @@ Skip this step only on UPDATE when no material change was made.
259
259
  - Never include a runtime-behavior ticket without a target backend environment, and never include an authenticated-surface ticket without sign-in credentials in the description.
260
260
  - Never invent custom field values. If the project requires a field you don't have, stop and ask.
261
261
  - Never overwrite a description without reading the current version first.
262
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
263
+ section or silently drop ledger rows.
262
264
  - All writes go through this skill so best practices are enforced uniformly. Downstream skills (e.g. `lisa:jira-create`) should delegate here rather than invoking `lisa:atlassian-access` write operations directly.
263
265
  - The gate logic (what makes a valid ticket) lives in `lisa:jira-validate-ticket`, NOT in this skill. This skill calls the validator at Phase 5.5 (pre-write) and Phase 7 (via `lisa:jira-verify` post-write). When a gate needs to change, change it in `lisa:jira-validate-ticket` — every caller (write path, dry-run path, post-write verify) picks it up automatically.
@@ -260,7 +260,7 @@ If the validator reports `PASS`, continue to Phase 6.
260
260
  ### UPDATE
261
261
 
262
262
  1. Call `mcp__linear-server__save_project` or `mcp__linear-server__save_issue` with **only the fields being changed**. Do NOT resend fields that weren't in the change set — Linear treats the call as a full overwrite of the listed fields.
263
- 2. Preserve description sections you are not editing — re-read via `/linear-read-issue` first.
263
+ 2. Preserve description sections you are not editing — re-read via `/linear-read-issue` first, including any existing canonical managed `## Lisa Usage` section unless the caller intentionally supplied an updated canonical section. Use the shared `usage-accounting` serializer/merge path rather than freehand edits to ledger rows.
264
264
 
265
265
  ## Phase 7 — Verify
266
266
 
@@ -285,6 +285,8 @@ Skip this step only on UPDATE when no material change was made.
285
285
  - Never include a runtime-behavior item without a target backend environment, and never include an authenticated-surface item without sign-in credentials in the description.
286
286
  - Never invent custom field values. If the team requires a field you don't have, stop and ask.
287
287
  - Never overwrite a description without reading the current version first.
288
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
289
+ section or silently drop ledger rows.
288
290
  - All Linear writes go through this skill so best practices are enforced uniformly. Downstream skills (e.g. `lisa:linear-create`) should delegate here rather than calling the MCP write tools directly.
289
291
  - The gate logic (what makes a valid item) lives in `lisa:linear-validate-issue`, NOT in this skill. This skill calls the validator at Phase 5.5 (pre-write) and Phase 7 (via `lisa:linear-verify` post-write). When a gate needs to change, change it in `lisa:linear-validate-issue` — every caller picks it up automatically.
290
292
  - This skill is the destination of the `lisa:tracker-write` shim when `tracker = "linear"`. Vendor-neutral callers (`notion-to-tracker`, `confluence-to-tracker`, `linear-to-tracker`, `github-to-tracker`) MUST go through `lisa:tracker-write`, not call this skill directly.
@@ -52,9 +52,12 @@ marker, **never** the project name:
52
52
 
53
53
  ## Phase 3 — Create or update
54
54
 
55
- **Marker normalization (both paths).** Before writing the `description`, ensure it contains
56
- **exactly one** marker line — inject the marker if the synthesized description lacks it. **Never write
57
- a markerless description** (including UPDATE / `source_ref`): that breaks future dedupe.
55
+ **Marker + usage-ledger preservation (both paths).** Before writing the `description`, ensure it
56
+ contains **exactly one** marker line — inject the marker if the synthesized description lacks it.
57
+ **Never write a markerless description** (including UPDATE / `source_ref`): that breaks future
58
+ dedupe. If the live Project description already contains the canonical managed `## Lisa Usage`
59
+ section, preserve it verbatim unless the caller intentionally supplied an updated canonical section;
60
+ use the shared `usage-accounting` serializer/merge path rather than hand-editing ledger rows.
58
61
 
59
62
  **CREATE:** `mcp__linear-server__save_project` with:
60
63
  - `name`: `$TITLE`
@@ -64,8 +67,9 @@ a markerless description** (including UPDATE / `source_ref`): that breaks future
64
67
  - `state`: Linear Project default (e.g. `backlog`)
65
68
 
66
69
  **UPDATE** (existing project or `source_ref`): `save_project` with the project id and **only** the
67
- changed fields — regenerate the marker-normalized `description`, and reconcile labels to **exactly
68
- one** PRD lifecycle label: add the role label, remove every other label in the resolved
70
+ changed fields — regenerate the marker-normalized `description` without dropping the managed
71
+ `## Lisa Usage` section, and reconcile labels to **exactly one** PRD lifecycle label: add the role
72
+ label, remove every other label in the resolved
69
73
  `${ALL_PRD_LABELS[@]}` set (config-resolved names, not a hard-coded list). Do **not** down-rank a
70
74
  Project whose current label is in the resolved `${PROGRESSED[@]}` set (already past `ready`) — leave
71
75
  it and report `reused (already past ready)`.
@@ -84,6 +88,8 @@ outcome: created | reused
84
88
 
85
89
  - Exactly one PRD lifecycle project-label at all times.
86
90
  - Match dedupe by marker, never by project name.
91
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
92
+ section or silently drop ledger rows.
87
93
  - Never down-rank a Project already past `ready`.
88
94
  - Source-side writer (`prd-*` project labels) — never touches issue-level build labels (`status:*`),
89
95
  which are `lisa:linear-write-issue`'s lane (see `config-resolution` self-host separation).
@@ -60,8 +60,8 @@ exceeds 100 blocks, create the page with the first ≤100 blocks then add the re
60
60
  accept the markdown content directly (it performs this conversion) — prefer that; the explicit block
61
61
  conversion is the curl-substrate path.
62
62
 
63
- **Marker normalization (both paths).** The page must always carry **exactly one** marker. On CREATE
64
- the marker is the first body block; on UPDATE never remove it. Never write a markerless page.
63
+ **Marker + usage-ledger preservation (both paths).** The page must always carry **exactly one**
64
+ marker. On CREATE the marker is the first body block; on UPDATE never remove it. Never write a markerless body. Never write a markerless page. If the existing page content already contains the canonical managed `## Lisa Usage` section, preserve that section when regenerating the page body unless the caller intentionally supplied an updated canonical section; use the shared `usage-accounting` serializer/merge path rather than freehand block edits to ledger rows.
65
65
 
66
66
  **CREATE:**
67
67
 
@@ -86,7 +86,7 @@ the marker is the first body block; on UPDATE never remove it. Never write a mar
86
86
  (`operation: archive-page` is page-level, so for blocks delete via the blocks API through
87
87
  `notion-access` or, where block deletion isn't available, replace their text in place) and
88
88
  `operation: append-blocks` the regenerated blocks. Do not duplicate the whole spec as a dated
89
- note, and never drop the marker.
89
+ note, and never drop the marker or an existing managed `## Lisa Usage` section.
90
90
 
91
91
  ## Phase 4 — Return
92
92
 
@@ -102,6 +102,8 @@ outcome: created | reused
102
102
 
103
103
  - All access via `lisa:notion-access`; never touch the Notion API/MCP directly.
104
104
  - Match dedupe by marker, never by title.
105
+ - Preserve an existing canonical `## Lisa Usage` section on update; never append a second usage
106
+ section or silently drop ledger rows.
105
107
  - Never down-rank a PRD whose Status is already past `ready`.
106
108
  - Resolve the Status vocabulary from config (`notion.statusProperty`, `notion.values.*`) — never
107
109
  hardcode value names.
@@ -8,7 +8,9 @@ allowed-tools: ["Skill", "Bash", "Read"]
8
8
 
9
9
  Thin dispatcher. Resolves the configured PRD `source` and delegates to the matching vendor PRD
10
10
  writer, which owns the concrete create/update, the lifecycle-role application, and the marker-based
11
- dedupe. This skill only routes it never talks to a source API itself.
11
+ dedupe. When the supplied PRD body already contains the canonical `## Lisa Usage` ledger, the
12
+ vendor writer must preserve that managed section on update instead of dropping it or reformatting it
13
+ ad hoc. This skill only routes — it never talks to a source API itself.
12
14
 
13
15
  See the `config-resolution` rule for the full configuration schema and the PRD lifecycle roles.
14
16
 
@@ -74,6 +76,8 @@ prior PRD-source-write behavior to preserve, so omitted means `draft`.
74
76
 
75
77
  - Never bypass dispatch — a vendor-neutral caller calling a `*-write-prd` skill directly defeats the
76
78
  per-project source switch (exactly the `tracker-write` discipline, mirrored).
79
+ - Never drop or duplicate an existing managed `## Lisa Usage` section. Writer-specific preservation
80
+ and fallback behavior belongs in the vendor writers and follows the `usage-accounting` contract.
77
81
  - Never accept a source outside `{notion, confluence, github, linear}`. `jira` and `file` fail loudly.
78
82
  - Never mutate the spec between layers. The vendor writers define their own create/dedupe contract.
79
83
  - Never invent a PRD lifecycle role string — resolve every role from `config-resolution` per vendor.
@@ -6,7 +6,12 @@ allowed-tools: ["Skill", "Bash", "Read"]
6
6
 
7
7
  # Tracker Write: $ARGUMENTS
8
8
 
9
- Thin dispatcher. Resolves the configured destination tracker and delegates to the matching vendor write skill.
9
+ Thin dispatcher. Resolves the configured destination tracker and delegates to the matching vendor
10
+ write skill.
11
+
12
+ When the incoming description/body already contains the canonical `## Lisa Usage` ledger, the vendor
13
+ writer must preserve that managed section on update and use the `usage-accounting` contract for any
14
+ body-vs-comment fallback. This shim only routes — it never edits tracker artifacts itself.
10
15
 
11
16
  See the `config-resolution` rule for the full configuration schema and skill-mapping table.
12
17
 
@@ -40,6 +45,9 @@ See the `config-resolution` rule for the full configuration schema and skill-map
40
45
  ## Rules
41
46
 
42
47
  - Never bypass dispatch — calling the vendor skill directly from a vendor-neutral caller defeats the per-project switch.
48
+ - Never drop, duplicate, or hand-rewrite an existing managed `## Lisa Usage` section in this shim.
49
+ Writer-specific preservation logic belongs in the vendor writers and follows the
50
+ `usage-accounting` contract.
43
51
  - Never accept a tracker value outside `{jira, github, linear}`.
44
52
  - Never mutate `$ARGUMENTS` between layers. The vendor skills define their own input contract.
45
53
  - Never inline gate logic here. All validation rules live in the vendor skills (`lisa:jira-validate-ticket` / `lisa:github-validate-issue` / `lisa:linear-validate-issue`); this skill only routes.
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: usage-accounting
3
+ description: "Shared usage-ledger utility for Lisa lifecycle flows and artifact writers. Delegates all direct-entry recording and rollup refreshes through one vendor-neutral contract so PRDs, tickets, evidence comments, PRs, and markdown artifacts preserve the canonical `## Lisa Usage` section instead of inventing per-flow formats."
4
+ allowed-tools: ["Skill", "Read", "Bash"]
5
+ ---
6
+
7
+ # Usage Accounting: $ARGUMENTS
8
+
9
+ Single chokepoint for Lisa usage-ledger writes. Caller skills (`research`, `plan`,
10
+ `implement`, `verify`, `debrief`, `intake`, `prd-source-write`, `tracker-write`,
11
+ `tracker-evidence`, later rollup/monitor flows) MUST delegate usage-entry writes and
12
+ rollup refreshes through this skill rather than hand-editing artifact bodies or comments.
13
+
14
+ This skill does not define the ledger format itself. The durable schema, token order,
15
+ rewrite invariants, and rollup semantics live in the `usage-accounting` rule. This skill
16
+ applies that contract to real artifacts and chooses the safest writable surface.
17
+
18
+ ## Invocation contract
19
+
20
+ The caller passes exactly one operation plus its arguments:
21
+
22
+ ```text
23
+ operation: record
24
+ artifact_ref: github:CodySwannGT/lisa#729
25
+ artifact_kind: github-issue
26
+ entry: { ...LisaUsageEntry fields... }
27
+
28
+ operation: rollup
29
+ artifact_ref: github:CodySwannGT/lisa#724
30
+ artifact_kind: github-issue
31
+ child_refs:
32
+ - github:CodySwannGT/lisa#729
33
+ - github:CodySwannGT/lisa#730
34
+
35
+ operation: record_and_rollup
36
+ artifact_ref: github:CodySwannGT/lisa#724
37
+ artifact_kind: github-issue
38
+ entry: { ...LisaUsageEntry fields... }
39
+ child_refs:
40
+ - github:CodySwannGT/lisa#729
41
+ ```
42
+
43
+ Required arguments:
44
+
45
+ - `operation`: one of `record`, `rollup`, or `record_and_rollup`.
46
+ - `artifact_ref`: canonical artifact identifier for the target being updated.
47
+ - `artifact_kind`: the writable host surface (`github-issue`, `github-pr-comment`, `jira-ticket`,
48
+ `linear-issue`, `notion-page`, `confluence-page`, `markdown-file`, or another caller-defined
49
+ host string with a matching read/write adapter).
50
+
51
+ Operation-specific arguments:
52
+
53
+ - `record` requires `entry`.
54
+ - `rollup` requires `child_refs` (empty is allowed when the caller wants a direct-only refresh).
55
+ - `record_and_rollup` requires `entry` and may also pass `child_refs`.
56
+
57
+ Optional arguments:
58
+
59
+ - `attachment_preference`: `body-first` (default) or `comment-only`.
60
+ - `comment_ref`: existing managed comment identifier when the ledger already lives in a comment.
61
+ - `parent_artifact_ref`: explicit parent for callers that need to anchor descendant rollups.
62
+ - `existing_body`: caller-supplied body snapshot when it already owns a fresh read.
63
+
64
+ The `entry` payload MUST satisfy the canonical usage-entry contract from the rule: stable
65
+ `entry_id`, `flow`, `run_id`, provider/model, source semantics, token fields, pricing fields,
66
+ and artifact refs. Callers do not get to omit the "unavailable" case; when trustworthy usage is
67
+ missing they still pass an explicit entry with `source: unavailable` and nullable token/cost
68
+ fields.
69
+
70
+ ## Return shape
71
+
72
+ Return structured output so callers can persist or log what happened without reparsing prose:
73
+
74
+ ```yaml
75
+ outcome: updated | comment-fallback | no-op | blocked
76
+ artifact_ref: "github:CodySwannGT/lisa#729"
77
+ surface:
78
+ kind: body | comment
79
+ ref: "<artifact or comment identifier>"
80
+ entry_ids:
81
+ direct:
82
+ - "<entry-id>"
83
+ child:
84
+ - "<rolled-up-child-entry-id>"
85
+ rollup:
86
+ direct_tokens: 1200
87
+ child_tokens: 450
88
+ total_tokens: 1650
89
+ direct_cost: 0.42
90
+ child_cost: 0.17
91
+ total_cost: 0.59
92
+ warnings:
93
+ - "<warning text>"
94
+ error:
95
+ code: "<stable-code>"
96
+ message: "<exact failure text>"
97
+ remediation: "<next step>"
98
+ ```
99
+
100
+ - `updated` means the preferred writable surface was updated successfully.
101
+ - `comment-fallback` means body/description edits were unsafe, so the canonical ledger landed in a
102
+ managed comment instead.
103
+ - `no-op` means the requested operation produced byte-identical ledger output.
104
+ - `blocked` means the caller asked for a write that could not be completed; preserve the exact host
105
+ failure text.
106
+
107
+ ## Workflow
108
+
109
+ ### Step 1 — Read the current managed surface
110
+
111
+ 1. Resolve the target artifact from `artifact_ref` / `artifact_kind`.
112
+ 2. Prefer a caller-supplied `existing_body` when present and trustworthy for this write.
113
+ 3. Otherwise read the current body/description from the host's native adapter.
114
+ 4. If the caller passed `comment_ref`, read that managed comment body too.
115
+
116
+ Never guess the prior ledger state. Always start from the current managed body/comment so rewrite
117
+ idempotency is preserved.
118
+
119
+ ### Step 2 — Choose the writable surface
120
+
121
+ Default policy is **body first**:
122
+
123
+ - If the host body/description is writable by the caller's normal writer path, update the body in
124
+ place and keep the canonical `## Lisa Usage` section there.
125
+ - If direct body edits are unsafe, unavailable, or likely to clobber other writer-owned content,
126
+ fall back to a **single managed comment** containing the same canonical section.
127
+ - If the caller explicitly passes `attachment_preference: comment-only`, skip the body attempt and
128
+ write the managed comment directly.
129
+
130
+ The fallback is part of the contract, not an error. Writers should prefer the artifact body they
131
+ already own, but evidence comments, immutable PR descriptions, or size-limited hosts may require
132
+ the managed comment path.
133
+
134
+ ### Step 3 — Apply the requested operation
135
+
136
+ All three operations use the shared utilities and rule contract; they differ only in which inputs
137
+ they require and whether they recompute child totals:
138
+
139
+ - `record`: upsert exactly one direct usage entry on the target artifact. Rewrite the entire
140
+ `## Lisa Usage` section in place using the canonical serializer; never append ad hoc rows.
141
+ - `rollup`: recompute the rollup token and visible totals from the target's current direct entries
142
+ plus usage discovered from `child_refs`. Dedupe strictly by stable `entry_id`.
143
+ - `record_and_rollup`: first upsert the direct entry, then refresh totals against `child_refs` in
144
+ the same managed write so callers do not produce split-brain ledger states.
145
+
146
+ The implementation path should use the shared utility layer (`parseLisaUsageSection`,
147
+ `mergeLisaUsageEntries`, `createLisaUsageRollup`, `upsertLisaUsageSection`) rather than duplicating
148
+ token parsing or markdown rendering in each caller.
149
+
150
+ ### Step 4 — Persist and report
151
+
152
+ 1. If the rendered ledger body is byte-identical to the current managed surface, return `outcome:
153
+ no-op`.
154
+ 2. Otherwise write the body or managed comment through the host adapter.
155
+ 3. Return the exact writable surface used, direct entry ids, rolled-up child entry ids, totals, and
156
+ any fallback warning.
157
+
158
+ If the host write fails, preserve the exact error text in `error.message`. Do not collapse write
159
+ failures into a generic "usage update failed."
160
+
161
+ ## Rules
162
+
163
+ - Never invent a per-flow usage format. All usage-ledger writes go through this skill and the
164
+ canonical `usage-accounting` rule.
165
+ - Never append a second `## Lisa Usage` section or a second managed usage comment.
166
+ - Never treat missing usage as zero. Callers must record explicit `source: unavailable` entries.
167
+ - Never skip rollup dedupe. Child totals are keyed by stable `entry_id`, not by child ref count.
168
+ - Never silently drop to comments. Return `outcome: comment-fallback` so the caller can surface the
169
+ writable surface that actually holds the ledger.
170
+ - Never overwrite unrelated artifact body content. Rewrite only the managed usage section/comment.