ftown-bridge 0.9.2 → 0.9.4
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/dist/create-ftown-session.d.ts +1 -0
- package/dist/create-ftown-session.js +3 -2
- package/dist/create-ftown-session.js.map +1 -1
- package/dist/harness-cli.js +1 -1
- package/dist/harness-cli.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/install-ftown-workflows-cli.d.ts +3 -0
- package/dist/install-ftown-workflows-cli.js +30 -0
- package/dist/install-ftown-workflows-cli.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/workflow-runner-cli.d.ts +2 -0
- package/dist/workflow-runner-cli.js +315 -0
- package/dist/workflow-runner-cli.js.map +1 -0
- package/dist/workflow-runner.d.ts +162 -0
- package/dist/workflow-runner.js +305 -0
- package/dist/workflow-runner.js.map +1 -0
- package/package.json +5 -3
- package/skills/ftown-workflows/SKILL.md +282 -0
- package/skills/ftown-workflows/scripts/example.flow.mjs +122 -0
- package/skills/ftown-workflows/scripts/ftown-workflows +4 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* example.flow.mjs — template workflow: parallel code review fan-out + synthesis.
|
|
3
|
+
*
|
|
4
|
+
* Run it inside an ftown session:
|
|
5
|
+
*
|
|
6
|
+
* ~/.ftown/ftown-workflows run example.flow.mjs \
|
|
7
|
+
* --args '{"files":["src/auth.ts","src/api.ts","src/db.ts"]}' \
|
|
8
|
+
* --workdir /path/to/your/repo
|
|
9
|
+
*
|
|
10
|
+
* Add --run-id <previous-id> to resume a partial run without re-running
|
|
11
|
+
* steps whose result files already exist.
|
|
12
|
+
*
|
|
13
|
+
* The script exports a default async function that receives a WorkflowContext.
|
|
14
|
+
* The engine wires FTOWN_SESSION_ID from the calling session so children are
|
|
15
|
+
* registered as its children and are cleaned up on completion.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('../../../src/workflow-runner.js').WorkflowContext} ctx
|
|
20
|
+
*/
|
|
21
|
+
export default async function (ctx) {
|
|
22
|
+
// ── 1. Unpack args ──────────────────────────────────────────────────────────
|
|
23
|
+
// ctx.args is whatever was passed via --args (JSON-parsed).
|
|
24
|
+
// Provide a sensible fallback so the example runs without arguments too.
|
|
25
|
+
const files = /** @type {string[]} */ (
|
|
26
|
+
Array.isArray(ctx.args?.files)
|
|
27
|
+
? ctx.args.files
|
|
28
|
+
: ['src/auth.ts', 'src/api.ts', 'src/db.ts']
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
ctx.phase('Setup');
|
|
32
|
+
ctx.log(`Reviewing ${files.length} file(s): ${files.join(', ')}`);
|
|
33
|
+
ctx.log(`Budget: ${ctx.budget.maxAgents ?? 'unlimited'} agents`);
|
|
34
|
+
|
|
35
|
+
// ── 2. Fan-out: one reviewer per file, all running in parallel ───────────────
|
|
36
|
+
// ctx.parallel() is a BARRIER — it waits for every thunk before returning.
|
|
37
|
+
// A thunk that errors or whose agent returns null produces a null entry;
|
|
38
|
+
// the whole call never rejects.
|
|
39
|
+
// The concurrency cap (--concurrency, default 4) limits how many real sessions
|
|
40
|
+
// run simultaneously — you can safely pass more thunks than the cap.
|
|
41
|
+
ctx.phase('Review');
|
|
42
|
+
|
|
43
|
+
const reviews = await ctx.parallel(
|
|
44
|
+
files.map((file) => async () => {
|
|
45
|
+
// Each thunk is an async function returning a string (or null on failure).
|
|
46
|
+
const result = await ctx.agent(
|
|
47
|
+
// The prompt is the full task description for this child session.
|
|
48
|
+
// Keep it self-contained — the child has no other context.
|
|
49
|
+
`You are a code reviewer. Review the file \`${file}\` for:
|
|
50
|
+
- Security vulnerabilities (auth bypass, injection, secret leakage)
|
|
51
|
+
- Correctness bugs (off-by-one, null dereference, missing error handling)
|
|
52
|
+
- Style issues that reduce readability
|
|
53
|
+
|
|
54
|
+
Reply with a concise bullet-point list. Start with "## ${file}".`,
|
|
55
|
+
{
|
|
56
|
+
// label becomes the step key and the result filename.
|
|
57
|
+
// Unique, filesystem-safe labels enable per-step resume.
|
|
58
|
+
label: `review-${file.replace(/[^a-z0-9]/gi, '-')}`,
|
|
59
|
+
// phase groups events in the log output.
|
|
60
|
+
phase: 'review',
|
|
61
|
+
// shell defaults to 'claude'; override here if needed.
|
|
62
|
+
// shell: 'claude',
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (result == null) {
|
|
67
|
+
ctx.log(`WARN: review of ${file} failed or timed out`);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// ── 3. Filter out any failed reviews before synthesising ────────────────────
|
|
74
|
+
const successfulReviews = reviews.filter(
|
|
75
|
+
/** @param {string | null} r */ (r) => r != null,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (successfulReviews.length === 0) {
|
|
79
|
+
ctx.log('ERROR: all reviews failed — cannot synthesise');
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ctx.log(`${successfulReviews.length}/${files.length} reviews succeeded`);
|
|
84
|
+
|
|
85
|
+
// ── 4. Single synthesis agent consolidates all reviewer findings ─────────────
|
|
86
|
+
// This is a sequential step — one agent, no parallelism needed.
|
|
87
|
+
ctx.phase('Synthesise');
|
|
88
|
+
|
|
89
|
+
const synthesis = await ctx.agent(
|
|
90
|
+
`You are a senior engineer writing a final code-review report.
|
|
91
|
+
Below are ${successfulReviews.length} individual file reviews.
|
|
92
|
+
Consolidate them into a single report with:
|
|
93
|
+
1. An executive summary (2-3 sentences).
|
|
94
|
+
2. Critical issues (must fix before merge).
|
|
95
|
+
3. Minor issues (nice to fix).
|
|
96
|
+
4. Positive observations.
|
|
97
|
+
|
|
98
|
+
--- REVIEWS ---
|
|
99
|
+
${successfulReviews.join('\n\n---\n\n')}`,
|
|
100
|
+
{
|
|
101
|
+
label: 'synthesis',
|
|
102
|
+
phase: 'synthesise',
|
|
103
|
+
// Use schema to get a structured JSON response instead of a string.
|
|
104
|
+
// When schema is set, agent() returns the parsed object (or null).
|
|
105
|
+
// Comment it out to get a plain string instead.
|
|
106
|
+
schema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
required: ['summary', 'critical', 'minor', 'positives'],
|
|
109
|
+
properties: {
|
|
110
|
+
summary: { type: 'string' },
|
|
111
|
+
critical: { type: 'array', items: { type: 'string' } },
|
|
112
|
+
minor: { type: 'array', items: { type: 'string' } },
|
|
113
|
+
positives: { type: 'array', items: { type: 'string' } },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ── 5. Return value is printed by the CLI (pretty by default, --json for raw) ─
|
|
120
|
+
ctx.log(`Done. Budget used: ${ctx.budget.spent()} agent spawn(s).`);
|
|
121
|
+
return synthesis;
|
|
122
|
+
}
|