@codyswann/lisa 2.342.3 → 2.342.5
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/lisa-work-item.mjs +164 -42
- package/dist/core/upstream-evidence-manifest.js +1 -1
- 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-agy/plugin.json +1 -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-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-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-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
|
@@ -21,6 +21,36 @@ const RELEASE_SUBJECT =
|
|
|
21
21
|
/^chore\(release\): \d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)? \[skip ci\]$/;
|
|
22
22
|
const ZERO_OID = /^0+$/;
|
|
23
23
|
const MARKER = "[lisa-pr-link]";
|
|
24
|
+
/**
|
|
25
|
+
* The prefix only — an anchored, fixed-length literal with no quantifier, so
|
|
26
|
+
* it cannot backtrack. The ReDoS was never here; it was in the `\s*(.+?)\s*$`
|
|
27
|
+
* tail, which is now string work.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately still a regex rather than `line.toLowerCase().startsWith(…)`.
|
|
30
|
+
* `toLowerCase()` applies FULL Unicode case mapping, so U+212A KELVIN SIGN
|
|
31
|
+
* folds to "k" and `WorK-Item:` written with it would be accepted — a format
|
|
32
|
+
* this has never accepted, because `/i` on a non-unicode pattern canonicalizes
|
|
33
|
+
* ASCII only.
|
|
34
|
+
*/
|
|
35
|
+
const WORK_ITEM_PREFIX = /^work-item:/i;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The value of a `Work-Item:` line, or null if this is not one.
|
|
39
|
+
*
|
|
40
|
+
* Replaces `/^Work-Item:\s*(.+?)\s*$/i`, which put a lazy quantifier between
|
|
41
|
+
* two `\s*` groups and backtracked super-linearly on a line that is mostly
|
|
42
|
+
* whitespace — over commit messages and PR bodies, which are not this
|
|
43
|
+
* script's to trust. Acceptance is unchanged: ASCII-case-insensitive prefix at
|
|
44
|
+
* the start of the line, non-empty value, surrounding whitespace ignored.
|
|
45
|
+
* @param {string} line One line of a commit message or PR body.
|
|
46
|
+
* @returns {string | null} The trimmed value, or null.
|
|
47
|
+
*/
|
|
48
|
+
function workItemLineValue(line) {
|
|
49
|
+
const match = WORK_ITEM_PREFIX.exec(line);
|
|
50
|
+
if (!match) return null;
|
|
51
|
+
const value = line.slice(match[0].length).trim();
|
|
52
|
+
return value === "" ? null : value;
|
|
53
|
+
}
|
|
24
54
|
const GUIDANCE = [
|
|
25
55
|
"Mention the ticket this work relates to, or ask Lisa to create one:",
|
|
26
56
|
" Work-Item: <configured-project-ticket>",
|
|
@@ -29,21 +59,9 @@ const GUIDANCE = [
|
|
|
29
59
|
class TrackingError extends Error {}
|
|
30
60
|
|
|
31
61
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* reader to three different places: a missing binary is an environment to
|
|
36
|
-
* provision, a refused credential is an access grant to obtain, and only the
|
|
37
|
-
* third is anything to do with the ticket.
|
|
38
|
-
*
|
|
39
|
-
* Collapsing them cost a real round trip. On Claude Code web, where `gh` is not
|
|
40
|
-
* pre-installed, every commit was refused with "issue #2202 does not exist or
|
|
41
|
-
* is inaccessible" — about an issue that was open and correct. The message
|
|
42
|
-
* accused the ticket, so that is where the reader went.
|
|
43
|
-
* @param {{status: number|null, error?: Error, stderr?: string}} result Spawn result.
|
|
44
|
-
* @param {string} ref Work item reference, for the message.
|
|
45
|
-
* @param {string} noun What the reference names.
|
|
46
|
-
* @returns {string} A message naming the actual fault.
|
|
62
|
+
* The tracker could not be ASKED — a missing binary, a refused credential, a
|
|
63
|
+
* call that never completed. Distinct from a TrackingError, which carries the
|
|
64
|
+
* tracker's own "no". Only this kind may be degraded.
|
|
47
65
|
*/
|
|
48
66
|
export class TrackerUnreachableError extends TrackingError {}
|
|
49
67
|
|
|
@@ -61,13 +79,35 @@ export class TrackerUnreachableError extends TrackingError {}
|
|
|
61
79
|
*/
|
|
62
80
|
export function githubFailure(result, ref) {
|
|
63
81
|
const reason = githubFailureReason(result, ref, "GitHub issue");
|
|
82
|
+
// ANY spawn-level error means the tracker could not be asked — the binary is
|
|
83
|
+
// missing (ENOENT), the call was killed at its deadline (ETIMEDOUT), the
|
|
84
|
+
// system could not fork (EAGAIN). Singling out ENOENT made a timed-out `gh`
|
|
85
|
+
// a non-degradable TrackingError, i.e. "this work item is invalid" because
|
|
86
|
+
// the network was slow.
|
|
64
87
|
const unreachable =
|
|
65
|
-
result.error
|
|
88
|
+
result.error !== undefined || /cannot authenticate/.test(reason);
|
|
66
89
|
return unreachable
|
|
67
90
|
? new TrackerUnreachableError(reason)
|
|
68
91
|
: new TrackingError(reason);
|
|
69
92
|
}
|
|
70
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Say which of three different failures a `gh` invocation actually hit.
|
|
96
|
+
*
|
|
97
|
+
* The three are indistinguishable from an exit status alone, and they send the
|
|
98
|
+
* reader to three different places: a missing binary is an environment to
|
|
99
|
+
* provision, a refused credential is an access grant to obtain, and only the
|
|
100
|
+
* third is anything to do with the ticket.
|
|
101
|
+
*
|
|
102
|
+
* Collapsing them cost a real round trip. On Claude Code web, where `gh` is not
|
|
103
|
+
* pre-installed, every commit was refused with "issue #2202 does not exist or
|
|
104
|
+
* is inaccessible" — about an issue that was open and correct. The message
|
|
105
|
+
* accused the ticket, so that is where the reader went.
|
|
106
|
+
* @param {{status: number|null, error?: Error, stderr?: string}} result Spawn result.
|
|
107
|
+
* @param {string} ref Work item reference, for the message.
|
|
108
|
+
* @param {string} noun What the reference names.
|
|
109
|
+
* @returns {string} A message naming the actual fault.
|
|
110
|
+
*/
|
|
71
111
|
export function githubFailureReason(result, ref, noun) {
|
|
72
112
|
if (result.error?.code === "ENOENT") {
|
|
73
113
|
return (
|
|
@@ -91,13 +131,38 @@ export function githubFailureReason(result, ref, noun) {
|
|
|
91
131
|
return `${noun} ${ref} does not exist or is inaccessible`;
|
|
92
132
|
}
|
|
93
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Wall-clock ceiling for any child process this script spawns.
|
|
136
|
+
*
|
|
137
|
+
* These run inside a git hook, so an unbounded call does not merely wait — it
|
|
138
|
+
* hangs the commit or push that invoked it, with no output explaining why. A
|
|
139
|
+
* tracker that has stopped answering must look like a tracker that is
|
|
140
|
+
* unreachable, which the degradation path already knows how to handle.
|
|
141
|
+
*/
|
|
142
|
+
const CHILD_TIMEOUT_MS = Number(
|
|
143
|
+
process.env.LISA_WORK_ITEM_TIMEOUT_MS || 30_000
|
|
144
|
+
);
|
|
145
|
+
|
|
94
146
|
function run(command, args, options = {}) {
|
|
95
147
|
const result = spawnSync(command, args, {
|
|
96
148
|
cwd: options.cwd ?? process.cwd(),
|
|
97
149
|
encoding: "utf8",
|
|
98
150
|
env: options.env ?? process.env,
|
|
99
151
|
input: options.input,
|
|
152
|
+
timeout: options.timeout ?? CHILD_TIMEOUT_MS,
|
|
153
|
+
// SIGKILL, not the default SIGTERM. `timeout` sends killSignal and then
|
|
154
|
+
// keeps waiting for the child to exit, so a child that traps or ignores
|
|
155
|
+
// SIGTERM hangs the hook exactly as long as it would have without any
|
|
156
|
+
// timeout at all. A deadline a child can decline is not a deadline.
|
|
157
|
+
killSignal: "SIGKILL",
|
|
100
158
|
});
|
|
159
|
+
// spawnSync reports a timeout via `error`, not a non-zero status, so the
|
|
160
|
+
// status check below would let a timed-out call through as success.
|
|
161
|
+
if (result.error && !options.allowFailure) {
|
|
162
|
+
throw new TrackerUnreachableError(
|
|
163
|
+
`${command} ${args.join(" ")} did not complete: ${result.error.message}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
101
166
|
if (result.status !== 0 && !options.allowFailure) {
|
|
102
167
|
throw new TrackingError(
|
|
103
168
|
options.error ?? `${command} ${args.join(" ")} failed`
|
|
@@ -122,10 +187,24 @@ function secureCurl(args, entries, options = {}) {
|
|
|
122
187
|
const input = `${entries
|
|
123
188
|
.map(([name, value]) => `${name} = ${curlConfigValue(value)}`)
|
|
124
189
|
.join("\n")}\n`;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
190
|
+
// --max-time bounds curl itself, so it exits on its own rather than being
|
|
191
|
+
// killed by the spawnSync ceiling; --connect-timeout fails fast on a host
|
|
192
|
+
// that is not answering at all. Kept below CHILD_TIMEOUT_MS so curl's own
|
|
193
|
+
// error message is what surfaces.
|
|
194
|
+
return run(
|
|
195
|
+
"curl",
|
|
196
|
+
[
|
|
197
|
+
"-fsS",
|
|
198
|
+
"--connect-timeout",
|
|
199
|
+
"10",
|
|
200
|
+
"--max-time",
|
|
201
|
+
"25",
|
|
202
|
+
"--config",
|
|
203
|
+
"-",
|
|
204
|
+
...args,
|
|
205
|
+
],
|
|
206
|
+
{ ...options, input }
|
|
207
|
+
);
|
|
129
208
|
}
|
|
130
209
|
|
|
131
210
|
function projectRoot() {
|
|
@@ -444,8 +523,8 @@ function parseTrailers(message) {
|
|
|
444
523
|
.split("\n")
|
|
445
524
|
.filter(Boolean)
|
|
446
525
|
.flatMap(line => {
|
|
447
|
-
const
|
|
448
|
-
return
|
|
526
|
+
const value = workItemLineValue(line);
|
|
527
|
+
return value === null ? [] : [value];
|
|
449
528
|
});
|
|
450
529
|
}
|
|
451
530
|
|
|
@@ -557,13 +636,34 @@ function typeFromLabels(labels) {
|
|
|
557
636
|
return namesFrom(labels).find(name => name.startsWith("type:"));
|
|
558
637
|
}
|
|
559
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Every sub-issue state for a GitHub issue, following pagination.
|
|
641
|
+
*
|
|
642
|
+
* Two things this gets right that the single-page version did not:
|
|
643
|
+
*
|
|
644
|
+
* A transport failure is classified through githubFailure, exactly as
|
|
645
|
+
* githubIssue does. Raising a bare TrackingError meant "gh is missing" and
|
|
646
|
+
* "the network is down" read as "this work item is invalid", so validateLive
|
|
647
|
+
* could not degrade and the hook hard-failed on a laptop with no connection.
|
|
648
|
+
*
|
|
649
|
+
* And it pages. `subIssues(first:100)` silently truncated, so an Epic with
|
|
650
|
+
* more than 100 children reported only the first page — assertLeaf then saw
|
|
651
|
+
* no open child beyond it and treated a container as a leaf, which is the
|
|
652
|
+
* precise condition leaf-only-lifecycle exists to prevent.
|
|
653
|
+
* @param {string} ref Canonical work-item ref, for error messages.
|
|
654
|
+
* @param {object} contract Resolved tracker contract.
|
|
655
|
+
* @param {string|number} number GitHub issue number.
|
|
656
|
+
* @returns {string[]} State of every sub-issue.
|
|
657
|
+
*/
|
|
560
658
|
function githubHierarchy(ref, contract, number) {
|
|
561
659
|
const [owner, repo] = contract.repository.split("/");
|
|
562
660
|
const query =
|
|
563
|
-
"query($owner:String!,$repo:String!,$number:Int
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
661
|
+
"query($owner:String!,$repo:String!,$number:Int!,$after:String){repository(owner:$owner,name:$repo){issue(number:$number){subIssues(first:100,after:$after){nodes{state}pageInfo{hasNextPage endCursor}}}}}";
|
|
662
|
+
const states = [];
|
|
663
|
+
const cursor = { after: null };
|
|
664
|
+
|
|
665
|
+
do {
|
|
666
|
+
const args = [
|
|
567
667
|
"api",
|
|
568
668
|
"graphql",
|
|
569
669
|
"-f",
|
|
@@ -574,19 +674,25 @@ function githubHierarchy(ref, contract, number) {
|
|
|
574
674
|
`repo=${repo}`,
|
|
575
675
|
"-F",
|
|
576
676
|
`number=${number}`,
|
|
577
|
-
]
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
677
|
+
];
|
|
678
|
+
if (cursor.after) args.push("-F", `after=${cursor.after}`);
|
|
679
|
+
const result = run("gh", args, { allowFailure: true });
|
|
680
|
+
if (result.status !== 0) throw githubFailure(result, ref);
|
|
681
|
+
|
|
682
|
+
const response = safeJson(result.stdout, `GitHub issue ${ref} hierarchy`);
|
|
683
|
+
const subIssues = response.data?.repository?.issue?.subIssues;
|
|
684
|
+
if (!Array.isArray(subIssues?.nodes)) {
|
|
685
|
+
throw new TrackingError(
|
|
686
|
+
`GitHub issue ${ref} did not expose native sub-issue hierarchy`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
states.push(...subIssues.nodes.map(child => child.state));
|
|
690
|
+
cursor.after = subIssues.pageInfo?.hasNextPage
|
|
691
|
+
? subIssues.pageInfo.endCursor
|
|
692
|
+
: null;
|
|
693
|
+
} while (cursor.after);
|
|
694
|
+
|
|
695
|
+
return states;
|
|
590
696
|
}
|
|
591
697
|
|
|
592
698
|
function githubIssue(ref, contract) {
|
|
@@ -1103,8 +1209,8 @@ function prWorkItem(body, contract) {
|
|
|
1103
1209
|
const matches = String(body ?? "")
|
|
1104
1210
|
.split(/\r?\n/)
|
|
1105
1211
|
.flatMap(line => {
|
|
1106
|
-
const
|
|
1107
|
-
return
|
|
1212
|
+
const value = workItemLineValue(line);
|
|
1213
|
+
return value === null ? [] : [value];
|
|
1108
1214
|
});
|
|
1109
1215
|
if (matches.length !== 1)
|
|
1110
1216
|
throw new TrackingError(
|
|
@@ -1152,9 +1258,25 @@ function currentPullRequest(number, repository = currentRepository()) {
|
|
|
1152
1258
|
: undefined;
|
|
1153
1259
|
}
|
|
1154
1260
|
|
|
1261
|
+
/**
|
|
1262
|
+
* Read a `--name <value>` option, falling back to an environment variable.
|
|
1263
|
+
*
|
|
1264
|
+
* A flag present but carrying no value — last on the line, or immediately
|
|
1265
|
+
* followed by another flag — used to yield `undefined` (or the next flag's
|
|
1266
|
+
* name) rather than falling back. `lisa … --ref` therefore silently discarded
|
|
1267
|
+
* a perfectly good value in the environment, and `--ref --json` bound the
|
|
1268
|
+
* literal string "--json". Both now behave as if the flag were absent.
|
|
1269
|
+
* @param {string[]} args Argument list.
|
|
1270
|
+
* @param {string} name Flag to read, including leading dashes.
|
|
1271
|
+
* @param {string} envName Environment variable to fall back to.
|
|
1272
|
+
* @returns {string | undefined} The resolved value, if any.
|
|
1273
|
+
*/
|
|
1155
1274
|
function option(args, name, envName) {
|
|
1156
1275
|
const index = args.indexOf(name);
|
|
1157
|
-
|
|
1276
|
+
if (index < 0) return process.env[envName];
|
|
1277
|
+
const value = args[index + 1];
|
|
1278
|
+
if (value === undefined || value.startsWith("-")) return process.env[envName];
|
|
1279
|
+
return value;
|
|
1158
1280
|
}
|
|
1159
1281
|
|
|
1160
1282
|
function bind(args) {
|
|
@@ -10,7 +10,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
10
10
|
"all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh": "1934a15e154fda3c2a40979a5cd6225661b14fe7bc77328a69ce499bea85dba7",
|
|
11
11
|
"all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh": "d9ea58150aa27e7f0aa9a4ef155ed446d30bdfc932d01c64e2b8ca211aa0de7b",
|
|
12
12
|
"all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh": "bf4132e49ba18e2f7e941520c299c15aeeacfd220e489f3d2eb8397f2048157b",
|
|
13
|
-
"all/copy-overwrite/scripts/lisa-work-item.mjs": "
|
|
13
|
+
"all/copy-overwrite/scripts/lisa-work-item.mjs": "e30016a49deff1c3cd3ae7951262aeb2ef3d472bce76bbbe2a177db6017c7cad",
|
|
14
14
|
"all/create-only/.claude/rules/PROJECT_RULES.md": "6e1c7923ad2a15356babc60c97e7f97be728d790af285b6a7cd7d1b4d39cd97f",
|
|
15
15
|
"all/create-only/.lisaignore": "3c9827e7e98daa46510b5357cd6dd9406da1821e64cc46d2eea9f311cd6d06b9",
|
|
16
16
|
"all/create-only/scripts/remote-agent-aws-setup.sh": "12d2204d34195ad4f81082e90a25dbbf7d2493debcc50c1cd37b32dc4648fc17",
|
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"name": "@codyswann/lisa",
|
|
123
|
-
"version": "2.342.
|
|
123
|
+
"version": "2.342.5",
|
|
124
124
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
125
125
|
"main": "dist/index.js",
|
|
126
126
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.342.
|
|
3
|
+
"version": "2.342.5",
|
|
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.342.
|
|
3
|
+
"version": "2.342.5",
|
|
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": "2.342.
|
|
3
|
+
"version": "2.342.5",
|
|
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.342.
|
|
3
|
+
"version": "2.342.5",
|
|
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.342.
|
|
3
|
+
"version": "2.342.5",
|
|
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"
|