loki-mode 9.49.4 → 9.50.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/references/memory-system.md +6 -1
- package/references/tool-orchestration.md +6 -1
- package/skills/troubleshooting.md +25 -6
- package/tests/detect-mock-problems.sh +17 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v9.
|
|
6
|
+
# Loki Mode v9.50.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v9.
|
|
473
|
+
**v9.50.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.50.1
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v9.
|
|
5
|
+
**Version:** v9.50.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var ht=Object.create;var{getPrototypeOf:gt,defineProperty:jG,getOwnPropertyNames:mt}=Object;var ut=Object.prototype.hasOwnProperty;function dt($){return this[$]}var pt,ct,lt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?pt??=new WeakMap:ct??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?ht(gt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of mt($))if(!ut.call(J,q))jG(J,q,{get:dt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var it=($)=>$;function at($,X){this[$]=it.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:at.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var HR={};B1(HR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as ot}from"url";import{existsSync as Zq}from"fs";import{homedir as st}from"os";function nt(){let $=UR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(UR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(st(),".loki")}var UR,L1;var k1=s(()=>{UR=LG(ot(import.meta.url));L1=nt()});import{readFileSync as rt}from"fs";import{resolve as tt,dirname as et}from"path";import{fileURLToPath as $e}from"url";function j9(){if(n3!==null)return n3;let $="9.
|
|
2
|
+
var ht=Object.create;var{getPrototypeOf:gt,defineProperty:jG,getOwnPropertyNames:mt}=Object;var ut=Object.prototype.hasOwnProperty;function dt($){return this[$]}var pt,ct,lt=($,X,Q)=>{var z=$!=null&&typeof $==="object";if(z){var Z=X?pt??=new WeakMap:ct??=new WeakMap,K=Z.get($);if(K)return K}Q=$!=null?ht(gt($)):{};let J=X||!$||!$.__esModule?jG(Q,"default",{value:$,enumerable:!0}):Q;for(let q of mt($))if(!ut.call(J,q))jG(J,q,{get:dt.bind($,q),enumerable:!0});if(z)Z.set($,J);return J};var zq=($,X)=>()=>(X||$((X={exports:{}}).exports,X),X.exports);var it=($)=>$;function at($,X){this[$]=it.bind(null,X)}var B1=($,X)=>{for(var Q in X)jG($,Q,{get:X[Q],enumerable:!0,configurable:!0,set:at.bind(X,Q)})};var s=($,X)=>()=>($&&(X=$($=0)),X);var w5=import.meta.require;var HR={};B1(HR,{lokiDir:()=>h0,homeLokiDir:()=>GQ,findRepoRootForVersion:()=>AG,REPO_ROOT:()=>L1});import{resolve as h2,dirname as LG}from"path";import{fileURLToPath as ot}from"url";import{existsSync as Zq}from"fs";import{homedir as st}from"os";function nt(){let $=UR;for(let X=0;X<6;X++){if(Zq(h2($,"VERSION"))&&Zq(h2($,"autonomy/run.sh")))return $;let Q=LG($);if(Q===$)break;$=Q}return h2(UR,"..","..","..")}function AG($){let X=$;for(let Q=0;Q<6;Q++){if(Zq(h2(X,"VERSION"))&&Zq(h2(X,"autonomy/run.sh")))return X;let z=LG(X);if(z===X)break;X=z}return h2($,"..","..","..")}function h0(){return process.env.LOKI_DIR??h2(process.cwd(),".loki")}function GQ(){return h2(st(),".loki")}var UR,L1;var k1=s(()=>{UR=LG(ot(import.meta.url));L1=nt()});import{readFileSync as rt}from"fs";import{resolve as tt,dirname as et}from"path";import{fileURLToPath as $e}from"url";function j9(){if(n3!==null)return n3;let $="9.50.1";if(typeof $==="string"&&$.length>0)return n3=$,n3;try{let X=et($e(import.meta.url)),Q=AG(X);n3=rt(tt(Q,"VERSION"),"utf-8").trim()}catch{n3="unknown"}return n3}var n3=null;var Kq=s(()=>{k1()});var jR={};B1(jR,{runOrThrow:()=>Me,run:()=>$1,readStreamCapped:()=>Jq,commandVersion:()=>je,commandExists:()=>g5,ShellError:()=>TG,MAX_STDOUT_BYTES:()=>OR});async function Jq($,X=OR){let Q=$.getReader(),z=new TextDecoder,Z="",K=0;try{while(K<X){let{done:J,value:q}=await Q.read();if(J)break;if(!q)continue;if(K+=q.byteLength,K>X){let V=q.byteLength-(K-X);Z+=z.decode(q.subarray(0,V),{stream:!0});break}Z+=z.decode(q,{stream:!0})}Z+=z.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return Z}async function $1($,X={}){let Q=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),z,Z;if(X.timeoutMs&&X.timeoutMs>0)z=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[K,J,q]=await Promise.all([Jq(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:K,stderr:J,exitCode:q}}finally{if(z)clearTimeout(z);if(Z)clearTimeout(Z)}}async function Me($,X={}){let Q=await $1($,X);if(Q.exitCode!==0)throw new TG(`command failed (${Q.exitCode}): ${$.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function g5($){let X=Oe($),Q=await $1(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Oe($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function je($,X="--version"){if(!await g5($))return null;let z=await $1([$,X],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var OR=16777216,TG;var y8=s(()=>{TG=class TG extends Error{message;exitCode;stdout;stderr;constructor($,X,Q,z){super($);this.message=$;this.exitCode=X;this.stdout=Q;this.stderr=z;this.name="ShellError"}}});function g2($){return Le?"":$}var Le,p0,$5,q1,F61,A1,f1,m5,r;var t7=s(()=>{Le=(process.env.NO_COLOR??"").length>0;p0=g2("\x1B[0;31m"),$5=g2("\x1B[0;32m"),q1=g2("\x1B[1;33m"),F61=g2("\x1B[0;34m"),A1=g2("\x1B[0;36m"),f1=g2("\x1B[1m"),m5=g2("\x1B[2m"),r=g2("\x1B[0m")});import{existsSync as Ee}from"fs";async function Z2(){if(BQ!==void 0)return BQ;let $="/opt/homebrew/bin/python3.12";if(Ee($))return BQ=$,$;let X=await g5("python3.12");if(X)return BQ=X,X;let Q=await g5("python3");return BQ=Q,Q}async function I4($,X={}){let Q=await Z2();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return $1([Q,"-c",$],X)}var BQ;var m2=s(()=>{y8()});var hR={};B1(hR,{runStatus:()=>$00});import{existsSync as u5,readFileSync as C9,readdirSync as xR,statSync as kR}from"fs";import{resolve as A5,basename as le}from"path";import{homedir as ie}from"os";function SR($){let X=Math.trunc($);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function yR($,X,Q){if(X===0)return null;let z=Math.trunc($*100/X),Z=Math.trunc($*Vq/X);if(Z>Vq)Z=Vq;let K=Vq-Z,J=$5;if(z>=80)J=p0;else if(z>=50)J=q1;let q="=".repeat(Math.max(0,Z))+" ".repeat(Math.max(0,K)),V=SR($),Y=SR(X);return` ${f1}${Q}${r} ${J}[${q}]${r} ${z}% (${V} / ${Y})`}async function oe(){if(await g5("jq"))return!0;return process.stdout.write(`${p0}Error: jq is required but not installed.${r}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -1337,4 +1337,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1337
1337
|
`),2}case"start":{let{runStart:z}=await Promise.resolve().then(() => (ft(),bt));return z(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1338
1338
|
`),process.stderr.write(_t),2}}PR();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var J61=await K61(Bun.argv.slice(2));process.exit(J61);
|
|
1339
1339
|
|
|
1340
|
-
//# debugId=
|
|
1340
|
+
//# debugId=1E8ADC416C83ADBB5ED460982F1FB05B
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.50.1",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider, opencode).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "9.
|
|
5
|
+
"version": "9.50.1",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
|
@@ -1255,7 +1255,12 @@ MemEvolve analyzes how memories are located and retrieved at inference time.
|
|
|
1255
1255
|
```
|
|
1256
1256
|
|
|
1257
1257
|
2. **Temporal Index:** Date-based episodic retrieval
|
|
1258
|
-
- `.loki/memory/episodic
|
|
1258
|
+
- Episodes are stored as `.loki/memory/episodic/<YYYY-MM-DD>/task-<id>.json`
|
|
1259
|
+
(`memory/storage.py:477,521`), so the DATE DIRECTORY is what provides
|
|
1260
|
+
temporal navigation
|
|
1261
|
+
- The index layer itself is `index.json` at the memory ROOT, not under
|
|
1262
|
+
`episodic/` (`memory/engine.py:151`). `memory/cross_project.py:67` walks
|
|
1263
|
+
the store recursively and explicitly SKIPS `index.json` sidecars
|
|
1259
1264
|
- Enables queries like "what happened last week"
|
|
1260
1265
|
|
|
1261
1266
|
3. **Keyword Search:** Skill matching by keywords
|
|
@@ -601,7 +601,12 @@ VERIFY:
|
|
|
601
601
|
|
|
602
602
|
## Key Metrics Dashboard
|
|
603
603
|
|
|
604
|
-
Track these metrics
|
|
604
|
+
Track these metrics using the shape below. NOTE: this is the reference schema
|
|
605
|
+
for the pattern, NOT a file Loki writes -- nothing in this repository produces
|
|
606
|
+
`.loki/metrics/dashboard.json`. The metrics that ARE written per run are
|
|
607
|
+
`.loki/metrics/efficiency/iteration-<N>.json` (`autonomy/run.sh:8303`),
|
|
608
|
+
`.loki/metrics/budget.json`, and `.loki/metrics/trust-events.jsonl`
|
|
609
|
+
(`autonomy/run.sh:2620`).
|
|
605
610
|
|
|
606
611
|
```json
|
|
607
612
|
{
|
|
@@ -150,14 +150,27 @@ rate_limit_handling:
|
|
|
150
150
|
circuit_breaker:
|
|
151
151
|
threshold: 3 failures in 60s
|
|
152
152
|
cooldown: 300s
|
|
153
|
-
state_file: ".loki/state/circuit-breakers.json"
|
|
154
153
|
```
|
|
155
154
|
|
|
156
155
|
### Circuit Breaker System
|
|
157
156
|
|
|
158
157
|
The circuit breaker prevents cascading failures by temporarily disabling operations that are repeatedly failing.
|
|
159
158
|
|
|
160
|
-
|
|
159
|
+
Loki ships three circuit breakers, and all of them hold state IN MEMORY for the
|
|
160
|
+
life of the run. None persists to a state file:
|
|
161
|
+
|
|
162
|
+
- `council_circuit_breaker_triggered()` (`autonomy/completion-council.sh:397`) --
|
|
163
|
+
stagnation and repeated done-signals
|
|
164
|
+
- the repeated-signature breaker (`loki-ts/src/runner/recovery_policy.ts:91`)
|
|
165
|
+
- the budget breaker (`loki-ts/src/runner/budget.ts:296`,
|
|
166
|
+
`check_budget_limit()` in `autonomy/run.sh`), which writes
|
|
167
|
+
`.loki/metrics/budget.json` and `.loki/signals/BUDGET_EXCEEDED`
|
|
168
|
+
|
|
169
|
+
#### Reference schema (DESIGN PATTERN -- not an artifact Loki writes)
|
|
170
|
+
|
|
171
|
+
The shape below describes the pattern for an implementer. No code in this
|
|
172
|
+
repository reads or writes `.loki/state/circuit-breakers.json`; do not expect
|
|
173
|
+
the file to exist at runtime.
|
|
161
174
|
|
|
162
175
|
```json
|
|
163
176
|
{
|
|
@@ -310,8 +323,12 @@ The circuit breaker prevents cascading failures by temporarily disabling operati
|
|
|
310
323
|
When a circuit breaker is OPEN:
|
|
311
324
|
|
|
312
325
|
1. **Check State**
|
|
326
|
+
|
|
327
|
+
Breaker state is in memory, so read it from the run's own output rather than
|
|
328
|
+
a file. The budget breaker is the one with durable artifacts:
|
|
313
329
|
```bash
|
|
314
|
-
cat .loki/
|
|
330
|
+
cat .loki/metrics/budget.json # budget_used vs the cap
|
|
331
|
+
ls .loki/signals/BUDGET_EXCEEDED # present once the cap is breached
|
|
315
332
|
```
|
|
316
333
|
|
|
317
334
|
2. **Calculate Wait Time**
|
|
@@ -461,7 +478,9 @@ A task should be permanently abandoned when:
|
|
|
461
478
|
| Scope no longer relevant | Project direction changed |
|
|
462
479
|
| Human explicitly abandons | Documented decision to deprioritize |
|
|
463
480
|
|
|
464
|
-
When abandoning, move task to `.loki/queue/
|
|
481
|
+
When abandoning, move the task to `.loki/queue/dead-letter.json` with the reason
|
|
482
|
+
documented. That is the real terminal queue (`autonomy/run.sh:6536`,
|
|
483
|
+
`dashboard/server.py:2348`); there is no `abandoned.json`.
|
|
465
484
|
|
|
466
485
|
### Recovery Strategies
|
|
467
486
|
|
|
@@ -470,7 +489,7 @@ When abandoning, move task to `.loki/queue/abandoned.json` with reason documente
|
|
|
470
489
|
| `retry_with_simpler_approach` | Complex implementation failed multiple times | Break into smaller subtasks, reduce scope, use simpler patterns |
|
|
471
490
|
| `dependency_blocked` | Task needs output from another task that failed | Wait for dependency resolution, check daily |
|
|
472
491
|
| `requires_human_review` | Security decision, unclear spec, or irreversible action | Log to `.loki/escalations/` and notify, do not retry |
|
|
473
|
-
| `permanent_abandon` | Met abandon criteria above | Move to `
|
|
492
|
+
| `permanent_abandon` | Met abandon criteria above | Move to `dead-letter.json`, document reason, move on |
|
|
474
493
|
|
|
475
494
|
### Human Escalation Triggers
|
|
476
495
|
|
|
@@ -523,7 +542,7 @@ Human decision on recovery path.
|
|
|
523
542
|
- Document recovery reason
|
|
524
543
|
- Reset failure_count if new approach
|
|
525
544
|
d. ELSE IF meets abandon criteria:
|
|
526
|
-
- Move to
|
|
545
|
+
- Move to dead-letter.json
|
|
527
546
|
- Document reason
|
|
528
547
|
e. ELSE IF needs human:
|
|
529
548
|
- Create escalation file
|
|
@@ -87,7 +87,16 @@ local_import_is_source() {
|
|
|
87
87
|
import_base="$test_dir/$spec"
|
|
88
88
|
|
|
89
89
|
case "$leaf" in
|
|
90
|
-
|
|
90
|
+
# TS NodeNext resolution: a spec written "./foo.js" (or .jsx/.mjs/.cjs)
|
|
91
|
+
# commonly names a real ".ts"/".tsx"/".mts"/".cts" source file -- the
|
|
92
|
+
# extension in the specifier is the post-build one, not the on-disk one.
|
|
93
|
+
# Try the literal path first, then its TS sibling, before falling
|
|
94
|
+
# through to "not a source import".
|
|
95
|
+
*.js) [ -f "$import_base" ] && return 0; [ -f "${import_base%.js}.ts" ] && return 0 ;;
|
|
96
|
+
*.jsx) [ -f "$import_base" ] && return 0; [ -f "${import_base%.jsx}.tsx" ] && return 0 ;;
|
|
97
|
+
*.mjs) [ -f "$import_base" ] && return 0; [ -f "${import_base%.mjs}.mts" ] && return 0 ;;
|
|
98
|
+
*.cjs) [ -f "$import_base" ] && return 0; [ -f "${import_base%.cjs}.cts" ] && return 0 ;;
|
|
99
|
+
*.ts|*.tsx|*.mts|*.cts|*.vue|*.svelte)
|
|
91
100
|
[ -f "$import_base" ] && return 0
|
|
92
101
|
;;
|
|
93
102
|
*.*) ;;
|
|
@@ -119,6 +128,11 @@ test_has_source_import() {
|
|
|
119
128
|
# does -- it is how a subprocess/E2E test points at the source it runs.
|
|
120
129
|
# Separate pattern (not an alternation) so the path stays BASH_REMATCH[1].
|
|
121
130
|
local cjs_resolve_re="require\.resolve[[:space:]]*\([[:space:]]*['\"](\.{1,2}/[^'\"]+)['\"]"
|
|
131
|
+
# `await import('./x.ts')` / `import('./x.ts')` -- the dynamic-import form,
|
|
132
|
+
# used to load the module under test after mock.module() has been set up
|
|
133
|
+
# (bun:test's documented pattern for stubbing an import before it resolves).
|
|
134
|
+
# Same relative-path capture as the static forms above.
|
|
135
|
+
local dynamic_import_re="import[[:space:]]*\([[:space:]]*['\"](\.{1,2}/[^'\"]+)['\"]"
|
|
122
136
|
# A test that hands a literal source path to a child process exercises the
|
|
123
137
|
# real code end-to-end without ever naming it in an import. Gated on the
|
|
124
138
|
# file importing child_process, because this flag unlocks a deliberately
|
|
@@ -212,6 +226,8 @@ test_has_source_import() {
|
|
|
212
226
|
spec="${BASH_REMATCH[1]}"
|
|
213
227
|
elif [[ "$line" =~ $cjs_re ]]; then
|
|
214
228
|
spec="${BASH_REMATCH[1]}"
|
|
229
|
+
elif [[ "$line" =~ $dynamic_import_re ]]; then
|
|
230
|
+
spec="${BASH_REMATCH[1]}"
|
|
215
231
|
elif [ "$has_subprocess" = true ] && [[ "$line" =~ $subproc_re ]]; then
|
|
216
232
|
spec="${BASH_REMATCH[1]}"
|
|
217
233
|
elif [ "$has_spawn_call" = true ] && [[ "$line" =~ $subproc_bare_re ]]; then
|