@codyswann/lisa 4.81.4 → 4.81.6
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-hooks/parity-safety-net.sh +236 -16
- package/dist/core/downstream-references.d.ts.map +1 -1
- package/dist/core/downstream-references.js +3 -0
- package/dist/core/downstream-references.js.map +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 +17 -12
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +236 -16
- 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 +30 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +13 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +236 -16
- package/plugins/lisa/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +13 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +236 -16
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +13 -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/hooks/parity-safety-net.sh +236 -16
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +13 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +236 -16
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -0
- package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +13 -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/hooks/parity-safety-net.sh +236 -16
- package/plugins/lisa-devin/rules/reference/config-resolution.md +1 -0
- package/plugins/lisa-devin/scripts/ready-queue-order.mjs +34 -14
- package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +13 -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/hooks/parity-safety-net.sh +236 -16
- package/plugins/src/base/rules/reference/config-resolution.md +1 -0
- package/plugins/src/base/scripts/ready-queue-order.mjs +34 -14
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +30 -0
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +13 -6
- package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +4 -3
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +9 -3
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-runtime.mjs +1 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +4 -1
- package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +4 -0
- package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +13 -1
|
@@ -18,9 +18,10 @@ session list from `claude agents`; and the project's own Telegram chat.
|
|
|
18
18
|
|
|
19
19
|
## Candidate selection
|
|
20
20
|
|
|
21
|
-
Leaf items in the `ready` role, ordered: assigned to
|
|
22
|
-
then
|
|
23
|
-
|
|
21
|
+
Leaf items in the `ready` role, ordered: assigned to an operator (`sessionManager.operatorIds`),
|
|
22
|
+
then to the manager's own tracker identity, then unassigned; within each, by priority, then oldest.
|
|
23
|
+
Items assigned to someone else, containers, human-held items and (when configured) items by
|
|
24
|
+
untrusted authors are skipped. As many as the machine has capacity for.
|
|
24
25
|
|
|
25
26
|
## Scope/bounds
|
|
26
27
|
|
|
@@ -123,9 +123,11 @@ function readEnvelopeQueue(ctx, tracker) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* `queue --tracker <github|jira|linear>`: the Ready queue,
|
|
126
|
+
* `queue --tracker <github|jira|linear>`: the Ready queue, the operator's
|
|
127
|
+
* items first (`sessionManager.operatorIds`), then mine, then unassigned.
|
|
127
128
|
* @param {object} ctx - CLI context
|
|
128
|
-
* @returns {Promise<{ json: object }>}
|
|
129
|
+
* @returns {Promise<{ json: object }>}
|
|
130
|
+
* `{tracker,me,operators,total,pages,ordered,excluded}`
|
|
129
131
|
*/
|
|
130
132
|
export async function queue(ctx) {
|
|
131
133
|
const tracker = ctx.values.tracker;
|
|
@@ -139,10 +141,14 @@ export async function queue(ctx) {
|
|
|
139
141
|
? fetchGithubQueue(ctx, project)
|
|
140
142
|
: readEnvelopeQueue(ctx, tracker);
|
|
141
143
|
const me = ctx.values.me ?? (await readLedger(ctx.root)).control.me ?? null;
|
|
144
|
+
const operators = config?.operatorIds ?? [];
|
|
142
145
|
const order = orderReadyQueue(items, {
|
|
143
146
|
me,
|
|
147
|
+
operators,
|
|
144
148
|
trustedAuthorIds: config?.trustedAuthorIds ?? undefined,
|
|
145
149
|
humanNeededLabel: humanNeededLabel(project, tracker),
|
|
146
150
|
});
|
|
147
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
json: { tracker, me, operators, total: items.length, pages, ...order },
|
|
153
|
+
};
|
|
148
154
|
}
|
|
@@ -111,7 +111,10 @@ Per cycle
|
|
|
111
111
|
queue --tracker jira|linear --input <file> [--me <id>]
|
|
112
112
|
<file> = {complete:true,pages,items:[raw vendor issues]}; exit 2 unless
|
|
113
113
|
complete is exactly true.
|
|
114
|
-
Both print {tracker,me,total,pages,ordered,excluded:[{ref,
|
|
114
|
+
Both print {tracker,me,operators,total,pages,ordered,excluded:[{ref,
|
|
115
|
+
reason}]}. ordered: items assigned to sessionManager.operatorIds
|
|
116
|
+
first, then to --me, then unassigned; each group by priority, then age.
|
|
117
|
+
Items assigned to anyone else are excluded (assigned-to-other).
|
|
115
118
|
--me defaults to the ledger's control.me.
|
|
116
119
|
ledger get [<ref>]
|
|
117
120
|
ledger record-spawn <ref> --worker <name> [--session-id <id>] [--repo <key>]
|
|
@@ -66,6 +66,7 @@ export const SESSION_MANAGER_DEFAULTS = Object.freeze({
|
|
|
66
66
|
* stall: StallBlock,
|
|
67
67
|
* notify: NotifyBlock,
|
|
68
68
|
* trustedAuthorIds: string[] | null,
|
|
69
|
+
* operatorIds?: string[] | null,
|
|
69
70
|
* }} SessionManagerConfig
|
|
70
71
|
* @typedef {{
|
|
71
72
|
* name: string,
|
|
@@ -120,6 +121,9 @@ export function resolveSessionManagerConfig(globalConfig, localConfig) {
|
|
|
120
121
|
...merged,
|
|
121
122
|
repos,
|
|
122
123
|
trustedAuthorIds: merged.trustedAuthorIds ?? null,
|
|
124
|
+
// Left as declared: absent stays undefined and an explicit null stays null,
|
|
125
|
+
// so validation can refuse the null instead of reading it as "no operators".
|
|
126
|
+
operatorIds: merged.operatorIds,
|
|
123
127
|
};
|
|
124
128
|
}
|
|
125
129
|
|
|
@@ -54,7 +54,19 @@ writes `$SM`, and pass `--project-root "$ROOT"` (the absolute project root) on e
|
|
|
54
54
|
bypass-permissions worker; say so plainly. The list holds stable tracker user ids as strings,
|
|
55
55
|
never logins: the GitHub numeric user id (`gh api users/<login> --jq .id`), the Jira
|
|
56
56
|
`accountId`, or the Linear user id. A login matches no one and silently excludes every item.
|
|
57
|
-
4. `
|
|
57
|
+
4. Set `sessionManager.operatorIds` to a non-empty **array** holding the operator's own tracker
|
|
58
|
+
user id, e.g. `"operatorIds": ["<operator-id>"]` (a bare string fails `validate`). Look up the
|
|
59
|
+
OPERATOR, not the credential: `gh api user` / `viewer.id` return whoever the tracker credential
|
|
60
|
+
belongs to, which is the agent account in exactly the case this key exists for. The manager dispatches
|
|
61
|
+
the operator's assigned items first, then items assigned to the tracker credential's identity
|
|
62
|
+
("me"), then unassigned ones, and skips items assigned to anyone else. When the tracker
|
|
63
|
+
credential is not the operator's (an agent or bot account), leaving this unset means the
|
|
64
|
+
operator's own Ready items are never dispatched; say so plainly. Unlike `trustedAuthorIds`, this
|
|
65
|
+
is matched against the assignee, exactly and case-sensitively, so use the same kind of id as
|
|
66
|
+
"me", spelled as the tracker API returns it: the GitHub **login** (`gh api users/<login> --jq
|
|
67
|
+
.login`), the Jira `accountId`, or the Linear user id (the operator's entry in the team's member
|
|
68
|
+
list, or `viewer.id` only when the operator runs the lookup with their own credential).
|
|
69
|
+
5. `node "$SM/session-manager.mjs" validate --project-root "$ROOT"` must print `valid: true`.
|
|
58
70
|
|
|
59
71
|
## Step 3: Store the bot token
|
|
60
72
|
|
|
@@ -808,6 +808,62 @@ follow_split() {
|
|
|
808
808
|
}
|
|
809
809
|
return 0;
|
|
810
810
|
}
|
|
811
|
+
# U records tell the walker the stream below may not match the shell, so
|
|
812
|
+
# a compound closer can no longer be trusted (follow_compound_close). They
|
|
813
|
+
# only ever ADD distrust; every other record is emitted exactly as before.
|
|
814
|
+
#
|
|
815
|
+
# A statement spanning lines (a quoted newline, a backslash continuation)
|
|
816
|
+
# reaches the line-oriented reader as several records, and a later line
|
|
817
|
+
# can spell `done` or `fi` on its own.
|
|
818
|
+
function record(kind, text) {
|
|
819
|
+
if (text ~ /\n/) { printf "U\t\n" }
|
|
820
|
+
printf "%s\t%s\n", kind, text;
|
|
821
|
+
}
|
|
822
|
+
# The first word of one separator-delimited piece of a comment, as the
|
|
823
|
+
# walker would read it if the split hands it over as a statement.
|
|
824
|
+
function comment_reserved(piece) {
|
|
825
|
+
sub(/^[[:space:]]+/, "", piece); sub(/[[:space:]].*$/, "", piece);
|
|
826
|
+
gsub(/["\047]/, "", piece);
|
|
827
|
+
return piece ~ /^(fi|done|esac|case|if|then|else|elif|do|while|until|for|select|function|!|\{|\})$/;
|
|
828
|
+
}
|
|
829
|
+
# This split does not recognise comments, so a comment is split as code. It
|
|
830
|
+
# is harmless only when that split cannot change any structure the walker
|
|
831
|
+
# tracks: no piece after a separator starts with a reserved word, quotes
|
|
832
|
+
# close, parentheses and braces balance, and nothing escapes or substitutes.
|
|
833
|
+
# Inside a case pattern a `)` ends the pattern, so it is never harmless
|
|
834
|
+
# there; a `(` is ignored there, which keeps the Gradle `#(` safe.
|
|
835
|
+
# Anything else is reported, and the walker stops restoring flow.
|
|
836
|
+
function comment_unsafe(from, pattern, j, c, cq, parens, braces, pieces, piece) {
|
|
837
|
+
cq = ""; parens = 0; braces = 0; pieces = 0; piece = "";
|
|
838
|
+
for (j = from; j <= n; j++) {
|
|
839
|
+
c = substr(s, j, 1);
|
|
840
|
+
if (c == "\n") { break }
|
|
841
|
+
if (c == "\\" || c == "`") { return 1 }
|
|
842
|
+
if (cq != "") {
|
|
843
|
+
if (c == cq) { cq = "" }
|
|
844
|
+
else if (cq == "\"" && c == "(" && substr(s, j - 1, 1) == "$") { return 1 }
|
|
845
|
+
piece = piece c; continue;
|
|
846
|
+
}
|
|
847
|
+
if (c == "\"" || c == "\047") { cq = c; piece = piece c; continue }
|
|
848
|
+
if (c ~ /[;&|(){}]/) {
|
|
849
|
+
if (pieces > 0 && comment_reserved(piece)) { return 1 }
|
|
850
|
+
pieces++; piece = "";
|
|
851
|
+
if (c == "(" && !pattern) { parens++ }
|
|
852
|
+
if (c == ")" && (pattern || --parens < 0)) { return 1 }
|
|
853
|
+
if (c == "{") { braces++ }
|
|
854
|
+
if (c == "}" && --braces < 0) { return 1 }
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
piece = piece c;
|
|
858
|
+
}
|
|
859
|
+
if (pieces > 0 && comment_reserved(piece)) { return 1 }
|
|
860
|
+
return cq != "" || parens != 0 || braces != 0;
|
|
861
|
+
}
|
|
862
|
+
function comment_check(at, pattern) {
|
|
863
|
+
if ((at == 1 || substr(s, at - 1, 1) ~ /[[:space:];&|()]/) && comment_unsafe(at + 1, pattern)) {
|
|
864
|
+
printf "U\t\n";
|
|
865
|
+
}
|
|
866
|
+
}
|
|
811
867
|
BEGIN {
|
|
812
868
|
s = ENVIRON["SN_FOLLOW_TEXT"]; n = length(s);
|
|
813
869
|
q = ""; esc = 0; start = 1; sep = "o"; parens = 0;
|
|
@@ -820,6 +876,7 @@ follow_split() {
|
|
|
820
876
|
if (functions > 0) {
|
|
821
877
|
if (q != "") { if (c == q) { q = "" } continue }
|
|
822
878
|
if (c == "\"" || c == "\047") { q = c; continue }
|
|
879
|
+
if (c == "#") { comment_check(i, 0) }
|
|
823
880
|
if (c == "{") { functions++ }
|
|
824
881
|
if (c == "}" && --functions == 0) {
|
|
825
882
|
printf "N\t%s\t%s\n", sep, function_name;
|
|
@@ -833,10 +890,11 @@ follow_split() {
|
|
|
833
890
|
quoted_expression = q == "\"" && ((c == "(" && substr(s, i - 1, 1) == "$" && !literal_dollars[i - 1]) || c == "`");
|
|
834
891
|
if (q != "" && !quoted_expression) { if (c == q) { q = "" } continue }
|
|
835
892
|
if (c == "\"" || c == "\047") { q = c; continue }
|
|
893
|
+
if (c == "#") { comment_check(i, cases > 0 && case_scopes[cases] == parens && case_patterns[cases]) }
|
|
836
894
|
if (c == "`") {
|
|
837
895
|
if (backtick) {
|
|
838
896
|
piece = substr(s, start, i - start);
|
|
839
|
-
if (piece ~ /[^[:space:]]/) {
|
|
897
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
840
898
|
printf "]\t\n"; start = backtick_start; q = backtick_quote; backtick = 0;
|
|
841
899
|
} else {
|
|
842
900
|
backtick_start = start; backtick_quote = q; q = ""; backtick = 1;
|
|
@@ -876,14 +934,14 @@ follow_split() {
|
|
|
876
934
|
brace_groups[++brace_depth] = group;
|
|
877
935
|
if (group) {
|
|
878
936
|
brace_sep = sep;
|
|
879
|
-
if (piece ~ /[^[:space:]]/) {
|
|
937
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
880
938
|
# A pipeline owns the whole compound command, not just the final
|
|
881
939
|
# statement before |. Snapshot before any brace-body cd/return.
|
|
882
940
|
outgoing = brace_pipe(i + 1, "{", "}");
|
|
883
941
|
brace_pipes[brace_depth] = brace_sep == "|" || outgoing;
|
|
884
942
|
if (brace_pipes[brace_depth]) { printf "(\t%s%s\nL\t%d\n", (brace_sep == "|" ? "|" : ""), (outgoing ? "+" : ""), list_async(i + 1) }
|
|
885
943
|
printf "G\t%s\nL\t%d\n", brace_sep, list_async(i + 1);
|
|
886
|
-
if (brace_redirect ~ /[^[:space:]]/) {
|
|
944
|
+
if (brace_redirect ~ /[^[:space:]]/) { record("I", brace_redirect) }
|
|
887
945
|
# P retains incoming stdin provenance without wrapping each body
|
|
888
946
|
# statement in another child: all statements share the brace cwd.
|
|
889
947
|
if (brace_sep == "|") { sep = "P" }
|
|
@@ -894,7 +952,7 @@ follow_split() {
|
|
|
894
952
|
group = brace_groups[brace_depth--];
|
|
895
953
|
if (group) {
|
|
896
954
|
piece = substr(s, start, i - start);
|
|
897
|
-
if (piece ~ /[^[:space:]]/) {
|
|
955
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
898
956
|
printf "H\t\n";
|
|
899
957
|
if (brace_pipes[brace_depth + 1]) { printf ")\t\n" }
|
|
900
958
|
start = i + 1; post_brace = 1; continue;
|
|
@@ -915,10 +973,10 @@ follow_split() {
|
|
|
915
973
|
}
|
|
916
974
|
if (group) {
|
|
917
975
|
piece = substr(s, start, i - start);
|
|
918
|
-
if (piece ~ /[^[:space:]]/) {
|
|
976
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
919
977
|
outgoing = brace_pipe(i + 1, "(", ")");
|
|
920
978
|
printf "(\t%s%s\nL\t%d\n", sep, (outgoing ? "+" : ""), list_async(i + 1);
|
|
921
|
-
if (brace_redirect ~ /[^[:space:]]/) {
|
|
979
|
+
if (brace_redirect ~ /[^[:space:]]/) { record("I", brace_redirect) }
|
|
922
980
|
if (sep == "|") { sep = "P" }
|
|
923
981
|
start = i + 1; continue;
|
|
924
982
|
}
|
|
@@ -927,12 +985,12 @@ follow_split() {
|
|
|
927
985
|
piece = substr(s, start, i - start);
|
|
928
986
|
group = scopes[parens--];
|
|
929
987
|
if (group == 2) {
|
|
930
|
-
if (piece ~ /[^[:space:]]/) {
|
|
988
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
931
989
|
printf "]\t\n"; start = expression_starts[parens + 1]; q = expression_quotes[parens + 1]; continue;
|
|
932
990
|
}
|
|
933
991
|
if (group) {
|
|
934
992
|
piece = substr(s, start, i - start);
|
|
935
|
-
if (piece ~ /[^[:space:]]/) {
|
|
993
|
+
if (piece ~ /[^[:space:]]/) { record(sep, piece); sep = "o" }
|
|
936
994
|
printf ")\t\n"; start = i + 1; post_brace = 1; continue;
|
|
937
995
|
}
|
|
938
996
|
}
|
|
@@ -942,7 +1000,7 @@ follow_split() {
|
|
|
942
1000
|
piece = substr(s, start, i - start);
|
|
943
1001
|
pipe = c == "|" && substr(s, i + 1, 1) != "|";
|
|
944
1002
|
if (pipe || sep == "|") { printf "(\t%s%s\nL\t%d\n", (sep == "|" ? "|" : ""), (pipe ? "+" : ""), list_async(start) }
|
|
945
|
-
if (piece ~ /[^[:space:]]/) {
|
|
1003
|
+
if (piece ~ /[^[:space:]]/) { record(post_brace ? "R" : sep, piece) }
|
|
946
1004
|
post_brace = 0;
|
|
947
1005
|
if (pipe || sep == "|") { printf ")\t\n" }
|
|
948
1006
|
if (c == ";" && substr(s, i + 1, 1) == ";" && cases > 0) {
|
|
@@ -962,7 +1020,7 @@ follow_split() {
|
|
|
962
1020
|
}
|
|
963
1021
|
}
|
|
964
1022
|
if (sep == "|") { printf "(\t|\nL\t%d\n", list_async(start) }
|
|
965
|
-
|
|
1023
|
+
record(sep, substr(s, start, n - start + 1));
|
|
966
1024
|
if (sep == "|") { printf ")\t\n" }
|
|
967
1025
|
}
|
|
968
1026
|
'
|
|
@@ -1432,6 +1490,77 @@ PY
|
|
|
1432
1490
|
follow_redirect_output="$(printf '%s' "$result" | jq -er '.output')" || follow_refuse "compound redirection" "invalid output projection"
|
|
1433
1491
|
}
|
|
1434
1492
|
|
|
1493
|
+
# Enter a compound command (`if`, `while`/`until`/`for`/`select`, `case`).
|
|
1494
|
+
#
|
|
1495
|
+
# Reaching anything inside it depends on runtime flow, so a definition there is
|
|
1496
|
+
# conditional. The enclosing flow state is saved so the matching closer can put
|
|
1497
|
+
# it back: a compound that has CLOSED no longer decides whether the next
|
|
1498
|
+
# top-level definition runs. Leaving the flag raised past the closer marked
|
|
1499
|
+
# every function defined after any loop or case as conditional, which refused
|
|
1500
|
+
# every Gradle-generated start script (CodySwannGT/lisa#4470). Brace groups
|
|
1501
|
+
# already restore this way through brace_flows.
|
|
1502
|
+
#
|
|
1503
|
+
# These read and write follow_scan's locals through bash's dynamic scoping.
|
|
1504
|
+
follow_compound_open() {
|
|
1505
|
+
local frame_scope=$scope_depth
|
|
1506
|
+
# A pipeline element is its own scope, but `if a | b; then` and
|
|
1507
|
+
# `x | while read l; do` open their compound at the ENCLOSING level.
|
|
1508
|
+
if [ "$scope_depth" -gt 0 ]; then
|
|
1509
|
+
case "${scope_kinds[scope_depth - 1]}" in '+' | '|' | '|+') frame_scope=$((scope_depth - 1)) ;; esac
|
|
1510
|
+
fi
|
|
1511
|
+
compound_flows[compound_depth]=$function_flow_unknown
|
|
1512
|
+
compound_kinds[compound_depth]="$1"
|
|
1513
|
+
compound_frame_scopes[compound_depth]=$frame_scope
|
|
1514
|
+
compound_opened_at="$compound_opened_at${i:-} "
|
|
1515
|
+
compound_depth=$((compound_depth + 1))
|
|
1516
|
+
function_flow_unknown=1
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
# Close the innermost compound, restoring the flow state that encloses it.
|
|
1520
|
+
#
|
|
1521
|
+
# Only a closer of the SAME kind as the innermost open compound may lower the
|
|
1522
|
+
# flag. The walker splits text, it does not parse shell: a comment's `;` or a
|
|
1523
|
+
# heredoc payload line can reach it as a `fi`/`done` statement. A closer that
|
|
1524
|
+
# matches nothing, or arrives after a heredoc (see compound_trusted), proves the
|
|
1525
|
+
# walk has lost the structure, so every later closer keeps the flag where it is
|
|
1526
|
+
# — the verdict this walker gave before closers restored anything.
|
|
1527
|
+
#
|
|
1528
|
+
# A conditional `return` inside the compound can skip whatever follows it in a
|
|
1529
|
+
# function body or sourced file, so it re-raises the flag, as H does for a brace
|
|
1530
|
+
# group. `break`/`continue` resume at or inside the loop, so they never skip a
|
|
1531
|
+
# definition after `done`, and `exit` ends the shell before any later call runs:
|
|
1532
|
+
# neither needs to persist past the closer. A `break` inside a function body
|
|
1533
|
+
# can end the CALLER's loop and skip the rest of that body, but such a call only
|
|
1534
|
+
# has that effect when the call itself sits inside a loop body, which is
|
|
1535
|
+
# already conditional.
|
|
1536
|
+
#
|
|
1537
|
+
# The closer must also sit in the scope its compound opened in: a `done` inside
|
|
1538
|
+
# a command substitution (`$( … )` or backticks) is a different shell's syntax
|
|
1539
|
+
# error, not the end of this loop. The one cross-scope closer is `done | cmd`,
|
|
1540
|
+
# whose `done` is the first element of a pipeline. Callers pass only a closer
|
|
1541
|
+
# spelled as the bare first word of its statement; see the fi/done arm.
|
|
1542
|
+
follow_compound_close() {
|
|
1543
|
+
local frame_scope
|
|
1544
|
+
if [ "$compound_trusted" -eq 0 ] || [ "$compound_depth" -eq 0 ] \
|
|
1545
|
+
|| [ "${compound_kinds[compound_depth - 1]}" != "$1" ]; then
|
|
1546
|
+
compound_trusted=0
|
|
1547
|
+
return 0
|
|
1548
|
+
fi
|
|
1549
|
+
frame_scope="${compound_frame_scopes[compound_depth - 1]}"
|
|
1550
|
+
if [ "$scope_depth" -ne "$frame_scope" ]; then
|
|
1551
|
+
case "$((scope_depth - frame_scope)):${scope_kinds[scope_depth - 1]}" in
|
|
1552
|
+
'1:+' | '1:|' | '1:|+') ;;
|
|
1553
|
+
*)
|
|
1554
|
+
compound_trusted=0
|
|
1555
|
+
return 0
|
|
1556
|
+
;;
|
|
1557
|
+
esac
|
|
1558
|
+
fi
|
|
1559
|
+
compound_depth=$((compound_depth - 1))
|
|
1560
|
+
function_flow_unknown="${compound_flows[compound_depth]}"
|
|
1561
|
+
if [ "$return_flow_unknown" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1435
1564
|
# Walk one text for the files it executes. Runs in the PARENT shell, never in a
|
|
1436
1565
|
# command substitution: follow_refuse() must be able to deny, and an exit inside
|
|
1437
1566
|
# a subshell ends that subshell rather than this hook — the mistake documented
|
|
@@ -1467,6 +1596,18 @@ follow_scan() {
|
|
|
1467
1596
|
local -a brace_descriptor_maps=() scope_descriptor_maps=()
|
|
1468
1597
|
local -a brace_stdin_cats=() brace_stdin_cwds=() brace_stdin_unknowns=() scope_stdin_cats=() scope_stdin_cwds=() scope_stdin_unknowns=()
|
|
1469
1598
|
local -a case_cwds=() case_unknowns=() case_changes=()
|
|
1599
|
+
# Open compound commands of THIS text, innermost last. See
|
|
1600
|
+
# follow_compound_open. The stack starts empty, so a closer can never lower
|
|
1601
|
+
# the flag below the value this scan was entered with.
|
|
1602
|
+
local compound_depth=0 compound_trusted=1 compound_opened_at=" " compound_prev
|
|
1603
|
+
local -a compound_flows=() compound_kinds=() compound_scopes=() compound_frame_scopes=() scope_kinds=()
|
|
1604
|
+
# The split treats every [[:space:]] byte as a blank, but bash separates words
|
|
1605
|
+
# only on space and tab (and ends a command on newline). A form feed,
|
|
1606
|
+
# vertical tab or carriage return is an ordinary word byte to bash, so
|
|
1607
|
+
# `<FF>done` is a command name there and a bare closer here. With none of the
|
|
1608
|
+
# three present, every [[:space:]] the split tests is a bash blank or a
|
|
1609
|
+
# newline, so this one check covers each such test in the split.
|
|
1610
|
+
case "$text" in *$'\f'* | *$'\v'* | *$'\r'*) compound_trusted=0 ;; esac
|
|
1470
1611
|
if [ "$function_call_depth" -eq 0 ] && [ "$invocation_kind" != source ]; then
|
|
1471
1612
|
# Ordinary child shells do not inherit unexported functions. Sourcing and
|
|
1472
1613
|
# function calls share the caller's definitions instead.
|
|
@@ -1480,6 +1621,14 @@ follow_scan() {
|
|
|
1480
1621
|
stmt="${line#*$'\t'}"
|
|
1481
1622
|
statement_conditional=0
|
|
1482
1623
|
case "$sep" in '&' | '?') statement_conditional=1 ;; esac
|
|
1624
|
+
# A heredoc payload reaches this walk as statements, so a payload line can
|
|
1625
|
+
# spell a closer. Once one starts, closers stop restoring flow for the rest
|
|
1626
|
+
# of this text. Checked before any skip so an unread payload still counts.
|
|
1627
|
+
# A here-string (`<<<`) carries no payload lines.
|
|
1628
|
+
# The same test also matches an arithmetic shift such as `$((1 << 2))`.
|
|
1629
|
+
# That only withholds a restore, which is the safe direction.
|
|
1630
|
+
case "${stmt//<<</}" in *'<<'*) compound_trusted=0 ;; esac
|
|
1631
|
+
if [ "$sep" = U ]; then compound_trusted=0; continue; fi
|
|
1483
1632
|
# A reached && successor observes the preceding successful command's cwd.
|
|
1484
1633
|
# Scope/list events invalidate this projection; it must never carry a
|
|
1485
1634
|
# child's or a background list's directory into another shell/list.
|
|
@@ -1570,7 +1719,7 @@ follow_scan() {
|
|
|
1570
1719
|
continue
|
|
1571
1720
|
;;
|
|
1572
1721
|
C)
|
|
1573
|
-
|
|
1722
|
+
follow_compound_open case
|
|
1574
1723
|
case_cwds[case_depth]="$follow_cwd"
|
|
1575
1724
|
case_unknowns[case_depth]="$follow_cwd_unknown"
|
|
1576
1725
|
case_changes[case_depth]=0
|
|
@@ -1589,6 +1738,9 @@ follow_scan() {
|
|
|
1589
1738
|
follow_cwd=""; follow_cwd_unknown=1
|
|
1590
1739
|
fi
|
|
1591
1740
|
case_depth=$((case_depth - 1))
|
|
1741
|
+
# K closes the case; the literal `esac` statement the split emits
|
|
1742
|
+
# after it is not a closer and must not close another frame.
|
|
1743
|
+
follow_compound_close case
|
|
1592
1744
|
fi
|
|
1593
1745
|
continue
|
|
1594
1746
|
;;
|
|
@@ -1601,6 +1753,10 @@ follow_scan() {
|
|
|
1601
1753
|
scope_descriptor_maps[scope_depth]="$follow_descriptor_map"
|
|
1602
1754
|
function_scopes[scope_depth]=$function_count
|
|
1603
1755
|
flow_scopes[scope_depth]=$function_flow_unknown
|
|
1756
|
+
compound_scopes[scope_depth]=$compound_depth
|
|
1757
|
+
# `[` is a substitution; for `(` the split's tail says whether this is
|
|
1758
|
+
# a pipeline element (`+`, `|`, `|+`) or a literal subshell.
|
|
1759
|
+
if [ "$sep" = '[' ]; then scope_kinds[scope_depth]='['; else scope_kinds[scope_depth]="$stmt"; fi
|
|
1604
1760
|
return_flows[scope_depth]=$return_flow_unknown
|
|
1605
1761
|
scope_stdin_cats[scope_depth]="$follow_stdin_cat"
|
|
1606
1762
|
scope_stdin_cwds[scope_depth]="$follow_stdin_cwd"
|
|
@@ -1626,6 +1782,16 @@ follow_scan() {
|
|
|
1626
1782
|
follow_cwd_unknown="${unknown_scopes[scope_depth]}"
|
|
1627
1783
|
function_flow_unknown="${flow_scopes[scope_depth]}"
|
|
1628
1784
|
return_flow_unknown="${return_flows[scope_depth]}"
|
|
1785
|
+
# A pipeline element is a scope of its own, so `done | cmd` closes, in
|
|
1786
|
+
# here, a loop opened outside. The scope must not undo that closer: the
|
|
1787
|
+
# flow after the pipeline is the one the loop's frame saved. The stack
|
|
1788
|
+
# itself is never rewound here. `if a | b; then` opens its `if` inside
|
|
1789
|
+
# the first element, and discarding that frame would let the `fi` close
|
|
1790
|
+
# an ENCLOSING compound instead.
|
|
1791
|
+
if [ "$compound_depth" -lt "${compound_scopes[scope_depth]}" ]; then
|
|
1792
|
+
function_flow_unknown="${compound_flows[compound_depth]}"
|
|
1793
|
+
if [ "$return_flow_unknown" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1794
|
+
fi
|
|
1629
1795
|
follow_stdin_cat="${scope_stdin_cats[scope_depth]}"
|
|
1630
1796
|
follow_stdin_cwd="${scope_stdin_cwds[scope_depth]}"
|
|
1631
1797
|
follow_stdin_unknown="${scope_stdin_unknowns[scope_depth]}"
|
|
@@ -1691,6 +1857,7 @@ follow_scan() {
|
|
|
1691
1857
|
n=${#toks[@]}
|
|
1692
1858
|
[ "$n" -gt 0 ] || continue
|
|
1693
1859
|
i=0
|
|
1860
|
+
compound_opened_at=" "
|
|
1694
1861
|
cmd_pos=1
|
|
1695
1862
|
function_lookup_allowed=1
|
|
1696
1863
|
eval_mode=0
|
|
@@ -1795,12 +1962,32 @@ follow_scan() {
|
|
|
1795
1962
|
fi
|
|
1796
1963
|
cmd_pos=0; i=$((i + 1)); continue
|
|
1797
1964
|
;;
|
|
1798
|
-
if
|
|
1799
|
-
|
|
1965
|
+
if)
|
|
1966
|
+
follow_compound_open if
|
|
1967
|
+
i=$((i + 1)); continue
|
|
1968
|
+
;;
|
|
1969
|
+
while | until)
|
|
1970
|
+
follow_compound_open loop
|
|
1800
1971
|
i=$((i + 1)); continue
|
|
1801
1972
|
;;
|
|
1802
1973
|
for | select)
|
|
1803
|
-
|
|
1974
|
+
follow_compound_open loop
|
|
1975
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1976
|
+
;;
|
|
1977
|
+
# A closer counts only as the bare, unquoted FIRST word of its
|
|
1978
|
+
# statement. Anything else that unwraps to one — `"done"`, `\fi`,
|
|
1979
|
+
# `x=1 done`, `eval done`, `command done`, the tail of `x='a; fi'` — is
|
|
1980
|
+
# not a reserved word to the shell, so its appearance means the walk
|
|
1981
|
+
# cannot prove where compounds end. A literal `esac` statement follows
|
|
1982
|
+
# every K record and is harmless in that one spelling.
|
|
1983
|
+
'fi' | 'done' | 'esac')
|
|
1984
|
+
if [ "$i" -ne 0 ] || [ "${toks[0]}" != "$t" ]; then
|
|
1985
|
+
compound_trusted=0
|
|
1986
|
+
elif [ "$t" = 'fi' ]; then
|
|
1987
|
+
follow_compound_close if
|
|
1988
|
+
elif [ "$t" = 'done' ]; then
|
|
1989
|
+
follow_compound_close loop
|
|
1990
|
+
fi
|
|
1804
1991
|
cmd_pos=0; i=$((i + 1)); continue
|
|
1805
1992
|
;;
|
|
1806
1993
|
# Prefixes that keep the command position open: assignments, wrappers,
|
|
@@ -1842,11 +2029,13 @@ follow_scan() {
|
|
|
1842
2029
|
continue
|
|
1843
2030
|
;;
|
|
1844
2031
|
sudo | env | command | exec | nohup | nice | time | builtin \
|
|
1845
|
-
| do | then | else | '!' | '{' \
|
|
2032
|
+
| do | then | else | elif | '!' | '{' \
|
|
1846
2033
|
| */sudo | */env | */nohup | */nice | */time)
|
|
1847
2034
|
case "$t" in
|
|
1848
2035
|
sudo | env | command | exec | nohup | nice | builtin | */sudo | */env | */nohup | */nice) function_lookup_allowed=0 ;;
|
|
1849
|
-
|
|
2036
|
+
# `elif` starts a condition list at a command position, so an
|
|
2037
|
+
# opener after it is a real compound (`elif if x; then …; fi`).
|
|
2038
|
+
do | then | else | elif) function_flow_unknown=1 ;;
|
|
1850
2039
|
esac
|
|
1851
2040
|
i=$((i + 1))
|
|
1852
2041
|
continue
|
|
@@ -2081,6 +2270,37 @@ follow_scan() {
|
|
|
2081
2270
|
# Keep successful-branch provenance separate from the continuation join.
|
|
2082
2271
|
# OR can skip its RHS and still reach a following &&, so it cannot prove
|
|
2083
2272
|
# that RHS's cwd. A semicolon observes every possible preceding outcome.
|
|
2273
|
+
# Every opener must have pushed a frame, or its own closer pops the
|
|
2274
|
+
# ENCLOSING one and restores flow inside a compound that is still open.
|
|
2275
|
+
# Bash reads a reserved word wherever a command may start, which this walk
|
|
2276
|
+
# does not fully model: after `coproc NAME`, after a function header whose
|
|
2277
|
+
# body is a bare compound (`f () while …`), or after `function NAME`. So an
|
|
2278
|
+
# opener word the walk did not open stops all later restores unless the
|
|
2279
|
+
# word before it is a plain argument (`echo if`, `# loops for x`), where
|
|
2280
|
+
# bash reads it as data too. `case` never opens here (the split's C record
|
|
2281
|
+
# does), so a statement-initial `case` means the split missed its `in`.
|
|
2282
|
+
if [ "$compound_trusted" -ne 0 ]; then
|
|
2283
|
+
for ((j = 0; j < n; j++)); do
|
|
2284
|
+
case "${toks[j]}" in
|
|
2285
|
+
coproc) compound_trusted=0 ;;
|
|
2286
|
+
function) [ "$j" -ne 0 ] || compound_trusted=0 ;;
|
|
2287
|
+
if | while | until | for | select | case)
|
|
2288
|
+
case "$compound_opened_at" in *" $j "*) continue ;; esac
|
|
2289
|
+
if [ "$j" -eq 0 ]; then
|
|
2290
|
+
compound_trusted=0
|
|
2291
|
+
else
|
|
2292
|
+
compound_prev="${toks[j - 1]}"
|
|
2293
|
+
case "$compound_prev" in
|
|
2294
|
+
'' | *[!A-Za-z0-9_./,:=+@%-]* | elif | coproc | function | time | then | do | else)
|
|
2295
|
+
compound_trusted=0
|
|
2296
|
+
;;
|
|
2297
|
+
esac
|
|
2298
|
+
fi
|
|
2299
|
+
;;
|
|
2300
|
+
esac
|
|
2301
|
+
[ "$compound_trusted" -ne 0 ] || break
|
|
2302
|
+
done
|
|
2303
|
+
fi
|
|
2084
2304
|
and_cwd="$follow_cwd"; and_unknown="$follow_cwd_unknown"
|
|
2085
2305
|
if [ "$sep" != '?' ] && [ "$function_flow_unknown" -eq 0 ]; then and_ready=1; fi
|
|
2086
2306
|
if [ "$statement_conditional" -ne 0 ] && { [ "$join_unknown" -ne 0 ] || [ "$follow_cwd_unknown" -ne 0 ] || [ "$follow_cwd" != "$join_cwd" ]; }; then
|
|
@@ -733,6 +733,7 @@ bypassed). `/lisa:setup:session-manager` writes it, detecting a multi-repo layou
|
|
|
733
733
|
| `sessionManager.notify.provider` | no | `telegram` | Only `telegram` is implemented. |
|
|
734
734
|
| `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>`. |
|
|
735
735
|
| `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. |
|
|
736
|
+
| `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. |
|
|
736
737
|
|
|
737
738
|
- **Non-secret only.** The bot token lives in `<stateDir>/.env` (directory 0700, file 0600),
|
|
738
739
|
written by setup. A credential-named field or a token-shaped value anywhere in this block is a
|