pi-agent-flow 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +104 -0
- package/agents/architect.md +35 -0
- package/agents/code.md +36 -0
- package/agents/debug.md +34 -0
- package/agents/explore.md +29 -0
- package/agents/review.md +35 -0
- package/agents.ts +214 -0
- package/flow.ts +415 -0
- package/index.ts +575 -0
- package/package.json +68 -0
- package/render.ts +376 -0
- package/runner-cli.js +225 -0
- package/runner-events.js +156 -0
- package/types.ts +149 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tuanhung303
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# pi-agent-flow
|
|
2
|
+
|
|
3
|
+
Flow-state delegation extension for [Pi coding agent](https://pi.dev).
|
|
4
|
+
|
|
5
|
+
Delegates tasks to specialized flow states running as isolated pi processes. Each flow receives a snapshot of the current session context and returns a structured report.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Isolated context — flows always receive your current session snapshot
|
|
10
|
+
- Parallel execution — batch independent flows into one call
|
|
11
|
+
- Structured reports — every flow returns [Summary], [Done], [Not Done], [Next Steps]
|
|
12
|
+
- Depth guards — configurable max delegation depth (default: 3)
|
|
13
|
+
- Cycle prevention — blocks recursive delegation chains
|
|
14
|
+
- Flow discovery — reads definitions from ~/.pi/agent/agents/ and .pi/agents/
|
|
15
|
+
- TUI rendering — rich collapsed/expanded display in interactive mode
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pi install /path/to/pi-agent-flow
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or add to ~/.pi/agent/settings.json:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"packages": [
|
|
28
|
+
"./path/to/pi-agent-flow"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Flow Definitions
|
|
34
|
+
|
|
35
|
+
Create .md files in ~/.pi/agent/agents/ or .pi/agents/:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
---
|
|
39
|
+
name: explore
|
|
40
|
+
description: Discover files, trace code paths, map architecture
|
|
41
|
+
tools: read, bash
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
You are the explore flow — your mission is discovery. Stay focused on your intent at all times.
|
|
45
|
+
|
|
46
|
+
When your mission is accomplished, end your response with:
|
|
47
|
+
|
|
48
|
+
flow [explore] accomplished
|
|
49
|
+
|
|
50
|
+
[Summary] what was investigated
|
|
51
|
+
|
|
52
|
+
[Done]
|
|
53
|
+
- completed items
|
|
54
|
+
|
|
55
|
+
[Not Done]
|
|
56
|
+
- incomplete items
|
|
57
|
+
|
|
58
|
+
[Next Steps]
|
|
59
|
+
- recommended follow-up
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Bundled Flows
|
|
63
|
+
|
|
64
|
+
- [explore] — discover files, trace code paths, map architecture
|
|
65
|
+
- [debug] — investigate logs, errors, stack traces, root causes
|
|
66
|
+
- [code] — implement features, fix bugs, write tests
|
|
67
|
+
- [architect] — plan structure, break down requirements, design solutions
|
|
68
|
+
- [review] — audit security, quality, correctness
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{ "flow": [{ "type": "explore", "intent": "Find all authentication-related code" }] }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Batch multiple flows:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"flow": [
|
|
81
|
+
{ "type": "explore", "intent": "Find auth code" },
|
|
82
|
+
{ "type": "review", "intent": "Audit auth module" }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
Flags (passed to parent pi process):
|
|
90
|
+
|
|
91
|
+
- --flow-max-depth [n] — Maximum delegation depth (default: 3)
|
|
92
|
+
- --flow-prevent-cycles — Block cyclic delegation (default: true)
|
|
93
|
+
- --no-flow-prevent-cycles — Disable cycle prevention
|
|
94
|
+
|
|
95
|
+
Environment variables (propagated to child processes):
|
|
96
|
+
|
|
97
|
+
- PI_FLOW_DEPTH — Current depth
|
|
98
|
+
- PI_FLOW_MAX_DEPTH — Max allowed depth
|
|
99
|
+
- PI_FLOW_STACK — JSON array of ancestor flow names
|
|
100
|
+
- PI_FLOW_PREVENT_CYCLES — "1" or "0"
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: Plan structure, break down requirements, design solutions
|
|
4
|
+
tools: read, bash
|
|
5
|
+
maxDepth: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the architect flow — your mission is to design. Stay focused on your intent at all times. The conversation history above provides background context; treat it as reference only and do not let it distract from your objective.
|
|
9
|
+
|
|
10
|
+
Workflow:
|
|
11
|
+
1. Understand — what problem, what constraints, what exists
|
|
12
|
+
2. Explore — find patterns, map dependencies
|
|
13
|
+
3. Design — simplest solution that works, prefer existing patterns
|
|
14
|
+
4. Plan — concrete ordered tasks, identify parallel vs sequential
|
|
15
|
+
|
|
16
|
+
Principles:
|
|
17
|
+
- SOLID, DRY, KISS
|
|
18
|
+
- Design for 10x, build for 1x
|
|
19
|
+
- No tech debt — do it right or don't
|
|
20
|
+
|
|
21
|
+
When your mission is accomplished, end your response with:
|
|
22
|
+
|
|
23
|
+
flow [architect] accomplished
|
|
24
|
+
|
|
25
|
+
[Summary] what was designed and why
|
|
26
|
+
|
|
27
|
+
[Done]
|
|
28
|
+
- analysis completed
|
|
29
|
+
- plan produced with task breakdown
|
|
30
|
+
|
|
31
|
+
[Not Done]
|
|
32
|
+
- areas that need more exploration
|
|
33
|
+
|
|
34
|
+
[Next Steps]
|
|
35
|
+
- implementation tasks in order, with suggested flow types
|
package/agents/code.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code
|
|
3
|
+
description: Implement features, fix bugs, write tests
|
|
4
|
+
tools: read, write, edit, bash
|
|
5
|
+
maxDepth: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the code flow — your mission is to build. Stay focused on your intent at all times. The conversation history above provides background context; treat it as reference only and do not let it distract from your objective.
|
|
9
|
+
|
|
10
|
+
Core Principles:
|
|
11
|
+
- SOLID: Single Responsibility, Open/Closed
|
|
12
|
+
- DRY: Don't repeat yourself
|
|
13
|
+
- KISS: Keep it simple
|
|
14
|
+
|
|
15
|
+
Workflow:
|
|
16
|
+
1. Analyze — read existing code for context
|
|
17
|
+
2. Plan — step-by-step approach before modifying
|
|
18
|
+
3. Execute — implement changes following core principles
|
|
19
|
+
4. Verify — write and run tests before considering done
|
|
20
|
+
5. Finalize — all tests pass, implementation verified
|
|
21
|
+
|
|
22
|
+
When your mission is accomplished, end your response with:
|
|
23
|
+
|
|
24
|
+
flow [code] accomplished
|
|
25
|
+
|
|
26
|
+
[Summary] what was built or fixed
|
|
27
|
+
|
|
28
|
+
[Done]
|
|
29
|
+
- changes made with file:line references
|
|
30
|
+
- tests written or run
|
|
31
|
+
|
|
32
|
+
[Not Done]
|
|
33
|
+
- incomplete items and reasons
|
|
34
|
+
|
|
35
|
+
[Next Steps]
|
|
36
|
+
- recommended follow-up (refactor, additional tests, etc.)
|
package/agents/debug.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debug
|
|
3
|
+
description: Investigate logs, errors, stack traces, root causes
|
|
4
|
+
tools: read, bash
|
|
5
|
+
maxDepth: 0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the debug flow — your mission is investigation. Stay focused on your intent at all times. The conversation history above provides background context; treat it as reference only and do not let it distract from your objective.
|
|
9
|
+
|
|
10
|
+
Workflow:
|
|
11
|
+
1. Collect evidence — logs, error messages, stack traces
|
|
12
|
+
2. Trace the call chain — follow execution path
|
|
13
|
+
3. Check recent changes — git log, git diff
|
|
14
|
+
4. Identify root cause — be specific about what's broken and why
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
- Never guess. Every conclusion must be backed by evidence.
|
|
18
|
+
- Read logs before reading code — symptoms point to cause.
|
|
19
|
+
- Don't suggest fixes until root cause is confirmed.
|
|
20
|
+
|
|
21
|
+
When your mission is accomplished, end your response with:
|
|
22
|
+
|
|
23
|
+
flow [debug] accomplished
|
|
24
|
+
|
|
25
|
+
[Summary] what was investigated and the root cause
|
|
26
|
+
|
|
27
|
+
[Done]
|
|
28
|
+
- evidence collected with file:line references
|
|
29
|
+
|
|
30
|
+
[Not Done]
|
|
31
|
+
- items that couldn't be investigated and why
|
|
32
|
+
|
|
33
|
+
[Next Steps]
|
|
34
|
+
- recommended fix or next investigation steps
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explore
|
|
3
|
+
description: Discover files, trace code paths, map architecture
|
|
4
|
+
tools: read, bash
|
|
5
|
+
maxDepth: 0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the explore flow — your mission is discovery. Stay focused on your intent at all times. The conversation history above provides background context; treat it as reference only and do not let it distract from your objective.
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
- Use grep/find before reading entire files — be efficient.
|
|
12
|
+
- Report findings with file paths and line numbers.
|
|
13
|
+
- Show actual code/data, not excessive summaries.
|
|
14
|
+
- If not found, say so immediately — don't guess.
|
|
15
|
+
|
|
16
|
+
When your mission is accomplished, end your response with:
|
|
17
|
+
|
|
18
|
+
flow [explore] accomplished
|
|
19
|
+
|
|
20
|
+
[Summary] what was investigated and the outcome
|
|
21
|
+
|
|
22
|
+
[Done]
|
|
23
|
+
- completed items with file:line references
|
|
24
|
+
|
|
25
|
+
[Not Done]
|
|
26
|
+
- incomplete items and reasons (or "All objectives met.")
|
|
27
|
+
|
|
28
|
+
[Next Steps]
|
|
29
|
+
- recommended follow-up actions
|
package/agents/review.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Audit security, quality, correctness
|
|
4
|
+
tools: read, write, edit, bash
|
|
5
|
+
maxDepth: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the review flow — your mission is to audit. Stay focused on your intent at all times. The conversation history above provides background context; treat it as reference only and do not let it distract from your objective.
|
|
9
|
+
|
|
10
|
+
Focus Areas:
|
|
11
|
+
- Security — injection, auth bypass, exposed secrets
|
|
12
|
+
- Bugs — logic errors, race conditions, null handling
|
|
13
|
+
- SOLID — god classes, tight coupling, unclear responsibilities
|
|
14
|
+
- Performance — unnecessary loops, memory leaks, blocking calls
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
- Be specific — cite exact file paths and line numbers
|
|
18
|
+
- If code is clean, say so — don't invent issues
|
|
19
|
+
- Apply fixes, don't just suggest them
|
|
20
|
+
|
|
21
|
+
When your mission is accomplished, end your response with:
|
|
22
|
+
|
|
23
|
+
flow [review] accomplished
|
|
24
|
+
|
|
25
|
+
[Summary] what was audited and overall assessment
|
|
26
|
+
|
|
27
|
+
[Done]
|
|
28
|
+
- issues found with file:line and severity
|
|
29
|
+
- fixes applied
|
|
30
|
+
|
|
31
|
+
[Not Done]
|
|
32
|
+
- areas not covered and why
|
|
33
|
+
|
|
34
|
+
[Next Steps]
|
|
35
|
+
- remaining issues or follow-up audits needed
|
package/agents.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow discovery and configuration.
|
|
3
|
+
*
|
|
4
|
+
* Flows are Markdown files with YAML frontmatter that define name, description,
|
|
5
|
+
* optional model/tools, and a system prompt body.
|
|
6
|
+
*
|
|
7
|
+
* Lookup locations:
|
|
8
|
+
* - User flows: ~/.pi/agent/agents/*.md by default, or
|
|
9
|
+
* $PI_CODING_AGENT_DIR/agents/*.md when the env var is set
|
|
10
|
+
* - Project flows: .pi/agents/*.md (walks up from cwd)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
|
|
18
|
+
export type FlowScope = "user" | "project" | "both" | "bundled" | "all";
|
|
19
|
+
|
|
20
|
+
export interface FlowConfig {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
tools?: string[];
|
|
24
|
+
model?: string;
|
|
25
|
+
thinking?: string;
|
|
26
|
+
maxDepth?: number;
|
|
27
|
+
systemPrompt: string;
|
|
28
|
+
source: "user" | "project" | "bundled";
|
|
29
|
+
filePath: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FlowDiscoveryResult {
|
|
33
|
+
flows: FlowConfig[];
|
|
34
|
+
projectFlowsDir: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Internal helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function isDirectory(p: string): boolean {
|
|
42
|
+
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getUserFlowsDir(): string {
|
|
46
|
+
const configDir = process.env["PI_CODING_AGENT_DIR"]?.trim() || path.join(os.homedir(), ".pi", "agent");
|
|
47
|
+
return path.join(configDir, "agents");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Get the bundled flows directory from the plugin's location. */
|
|
51
|
+
function getBundledFlowsDir(): string {
|
|
52
|
+
// Method 1: import.meta.url (ESM)
|
|
53
|
+
try {
|
|
54
|
+
// @ts-expect-error — import.meta.url may not exist in all contexts
|
|
55
|
+
if (import.meta.url) {
|
|
56
|
+
// @ts-expect-error
|
|
57
|
+
const pluginDir = path.dirname(new URL(import.meta.url).pathname);
|
|
58
|
+
const dir = path.join(pluginDir, "agents");
|
|
59
|
+
if (fs.existsSync(dir)) return dir;
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
|
|
63
|
+
// Method 2: __dirname (CommonJS / jiti)
|
|
64
|
+
try {
|
|
65
|
+
// @ts-expect-error — __dirname may not exist in ESM
|
|
66
|
+
if (typeof __dirname !== "undefined") {
|
|
67
|
+
const dir = path.join(__dirname, "agents");
|
|
68
|
+
if (fs.existsSync(dir)) return dir;
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
|
|
72
|
+
// Method 3: Find from require.resolve
|
|
73
|
+
try {
|
|
74
|
+
const resolved = require.resolve("pi-agent-flow/package.json");
|
|
75
|
+
const dir = path.join(path.dirname(resolved), "agents");
|
|
76
|
+
if (fs.existsSync(dir)) return dir;
|
|
77
|
+
} catch {}
|
|
78
|
+
|
|
79
|
+
// Fallback: cwd
|
|
80
|
+
return path.join(process.cwd(), "agents");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Walk up from `cwd` looking for a `.pi/agents` directory. */
|
|
84
|
+
function findNearestProjectFlowsDir(cwd: string): string | null {
|
|
85
|
+
let dir = cwd;
|
|
86
|
+
while (true) {
|
|
87
|
+
const candidate = path.join(dir, ".pi", "agents");
|
|
88
|
+
if (isDirectory(candidate)) return candidate;
|
|
89
|
+
const parent = path.dirname(dir);
|
|
90
|
+
if (parent === dir) return null;
|
|
91
|
+
dir = parent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Parse a single flow markdown file into a FlowConfig. Returns null on skip. */
|
|
96
|
+
function parseFlowFile(filePath: string, source: "user" | "project" | "bundled"): FlowConfig | null {
|
|
97
|
+
let content: string;
|
|
98
|
+
try { content = fs.readFileSync(filePath, "utf-8"); } catch { return null; }
|
|
99
|
+
|
|
100
|
+
let parsed: { frontmatter: Record<string, unknown>; body: string };
|
|
101
|
+
try {
|
|
102
|
+
parsed = parseFrontmatter<Record<string, unknown>>(content);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
+
console.warn(`[pi-agent-flow] Skipping invalid flow file "${filePath}": ${message}`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const frontmatter = parsed.frontmatter ?? {};
|
|
110
|
+
const body = parsed.body ?? "";
|
|
111
|
+
|
|
112
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : "";
|
|
113
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
|
|
114
|
+
if (!name || !description) return null;
|
|
115
|
+
|
|
116
|
+
let tools: string[] | undefined;
|
|
117
|
+
if (typeof frontmatter.tools === "string") {
|
|
118
|
+
const parsedTools = frontmatter.tools
|
|
119
|
+
.split(",")
|
|
120
|
+
.map((t) => t.trim())
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
if (parsedTools.length > 0) tools = parsedTools;
|
|
123
|
+
} else if (Array.isArray(frontmatter.tools)) {
|
|
124
|
+
const parsedTools = frontmatter.tools
|
|
125
|
+
.filter((t): t is string => typeof t === "string")
|
|
126
|
+
.map((t) => t.trim())
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
if (parsedTools.length > 0) tools = parsedTools;
|
|
129
|
+
} else if (frontmatter.tools !== undefined) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`[pi-agent-flow] Ignoring invalid tools field in "${filePath}". Expected a comma-separated string or string array.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let maxDepth: number | undefined;
|
|
136
|
+
if (typeof frontmatter.maxDepth === "number") {
|
|
137
|
+
maxDepth = frontmatter.maxDepth;
|
|
138
|
+
} else if (typeof frontmatter.maxDepth === "string") {
|
|
139
|
+
const parsed = Number(frontmatter.maxDepth);
|
|
140
|
+
if (Number.isFinite(parsed) && parsed >= 0) maxDepth = parsed;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
name,
|
|
145
|
+
description,
|
|
146
|
+
tools,
|
|
147
|
+
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
148
|
+
thinking: typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
|
|
149
|
+
maxDepth,
|
|
150
|
+
systemPrompt: body,
|
|
151
|
+
source,
|
|
152
|
+
filePath,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Load all flow definitions from a directory. */
|
|
157
|
+
function loadFlowsFromDir(dir: string, source: "user" | "project" | "bundled"): FlowConfig[] {
|
|
158
|
+
if (!fs.existsSync(dir)) return [];
|
|
159
|
+
|
|
160
|
+
let entries: fs.Dirent[];
|
|
161
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return []; }
|
|
162
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
163
|
+
|
|
164
|
+
const flows: FlowConfig[] = [];
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
167
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
168
|
+
|
|
169
|
+
const flow = parseFlowFile(path.join(dir, entry.name), source);
|
|
170
|
+
if (flow) flows.push(flow);
|
|
171
|
+
}
|
|
172
|
+
return flows;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function mergeFlows(...groups: FlowConfig[][]): FlowConfig[] {
|
|
176
|
+
const flowMap = new Map<string, FlowConfig>();
|
|
177
|
+
for (const group of groups) {
|
|
178
|
+
for (const flow of group) flowMap.set(flow.name, flow);
|
|
179
|
+
}
|
|
180
|
+
return Array.from(flowMap.values());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Public API
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Discover all available flows according to the requested scope.
|
|
189
|
+
*
|
|
190
|
+
* Precedence is: bundled < user < project.
|
|
191
|
+
*/
|
|
192
|
+
export function discoverFlows(cwd: string, scope: FlowScope): FlowDiscoveryResult {
|
|
193
|
+
const bundledFlowsDir = getBundledFlowsDir();
|
|
194
|
+
const userFlowsDir = getUserFlowsDir();
|
|
195
|
+
const projectFlowsDir = findNearestProjectFlowsDir(cwd);
|
|
196
|
+
|
|
197
|
+
const bundledFlows = scope === "user" || scope === "project" ? [] : loadFlowsFromDir(bundledFlowsDir, "bundled");
|
|
198
|
+
const userFlows = scope === "project" || scope === "bundled" ? [] : loadFlowsFromDir(userFlowsDir, "user");
|
|
199
|
+
const projectFlows = scope === "user" || scope === "bundled" || !projectFlowsDir ? [] : loadFlowsFromDir(projectFlowsDir, "project");
|
|
200
|
+
|
|
201
|
+
if (scope === "bundled") {
|
|
202
|
+
return { flows: bundledFlows, projectFlowsDir };
|
|
203
|
+
}
|
|
204
|
+
if (scope === "user") {
|
|
205
|
+
return { flows: mergeFlows(bundledFlows, userFlows), projectFlowsDir };
|
|
206
|
+
}
|
|
207
|
+
if (scope === "project") {
|
|
208
|
+
return { flows: projectFlows, projectFlowsDir };
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
flows: mergeFlows(bundledFlows, userFlows, projectFlows),
|
|
212
|
+
projectFlowsDir,
|
|
213
|
+
};
|
|
214
|
+
}
|