@codyswann/lisa 4.27.9 → 4.28.1
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/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 +43 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +5 -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-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-validate-tracker-mapping/SKILL.md +134 -3
- package/plugins/lisa/rules/eager/config-resolution.md +23 -0
- package/plugins/lisa/rules/reference/config-resolution.md +65 -0
- package/plugins/lisa/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa/skills/lisa-validate-tracker-mapping/SKILL.md +135 -4
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa-agy/skills/lisa-validate-tracker-mapping/SKILL.md +135 -4
- 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/rules/eager/config-resolution.md +23 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +65 -0
- package/plugins/lisa-copilot/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa-copilot/skills/lisa-validate-tracker-mapping/SKILL.md +135 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +65 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +23 -0
- package/plugins/lisa-cursor/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa-cursor/skills/lisa-validate-tracker-mapping/SKILL.md +135 -4
- 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/rules/eager/config-resolution.md +23 -0
- package/plugins/src/base/rules/reference/config-resolution.md +65 -0
- package/plugins/src/base/scripts/repo-scope-vocabulary-audit.mjs +458 -0
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/src/base/skills/lisa-validate-tracker-mapping/SKILL.md +135 -4
- package/scripts/lib/mutation-performance-reporter.mjs +135 -0
- package/scripts/mutation-performance-measure.mjs +1794 -0
- package/scripts/mutation-performance.schema.json +103 -0
- package/typescript/create-only/.github/workflows/review-evidence.yml +18 -10
|
@@ -39,6 +39,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
39
39
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup: if base is `staging`, env is `staging`).
|
|
40
40
|
3. If `done` in config is a **string** (not a map), use it directly regardless of env.
|
|
41
41
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
42
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
43
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
44
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
45
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
46
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
47
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
48
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
49
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
50
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
51
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
52
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
53
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
54
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
55
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
56
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
57
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
58
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
59
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
60
|
+
→ "Promotion completeness".
|
|
42
61
|
|
|
43
62
|
```bash
|
|
44
63
|
# Resolve env, then DONE.
|
|
@@ -334,10 +353,10 @@ A `done` env status (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
334
353
|
|
|
335
354
|
If the lifecycle run returned Success:
|
|
336
355
|
1. **Confirm the PR merged.** Read the live state of the ticket's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
337
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
356
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave in `$CLAIMED` for a later cycle), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
338
357
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the ticket under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it in `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once it is merged, applies this same env transition. Do **not** comment "Build complete" or file anything: the work is in-flight, not done.
|
|
339
358
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the ticket in `$CLAIMED` for human investigation.
|
|
340
|
-
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
359
|
+
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
341
360
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
342
361
|
- If `jira.workflow.done` is a string, that status is terminal.
|
|
343
362
|
- If `jira.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env statuses such as `On Dev` and `On Stg` are not terminal and must remain unresolved / open.
|
|
@@ -42,6 +42,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
42
42
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup).
|
|
43
43
|
3. If `done` is a **string** in config, use it directly regardless of env.
|
|
44
44
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
45
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
46
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
47
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
48
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
49
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
50
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
51
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
52
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
53
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
54
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
55
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
56
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
57
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
58
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
59
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
60
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
61
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
62
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
63
|
+
→ "Promotion completeness".
|
|
45
64
|
|
|
46
65
|
```bash
|
|
47
66
|
TARGET_ENV="${target_env:-}"
|
|
@@ -333,10 +352,10 @@ A `done` env state (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
333
352
|
|
|
334
353
|
If the lifecycle run returned Success:
|
|
335
354
|
1. **Confirm the PR merged.** Read the live state of the Issue's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
336
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
355
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave at `$CLAIMED`), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
337
356
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the Issue under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it at `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once merged, applies this same env transition. Do **not** comment "Build complete" or change the native state.
|
|
338
357
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the Issue at `$CLAIMED`.
|
|
339
|
-
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
358
|
+
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
340
359
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
341
360
|
- If `linear.workflow.done` is a string, that state is terminal.
|
|
342
361
|
- If `linear.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env states such as `On Dev` and `On Stg` are not terminal — they are typed `started`, so the Issue correctly stays open and on the board.
|
|
@@ -352,8 +352,9 @@ the vendor build-intake runs**, skipping the claim transition (the item is alrea
|
|
|
352
352
|
2. **On agent success**, apply the scanner's post-agent transition yourself: `claimed → done`,
|
|
353
353
|
where `done` is **env-resolved** exactly as `lisa:<tracker>-build-intake` resolves it (per
|
|
354
354
|
`config-resolution` env-keyed `done`: explicit `target_env` arg wins; else reverse-lookup the
|
|
355
|
-
env from the resulting PR's base branch via `deploy.branches`;
|
|
356
|
-
|
|
355
|
+
env from the resulting PR's base branch via `deploy.branches`; then **cap the resolved env by
|
|
356
|
+
the promotion-completeness gate** — see "Promotion completeness" below; if `done` is a map and
|
|
357
|
+
env is unresolvable, fail loudly — never guess). repair-intake owns this transition because it is
|
|
357
358
|
standing in for the scanner that never got to finish it.
|
|
358
359
|
3. **On a surfaced blocker** (agent reports it cannot proceed), leave/move the item to `blocked`
|
|
359
360
|
with a `[lisa-repair-intake]` note (see Loop prevention). When the surfaced blocker is something
|
|
@@ -393,9 +394,14 @@ same reviewer. Use the same paginated REST reduction documented by
|
|
|
393
394
|
complete and the only thing missing is the env transition the build-intake never applied (its merge
|
|
394
395
|
gate left the item `claimed` because the merge landed after its agent returned). Do **not** re-dispatch
|
|
395
396
|
or file anything: apply the scanner's post-agent env-resolved `claimed → done` transition (the
|
|
396
|
-
resume-sequence step 2, env-resolved from the merged PR's base branch
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
resume-sequence step 2, env-resolved from the merged PR's base branch **and capped by the
|
|
398
|
+
promotion-completeness gate below**) and record it as a repair write.
|
|
399
|
+
|
|
400
|
+
**Run the gate before the write, never after.** The blocker path (step 4) assumes the item is still
|
|
401
|
+
`claimed`, so a deploy failure discovered *after* a lifecycle write has nowhere to go. Resolve every
|
|
402
|
+
rung first: if the rung's deploy concluded anything but `success`, write **nothing**, leave the item
|
|
403
|
+
`claimed`, and fall through to the blocker path; if it has not concluded, write nothing and leave it
|
|
404
|
+
`claimed` for a later cycle. Only a fully resolved gate result is ever written.
|
|
399
405
|
|
|
400
406
|
**3. PR only behind its base → re-sync in place (mechanical, not a blocker).** If the PR is clean but
|
|
401
407
|
behind its base — `mergeStateStatus == BEHIND` while `mergeable != CONFLICTING` and no required check
|
|
@@ -495,6 +501,48 @@ recurred (<date>, <ref>), so we're raising it once more for your review.`); a ma
|
|
|
495
501
|
_completed_ is a regression path, not a decline. Honor the backoff window and state fingerprint
|
|
496
502
|
(Loop prevention) so re-runs over the same unchanged blocker are no-ops.
|
|
497
503
|
|
|
504
|
+
#### Promotion completeness: the base branch is not the delivery
|
|
505
|
+
|
|
506
|
+
A merged PR's base branch names the environment the change **entered**, not the environments it has
|
|
507
|
+
**reached**. A hotfix merged straight to `main` enters production out of order: the reverse-lookup
|
|
508
|
+
resolves `production`, `production` is terminal, and the item closes while `staging` has none of the
|
|
509
|
+
fix and cannot deploy. The evidence is real — merged PR, merge-commit ancestry in `main`, a green
|
|
510
|
+
production deploy — and the inference from it is still wrong.
|
|
511
|
+
|
|
512
|
+
So before writing any env-resolved `done`, walk `deploy.order` from its **lowest** rung and keep
|
|
513
|
+
rungs while they are **reached**, stopping at the first that is not. A rung is reached when both
|
|
514
|
+
hold for its `deploy.branches` branch:
|
|
515
|
+
|
|
516
|
+
- **Ancestry** — `git fetch origin <branch> && git merge-base --is-ancestor <merge-sha> origin/<branch>`,
|
|
517
|
+
asserted for **every** env branch at or below the resolved env — never only the PR's base.
|
|
518
|
+
- **Deploy health** — that branch's most recent deploy **concluded `success`**. Read the
|
|
519
|
+
`conclusion`, never the `status`: an in-flight deploy has a null conclusion and is
|
|
520
|
+
indistinguishable from a pass on the status field alone. Only `success` promotes — a null
|
|
521
|
+
conclusion and every other conclusion (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`,
|
|
522
|
+
`stale`, `action_required`) leave the rung unreached. An unconcluded deploy is unknown, not green
|
|
523
|
+
— do not write that rung this cycle; a later cycle reads a concluded run. Where a project exposes
|
|
524
|
+
no deploy surface for a branch at all, ancestry alone decides that rung.
|
|
525
|
+
|
|
526
|
+
Write the highest **contiguously reached** rung at or below the resolved env — never a higher rung,
|
|
527
|
+
even when the higher rung is reached and a lower one is not. A merge present in `dev` and `main` but
|
|
528
|
+
absent from `staging` writes `dev` (`On Dev`), which is intermediate: the item stays natively open
|
|
529
|
+
and the promotion gap is what holds it open. When no rung is reached, write nothing and leave the
|
|
530
|
+
item in its current role.
|
|
531
|
+
|
|
532
|
+
The refusal is named, never silent. When the gate caps the env below the resolved one, the
|
|
533
|
+
`[lisa-repair-intake]` note MUST carry all three fields —
|
|
534
|
+
`<first unreached env> (<its branch>) — <condition>` — where the condition is exactly one of
|
|
535
|
+
`missing ancestry`, `deploy unknown: <run URL, or "no concluded run">` (an in-flight run, or a
|
|
536
|
+
branch with a deploy surface but nothing concluded), or
|
|
537
|
+
`deploy concluded <conclusion>: <run URL>`. A failing run named without its environment and branch,
|
|
538
|
+
or an environment named without its condition, is an incomplete note.
|
|
539
|
+
|
|
540
|
+
**Do not classify the gap away.** An open back-fill PR against a skipped environment branch is
|
|
541
|
+
*outstanding delivery*, not branch hygiene: it is the evidence that a rung was skipped, so it holds
|
|
542
|
+
the item open. Recording "the fix reached dev and production but skipped staging, so that PR is a
|
|
543
|
+
branch back-fill, not outstanding delivery" and then closing anyway is this defect exactly — the
|
|
544
|
+
condition observed, filed under the wrong heading, and overridden.
|
|
545
|
+
|
|
498
546
|
### Build `blocked` → re-evaluate, unblock if cleared
|
|
499
547
|
|
|
500
548
|
1. Read the block reason and classify the blocker (see Blocker classification & clearing). An item
|
|
@@ -556,6 +604,18 @@ native-open / active / unresolved:
|
|
|
556
604
|
2. Verify the terminal `done` role is the true final value per `leaf-only-lifecycle` and
|
|
557
605
|
`config-resolution` env-keyed `done`. Intermediate env labels (for example `status:on-dev` or
|
|
558
606
|
`status:on-stg`) are not terminal and must stay open.
|
|
607
|
+
|
|
608
|
+
Verify too that the terminal role was **earned**, not merely worn: re-run the
|
|
609
|
+
promotion-completeness gate (above) against the item's merge commit, and close **only** when the
|
|
610
|
+
gate reports the terminal rung itself as reached. On a branch with a deploy surface that means
|
|
611
|
+
`conclusion == success`; an in-flight run, or no concluded run at all, does **not** reach the
|
|
612
|
+
rung and does **not** authorize closure. Where a branch exposes no deploy surface, ancestry alone
|
|
613
|
+
decides it, exactly as the gate documents. If any env branch at or below the terminal one lacks
|
|
614
|
+
the commit or fails its deploy-health test, do **not** close: demote the item to the highest
|
|
615
|
+
contiguously reached rung, keep it open, and refresh a `[lisa-repair-intake]` note carrying the
|
|
616
|
+
full refusal reason (env, branch, condition). A terminal label applied before the gate existed is
|
|
617
|
+
exactly how an out-of-order hotfix reaches native closure — so the label alone is never
|
|
618
|
+
sufficient evidence to close.
|
|
559
619
|
3. Perform the provider-native terminal action idempotently:
|
|
560
620
|
- GitHub: `gh issue close <number> --repo <org>/<repo> --reason completed`.
|
|
561
621
|
- Linear: move the issue to the configured Done / Completed native workflow state if available;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lisa-validate-tracker-mapping
|
|
3
|
-
description: "Detect and repair drift between a project's configured Lisa status/label mappings and the live tracker/source workflow. Compares every lifecycle role in `.lisa.config.json` (JIRA `jira.workflow` statuses, GitHub/Linear `labels.{build,prd}`, Notion `notion.values` select options, Confluence `confluence.parents`) against the authoritative live names the access layer reports — catching renames, deletions, and case drift (e.g. config `On Stg` vs live `ON STG`). Read-only by default; `repair=true` rewrites the config to the canonical live names (config is fixed, never the tracker). Also
|
|
3
|
+
description: "Detect and repair drift between a project's configured Lisa status/label mappings and the live tracker/source workflow. Compares every lifecycle role in `.lisa.config.json` (JIRA `jira.workflow` statuses, GitHub/Linear `labels.{build,prd}`, Notion `notion.values` select options, Confluence `confluence.parents`) against the authoritative live names the access layer reports — catching renames, deletions, and case drift (e.g. config `On Stg` vs live `ON STG`). Read-only by default; `repair=true` rewrites the config to the canonical live names (config is fixed, never the tracker). Also audits the vocabularies no config key declares: `repo:` scope, where an item scoped only by an alias is valid yet invisible to every `repo:<name>` scan (DRIFTED), and `type:`/`priority:`/`component:`/`points:` smells (advisory). Neither is auto-repaired. Audits the current repo by default, or sweeps a set of projects via `projects=<glob>` / `workspaces=<file>`. Safe to schedule for continuous drift detection."
|
|
4
4
|
allowed-tools: ["Skill", "Bash", "Read", "Write", "Edit", "AskUserQuestion"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -125,8 +125,8 @@ One role carries a further check that name-existence cannot express:
|
|
|
125
125
|
A project's verdict:
|
|
126
126
|
|
|
127
127
|
- **VALID** — every role is VALID.
|
|
128
|
-
- **DRIFTED** — at least one CASE_DRIFT, MISSING, or INVERTED role, none of which is UNRESOLVABLE. An INVERTED `ready` is never VALID, no matter how cleanly it resolves.
|
|
129
|
-
- **UNRESOLVABLE** — the live set couldn't be enumerated (auth mismatch, missing tracker config, access failure). Distinguish this loudly from VALID — an unresolved audit is not a passing audit.
|
|
128
|
+
- **DRIFTED** — at least one CASE_DRIFT, MISSING, or INVERTED role, none of which is UNRESOLVABLE, **or** any Step 4b repo-scope finding. An INVERTED `ready` is never VALID, no matter how cleanly it resolves.
|
|
129
|
+
- **UNRESOLVABLE** — the live set couldn't be enumerated (auth mismatch, missing tracker config, access failure), **or** Step 4b could not derive a repo vocabulary. Distinguish this loudly from VALID — an unresolved audit is not a passing audit.
|
|
130
130
|
|
|
131
131
|
## Step 4b — Scoping-label smells (GitHub, advisory)
|
|
132
132
|
|
|
@@ -211,6 +211,99 @@ Three finding kinds, all `severity: "advisory"`:
|
|
|
211
211
|
Do not re-derive any of these thresholds in prose or in a second script. The
|
|
212
212
|
module is the single definition; this step calls it.
|
|
213
213
|
|
|
214
|
+
## Step 4c — Repo-scope vocabulary (all trackers, counts toward the verdict)
|
|
215
|
+
|
|
216
|
+
Steps 2–4 audit **lifecycle** roles, and they can only ever audit lifecycle
|
|
217
|
+
roles: every pair they compare comes from a `(role, configured-name)` mapping
|
|
218
|
+
declared in `.lisa.config.json`. The **scoping** vocabulary is declared in no
|
|
219
|
+
config key at all, so there is nothing to compare a live name against.
|
|
220
|
+
|
|
221
|
+
`repo:` is the scoping family where that blind spot costs the most, because a
|
|
222
|
+
wrong answer there is not a matter of taste: it decides which work items a
|
|
223
|
+
repository's build queue can see. When the repo vocabulary splits, a scan
|
|
224
|
+
filtering on the canonical spelling returns **fewer items, not an error**, and
|
|
225
|
+
an empty result is indistinguishable from "nothing to do". Work sits unrouted
|
|
226
|
+
and nobody is told. This step is what makes that impossible.
|
|
227
|
+
|
|
228
|
+
> The **open** scoping families — `type:`, `priority:`, `points:`,
|
|
229
|
+
> `component:` — are a separate problem with a separate answer, tracked
|
|
230
|
+
> upstream on #3420. An open vocabulary has no wrong member, only inconsistent
|
|
231
|
+
> ones, so findings about it must stay advisory and must never become a gate.
|
|
232
|
+
> That constraint exists to stop a *guess* becoming a gate; it is not weakened
|
|
233
|
+
> here, because nothing in this step is a guess. Do not extend this step to
|
|
234
|
+
> those families.
|
|
235
|
+
|
|
236
|
+
`assertRepoScope` (`lisa-work-item.mjs`, #1957) accepts **three** spellings as
|
|
237
|
+
valid repo scope, while every build-intake scanner filters on one:
|
|
238
|
+
|
|
239
|
+
| spelling | accepted by validation | found by a `repo:<name>` scan |
|
|
240
|
+
| --- | --- | --- |
|
|
241
|
+
| `repo:<name>` (canonical) | yes | **yes** |
|
|
242
|
+
| bare `<name>` label | yes | **no** |
|
|
243
|
+
| Jira component `<name>` | yes | **no** |
|
|
244
|
+
|
|
245
|
+
The bare branch is deliberate and load-bearing — Sentry-provenance items arrive
|
|
246
|
+
carrying only the bare repo name — so it is **not** to be tightened away. What
|
|
247
|
+
this step reports is the *disagreement* between the two columns: an item that
|
|
248
|
+
passes validation while being invisible to every scan looking for it. That is a
|
|
249
|
+
checkable fact about a specific item, not a heuristic about a name, which is
|
|
250
|
+
why these findings count toward the verdict.
|
|
251
|
+
|
|
252
|
+
### Derive the vocabulary — never declare it in config
|
|
253
|
+
|
|
254
|
+
Do **not** add a config key for this, and do not accept one if you find it. A
|
|
255
|
+
hand-written repo vocabulary is per-project curation of a list that is already
|
|
256
|
+
derivable, and it becomes its own drift surface — the same objection that
|
|
257
|
+
rejected declaring the open vocabularies. A key nothing reads would look like
|
|
258
|
+
an assertion while asserting nothing. Derive it instead:
|
|
259
|
+
|
|
260
|
+
- **Single-project mode**: the current-repo identity ladder from
|
|
261
|
+
`config-resolution` — `.lisa.config.local.json` `repo` → `.lisa.config.json`
|
|
262
|
+
`repo` → `.lisa.config.json` `github.repo` → `basename -s .git` of the git
|
|
263
|
+
remote.
|
|
264
|
+
- **Batch mode** (`projects=<glob>` / `workspaces=<file>`): the union of that
|
|
265
|
+
ladder across every resolved project, which is exactly the set of repos this
|
|
266
|
+
one tracker serves.
|
|
267
|
+
|
|
268
|
+
If the vocabulary resolves **empty**, the verdict is `UNRESOLVABLE`, never
|
|
269
|
+
`VALID`. A check that reports "nothing wrong" having looked at nothing is the
|
|
270
|
+
same failure this step exists to catch.
|
|
271
|
+
|
|
272
|
+
### Collect and classify
|
|
273
|
+
|
|
274
|
+
Feed the derived vocabulary, the live work items with their labels (and Jira
|
|
275
|
+
components), and the tracker's declared label set — the declared set is what
|
|
276
|
+
catches a malformed marker carrying zero items:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/repo-scope-vocabulary-audit.mjs" \
|
|
280
|
+
< /tmp/lisa-repo-scope-input.json
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Input is `{ knownRepos: [...], items: [{ ref, labels, components }], labels: [...] }`.
|
|
284
|
+
It returns `{ verdict, findings, knownAliases, vocabulary }` and always exits
|
|
285
|
+
`0` — the verdict is the answer, and the exit status is not a second, quieter
|
|
286
|
+
channel for it. Three finding kinds, all `severity: "drift"`:
|
|
287
|
+
|
|
288
|
+
- **`unstamped-alias`** — work items carry an accepted alias (bare label or
|
|
289
|
+
Jira component) for a known repo **without** the canonical `repo:<name>`
|
|
290
|
+
beside it. They pass validation and no scan finds them. The finding names the
|
|
291
|
+
alias and every item carrying it.
|
|
292
|
+
- **`malformed-marker`** — a label shaped like a repo marker with the wrong
|
|
293
|
+
separator (`repo-frontend`, `repo_backend`). It scopes nothing at all: no
|
|
294
|
+
scan filters on it and validation does not accept it either.
|
|
295
|
+
- **`undeclared-scope`** — a `repo:<value>` label naming a repo outside the
|
|
296
|
+
derived vocabulary. Either the project set is incomplete or the label is a
|
|
297
|
+
misspelling routing work nowhere.
|
|
298
|
+
|
|
299
|
+
An alias on an item that **also** carries the canonical marker is reported in
|
|
300
|
+
`knownAliases`, **not** as drift — every scan still finds that item, so nothing
|
|
301
|
+
is wrong. That distinction is the whole check: an alias cannot be judged from
|
|
302
|
+
the label set alone, only per item.
|
|
303
|
+
|
|
304
|
+
Fold `DRIFTED` from this step into the project verdict exactly as a
|
|
305
|
+
`CASE_DRIFT` or `MISSING` role does.
|
|
306
|
+
|
|
214
307
|
## Step 5 — Report
|
|
215
308
|
|
|
216
309
|
Per project, print a terminal-first section:
|
|
@@ -236,6 +329,24 @@ An INVERTED `ready` gets a full line rather than a one-word status, because the
|
|
|
236
329
|
via /lisa:setup:linear, then set linear.workflow.ready to it.
|
|
237
330
|
```
|
|
238
331
|
|
|
332
|
+
Print the Step 4c findings **with this mapping table**, not as a separate
|
|
333
|
+
advisory block — they are drift, and a reader must not mistake them for
|
|
334
|
+
smells. Name the items, because "which tickets are invisible" is the operator's
|
|
335
|
+
actual question:
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
repo scope derived vocabulary: backend, frontend, infrastructure
|
|
339
|
+
backend 2 items scoped by the bare label only UNSTAMPED_ALIAS
|
|
340
|
+
SE-9, SE-10 pass repo-scope validation but carry no "repo:backend",
|
|
341
|
+
so every build scan for backend skips them and reports no error.
|
|
342
|
+
Stamping "repo:backend" on those items is a tracker change a human makes.
|
|
343
|
+
repo-frontend 0 items MALFORMED_MARKER
|
|
344
|
+
repo:mobile outside the derived vocabulary UNDECLARED_SCOPE
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
A project whose repo vocabulary is in good order prints
|
|
348
|
+
`repo scope: VALID` and nothing else.
|
|
349
|
+
|
|
239
350
|
Print the Step 4b findings, when there are any, as a clearly separate advisory
|
|
240
351
|
block **below** the mapping table so nobody reads a smell as a mapping failure:
|
|
241
352
|
|
|
@@ -250,7 +361,7 @@ Write those lines for a non-technical operator: say what was seen and what a
|
|
|
250
361
|
person might do about it, never "FAIL". A healthy repository prints
|
|
251
362
|
`scoping labels: none` and nothing else.
|
|
252
363
|
|
|
253
|
-
End with a roll-up: counts of VALID / DRIFTED / UNRESOLVABLE projects and the exact next command (`… repair=true` when drift is auto-repairable; an admin note when a status is genuinely MISSING; `/lisa:setup:linear` when a `ready` is INVERTED). Scoping findings are reported alongside that roll-up as a **separate count**, never folded into DRIFTED.
|
|
364
|
+
End with a roll-up: counts of VALID / DRIFTED / UNRESOLVABLE projects and the exact next command (`… repair=true` when drift is auto-repairable; an admin note when a status is genuinely MISSING; `/lisa:setup:linear` when a `ready` is INVERTED; an admin note naming the items when a repo scope is UNSTAMPED_ALIAS). Scoping-label findings are reported alongside that roll-up as a **separate count**, never folded into DRIFTED.
|
|
254
365
|
|
|
255
366
|
## Step 6 — Repair (only when `repair=true`)
|
|
256
367
|
|
|
@@ -305,6 +416,22 @@ real issue metadata on a coin flip. Report the finding, name the two spellings,
|
|
|
305
416
|
and leave the decision to a human — and note that reconciling them means
|
|
306
417
|
relabelling the issues, which is a tracker mutation this skill never performs.
|
|
307
418
|
|
|
419
|
+
### Repo-scope findings — never repaired, for a different reason
|
|
420
|
+
|
|
421
|
+
Step 4c findings are never auto-repaired either, but not because the right
|
|
422
|
+
answer is unknowable — often it is obvious. It is because **every** available
|
|
423
|
+
fix is a **tracker** mutation, and `repair=true` writes the config only:
|
|
424
|
+
|
|
425
|
+
- an `unstamped-alias` is fixed by adding `repo:<name>` to the work items,
|
|
426
|
+
- a `malformed-marker` by renaming or deleting a label,
|
|
427
|
+
- an `undeclared-scope` by deciding whether the name is a real repository (so
|
|
428
|
+
the project set is incomplete) or a misspelling (so the label is wrong).
|
|
429
|
+
|
|
430
|
+
There is no config edit that resolves any of them, and there is deliberately no
|
|
431
|
+
config key to edit — see Step 4c on why the vocabulary is derived. Report the
|
|
432
|
+
finding, name the items, and leave the tracker to a human.
|
|
433
|
+
|
|
434
|
+
|
|
308
435
|
### Invalidate the verification cache
|
|
309
436
|
|
|
310
437
|
After any JIRA repair, clear the `setup-jira` reachability cache so it re-verifies the new mapping:
|
|
@@ -334,3 +461,7 @@ Re-print the project section showing the post-repair mapping and the new verdict
|
|
|
334
461
|
- Reuse the config-resolution defaults and the vendor access skills (`atlassian-access`, `gh`, Linear/Notion MCP); do not invent a parallel mapping or lifecycle vocabulary.
|
|
335
462
|
- Scoping-label findings (Step 4b) are **advisory**. They never change a project's verdict, never change this skill's exit status, and are never auto-repaired. Do not add a flag that turns them into a gate.
|
|
336
463
|
- Never let Step 4b fail the run. A `gh` error, a non-GitHub tracker, or an empty repository means the scoping section reports "not collected" — the lifecycle verdict is unaffected either way.
|
|
464
|
+
- Step 4c repo-scope findings are **drift**, not smells: they count toward the verdict and are reported with the mapping table. They are still never auto-repaired, because every fix is a tracker mutation.
|
|
465
|
+
- Derive the repo vocabulary from the current-repo identity ladder (and, in batch mode, the resolved project set). Never introduce a config key declaring it, and never trust one — a hand-written vocabulary is a second thing to drift, and a key nothing reads would assert nothing while looking like an assertion.
|
|
466
|
+
- An empty derived repo vocabulary is `UNRESOLVABLE`, never `VALID`. The whole point of Step 4c is that a silent zero is the failure, so a zero-length vocabulary must never read as a pass.
|
|
467
|
+
- Do not extend Step 4c to the open scoping families (`type:`, `priority:`, `points:`, `component:`). Findings about those are heuristic and must stay advisory — see #3420.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measurement-only Stryker reporter for CodySwannGT/lisa#3304.
|
|
3
|
+
*
|
|
4
|
+
* It deliberately observes lifecycle boundaries only. In particular it does
|
|
5
|
+
* not implement `onMutantTested`, so enabling it cannot add I/O to Stryker's
|
|
6
|
+
* per-mutant hot path. Evidence is retained in memory and committed atomically
|
|
7
|
+
* once, during wrap-up.
|
|
8
|
+
* @module scripts/lib/mutation-performance-reporter
|
|
9
|
+
*/
|
|
10
|
+
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
commonTokens,
|
|
15
|
+
declareClassPlugin,
|
|
16
|
+
PluginKind,
|
|
17
|
+
tokens,
|
|
18
|
+
} from "@stryker-mutator/api/plugin";
|
|
19
|
+
|
|
20
|
+
const defaultNow = () => new Date().toISOString();
|
|
21
|
+
|
|
22
|
+
const testLocation = test => {
|
|
23
|
+
const position = test.startPosition ?? test.location ?? {};
|
|
24
|
+
const line = position.line ?? position.start?.line ?? 0;
|
|
25
|
+
const column = position.column ?? position.start?.column ?? 0;
|
|
26
|
+
return `${line}:${column}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const canonicalTest = test => ({
|
|
30
|
+
file: String(test.fileName ?? test.file ?? ""),
|
|
31
|
+
name: String(test.name ?? test.description ?? test.id ?? ""),
|
|
32
|
+
location: testLocation(test),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** Lifecycle-only reporter consumed through Stryker's reporter plugin API. */
|
|
36
|
+
export class LisaMeasurementReporter {
|
|
37
|
+
static inject = tokens(commonTokens.options);
|
|
38
|
+
|
|
39
|
+
#outputFile;
|
|
40
|
+
#now;
|
|
41
|
+
#startedAt;
|
|
42
|
+
#dryRun = null;
|
|
43
|
+
#planCount = null;
|
|
44
|
+
#planAt = null;
|
|
45
|
+
#reportReadyAt = null;
|
|
46
|
+
#written = false;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {object} options Stryker options or a focused-test configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.#outputFile =
|
|
53
|
+
options.outputFile ??
|
|
54
|
+
process.env.LISA_MUTATION_PHASE_EVENTS ??
|
|
55
|
+
path.resolve(
|
|
56
|
+
path.dirname(
|
|
57
|
+
options.jsonReporter?.fileName ?? "reports/stryker-report.json"
|
|
58
|
+
),
|
|
59
|
+
"phase-events.json"
|
|
60
|
+
);
|
|
61
|
+
this.#now = options.now ?? defaultNow;
|
|
62
|
+
this.#startedAt = this.#now();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Retain exact dry-run timing and test identity in memory. */
|
|
66
|
+
onDryRunCompleted(event) {
|
|
67
|
+
const result = event.result ?? event;
|
|
68
|
+
const timing = event.timing ?? result.timing ?? { net: 0, overhead: 0 };
|
|
69
|
+
this.#dryRun = {
|
|
70
|
+
at: this.#now(),
|
|
71
|
+
net_ms: Number(timing.net),
|
|
72
|
+
overhead_ms: Number(timing.overhead),
|
|
73
|
+
tests: (result.tests ?? event.tests ?? []).map(canonicalTest),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Retain the planned mutant count, without per-mutant callbacks. */
|
|
78
|
+
onMutationTestingPlanReady(event) {
|
|
79
|
+
this.#planCount = (event.mutantPlans ?? event.mutants ?? []).length;
|
|
80
|
+
this.#planAt = this.#now();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Compatibility with the focused fake lifecycle and final-report timing. */
|
|
84
|
+
onMutationTestReportReady(report) {
|
|
85
|
+
if (this.#planCount === null) {
|
|
86
|
+
this.#planCount =
|
|
87
|
+
report.mutants?.length ??
|
|
88
|
+
Object.values(report.files ?? {}).reduce(
|
|
89
|
+
(count, file) => count + (file.mutants?.length ?? 0),
|
|
90
|
+
0
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (this.#planAt === null) this.#planAt = this.#now();
|
|
94
|
+
this.#reportReadyAt = this.#now();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Atomically write the one lifecycle artifact. */
|
|
98
|
+
async wrapUp() {
|
|
99
|
+
if (this.#written) {
|
|
100
|
+
throw new Error("measurement reporter evidence was already written");
|
|
101
|
+
}
|
|
102
|
+
if (!this.#dryRun || this.#planCount === null || !this.#reportReadyAt) {
|
|
103
|
+
throw new Error("measurement reporter lifecycle is incomplete");
|
|
104
|
+
}
|
|
105
|
+
this.#written = true;
|
|
106
|
+
const evidence = {
|
|
107
|
+
schema_version: "lisa.mutation-phase-events/v1",
|
|
108
|
+
started_at: this.#startedAt,
|
|
109
|
+
dry_run_at: this.#dryRun.at,
|
|
110
|
+
dry_run_ms: this.#dryRun.net_ms + this.#dryRun.overhead_ms,
|
|
111
|
+
dry_run_net_ms: this.#dryRun.net_ms,
|
|
112
|
+
dry_run_overhead_ms: this.#dryRun.overhead_ms,
|
|
113
|
+
tests: this.#dryRun.tests,
|
|
114
|
+
plan_count: this.#planCount,
|
|
115
|
+
plan_ready_at: this.#planAt,
|
|
116
|
+
report_ready_at: this.#reportReadyAt,
|
|
117
|
+
finished_at: this.#now(),
|
|
118
|
+
};
|
|
119
|
+
await mkdir(path.dirname(this.#outputFile), { recursive: true });
|
|
120
|
+
const temporary = `${this.#outputFile}.tmp`;
|
|
121
|
+
await writeFile(temporary, `${JSON.stringify(evidence, null, 2)}\n`, {
|
|
122
|
+
flag: "wx",
|
|
123
|
+
mode: 0o600,
|
|
124
|
+
});
|
|
125
|
+
await rename(temporary, this.#outputFile);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const strykerPlugins = [
|
|
130
|
+
declareClassPlugin(
|
|
131
|
+
PluginKind.Reporter,
|
|
132
|
+
"lisa-measurement",
|
|
133
|
+
LisaMeasurementReporter
|
|
134
|
+
),
|
|
135
|
+
];
|