ralphflow 0.2.0 → 0.4.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/README.md +67 -27
- package/dist/chunk-GVOJO5IN.js +274 -0
- package/dist/ralphflow.js +372 -322
- package/dist/server-O6J52DZT.js +323 -0
- package/package.json +7 -2
- package/src/dashboard/ui/index.html +838 -0
- package/src/templates/code-implementation/loops/00-story-loop/prompt.md +19 -11
- package/src/templates/code-implementation/loops/01-tasks-loop/prompt.md +7 -5
- package/src/templates/code-implementation/loops/02-delivery-loop/prompt.md +4 -2
- package/src/templates/research/loops/00-discovery-loop/prompt.md +7 -5
- package/src/templates/research/loops/01-research-loop/prompt.md +7 -5
- package/src/templates/research/loops/02-story-loop/prompt.md +4 -2
- package/src/templates/research/loops/03-document-loop/prompt.md +4 -2
package/README.md
CHANGED
|
@@ -11,26 +11,27 @@ Define pipelines as loops, coordinate parallel agents via file-based trackers, a
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
15
|
-
|
|
14
|
+
# In your project with a CLAUDE.md
|
|
15
|
+
npx ralphflow
|
|
16
|
+
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
npx ralphflow init --template code-implementation --name my-app
|
|
18
|
+
The interactive menu lets you initialize apps, run individual loops, run all loops in sequence, launch the web dashboard, or check status — no flags needed.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
npx ralphflow run story
|
|
20
|
+
### Advanced Usage
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
```bash
|
|
23
|
+
# Non-interactive commands (same as before)
|
|
24
|
+
npx ralphflow init --template code-implementation --name my-app
|
|
25
|
+
npx ralphflow run story
|
|
24
26
|
npx ralphflow run tasks
|
|
25
|
-
|
|
26
|
-
# 5. Run with multiple agents for parallel execution
|
|
27
|
-
npx ralphflow run tasks --agents 3
|
|
28
|
-
|
|
29
|
-
# 6. Deliver — review, get feedback, resolve
|
|
27
|
+
npx ralphflow run tasks --multi-agent # Multi-agent — one terminal per agent
|
|
30
28
|
npx ralphflow run delivery
|
|
31
|
-
|
|
32
|
-
# 7. Check progress anytime
|
|
33
29
|
npx ralphflow status
|
|
30
|
+
|
|
31
|
+
# Web dashboard
|
|
32
|
+
npx ralphflow dashboard # Start dashboard at http://localhost:4242
|
|
33
|
+
npx ralphflow run tasks --ui # Run with live dashboard alongside
|
|
34
|
+
npx ralphflow e2e --ui # E2E with live dashboard
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
## How It Works
|
|
@@ -41,9 +42,9 @@ The default `code-implementation` template ships with three loops:
|
|
|
41
42
|
|
|
42
43
|
| Loop | Purpose | Mode |
|
|
43
44
|
|------|---------|------|
|
|
44
|
-
| **Story Loop** | Break features into stories and tasks | Interactive
|
|
45
|
-
| **Tasks Loop** | Implement tasks, commit code, update CLAUDE.md | Single or multi-agent (
|
|
46
|
-
| **Delivery Loop** | Review completed work, gather feedback | Interactive
|
|
45
|
+
| **Story Loop** | Break features into stories and tasks | Interactive Claude Code session |
|
|
46
|
+
| **Tasks Loop** | Implement tasks, commit code, update CLAUDE.md | Single or multi-agent (`--multi-agent`) |
|
|
47
|
+
| **Delivery Loop** | Review completed work, gather feedback | Interactive Claude Code session |
|
|
47
48
|
|
|
48
49
|
### Pipeline Flow
|
|
49
50
|
|
|
@@ -83,20 +84,61 @@ Requires `CLAUDE.md` to exist in your project root. If it doesn't, you'll be pro
|
|
|
83
84
|
Runs a loop. Handles the iteration cycle — spawning Claude, detecting completion signals, and restarting on `kill -INT $PPID`.
|
|
84
85
|
|
|
85
86
|
```bash
|
|
86
|
-
npx ralphflow run story # Run story loop
|
|
87
|
+
npx ralphflow run story # Run story loop (interactive Claude session)
|
|
87
88
|
npx ralphflow run tasks # Run tasks loop (single agent)
|
|
88
|
-
npx ralphflow run tasks --
|
|
89
|
+
npx ralphflow run tasks --multi-agent # Run as a multi-agent instance (auto-assigns agent ID)
|
|
89
90
|
npx ralphflow run delivery # Run delivery loop
|
|
90
91
|
npx ralphflow run story --flow my-app # Specify which flow (when multiple exist)
|
|
91
92
|
npx ralphflow run tasks --max-iterations 5 # Limit iterations
|
|
92
93
|
```
|
|
93
94
|
|
|
95
|
+
Each `run` command opens a full interactive Claude Code session. Claude owns the terminal — you see everything it does in real time.
|
|
96
|
+
|
|
97
|
+
**Multi-agent mode:** Instead of spawning N agents from one process, each terminal is one agent. Open multiple terminals, run `--multi-agent` in each, and they auto-assign sequential agent IDs (`agent-1`, `agent-2`, ...) via PID-based lock files. Stale agents are automatically cleaned up.
|
|
98
|
+
|
|
94
99
|
**Options:**
|
|
95
|
-
-
|
|
100
|
+
- `--multi-agent` — Run as a multi-agent instance (auto-assigns agent ID)
|
|
101
|
+
- `--ui` — Start the web dashboard alongside execution
|
|
96
102
|
- `-m, --model <model>` — Claude model to use
|
|
97
103
|
- `-n, --max-iterations <n>` — Maximum iterations (default: 30)
|
|
98
104
|
- `-f, --flow <name>` — Which flow to run (auto-detected if only one exists)
|
|
99
105
|
|
|
106
|
+
### `npx ralphflow dashboard`
|
|
107
|
+
|
|
108
|
+
Starts a real-time web dashboard at `http://localhost:4242`. Shows all apps, loop pipelines, tracker status, and lets you edit prompt files — all updated live via WebSocket as agents work.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx ralphflow dashboard # Default port 4242
|
|
112
|
+
npx ralphflow dashboard -p 3000 # Custom port
|
|
113
|
+
npx ralphflow ui # Alias
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Features:**
|
|
117
|
+
- Live pipeline view with color-coded status (complete/running/pending)
|
|
118
|
+
- Per-loop detail: stage, active item, progress bar, agent table
|
|
119
|
+
- Prompt editor with Cmd+S save and dirty indicator
|
|
120
|
+
- Live tracker viewer (auto-updates as agents write)
|
|
121
|
+
- WebSocket auto-reconnect with exponential backoff
|
|
122
|
+
|
|
123
|
+
**Options:**
|
|
124
|
+
- `-p, --port <port>` — Port number (default: 4242)
|
|
125
|
+
|
|
126
|
+
### `npx ralphflow e2e`
|
|
127
|
+
|
|
128
|
+
Runs all loops end-to-end with SQLite orchestration. Skips loops already completed.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx ralphflow e2e # Run all loops
|
|
132
|
+
npx ralphflow e2e --ui # With live dashboard
|
|
133
|
+
npx ralphflow e2e --flow my-app # Specific flow
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Options:**
|
|
137
|
+
- `--ui` — Start the web dashboard alongside execution
|
|
138
|
+
- `-m, --model <model>` — Claude model to use
|
|
139
|
+
- `-n, --max-iterations <n>` — Maximum iterations per loop (default: 30)
|
|
140
|
+
- `-f, --flow <name>` — Which flow to run (auto-detected if only one exists)
|
|
141
|
+
|
|
100
142
|
### `npx ralphflow status`
|
|
101
143
|
|
|
102
144
|
Shows the current state of all loops across all flows.
|
|
@@ -109,12 +151,10 @@ npx ralphflow status --flow my-app # Specific flow
|
|
|
109
151
|
```
|
|
110
152
|
RalphFlow — my-app
|
|
111
153
|
|
|
112
|
-
Loop Stage
|
|
113
|
-
Story Loop analyze
|
|
114
|
-
Tasks Loop —
|
|
115
|
-
|
|
116
|
-
agent-2 verify-document TASK-5 2026-03-08T10:01
|
|
117
|
-
Delivery Loop idle none 0/0
|
|
154
|
+
Loop Stage Active Progress
|
|
155
|
+
Story Loop analyze none 0/0
|
|
156
|
+
Tasks Loop — none 3/6
|
|
157
|
+
Delivery Loop idle none 0/0
|
|
118
158
|
```
|
|
119
159
|
|
|
120
160
|
**Options:**
|
|
@@ -142,7 +182,7 @@ Story → Tasks → Delivery pipeline for code projects. Battle-tested across 28
|
|
|
142
182
|
|
|
143
183
|
### `research`
|
|
144
184
|
|
|
145
|
-
|
|
185
|
+
Discovery → Research → Story → Document pipeline for research projects. Four loops: discovery (decompose topics), research (investigate with multi-agent), story (synthesize narratives), and document (compile final output).
|
|
146
186
|
|
|
147
187
|
## Project Structure
|
|
148
188
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
var LOOP_ALIASES = {
|
|
6
|
+
// code-implementation aliases
|
|
7
|
+
story: "story-loop",
|
|
8
|
+
stories: "story-loop",
|
|
9
|
+
tasks: "tasks-loop",
|
|
10
|
+
task: "tasks-loop",
|
|
11
|
+
delivery: "delivery-loop",
|
|
12
|
+
deliver: "delivery-loop",
|
|
13
|
+
// research aliases
|
|
14
|
+
discovery: "discovery-loop",
|
|
15
|
+
discover: "discovery-loop",
|
|
16
|
+
research: "research-loop",
|
|
17
|
+
document: "document-loop",
|
|
18
|
+
doc: "document-loop"
|
|
19
|
+
};
|
|
20
|
+
function listFlows(cwd) {
|
|
21
|
+
const baseDir = join(cwd, ".ralph-flow");
|
|
22
|
+
if (!existsSync(baseDir)) return [];
|
|
23
|
+
return readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).filter((d) => existsSync(join(baseDir, d.name, "ralphflow.yaml"))).map((d) => d.name);
|
|
24
|
+
}
|
|
25
|
+
function resolveFlowDir(cwd, flowName) {
|
|
26
|
+
const baseDir = join(cwd, ".ralph-flow");
|
|
27
|
+
if (!existsSync(baseDir)) {
|
|
28
|
+
throw new Error("No .ralph-flow/ found. Run `npx ralphflow init` first.");
|
|
29
|
+
}
|
|
30
|
+
const flows = listFlows(cwd);
|
|
31
|
+
if (flows.length === 0) {
|
|
32
|
+
throw new Error("No flows found in .ralph-flow/. Run `npx ralphflow init` first.");
|
|
33
|
+
}
|
|
34
|
+
if (flowName) {
|
|
35
|
+
if (!flows.includes(flowName)) {
|
|
36
|
+
throw new Error(`Flow "${flowName}" not found. Available: ${flows.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
return join(baseDir, flowName);
|
|
39
|
+
}
|
|
40
|
+
if (flows.length === 1) {
|
|
41
|
+
return join(baseDir, flows[0]);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Multiple flows found: ${flows.join(", ")}. Use --flow <name> to specify which one.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function loadConfig(flowDir) {
|
|
48
|
+
const configPath = join(flowDir, "ralphflow.yaml");
|
|
49
|
+
if (!existsSync(configPath)) {
|
|
50
|
+
throw new Error(`No ralphflow.yaml found in ${flowDir}`);
|
|
51
|
+
}
|
|
52
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
53
|
+
const config = parseYaml(raw);
|
|
54
|
+
if (!config.name) {
|
|
55
|
+
throw new Error('ralphflow.yaml: missing required field "name"');
|
|
56
|
+
}
|
|
57
|
+
if (!config.loops || Object.keys(config.loops).length === 0) {
|
|
58
|
+
throw new Error('ralphflow.yaml: missing required field "loops"');
|
|
59
|
+
}
|
|
60
|
+
if (!config.dir) {
|
|
61
|
+
config.dir = ".ralph-flow";
|
|
62
|
+
}
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
function resolveLoop(config, name) {
|
|
66
|
+
if (config.loops[name]) {
|
|
67
|
+
return { key: name, loop: config.loops[name] };
|
|
68
|
+
}
|
|
69
|
+
const aliased = LOOP_ALIASES[name.toLowerCase()];
|
|
70
|
+
if (aliased && config.loops[aliased]) {
|
|
71
|
+
return { key: aliased, loop: config.loops[aliased] };
|
|
72
|
+
}
|
|
73
|
+
for (const [key, loop] of Object.entries(config.loops)) {
|
|
74
|
+
if (key.startsWith(name) || loop.name.toLowerCase().includes(name.toLowerCase())) {
|
|
75
|
+
return { key, loop };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const available = Object.keys(config.loops).join(", ");
|
|
79
|
+
throw new Error(`Unknown loop "${name}". Available: ${available}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/core/db.ts
|
|
83
|
+
import Database from "better-sqlite3";
|
|
84
|
+
import { join as join2 } from "path";
|
|
85
|
+
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
86
|
+
var SCHEMA = `
|
|
87
|
+
CREATE TABLE IF NOT EXISTS loop_state (
|
|
88
|
+
flow_name TEXT NOT NULL,
|
|
89
|
+
loop_key TEXT NOT NULL,
|
|
90
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
91
|
+
iterations_run INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
completed_at TEXT,
|
|
93
|
+
PRIMARY KEY (flow_name, loop_key)
|
|
94
|
+
);
|
|
95
|
+
`;
|
|
96
|
+
var _db = null;
|
|
97
|
+
function getDb(cwd) {
|
|
98
|
+
if (_db) return _db;
|
|
99
|
+
const dir = join2(cwd, ".ralph-flow");
|
|
100
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
101
|
+
_db = new Database(join2(dir, ".ralphflow.db"));
|
|
102
|
+
_db.pragma("journal_mode = WAL");
|
|
103
|
+
_db.exec(SCHEMA);
|
|
104
|
+
return _db;
|
|
105
|
+
}
|
|
106
|
+
function getLoopStatus(db, flow, loopKey) {
|
|
107
|
+
const row = db.prepare("SELECT status FROM loop_state WHERE flow_name = ? AND loop_key = ?").get(flow, loopKey);
|
|
108
|
+
return row ? row.status : "pending";
|
|
109
|
+
}
|
|
110
|
+
function isLoopComplete(db, flow, loopKey) {
|
|
111
|
+
return getLoopStatus(db, flow, loopKey) === "complete";
|
|
112
|
+
}
|
|
113
|
+
function markLoopRunning(db, flow, loopKey) {
|
|
114
|
+
db.prepare(`
|
|
115
|
+
INSERT INTO loop_state (flow_name, loop_key, status, iterations_run)
|
|
116
|
+
VALUES (?, ?, 'running', 0)
|
|
117
|
+
ON CONFLICT(flow_name, loop_key) DO UPDATE SET status = 'running'
|
|
118
|
+
`).run(flow, loopKey);
|
|
119
|
+
}
|
|
120
|
+
function incrementIteration(db, flow, loopKey) {
|
|
121
|
+
db.prepare(`
|
|
122
|
+
UPDATE loop_state SET iterations_run = iterations_run + 1
|
|
123
|
+
WHERE flow_name = ? AND loop_key = ?
|
|
124
|
+
`).run(flow, loopKey);
|
|
125
|
+
}
|
|
126
|
+
function markLoopComplete(db, flow, loopKey) {
|
|
127
|
+
db.prepare(`
|
|
128
|
+
UPDATE loop_state SET status = 'complete', completed_at = datetime('now')
|
|
129
|
+
WHERE flow_name = ? AND loop_key = ?
|
|
130
|
+
`).run(flow, loopKey);
|
|
131
|
+
}
|
|
132
|
+
function getAllLoopStates(db, flow) {
|
|
133
|
+
return db.prepare("SELECT * FROM loop_state WHERE flow_name = ?").all(flow);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/core/status.ts
|
|
137
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
138
|
+
import { join as join3 } from "path";
|
|
139
|
+
import chalk from "chalk";
|
|
140
|
+
import Table from "cli-table3";
|
|
141
|
+
async function showStatus(cwd, flowName) {
|
|
142
|
+
const flows = flowName ? [flowName] : listFlows(cwd);
|
|
143
|
+
if (flows.length === 0) {
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(chalk.yellow(" No flows found. Run `npx ralphflow init` first."));
|
|
146
|
+
console.log();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
for (const flow of flows) {
|
|
150
|
+
const flowDir = resolveFlowDir(cwd, flow);
|
|
151
|
+
const config = loadConfig(flowDir);
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(chalk.bold(` RalphFlow \u2014 ${flow}`));
|
|
154
|
+
console.log();
|
|
155
|
+
const table = new Table({
|
|
156
|
+
chars: {
|
|
157
|
+
top: "",
|
|
158
|
+
"top-mid": "",
|
|
159
|
+
"top-left": "",
|
|
160
|
+
"top-right": "",
|
|
161
|
+
bottom: "",
|
|
162
|
+
"bottom-mid": "",
|
|
163
|
+
"bottom-left": "",
|
|
164
|
+
"bottom-right": "",
|
|
165
|
+
left: " ",
|
|
166
|
+
"left-mid": "",
|
|
167
|
+
mid: "",
|
|
168
|
+
"mid-mid": "",
|
|
169
|
+
right: "",
|
|
170
|
+
"right-mid": "",
|
|
171
|
+
middle: " "
|
|
172
|
+
},
|
|
173
|
+
style: { "padding-left": 0, "padding-right": 1 },
|
|
174
|
+
head: [
|
|
175
|
+
chalk.dim("Loop"),
|
|
176
|
+
chalk.dim("Stage"),
|
|
177
|
+
chalk.dim("Active"),
|
|
178
|
+
chalk.dim("Progress")
|
|
179
|
+
]
|
|
180
|
+
});
|
|
181
|
+
const sortedLoops = Object.entries(config.loops).sort(([, a], [, b]) => a.order - b.order);
|
|
182
|
+
for (const [key, loop] of sortedLoops) {
|
|
183
|
+
const status = parseTracker(loop.tracker, flowDir, loop.name);
|
|
184
|
+
table.push([
|
|
185
|
+
loop.name,
|
|
186
|
+
status.stage,
|
|
187
|
+
status.active,
|
|
188
|
+
`${status.completed}/${status.total}`
|
|
189
|
+
]);
|
|
190
|
+
if (status.agents && status.agents.length > 0) {
|
|
191
|
+
for (const agent of status.agents) {
|
|
192
|
+
table.push([
|
|
193
|
+
chalk.dim(` ${agent.name}`),
|
|
194
|
+
chalk.dim(agent.stage),
|
|
195
|
+
chalk.dim(agent.activeTask),
|
|
196
|
+
chalk.dim(agent.lastHeartbeat)
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
console.log(table.toString());
|
|
202
|
+
}
|
|
203
|
+
console.log();
|
|
204
|
+
}
|
|
205
|
+
function parseTracker(trackerPath, flowDir, loopName) {
|
|
206
|
+
const fullPath = join3(flowDir, trackerPath);
|
|
207
|
+
const status = {
|
|
208
|
+
loop: loopName,
|
|
209
|
+
stage: "\u2014",
|
|
210
|
+
active: "none",
|
|
211
|
+
completed: 0,
|
|
212
|
+
total: 0
|
|
213
|
+
};
|
|
214
|
+
if (!existsSync3(fullPath)) {
|
|
215
|
+
return status;
|
|
216
|
+
}
|
|
217
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
218
|
+
const lines = content.split("\n");
|
|
219
|
+
for (const line of lines) {
|
|
220
|
+
const metaMatch = line.match(/^- (\w[\w_]*): (.+)$/);
|
|
221
|
+
if (metaMatch) {
|
|
222
|
+
const [, key, value] = metaMatch;
|
|
223
|
+
if (key === "stage") status.stage = value.trim();
|
|
224
|
+
if (key === "active_story" || key === "active_task") status.active = value.trim();
|
|
225
|
+
if (key === "completed_stories" || key === "completed_tasks") {
|
|
226
|
+
const arrayMatch = value.match(/\[(.+)\]/);
|
|
227
|
+
if (arrayMatch) {
|
|
228
|
+
status.completed = arrayMatch[1].split(",").filter((s) => s.trim()).length;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const unchecked = (content.match(/- \[ \]/g) || []).length;
|
|
234
|
+
const checked = (content.match(/- \[x\]/gi) || []).length;
|
|
235
|
+
if (unchecked + checked > 0) {
|
|
236
|
+
status.total = unchecked + checked;
|
|
237
|
+
status.completed = checked;
|
|
238
|
+
}
|
|
239
|
+
const agentTableMatch = content.match(/\| agent \|.*\n\|[-|]+\n((?:\|.*\n)*)/);
|
|
240
|
+
if (agentTableMatch) {
|
|
241
|
+
const agentRows = agentTableMatch[1].trim().split("\n");
|
|
242
|
+
status.agents = [];
|
|
243
|
+
for (const row of agentRows) {
|
|
244
|
+
const cells = row.split("|").map((s) => s.trim()).filter(Boolean);
|
|
245
|
+
if (cells.length >= 4) {
|
|
246
|
+
status.agents.push({
|
|
247
|
+
name: cells[0],
|
|
248
|
+
activeTask: cells[1],
|
|
249
|
+
stage: cells[2],
|
|
250
|
+
lastHeartbeat: cells[3]
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (status.agents.length === 0) {
|
|
255
|
+
status.agents = void 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return status;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export {
|
|
262
|
+
listFlows,
|
|
263
|
+
resolveFlowDir,
|
|
264
|
+
loadConfig,
|
|
265
|
+
resolveLoop,
|
|
266
|
+
getDb,
|
|
267
|
+
isLoopComplete,
|
|
268
|
+
markLoopRunning,
|
|
269
|
+
incrementIteration,
|
|
270
|
+
markLoopComplete,
|
|
271
|
+
getAllLoopStates,
|
|
272
|
+
showStatus,
|
|
273
|
+
parseTracker
|
|
274
|
+
};
|