@codyswann/lisa 2.94.0 → 2.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/scripts/queue-health-classification.mjs +157 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-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-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-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-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-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-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/queue-health-classification.mjs +157 -0
package/package.json
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"lodash": ">=4.18.1"
|
|
83
83
|
},
|
|
84
84
|
"name": "@codyswann/lisa",
|
|
85
|
-
"version": "2.
|
|
85
|
+
"version": "2.95.0",
|
|
86
86
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
87
87
|
"main": "dist/index.js",
|
|
88
88
|
"exports": {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared queue-health classification helpers for `/lisa:queue-status`.
|
|
4
|
+
*
|
|
5
|
+
* Queue readers normalize vendor-specific lifecycle data into the small set of
|
|
6
|
+
* signals below so queue-status can distinguish quiet, healthy, stuck, and
|
|
7
|
+
* misconfigured queues without inventing a second lifecycle vocabulary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const QUEUE_HEALTH_VERDICTS = [
|
|
11
|
+
"IDLE",
|
|
12
|
+
"HEALTHY",
|
|
13
|
+
"ATTENTION_NEEDED",
|
|
14
|
+
"MISCONFIGURED",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {"IDLE" | "HEALTHY" | "ATTENTION_NEEDED" | "MISCONFIGURED"} QueueHealthVerdict
|
|
19
|
+
*
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* readonly queueResolved?: boolean
|
|
22
|
+
* readonly namespaceAdopted?: boolean
|
|
23
|
+
* readonly readyCount?: number
|
|
24
|
+
* readonly activeCount?: number
|
|
25
|
+
* readonly blockedCount?: number
|
|
26
|
+
* readonly stalledCount?: number
|
|
27
|
+
* readonly resolutionError?: string | null
|
|
28
|
+
* }} QueueHealthInput
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Classify a queue using the same high-level concepts intake and repair-intake
|
|
33
|
+
* already rely on:
|
|
34
|
+
* - queue must resolve from config;
|
|
35
|
+
* - lifecycle namespace must be adopted/present;
|
|
36
|
+
* - blocked or stalled work means operator attention is needed;
|
|
37
|
+
* - otherwise ready or active work is healthy;
|
|
38
|
+
* - otherwise the queue is truly idle.
|
|
39
|
+
*
|
|
40
|
+
* @param {QueueHealthInput} input
|
|
41
|
+
* @returns {{
|
|
42
|
+
* readonly verdict: QueueHealthVerdict
|
|
43
|
+
* readonly reasons: readonly string[]
|
|
44
|
+
* readonly counts: Readonly<{
|
|
45
|
+
* ready: number
|
|
46
|
+
* active: number
|
|
47
|
+
* blocked: number
|
|
48
|
+
* stalled: number
|
|
49
|
+
* attentionNeeded: number
|
|
50
|
+
* }>
|
|
51
|
+
* }}
|
|
52
|
+
*/
|
|
53
|
+
export function classifyQueueHealth(input = {}) {
|
|
54
|
+
const counts = {
|
|
55
|
+
ready: normalizeCount(input.readyCount),
|
|
56
|
+
active: normalizeCount(input.activeCount),
|
|
57
|
+
blocked: normalizeCount(input.blockedCount),
|
|
58
|
+
stalled: normalizeCount(input.stalledCount),
|
|
59
|
+
};
|
|
60
|
+
const attentionNeeded = counts.blocked + counts.stalled;
|
|
61
|
+
|
|
62
|
+
if (input.queueResolved === false || hasContent(input.resolutionError)) {
|
|
63
|
+
return {
|
|
64
|
+
verdict: "MISCONFIGURED",
|
|
65
|
+
reasons: ["queue-unresolved"],
|
|
66
|
+
counts: {
|
|
67
|
+
...counts,
|
|
68
|
+
attentionNeeded,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (input.namespaceAdopted === false) {
|
|
74
|
+
return {
|
|
75
|
+
verdict: "MISCONFIGURED",
|
|
76
|
+
reasons: ["lifecycle-namespace-absent"],
|
|
77
|
+
counts: {
|
|
78
|
+
...counts,
|
|
79
|
+
attentionNeeded,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (attentionNeeded > 0) {
|
|
85
|
+
return {
|
|
86
|
+
verdict: "ATTENTION_NEEDED",
|
|
87
|
+
reasons: [
|
|
88
|
+
counts.blocked > 0 ? "blocked-work-present" : null,
|
|
89
|
+
counts.stalled > 0 ? "stalled-work-present" : null,
|
|
90
|
+
].filter(Boolean),
|
|
91
|
+
counts: {
|
|
92
|
+
...counts,
|
|
93
|
+
attentionNeeded,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (counts.ready > 0 || counts.active > 0) {
|
|
99
|
+
return {
|
|
100
|
+
verdict: "HEALTHY",
|
|
101
|
+
reasons: [
|
|
102
|
+
counts.ready > 0 ? "ready-work-present" : null,
|
|
103
|
+
counts.active > 0 ? "active-work-in-flight" : null,
|
|
104
|
+
].filter(Boolean),
|
|
105
|
+
counts: {
|
|
106
|
+
...counts,
|
|
107
|
+
attentionNeeded,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
verdict: "IDLE",
|
|
114
|
+
reasons: ["no-actionable-work"],
|
|
115
|
+
counts: {
|
|
116
|
+
...counts,
|
|
117
|
+
attentionNeeded,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Combine individual queue verdicts into the overall queue-status verdict.
|
|
124
|
+
*
|
|
125
|
+
* @param {readonly { verdict: QueueHealthVerdict }[]} sections
|
|
126
|
+
* @returns {QueueHealthVerdict}
|
|
127
|
+
*/
|
|
128
|
+
export function computeOverallQueueVerdict(sections) {
|
|
129
|
+
const verdicts = sections.map(section => section.verdict);
|
|
130
|
+
|
|
131
|
+
if (verdicts.includes("MISCONFIGURED")) {
|
|
132
|
+
return "MISCONFIGURED";
|
|
133
|
+
}
|
|
134
|
+
if (verdicts.includes("ATTENTION_NEEDED")) {
|
|
135
|
+
return "ATTENTION_NEEDED";
|
|
136
|
+
}
|
|
137
|
+
if (verdicts.includes("HEALTHY")) {
|
|
138
|
+
return "HEALTHY";
|
|
139
|
+
}
|
|
140
|
+
return "IDLE";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {number | null | undefined} value
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
function normalizeCount(value) {
|
|
148
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string | null | undefined} value
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
function hasContent(value) {
|
|
156
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
157
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.95.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.95.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared queue-health classification helpers for `/lisa:queue-status`.
|
|
4
|
+
*
|
|
5
|
+
* Queue readers normalize vendor-specific lifecycle data into the small set of
|
|
6
|
+
* signals below so queue-status can distinguish quiet, healthy, stuck, and
|
|
7
|
+
* misconfigured queues without inventing a second lifecycle vocabulary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const QUEUE_HEALTH_VERDICTS = [
|
|
11
|
+
"IDLE",
|
|
12
|
+
"HEALTHY",
|
|
13
|
+
"ATTENTION_NEEDED",
|
|
14
|
+
"MISCONFIGURED",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {"IDLE" | "HEALTHY" | "ATTENTION_NEEDED" | "MISCONFIGURED"} QueueHealthVerdict
|
|
19
|
+
*
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* readonly queueResolved?: boolean
|
|
22
|
+
* readonly namespaceAdopted?: boolean
|
|
23
|
+
* readonly readyCount?: number
|
|
24
|
+
* readonly activeCount?: number
|
|
25
|
+
* readonly blockedCount?: number
|
|
26
|
+
* readonly stalledCount?: number
|
|
27
|
+
* readonly resolutionError?: string | null
|
|
28
|
+
* }} QueueHealthInput
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Classify a queue using the same high-level concepts intake and repair-intake
|
|
33
|
+
* already rely on:
|
|
34
|
+
* - queue must resolve from config;
|
|
35
|
+
* - lifecycle namespace must be adopted/present;
|
|
36
|
+
* - blocked or stalled work means operator attention is needed;
|
|
37
|
+
* - otherwise ready or active work is healthy;
|
|
38
|
+
* - otherwise the queue is truly idle.
|
|
39
|
+
*
|
|
40
|
+
* @param {QueueHealthInput} input
|
|
41
|
+
* @returns {{
|
|
42
|
+
* readonly verdict: QueueHealthVerdict
|
|
43
|
+
* readonly reasons: readonly string[]
|
|
44
|
+
* readonly counts: Readonly<{
|
|
45
|
+
* ready: number
|
|
46
|
+
* active: number
|
|
47
|
+
* blocked: number
|
|
48
|
+
* stalled: number
|
|
49
|
+
* attentionNeeded: number
|
|
50
|
+
* }>
|
|
51
|
+
* }}
|
|
52
|
+
*/
|
|
53
|
+
export function classifyQueueHealth(input = {}) {
|
|
54
|
+
const counts = {
|
|
55
|
+
ready: normalizeCount(input.readyCount),
|
|
56
|
+
active: normalizeCount(input.activeCount),
|
|
57
|
+
blocked: normalizeCount(input.blockedCount),
|
|
58
|
+
stalled: normalizeCount(input.stalledCount),
|
|
59
|
+
};
|
|
60
|
+
const attentionNeeded = counts.blocked + counts.stalled;
|
|
61
|
+
|
|
62
|
+
if (input.queueResolved === false || hasContent(input.resolutionError)) {
|
|
63
|
+
return {
|
|
64
|
+
verdict: "MISCONFIGURED",
|
|
65
|
+
reasons: ["queue-unresolved"],
|
|
66
|
+
counts: {
|
|
67
|
+
...counts,
|
|
68
|
+
attentionNeeded,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (input.namespaceAdopted === false) {
|
|
74
|
+
return {
|
|
75
|
+
verdict: "MISCONFIGURED",
|
|
76
|
+
reasons: ["lifecycle-namespace-absent"],
|
|
77
|
+
counts: {
|
|
78
|
+
...counts,
|
|
79
|
+
attentionNeeded,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (attentionNeeded > 0) {
|
|
85
|
+
return {
|
|
86
|
+
verdict: "ATTENTION_NEEDED",
|
|
87
|
+
reasons: [
|
|
88
|
+
counts.blocked > 0 ? "blocked-work-present" : null,
|
|
89
|
+
counts.stalled > 0 ? "stalled-work-present" : null,
|
|
90
|
+
].filter(Boolean),
|
|
91
|
+
counts: {
|
|
92
|
+
...counts,
|
|
93
|
+
attentionNeeded,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (counts.ready > 0 || counts.active > 0) {
|
|
99
|
+
return {
|
|
100
|
+
verdict: "HEALTHY",
|
|
101
|
+
reasons: [
|
|
102
|
+
counts.ready > 0 ? "ready-work-present" : null,
|
|
103
|
+
counts.active > 0 ? "active-work-in-flight" : null,
|
|
104
|
+
].filter(Boolean),
|
|
105
|
+
counts: {
|
|
106
|
+
...counts,
|
|
107
|
+
attentionNeeded,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
verdict: "IDLE",
|
|
114
|
+
reasons: ["no-actionable-work"],
|
|
115
|
+
counts: {
|
|
116
|
+
...counts,
|
|
117
|
+
attentionNeeded,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Combine individual queue verdicts into the overall queue-status verdict.
|
|
124
|
+
*
|
|
125
|
+
* @param {readonly { verdict: QueueHealthVerdict }[]} sections
|
|
126
|
+
* @returns {QueueHealthVerdict}
|
|
127
|
+
*/
|
|
128
|
+
export function computeOverallQueueVerdict(sections) {
|
|
129
|
+
const verdicts = sections.map(section => section.verdict);
|
|
130
|
+
|
|
131
|
+
if (verdicts.includes("MISCONFIGURED")) {
|
|
132
|
+
return "MISCONFIGURED";
|
|
133
|
+
}
|
|
134
|
+
if (verdicts.includes("ATTENTION_NEEDED")) {
|
|
135
|
+
return "ATTENTION_NEEDED";
|
|
136
|
+
}
|
|
137
|
+
if (verdicts.includes("HEALTHY")) {
|
|
138
|
+
return "HEALTHY";
|
|
139
|
+
}
|
|
140
|
+
return "IDLE";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {number | null | undefined} value
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
function normalizeCount(value) {
|
|
148
|
+
return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string | null | undefined} value
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
function hasContent(value) {
|
|
156
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
157
|
+
}
|