@codyswann/lisa 4.87.1 → 4.87.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lib/completion-proof-content.mjs +20 -6
- package/all/copy-overwrite/scripts/lisa-hooks/block-blind-automerge.sh +10 -8
- package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +14 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -4
- package/plugins/lisa/hooks/block-blind-automerge.sh +10 -8
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -4
- package/plugins/lisa-agy/hooks/block-blind-automerge.sh +10 -8
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -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-cdk-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-blind-automerge.sh +10 -8
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-blind-automerge.sh +10 -8
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -4
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/hooks/block-blind-automerge.sh +10 -8
- package/plugins/lisa-devin/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/lisa-devin/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -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-expo-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.devin-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-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.devin-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-nestjs-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.devin-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-openclaw-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.devin-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-phaser-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.devin-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-rails-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.devin-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-typescript-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.devin-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/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-blind-automerge.sh +10 -8
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +14 -7
- package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +2 -2
- package/plugins/src/base/skills/lisa-session-manager/scripts/ledger-questions.mjs +9 -4
- package/scripts/lisa-ruleset-payload.mjs +17 -2
package/package.json
CHANGED
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"qs": "^6.16.0"
|
|
190
190
|
},
|
|
191
191
|
"name": "@codyswann/lisa",
|
|
192
|
-
"version": "4.87.
|
|
192
|
+
"version": "4.87.3",
|
|
193
193
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
194
194
|
"main": "dist/index.js",
|
|
195
195
|
"exports": {
|
|
@@ -347,7 +347,7 @@
|
|
|
347
347
|
"test": "tests"
|
|
348
348
|
},
|
|
349
349
|
"types": "./dist/index.d.ts",
|
|
350
|
-
"lisaReleaseCommit": "
|
|
351
|
-
"gitHead": "
|
|
352
|
-
"lisaReleaseTag": "v4.87.
|
|
350
|
+
"lisaReleaseCommit": "5a1944f7ad31a802d3f47945995b2f938f187a8d",
|
|
351
|
+
"gitHead": "5a1944f7ad31a802d3f47945995b2f938f187a8d",
|
|
352
|
+
"lisaReleaseTag": "v4.87.3"
|
|
353
353
|
}
|
|
@@ -661,17 +661,24 @@ step (e) first — which discriminates a live objection from a stranded one by
|
|
|
661
661
|
unresolved thread count, not by `reviewDecision` — and arm afterwards.
|
|
662
662
|
|
|
663
663
|
**Two ruleset settings decide whether a standing verdict ever clears itself, and
|
|
664
|
-
|
|
665
|
-
than assuming, with `gh api repos/<owner>/<repo>/
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
664
|
+
neither one clears a change request.** Verify them for the repo you are in
|
|
665
|
+
rather than assuming, with `gh api repos/<owner>/<repo>/rules/branches/<base>`
|
|
666
|
+
(the rules in force on the base branch, with their parameters; the
|
|
667
|
+
`gh api repos/<owner>/<repo>/rulesets` list carries no rule parameters):
|
|
668
|
+
|
|
669
|
+
- `dismiss_stale_reviews_on_push` dismisses stale **approvals** only. The `base`
|
|
670
|
+
ruleset Lisa generates defaults it to `true` when
|
|
671
|
+
`policy.protect.dismiss_stale_reviews` is undeclared, and keeps a declared
|
|
672
|
+
value (CodySwannGT/lisa#4574), but a
|
|
673
|
+
`CHANGES_REQUESTED` review made on an older head stays in force after a push
|
|
674
|
+
under either setting (measured on a probe repository on 2026-11-01: the push
|
|
675
|
+
changed the diff and `reviewDecision` stayed `CHANGES_REQUESTED`), so
|
|
676
|
+
**pushing the fix never clears it**.
|
|
670
677
|
- `required_approving_review_count: 0` — no approval is required to merge, so a
|
|
671
678
|
standing `CHANGES_REQUESTED` is not holding a slot for a review that would
|
|
672
679
|
otherwise arrive. Nothing is scheduled to clear it.
|
|
673
680
|
|
|
674
|
-
|
|
681
|
+
Where both hold, the block is **indefinite, not slow**. Waiting is not a
|
|
675
682
|
strategy; something has to act.
|
|
676
683
|
|
|
677
684
|
**Unresolved threads block independently of the verdict.** This repository sets
|
|
@@ -275,7 +275,7 @@ No create, no update, no rotate. Writing secrets or their notes requires an auth
|
|
|
275
275
|
|
|
276
276
|
The two writers are siblings, not modes: `rotate-secret.mjs` replaces a value **at its source**, and `sync-secret-to-ci.mjs` copies one **into a second store** without touching the source. Each needs an authority the resolver must not hold, so each is its own program with its own declaration list.
|
|
277
277
|
|
|
278
|
-
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the
|
|
278
|
+
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the second documented exception in Rule 4, held to the same limits (no shell, the CLI's output never shown, here through `--output none`, and a failure message that never repeats the command line).
|
|
279
279
|
|
|
280
280
|
## Rotating credentials
|
|
281
281
|
|
|
@@ -429,6 +429,6 @@ Cache **in-process only**. Never write a resolved value to disk except through t
|
|
|
429
429
|
1. **Never read a keychain, `.env`, or provider CLI outside this skill.** One chokepoint is what makes the single-store rule enforceable.
|
|
430
430
|
2. **Never log a secret value.** Print a length or a hash prefix when proving identity.
|
|
431
431
|
3. **Never write a resolved value to disk** except through the materialization contract, on a surface whose capabilities permit it.
|
|
432
|
-
4. **Never pass a secret as a command-line argument.**
|
|
432
|
+
4. **Never pass a secret as a command-line argument.** There are two documented exceptions, both Bitwarden writes whose CLI (2.1.0) takes the value only on the command line: `bws secret edit --value` (the rotation write, `writeSecret`) and `bws secret create` (`store-token`'s `createSecret`). Each is confined to that single operation and noted in the code, and both are held to the same limits: no shell, the CLI's output (which echoes the record) is never shown (`create` passes `--output none`; `edit`'s output is captured and discarded), and a failure message never repeats the command line.
|
|
433
433
|
5. **Verify a credential when it is stored, not when it is first used.** An unverified credential is indistinguishable from a broken one, and the gap between the two is measured in weeks.
|
|
434
434
|
6. **Treat a mismatch as stop-and-ask.** If a value differs between two places, they are two live credentials — not a stale copy to be discarded. Deleting the one you cannot verify leaves a working credential that no record accounts for.
|
|
@@ -170,8 +170,8 @@ Per cycle
|
|
|
170
170
|
[--sender <name>] STDIN: question
|
|
171
171
|
--kind: worker-question (default) | repo-route | exhausted | decision.
|
|
172
172
|
No message id with no channel. A re-ask keeps the item's marker
|
|
173
|
-
fields (markerType, marker
|
|
174
|
-
re-ping of the same text.
|
|
173
|
+
fields (markerType, marker); markedAt and a queued answer survive only
|
|
174
|
+
a re-ping of the same text, so a changed question must be marked again.
|
|
175
175
|
ledger marked <ref> --marker-type label|role --marker <value>
|
|
176
176
|
--comment-id <id>
|
|
177
177
|
Records that the human-needed marker was applied on the tracker and
|
|
@@ -177,9 +177,14 @@ export function listPendingQuestions(ledger, answerCommand) {
|
|
|
177
177
|
* gets a placeholder item with zero attempts.
|
|
178
178
|
*
|
|
179
179
|
* A re-ask keeps how the item is marked on the tracker (`markerType`,
|
|
180
|
-
* `marker
|
|
181
|
-
*
|
|
182
|
-
*
|
|
180
|
+
* `marker`), so re-sending a question never makes the manager forget the
|
|
181
|
+
* marker it must remove. `markedAt` survives only a re-ping of the same text:
|
|
182
|
+
* a changed question has not been posted or read back yet, so it reads as
|
|
183
|
+
* unmarked until `ledger marked` records the new question comment. Keeping it
|
|
184
|
+
* would let the next cycle run `item-answer` against the old question comment
|
|
185
|
+
* and queue a reply to the old question as the answer to the new one
|
|
186
|
+
* (CodySwannGT/lisa#4570). A queued answer survives only a re-ping of the same
|
|
187
|
+
* text for the same reason: it answered that text.
|
|
183
188
|
* `kind` defaults to the previous kind, then `worker-question`.
|
|
184
189
|
* @param {string} projectRoot - Absolute project root
|
|
185
190
|
* @param {{ref: string, text: string, telegramMessageId: number|null,
|
|
@@ -213,7 +218,7 @@ export function setPendingQuestion(
|
|
|
213
218
|
telegramMessageId: telegramMessageId ?? null,
|
|
214
219
|
markerType: existing?.markerType ?? null,
|
|
215
220
|
marker: existing?.marker ?? null,
|
|
216
|
-
markedAt: existing?.markedAt ?? null,
|
|
221
|
+
markedAt: sameText ? (existing?.markedAt ?? null) : null,
|
|
217
222
|
managerCommentIds: existing?.managerCommentIds ?? [],
|
|
218
223
|
answer: sameText ? (existing.answer ?? null) : null,
|
|
219
224
|
},
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
# for hours before someone queried the field by hand.
|
|
17
17
|
#
|
|
18
18
|
# The stale case is the one actually observed, and it is the one prose cannot
|
|
19
|
-
# help with:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
19
|
+
# help with: a CHANGES_REQUESTED review stays attached to the commit it was made
|
|
20
|
+
# on and pushing the fix never clears it, while
|
|
21
|
+
# `required_approving_review_count: 0` means no scheduled re-review will ever
|
|
22
|
+
# clear it either. The PR is blocked indefinitely and every check-based signal
|
|
23
|
+
# reads ready. `dismiss_stale_reviews_on_push` does not help: GitHub dismisses
|
|
24
|
+
# stale APPROVALS only, so a change request survives a push under either
|
|
25
|
+
# setting (measured 2026-11-01, CodySwannGT/lisa#4574).
|
|
24
26
|
#
|
|
25
27
|
# WHAT IT CHECKS, AND WHY THAT AND NOT THE CHECK ROLLUP
|
|
26
28
|
#
|
|
@@ -1048,9 +1050,9 @@ REFUSAL = """Blocked: arming auto-merge on %s would report a merge that can neve
|
|
|
1048
1050
|
|
|
1049
1051
|
GitHub reports reviewDecision = CHANGES_REQUESTED. That is a merge blocker with
|
|
1050
1052
|
NO check-run representation: the failing-check count reads 0 and the checks tab
|
|
1051
|
-
is entirely green while the PR is permanently unmergeable.
|
|
1052
|
-
|
|
1053
|
-
no re-review is scheduled to.
|
|
1053
|
+
is entirely green while the PR is permanently unmergeable. Pushing the fix does
|
|
1054
|
+
not clear it (dismiss_stale_reviews_on_push dismisses stale approvals only, not
|
|
1055
|
+
change requests), and no re-review is scheduled to.
|
|
1054
1056
|
|
|
1055
1057
|
Clear the blocker, then arm:
|
|
1056
1058
|
- resolve every review thread and address or reply to the feedback
|
|
@@ -659,17 +659,24 @@ step (e) first — which discriminates a live objection from a stranded one by
|
|
|
659
659
|
unresolved thread count, not by `reviewDecision` — and arm afterwards.
|
|
660
660
|
|
|
661
661
|
**Two ruleset settings decide whether a standing verdict ever clears itself, and
|
|
662
|
-
|
|
663
|
-
than assuming, with `gh api repos/<owner>/<repo>/
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
662
|
+
neither one clears a change request.** Verify them for the repo you are in
|
|
663
|
+
rather than assuming, with `gh api repos/<owner>/<repo>/rules/branches/<base>`
|
|
664
|
+
(the rules in force on the base branch, with their parameters; the
|
|
665
|
+
`gh api repos/<owner>/<repo>/rulesets` list carries no rule parameters):
|
|
666
|
+
|
|
667
|
+
- `dismiss_stale_reviews_on_push` dismisses stale **approvals** only. The `base`
|
|
668
|
+
ruleset Lisa generates defaults it to `true` when
|
|
669
|
+
`policy.protect.dismiss_stale_reviews` is undeclared, and keeps a declared
|
|
670
|
+
value (CodySwannGT/lisa#4574), but a
|
|
671
|
+
`CHANGES_REQUESTED` review made on an older head stays in force after a push
|
|
672
|
+
under either setting (measured on a probe repository on 2026-11-01: the push
|
|
673
|
+
changed the diff and `reviewDecision` stayed `CHANGES_REQUESTED`), so
|
|
674
|
+
**pushing the fix never clears it**.
|
|
668
675
|
- `required_approving_review_count: 0` — no approval is required to merge, so a
|
|
669
676
|
standing `CHANGES_REQUESTED` is not holding a slot for a review that would
|
|
670
677
|
otherwise arrive. Nothing is scheduled to clear it.
|
|
671
678
|
|
|
672
|
-
|
|
679
|
+
Where both hold, the block is **indefinite, not slow**. Waiting is not a
|
|
673
680
|
strategy; something has to act.
|
|
674
681
|
|
|
675
682
|
**Unresolved threads block independently of the verdict.** This repository sets
|
|
@@ -273,7 +273,7 @@ No create, no update, no rotate. Writing secrets or their notes requires an auth
|
|
|
273
273
|
|
|
274
274
|
The two writers are siblings, not modes: `rotate-secret.mjs` replaces a value **at its source**, and `sync-secret-to-ci.mjs` copies one **into a second store** without touching the source. Each needs an authority the resolver must not hold, so each is its own program with its own declaration list.
|
|
275
275
|
|
|
276
|
-
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the
|
|
276
|
+
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the second documented exception in Rule 4, held to the same limits (no shell, the CLI's output never shown, here through `--output none`, and a failure message that never repeats the command line).
|
|
277
277
|
|
|
278
278
|
## Rotating credentials
|
|
279
279
|
|
|
@@ -427,6 +427,6 @@ Cache **in-process only**. Never write a resolved value to disk except through t
|
|
|
427
427
|
1. **Never read a keychain, `.env`, or provider CLI outside this skill.** One chokepoint is what makes the single-store rule enforceable.
|
|
428
428
|
2. **Never log a secret value.** Print a length or a hash prefix when proving identity.
|
|
429
429
|
3. **Never write a resolved value to disk** except through the materialization contract, on a surface whose capabilities permit it.
|
|
430
|
-
4. **Never pass a secret as a command-line argument.**
|
|
430
|
+
4. **Never pass a secret as a command-line argument.** There are two documented exceptions, both Bitwarden writes whose CLI (2.1.0) takes the value only on the command line: `bws secret edit --value` (the rotation write, `writeSecret`) and `bws secret create` (`store-token`'s `createSecret`). Each is confined to that single operation and noted in the code, and both are held to the same limits: no shell, the CLI's output (which echoes the record) is never shown (`create` passes `--output none`; `edit`'s output is captured and discarded), and a failure message never repeats the command line.
|
|
431
431
|
5. **Verify a credential when it is stored, not when it is first used.** An unverified credential is indistinguishable from a broken one, and the gap between the two is measured in weeks.
|
|
432
432
|
6. **Treat a mismatch as stop-and-ask.** If a value differs between two places, they are two live credentials — not a stale copy to be discarded. Deleting the one you cannot verify leaves a working credential that no record accounts for.
|
|
@@ -170,8 +170,8 @@ Per cycle
|
|
|
170
170
|
[--sender <name>] STDIN: question
|
|
171
171
|
--kind: worker-question (default) | repo-route | exhausted | decision.
|
|
172
172
|
No message id with no channel. A re-ask keeps the item's marker
|
|
173
|
-
fields (markerType, marker
|
|
174
|
-
re-ping of the same text.
|
|
173
|
+
fields (markerType, marker); markedAt and a queued answer survive only
|
|
174
|
+
a re-ping of the same text, so a changed question must be marked again.
|
|
175
175
|
ledger marked <ref> --marker-type label|role --marker <value>
|
|
176
176
|
--comment-id <id>
|
|
177
177
|
Records that the human-needed marker was applied on the tracker and
|
|
@@ -177,9 +177,14 @@ export function listPendingQuestions(ledger, answerCommand) {
|
|
|
177
177
|
* gets a placeholder item with zero attempts.
|
|
178
178
|
*
|
|
179
179
|
* A re-ask keeps how the item is marked on the tracker (`markerType`,
|
|
180
|
-
* `marker
|
|
181
|
-
*
|
|
182
|
-
*
|
|
180
|
+
* `marker`), so re-sending a question never makes the manager forget the
|
|
181
|
+
* marker it must remove. `markedAt` survives only a re-ping of the same text:
|
|
182
|
+
* a changed question has not been posted or read back yet, so it reads as
|
|
183
|
+
* unmarked until `ledger marked` records the new question comment. Keeping it
|
|
184
|
+
* would let the next cycle run `item-answer` against the old question comment
|
|
185
|
+
* and queue a reply to the old question as the answer to the new one
|
|
186
|
+
* (CodySwannGT/lisa#4570). A queued answer survives only a re-ping of the same
|
|
187
|
+
* text for the same reason: it answered that text.
|
|
183
188
|
* `kind` defaults to the previous kind, then `worker-question`.
|
|
184
189
|
* @param {string} projectRoot - Absolute project root
|
|
185
190
|
* @param {{ref: string, text: string, telegramMessageId: number|null,
|
|
@@ -213,7 +218,7 @@ export function setPendingQuestion(
|
|
|
213
218
|
telegramMessageId: telegramMessageId ?? null,
|
|
214
219
|
markerType: existing?.markerType ?? null,
|
|
215
220
|
marker: existing?.marker ?? null,
|
|
216
|
-
markedAt: existing?.markedAt ?? null,
|
|
221
|
+
markedAt: sameText ? (existing?.markedAt ?? null) : null,
|
|
217
222
|
managerCommentIds: existing?.managerCommentIds ?? [],
|
|
218
223
|
answer: sameText ? (existing.answer ?? null) : null,
|
|
219
224
|
},
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
# for hours before someone queried the field by hand.
|
|
17
17
|
#
|
|
18
18
|
# The stale case is the one actually observed, and it is the one prose cannot
|
|
19
|
-
# help with:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
19
|
+
# help with: a CHANGES_REQUESTED review stays attached to the commit it was made
|
|
20
|
+
# on and pushing the fix never clears it, while
|
|
21
|
+
# `required_approving_review_count: 0` means no scheduled re-review will ever
|
|
22
|
+
# clear it either. The PR is blocked indefinitely and every check-based signal
|
|
23
|
+
# reads ready. `dismiss_stale_reviews_on_push` does not help: GitHub dismisses
|
|
24
|
+
# stale APPROVALS only, so a change request survives a push under either
|
|
25
|
+
# setting (measured 2026-11-01, CodySwannGT/lisa#4574).
|
|
24
26
|
#
|
|
25
27
|
# WHAT IT CHECKS, AND WHY THAT AND NOT THE CHECK ROLLUP
|
|
26
28
|
#
|
|
@@ -1048,9 +1050,9 @@ REFUSAL = """Blocked: arming auto-merge on %s would report a merge that can neve
|
|
|
1048
1050
|
|
|
1049
1051
|
GitHub reports reviewDecision = CHANGES_REQUESTED. That is a merge blocker with
|
|
1050
1052
|
NO check-run representation: the failing-check count reads 0 and the checks tab
|
|
1051
|
-
is entirely green while the PR is permanently unmergeable.
|
|
1052
|
-
|
|
1053
|
-
no re-review is scheduled to.
|
|
1053
|
+
is entirely green while the PR is permanently unmergeable. Pushing the fix does
|
|
1054
|
+
not clear it (dismiss_stale_reviews_on_push dismisses stale approvals only, not
|
|
1055
|
+
change requests), and no re-review is scheduled to.
|
|
1054
1056
|
|
|
1055
1057
|
Clear the blocker, then arm:
|
|
1056
1058
|
- resolve every review thread and address or reply to the feedback
|
|
@@ -659,17 +659,24 @@ step (e) first — which discriminates a live objection from a stranded one by
|
|
|
659
659
|
unresolved thread count, not by `reviewDecision` — and arm afterwards.
|
|
660
660
|
|
|
661
661
|
**Two ruleset settings decide whether a standing verdict ever clears itself, and
|
|
662
|
-
|
|
663
|
-
than assuming, with `gh api repos/<owner>/<repo>/
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
662
|
+
neither one clears a change request.** Verify them for the repo you are in
|
|
663
|
+
rather than assuming, with `gh api repos/<owner>/<repo>/rules/branches/<base>`
|
|
664
|
+
(the rules in force on the base branch, with their parameters; the
|
|
665
|
+
`gh api repos/<owner>/<repo>/rulesets` list carries no rule parameters):
|
|
666
|
+
|
|
667
|
+
- `dismiss_stale_reviews_on_push` dismisses stale **approvals** only. The `base`
|
|
668
|
+
ruleset Lisa generates defaults it to `true` when
|
|
669
|
+
`policy.protect.dismiss_stale_reviews` is undeclared, and keeps a declared
|
|
670
|
+
value (CodySwannGT/lisa#4574), but a
|
|
671
|
+
`CHANGES_REQUESTED` review made on an older head stays in force after a push
|
|
672
|
+
under either setting (measured on a probe repository on 2026-11-01: the push
|
|
673
|
+
changed the diff and `reviewDecision` stayed `CHANGES_REQUESTED`), so
|
|
674
|
+
**pushing the fix never clears it**.
|
|
668
675
|
- `required_approving_review_count: 0` — no approval is required to merge, so a
|
|
669
676
|
standing `CHANGES_REQUESTED` is not holding a slot for a review that would
|
|
670
677
|
otherwise arrive. Nothing is scheduled to clear it.
|
|
671
678
|
|
|
672
|
-
|
|
679
|
+
Where both hold, the block is **indefinite, not slow**. Waiting is not a
|
|
673
680
|
strategy; something has to act.
|
|
674
681
|
|
|
675
682
|
**Unresolved threads block independently of the verdict.** This repository sets
|
|
@@ -273,7 +273,7 @@ No create, no update, no rotate. Writing secrets or their notes requires an auth
|
|
|
273
273
|
|
|
274
274
|
The two writers are siblings, not modes: `rotate-secret.mjs` replaces a value **at its source**, and `sync-secret-to-ci.mjs` copies one **into a second store** without touching the source. Each needs an authority the resolver must not hold, so each is its own program with its own declaration list.
|
|
275
275
|
|
|
276
|
-
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the
|
|
276
|
+
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the second documented exception in Rule 4, held to the same limits (no shell, the CLI's output never shown, here through `--output none`, and a failure message that never repeats the command line).
|
|
277
277
|
|
|
278
278
|
## Rotating credentials
|
|
279
279
|
|
|
@@ -427,6 +427,6 @@ Cache **in-process only**. Never write a resolved value to disk except through t
|
|
|
427
427
|
1. **Never read a keychain, `.env`, or provider CLI outside this skill.** One chokepoint is what makes the single-store rule enforceable.
|
|
428
428
|
2. **Never log a secret value.** Print a length or a hash prefix when proving identity.
|
|
429
429
|
3. **Never write a resolved value to disk** except through the materialization contract, on a surface whose capabilities permit it.
|
|
430
|
-
4. **Never pass a secret as a command-line argument.**
|
|
430
|
+
4. **Never pass a secret as a command-line argument.** There are two documented exceptions, both Bitwarden writes whose CLI (2.1.0) takes the value only on the command line: `bws secret edit --value` (the rotation write, `writeSecret`) and `bws secret create` (`store-token`'s `createSecret`). Each is confined to that single operation and noted in the code, and both are held to the same limits: no shell, the CLI's output (which echoes the record) is never shown (`create` passes `--output none`; `edit`'s output is captured and discarded), and a failure message never repeats the command line.
|
|
431
431
|
5. **Verify a credential when it is stored, not when it is first used.** An unverified credential is indistinguishable from a broken one, and the gap between the two is measured in weeks.
|
|
432
432
|
6. **Treat a mismatch as stop-and-ask.** If a value differs between two places, they are two live credentials — not a stale copy to be discarded. Deleting the one you cannot verify leaves a working credential that no record accounts for.
|
|
@@ -170,8 +170,8 @@ Per cycle
|
|
|
170
170
|
[--sender <name>] STDIN: question
|
|
171
171
|
--kind: worker-question (default) | repo-route | exhausted | decision.
|
|
172
172
|
No message id with no channel. A re-ask keeps the item's marker
|
|
173
|
-
fields (markerType, marker
|
|
174
|
-
re-ping of the same text.
|
|
173
|
+
fields (markerType, marker); markedAt and a queued answer survive only
|
|
174
|
+
a re-ping of the same text, so a changed question must be marked again.
|
|
175
175
|
ledger marked <ref> --marker-type label|role --marker <value>
|
|
176
176
|
--comment-id <id>
|
|
177
177
|
Records that the human-needed marker was applied on the tracker and
|
|
@@ -177,9 +177,14 @@ export function listPendingQuestions(ledger, answerCommand) {
|
|
|
177
177
|
* gets a placeholder item with zero attempts.
|
|
178
178
|
*
|
|
179
179
|
* A re-ask keeps how the item is marked on the tracker (`markerType`,
|
|
180
|
-
* `marker
|
|
181
|
-
*
|
|
182
|
-
*
|
|
180
|
+
* `marker`), so re-sending a question never makes the manager forget the
|
|
181
|
+
* marker it must remove. `markedAt` survives only a re-ping of the same text:
|
|
182
|
+
* a changed question has not been posted or read back yet, so it reads as
|
|
183
|
+
* unmarked until `ledger marked` records the new question comment. Keeping it
|
|
184
|
+
* would let the next cycle run `item-answer` against the old question comment
|
|
185
|
+
* and queue a reply to the old question as the answer to the new one
|
|
186
|
+
* (CodySwannGT/lisa#4570). A queued answer survives only a re-ping of the same
|
|
187
|
+
* text for the same reason: it answered that text.
|
|
183
188
|
* `kind` defaults to the previous kind, then `worker-question`.
|
|
184
189
|
* @param {string} projectRoot - Absolute project root
|
|
185
190
|
* @param {{ref: string, text: string, telegramMessageId: number|null,
|
|
@@ -213,7 +218,7 @@ export function setPendingQuestion(
|
|
|
213
218
|
telegramMessageId: telegramMessageId ?? null,
|
|
214
219
|
markerType: existing?.markerType ?? null,
|
|
215
220
|
marker: existing?.marker ?? null,
|
|
216
|
-
markedAt: existing?.markedAt ?? null,
|
|
221
|
+
markedAt: sameText ? (existing?.markedAt ?? null) : null,
|
|
217
222
|
managerCommentIds: existing?.managerCommentIds ?? [],
|
|
218
223
|
answer: sameText ? (existing.answer ?? null) : null,
|
|
219
224
|
},
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
# for hours before someone queried the field by hand.
|
|
17
17
|
#
|
|
18
18
|
# The stale case is the one actually observed, and it is the one prose cannot
|
|
19
|
-
# help with:
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
19
|
+
# help with: a CHANGES_REQUESTED review stays attached to the commit it was made
|
|
20
|
+
# on and pushing the fix never clears it, while
|
|
21
|
+
# `required_approving_review_count: 0` means no scheduled re-review will ever
|
|
22
|
+
# clear it either. The PR is blocked indefinitely and every check-based signal
|
|
23
|
+
# reads ready. `dismiss_stale_reviews_on_push` does not help: GitHub dismisses
|
|
24
|
+
# stale APPROVALS only, so a change request survives a push under either
|
|
25
|
+
# setting (measured 2026-11-01, CodySwannGT/lisa#4574).
|
|
24
26
|
#
|
|
25
27
|
# WHAT IT CHECKS, AND WHY THAT AND NOT THE CHECK ROLLUP
|
|
26
28
|
#
|
|
@@ -1048,9 +1050,9 @@ REFUSAL = """Blocked: arming auto-merge on %s would report a merge that can neve
|
|
|
1048
1050
|
|
|
1049
1051
|
GitHub reports reviewDecision = CHANGES_REQUESTED. That is a merge blocker with
|
|
1050
1052
|
NO check-run representation: the failing-check count reads 0 and the checks tab
|
|
1051
|
-
is entirely green while the PR is permanently unmergeable.
|
|
1052
|
-
|
|
1053
|
-
no re-review is scheduled to.
|
|
1053
|
+
is entirely green while the PR is permanently unmergeable. Pushing the fix does
|
|
1054
|
+
not clear it (dismiss_stale_reviews_on_push dismisses stale approvals only, not
|
|
1055
|
+
change requests), and no re-review is scheduled to.
|
|
1054
1056
|
|
|
1055
1057
|
Clear the blocker, then arm:
|
|
1056
1058
|
- resolve every review thread and address or reply to the feedback
|
|
@@ -659,17 +659,24 @@ step (e) first — which discriminates a live objection from a stranded one by
|
|
|
659
659
|
unresolved thread count, not by `reviewDecision` — and arm afterwards.
|
|
660
660
|
|
|
661
661
|
**Two ruleset settings decide whether a standing verdict ever clears itself, and
|
|
662
|
-
|
|
663
|
-
than assuming, with `gh api repos/<owner>/<repo>/
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
662
|
+
neither one clears a change request.** Verify them for the repo you are in
|
|
663
|
+
rather than assuming, with `gh api repos/<owner>/<repo>/rules/branches/<base>`
|
|
664
|
+
(the rules in force on the base branch, with their parameters; the
|
|
665
|
+
`gh api repos/<owner>/<repo>/rulesets` list carries no rule parameters):
|
|
666
|
+
|
|
667
|
+
- `dismiss_stale_reviews_on_push` dismisses stale **approvals** only. The `base`
|
|
668
|
+
ruleset Lisa generates defaults it to `true` when
|
|
669
|
+
`policy.protect.dismiss_stale_reviews` is undeclared, and keeps a declared
|
|
670
|
+
value (CodySwannGT/lisa#4574), but a
|
|
671
|
+
`CHANGES_REQUESTED` review made on an older head stays in force after a push
|
|
672
|
+
under either setting (measured on a probe repository on 2026-11-01: the push
|
|
673
|
+
changed the diff and `reviewDecision` stayed `CHANGES_REQUESTED`), so
|
|
674
|
+
**pushing the fix never clears it**.
|
|
668
675
|
- `required_approving_review_count: 0` — no approval is required to merge, so a
|
|
669
676
|
standing `CHANGES_REQUESTED` is not holding a slot for a review that would
|
|
670
677
|
otherwise arrive. Nothing is scheduled to clear it.
|
|
671
678
|
|
|
672
|
-
|
|
679
|
+
Where both hold, the block is **indefinite, not slow**. Waiting is not a
|
|
673
680
|
strategy; something has to act.
|
|
674
681
|
|
|
675
682
|
**Unresolved threads block independently of the verdict.** This repository sets
|
|
@@ -273,7 +273,7 @@ No create, no update, no rotate. Writing secrets or their notes requires an auth
|
|
|
273
273
|
|
|
274
274
|
The two writers are siblings, not modes: `rotate-secret.mjs` replaces a value **at its source**, and `sync-secret-to-ci.mjs` copies one **into a second store** without touching the source. Each needs an authority the resolver must not hold, so each is its own program with its own declaration list.
|
|
275
275
|
|
|
276
|
-
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the
|
|
276
|
+
A third, narrow writer lives outside this skill: the session manager's `store-token` (`lisa-session-manager/scripts/token-store.mjs`) stores the Telegram bot token as `TELEGRAM_BOT_TOKEN_<NAME>` through `providers.mjs` `createSecret` (a new record) or `writeSecret` (`--replace`), then reads it back through the ordinary provider view and compares hashes before deleting the plaintext file it replaces. It writes that one name only, and only when an operator runs it. `createSecret` is Bitwarden-only; on any other provider the operator creates the secret and `store-token` verifies it. Like `bws secret edit`, `bws secret create` (2.1.0) takes the value as a positional argument and its help offers no stdin or file option, so the value is in argv for the life of that one call: the second documented exception in Rule 4, held to the same limits (no shell, the CLI's output never shown, here through `--output none`, and a failure message that never repeats the command line).
|
|
277
277
|
|
|
278
278
|
## Rotating credentials
|
|
279
279
|
|
|
@@ -427,6 +427,6 @@ Cache **in-process only**. Never write a resolved value to disk except through t
|
|
|
427
427
|
1. **Never read a keychain, `.env`, or provider CLI outside this skill.** One chokepoint is what makes the single-store rule enforceable.
|
|
428
428
|
2. **Never log a secret value.** Print a length or a hash prefix when proving identity.
|
|
429
429
|
3. **Never write a resolved value to disk** except through the materialization contract, on a surface whose capabilities permit it.
|
|
430
|
-
4. **Never pass a secret as a command-line argument.**
|
|
430
|
+
4. **Never pass a secret as a command-line argument.** There are two documented exceptions, both Bitwarden writes whose CLI (2.1.0) takes the value only on the command line: `bws secret edit --value` (the rotation write, `writeSecret`) and `bws secret create` (`store-token`'s `createSecret`). Each is confined to that single operation and noted in the code, and both are held to the same limits: no shell, the CLI's output (which echoes the record) is never shown (`create` passes `--output none`; `edit`'s output is captured and discarded), and a failure message never repeats the command line.
|
|
431
431
|
5. **Verify a credential when it is stored, not when it is first used.** An unverified credential is indistinguishable from a broken one, and the gap between the two is measured in weeks.
|
|
432
432
|
6. **Treat a mismatch as stop-and-ask.** If a value differs between two places, they are two live credentials — not a stale copy to be discarded. Deleting the one you cannot verify leaves a working credential that no record accounts for.
|
|
@@ -170,8 +170,8 @@ Per cycle
|
|
|
170
170
|
[--sender <name>] STDIN: question
|
|
171
171
|
--kind: worker-question (default) | repo-route | exhausted | decision.
|
|
172
172
|
No message id with no channel. A re-ask keeps the item's marker
|
|
173
|
-
fields (markerType, marker
|
|
174
|
-
re-ping of the same text.
|
|
173
|
+
fields (markerType, marker); markedAt and a queued answer survive only
|
|
174
|
+
a re-ping of the same text, so a changed question must be marked again.
|
|
175
175
|
ledger marked <ref> --marker-type label|role --marker <value>
|
|
176
176
|
--comment-id <id>
|
|
177
177
|
Records that the human-needed marker was applied on the tracker and
|