@codyswann/lisa 4.83.2 → 4.84.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/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -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 +17 -13
- 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-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa/scripts/ready-queue-order.mjs +109 -6
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/ready-queue-order.mjs +109 -6
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-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/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +109 -6
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -0
- package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +109 -6
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-devin/scripts/ready-queue-order.mjs +109 -6
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-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/rules/reference/config-resolution.md +1 -0
- package/plugins/src/base/scripts/ready-queue-order.mjs +109 -6
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +39 -0
- package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +6 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +1 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
- package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +8 -1
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +276 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.84.1",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -735,6 +735,7 @@ have a `.lisa.config.json` and their own `.git` (written as `"root": "."`).
|
|
|
735
735
|
| `sessionManager.notify.stateDir` | **yes** | — | This project's own Telegram state dir. Never the shared default `~/.claude/channels/telegram`, because one bot per state dir is how projects stay isolated. Setup writes `~/.claude/channels/telegram-<project>`. |
|
|
736
736
|
| `sessionManager.trustedAuthorIds` | no | — | Optional list of **stable tracker user ids**, as strings: the GitHub numeric user id (`gh api users/<login> --jq .id`), the Jira `accountId`, or the Linear user id. Not logins or display names: matching compares ids, so a login silently matches no one and excludes every item. When set, the manager takes only items authored by these people. |
|
|
737
737
|
| `sessionManager.operatorIds` | no | — | Optional, non-empty list of the people the manager works for, as tracker user ids matched against the **assignee** the same way "me" is: the GitHub **login**, the Jira `accountId`, or the Linear user id, spelled exactly as the tracker API returns it (matching is case-sensitive, so look the OPERATOR up rather than typing from memory: `gh api users/<operator-login> --jq .login` returns the canonical GitHub spelling. Do not use `gh api user`, which returns whoever the credential belongs to — the agent account, when that is the case this key exists for). Dispatch order is assigned to an operator, then to "me" (the tracker credential's identity), then unassigned; items assigned to anyone else are skipped. Set it whenever the tracker credential is not the operator's own (an agent account), or the operator's Ready items are never dispatched. |
|
|
738
|
+
| `sessionManager.backlogPickup` | no | `false` | Opt-in lowest dispatch tier. When the Ready queue is exhausted and capacity remains, the manager considers backlog items (open leaves in no lifecycle role) with the Ready exclusions plus any unfinished blocker; each one that passes the tracker validator (all gates, including the stateless-pickup dry-run) is assigned to the first `operatorIds` entry, moved to Ready in one write, read back and dispatched. A failed validation is cached against the item's `updatedAt`, so it is re-judged only after the item changes. Requires `operatorIds`. |
|
|
738
739
|
|
|
739
740
|
- **Non-secret only.** The bot token lives in `<stateDir>/.env` (directory 0700, file 0600),
|
|
740
741
|
written by setup. A credential-named field or a token-shaped value anywhere in this block is a
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
*
|
|
30
30
|
* Held items are judged by {@link isHumanGated}, the one shared answer to "is
|
|
31
31
|
* a person holding this?", rather than by a local label check (#3805).
|
|
32
|
+
*
|
|
33
|
+
* {@link orderBacklogQueue} ranks a second, lower tier: open leaf items in no
|
|
34
|
+
* lifecycle role, which the session manager may promote to Ready when
|
|
35
|
+
* `sessionManager.backlogPickup` is on. It applies every Ready exclusion plus
|
|
36
|
+
* two of its own (an unfinished blocker, an unchanged validation failure), and
|
|
37
|
+
* its list is always walked after the whole Ready queue.
|
|
32
38
|
* @module ready-queue-order
|
|
33
39
|
*/
|
|
34
40
|
|
|
@@ -67,6 +73,9 @@ const LINEAR_NO_PRIORITY = 4;
|
|
|
67
73
|
/** Linear workflow state types that leave a child finished. */
|
|
68
74
|
const LINEAR_FINISHED_STATE_TYPES = new Set(["completed", "canceled"]);
|
|
69
75
|
|
|
76
|
+
/** Jira link types whose inward side reads as "this item is blocked by". */
|
|
77
|
+
const JIRA_BLOCKED_BY = /blocked by/i;
|
|
78
|
+
|
|
70
79
|
/**
|
|
71
80
|
* @param {unknown} value
|
|
72
81
|
* @returns {string}
|
|
@@ -88,6 +97,26 @@ function idOrNull(value) {
|
|
|
88
97
|
return Number.isSafeInteger(value) ? String(value) : null;
|
|
89
98
|
}
|
|
90
99
|
|
|
100
|
+
/**
|
|
101
|
+
* A non-negative integer count, 0 for anything else.
|
|
102
|
+
* @param {unknown} value
|
|
103
|
+
* @returns {number}
|
|
104
|
+
*/
|
|
105
|
+
function count(value) {
|
|
106
|
+
const number = Number(value);
|
|
107
|
+
return Number.isInteger(number) && number > 0 ? number : 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The rows of an array or a GraphQL `{nodes}` connection.
|
|
112
|
+
* @param {unknown} list
|
|
113
|
+
* @returns {any[]}
|
|
114
|
+
*/
|
|
115
|
+
function nodesOf(list) {
|
|
116
|
+
if (Array.isArray(list)) return list;
|
|
117
|
+
return Array.isArray(list?.nodes) ? list.nodes : [];
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
/**
|
|
92
121
|
* Label names from strings, `{name}` objects or a GraphQL `{nodes}` connection.
|
|
93
122
|
* @param {unknown} labels
|
|
@@ -160,11 +189,14 @@ export function normalizeGithubIssue(issue, options = {}) {
|
|
|
160
189
|
assigneeId: idOrNull(assignee?.login),
|
|
161
190
|
priorityRank: ranks.length > 0 ? Math.min(...ranks) : GITHUB_NO_PRIORITY,
|
|
162
191
|
createdAt: text(issue.created_at),
|
|
192
|
+
updatedAt: text(issue.updated_at),
|
|
163
193
|
labels,
|
|
164
194
|
body: text(issue.body),
|
|
165
195
|
// Authors are trusted by stable id, never by login (ready-role-filing).
|
|
166
196
|
authorId: idOrNull(issue.user?.id),
|
|
167
197
|
isContainer: Number(summary?.total ?? 0) > Number(summary?.completed ?? 0),
|
|
198
|
+
// `blocked_by` in the dependency summary counts only OPEN blockers.
|
|
199
|
+
openBlockers: count(issue.issue_dependencies_summary?.blocked_by),
|
|
168
200
|
});
|
|
169
201
|
}
|
|
170
202
|
|
|
@@ -182,6 +214,12 @@ export function normalizeJiraIssue(issue) {
|
|
|
182
214
|
const openSubtasks = (Array.isArray(fields.subtasks) ? fields.subtasks : [])
|
|
183
215
|
.map(sub => sub?.fields?.status?.statusCategory?.key)
|
|
184
216
|
.filter(category => category !== "done");
|
|
217
|
+
const openBlockers = nodesOf(fields.issuelinks).filter(
|
|
218
|
+
link =>
|
|
219
|
+
link?.inwardIssue &&
|
|
220
|
+
JIRA_BLOCKED_BY.test(text(link.type?.inward)) &&
|
|
221
|
+
link.inwardIssue.fields?.status?.statusCategory?.key !== "done"
|
|
222
|
+
).length;
|
|
185
223
|
|
|
186
224
|
return shape({
|
|
187
225
|
ref: text(issue?.key),
|
|
@@ -189,6 +227,7 @@ export function normalizeJiraIssue(issue) {
|
|
|
189
227
|
assigneeId: idOrNull(fields.assignee?.accountId),
|
|
190
228
|
priorityRank: JIRA_PRIORITY_RANKS[priority] ?? JIRA_UNKNOWN_PRIORITY,
|
|
191
229
|
createdAt: text(fields.created),
|
|
230
|
+
updatedAt: text(fields.updated),
|
|
192
231
|
labels,
|
|
193
232
|
body: adfText(fields.description),
|
|
194
233
|
authorId: idOrNull(fields.reporter?.accountId ?? fields.creator?.accountId),
|
|
@@ -196,6 +235,7 @@ export function normalizeJiraIssue(issue) {
|
|
|
196
235
|
Number(type.hierarchyLevel ?? 0) > 0 ||
|
|
197
236
|
text(type.name).toLowerCase() === "epic" ||
|
|
198
237
|
openSubtasks.length > 0,
|
|
238
|
+
openBlockers,
|
|
199
239
|
});
|
|
200
240
|
}
|
|
201
241
|
|
|
@@ -213,6 +253,13 @@ export function normalizeLinearIssue(issue) {
|
|
|
213
253
|
? issue.children
|
|
214
254
|
: [];
|
|
215
255
|
const labels = labelNames(issue?.labels);
|
|
256
|
+
// Linear has no "blocked by" relation type: B blocked by A is A's `blocks`
|
|
257
|
+
// relation, read from B as an inverse relation.
|
|
258
|
+
const openBlockers = nodesOf(issue?.inverseRelations).filter(
|
|
259
|
+
relation =>
|
|
260
|
+
relation?.type === "blocks" &&
|
|
261
|
+
!LINEAR_FINISHED_STATE_TYPES.has(relation.issue?.state?.type)
|
|
262
|
+
).length;
|
|
216
263
|
|
|
217
264
|
return shape({
|
|
218
265
|
ref: text(issue?.identifier),
|
|
@@ -223,18 +270,22 @@ export function normalizeLinearIssue(issue) {
|
|
|
223
270
|
? native - 1
|
|
224
271
|
: LINEAR_NO_PRIORITY,
|
|
225
272
|
createdAt: text(issue?.createdAt),
|
|
273
|
+
updatedAt: text(issue?.updatedAt),
|
|
226
274
|
labels,
|
|
227
275
|
body: text(issue?.description),
|
|
228
276
|
authorId: idOrNull(issue?.creator?.id),
|
|
229
277
|
isContainer: children.some(
|
|
230
278
|
child => !LINEAR_FINISHED_STATE_TYPES.has(child?.state?.type)
|
|
231
279
|
),
|
|
280
|
+
openBlockers,
|
|
232
281
|
});
|
|
233
282
|
}
|
|
234
283
|
|
|
235
284
|
/**
|
|
236
285
|
* The one item shape every normalizer returns, in a fixed key order.
|
|
237
|
-
*
|
|
286
|
+
* `updatedAt` keys the backlog tier's validation cache; `openBlockers` counts
|
|
287
|
+
* dependencies whose blocker is not finished. The Ready tier ignores both.
|
|
288
|
+
* @param {{ ref: string, title: string, assigneeId: string | null, priorityRank: number, createdAt: string, updatedAt: string, labels: string[], body: string, authorId: string | null, isContainer: boolean, openBlockers: number }} input
|
|
238
289
|
*/
|
|
239
290
|
function shape(input) {
|
|
240
291
|
return {
|
|
@@ -243,10 +294,12 @@ function shape(input) {
|
|
|
243
294
|
assigneeId: input.assigneeId,
|
|
244
295
|
priorityRank: input.priorityRank,
|
|
245
296
|
createdAt: input.createdAt,
|
|
297
|
+
updatedAt: input.updatedAt,
|
|
246
298
|
labels: input.labels,
|
|
247
299
|
body: input.body,
|
|
248
300
|
authorId: input.authorId,
|
|
249
301
|
isContainer: input.isContainer,
|
|
302
|
+
openBlockers: input.openBlockers,
|
|
250
303
|
repoLabels: repoLabelsOf(input.labels),
|
|
251
304
|
};
|
|
252
305
|
}
|
|
@@ -320,7 +373,50 @@ function compareWithinGroup(left, right) {
|
|
|
320
373
|
* @returns {{ ordered: Record<string, any>[], excluded: { ref: string, reason: string }[] }}
|
|
321
374
|
*/
|
|
322
375
|
export function orderReadyQueue(items, options = {}) {
|
|
323
|
-
const context =
|
|
376
|
+
const context = orderingContext(options);
|
|
377
|
+
return rank(items, item => exclusionReason(item, context), context);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Order normalized backlog items — open leaves in no lifecycle role — for
|
|
382
|
+
* promotion, the tier walked only after the whole Ready queue.
|
|
383
|
+
*
|
|
384
|
+
* Every Ready exclusion applies, then two more: an item with an unfinished
|
|
385
|
+
* blocker (`blocked-by-open-item`) cannot be taken to completion, and an item
|
|
386
|
+
* that already failed validation at its current `updatedAt`
|
|
387
|
+
* (`failed-validation-unchanged`, from `validationFailures`) would only fail
|
|
388
|
+
* again, so it waits until someone edits it. Grouping and ordering match
|
|
389
|
+
* {@link orderReadyQueue}.
|
|
390
|
+
* @param {readonly Record<string, any>[]} items - Normalized items
|
|
391
|
+
* @param {{ me?: string | null, operators?: readonly unknown[], trustedAuthorIds?: readonly unknown[], humanNeededLabel?: string, validationFailures?: Readonly<Record<string, { updatedAt?: string }>> }} [options]
|
|
392
|
+
* @returns {{ ordered: Record<string, any>[], excluded: { ref: string, reason: string }[] }}
|
|
393
|
+
*/
|
|
394
|
+
export function orderBacklogQueue(items, options = {}) {
|
|
395
|
+
const context = orderingContext(options);
|
|
396
|
+
const failures = options.validationFailures ?? {};
|
|
397
|
+
return rank(
|
|
398
|
+
items,
|
|
399
|
+
item => {
|
|
400
|
+
const reason = exclusionReason(item, context);
|
|
401
|
+
if (reason !== null) return reason;
|
|
402
|
+
if (count(item.openBlockers) > 0) return "blocked-by-open-item";
|
|
403
|
+
const failed = Object.hasOwn(failures, item.ref)
|
|
404
|
+
? failures[item.ref]
|
|
405
|
+
: null;
|
|
406
|
+
return failed && text(failed.updatedAt) === text(item.updatedAt)
|
|
407
|
+
? "failed-validation-unchanged"
|
|
408
|
+
: null;
|
|
409
|
+
},
|
|
410
|
+
context
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* The identities and policy every ordering judges against.
|
|
416
|
+
* @param {{ me?: string | null, operators?: readonly unknown[], trustedAuthorIds?: readonly unknown[], humanNeededLabel?: string }} options
|
|
417
|
+
*/
|
|
418
|
+
function orderingContext(options) {
|
|
419
|
+
return {
|
|
324
420
|
me: idOrNull(options.me),
|
|
325
421
|
operators: new Set((options.operators ?? []).map(idOrNull).filter(Boolean)),
|
|
326
422
|
trusted: new Set(
|
|
@@ -328,10 +424,17 @@ export function orderReadyQueue(items, options = {}) {
|
|
|
328
424
|
),
|
|
329
425
|
humanNeededLabel: options.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL,
|
|
330
426
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Split items into ordered eligible ones and excluded ones with reasons.
|
|
431
|
+
* @param {readonly Record<string, any>[] | undefined} items
|
|
432
|
+
* @param {(item: Record<string, any>) => string | null} judge - Exclusion reason, or null
|
|
433
|
+
* @param {ReturnType<typeof orderingContext>} context
|
|
434
|
+
* @returns {{ ordered: Record<string, any>[], excluded: { ref: string, reason: string }[] }}
|
|
435
|
+
*/
|
|
436
|
+
function rank(items, judge, context) {
|
|
437
|
+
const judged = (items ?? []).map(item => ({ item, reason: judge(item) }));
|
|
335
438
|
const eligible = judged
|
|
336
439
|
.filter(one => one.reason === null)
|
|
337
440
|
.map(one => one.item);
|
|
@@ -819,10 +819,38 @@ export function validateSessionManager(block) {
|
|
|
819
819
|
]
|
|
820
820
|
: []),
|
|
821
821
|
...validateOperatorIds(block.operatorIds),
|
|
822
|
+
...validateBacklogPickup(block.backlogPickup, block.operatorIds),
|
|
822
823
|
...findCredentials(block, "sessionManager"),
|
|
823
824
|
];
|
|
824
825
|
}
|
|
825
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Validate `sessionManager.backlogPickup`: opt-in promotion of validated
|
|
829
|
+
* backlog items to Ready, assigned to `operatorIds[0]`. Without an operator
|
|
830
|
+
* there is nobody to assign the promoted item to, so it is refused rather
|
|
831
|
+
* than promoting work into the agent's own name.
|
|
832
|
+
* @param {unknown} value The value, if present.
|
|
833
|
+
* @param {unknown} operatorIds `sessionManager.operatorIds`, if present.
|
|
834
|
+
* @returns {string[]} Problems found.
|
|
835
|
+
*/
|
|
836
|
+
function validateBacklogPickup(value, operatorIds) {
|
|
837
|
+
if (value === undefined) return [];
|
|
838
|
+
if (typeof value !== "boolean") {
|
|
839
|
+
return [
|
|
840
|
+
`sessionManager.backlogPickup must be true or false (absent means false).`,
|
|
841
|
+
];
|
|
842
|
+
}
|
|
843
|
+
const hasOperator =
|
|
844
|
+
Array.isArray(operatorIds) &&
|
|
845
|
+
operatorIds.some(id => typeof id === "string" && id.trim() !== "");
|
|
846
|
+
return value && !hasOperator
|
|
847
|
+
? [
|
|
848
|
+
`sessionManager.backlogPickup needs sessionManager.operatorIds: a ` +
|
|
849
|
+
`promoted backlog item is assigned to the first operator.`,
|
|
850
|
+
]
|
|
851
|
+
: [];
|
|
852
|
+
}
|
|
853
|
+
|
|
826
854
|
/**
|
|
827
855
|
* Validate every block this plan introduced.
|
|
828
856
|
* @param {object} cfg Parsed `.lisa.config.json`.
|