aoaoe 1.3.0 → 2.0.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/dist/ab-reasoning.d.ts +42 -0
- package/dist/ab-reasoning.js +91 -0
- package/dist/alert-rules.d.ts +42 -0
- package/dist/alert-rules.js +94 -0
- package/dist/fleet-federation.d.ts +34 -0
- package/dist/fleet-federation.js +55 -0
- package/dist/index.js +215 -1
- package/dist/input.d.ts +33 -0
- package/dist/input.js +100 -0
- package/dist/multi-reasoner.d.ts +36 -0
- package/dist/multi-reasoner.js +87 -0
- package/dist/output-archival.d.ts +23 -0
- package/dist/output-archival.js +72 -0
- package/dist/runbook-generator.d.ts +21 -0
- package/dist/runbook-generator.js +104 -0
- package/dist/session-checkpoint.d.ts +55 -0
- package/dist/session-checkpoint.js +69 -0
- package/dist/token-quota.d.ts +45 -0
- package/dist/token-quota.js +76 -0
- package/dist/workflow-chain.d.ts +33 -0
- package/dist/workflow-chain.js +69 -0
- package/dist/workflow-cost-forecast.d.ts +22 -0
- package/dist/workflow-cost-forecast.js +55 -0
- package/dist/workflow-templates.d.ts +25 -0
- package/dist/workflow-templates.js +92 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// token-quota.ts — per-model token quotas for fleet-wide rate limiting.
|
|
2
|
+
// extends the USD-based rate limiter with token-level granularity per model.
|
|
3
|
+
/**
|
|
4
|
+
* Manage per-model token quotas.
|
|
5
|
+
*/
|
|
6
|
+
export class TokenQuotaManager {
|
|
7
|
+
quotas;
|
|
8
|
+
samples = [];
|
|
9
|
+
windowMs;
|
|
10
|
+
constructor(quotas = [], windowMs = 3_600_000) {
|
|
11
|
+
this.quotas = new Map(quotas.map((q) => [q.model, q]));
|
|
12
|
+
this.windowMs = windowMs;
|
|
13
|
+
}
|
|
14
|
+
/** Set or update a quota for a model. */
|
|
15
|
+
setQuota(model, maxInput, maxOutput) {
|
|
16
|
+
this.quotas.set(model, { model, maxInputTokensPerHour: maxInput, maxOutputTokensPerHour: maxOutput });
|
|
17
|
+
}
|
|
18
|
+
/** Record token usage for a model. */
|
|
19
|
+
recordUsage(model, inputTokens, outputTokens, now = Date.now()) {
|
|
20
|
+
this.samples.push({ timestamp: now, model, inputTokens, outputTokens });
|
|
21
|
+
this.prune(now);
|
|
22
|
+
}
|
|
23
|
+
/** Check if a model is within its token quota. */
|
|
24
|
+
getStatus(model, now = Date.now()) {
|
|
25
|
+
this.prune(now);
|
|
26
|
+
const quota = this.quotas.get(model);
|
|
27
|
+
if (!quota) {
|
|
28
|
+
return { model, inputTokensUsed: 0, outputTokensUsed: 0, inputLimit: Infinity, outputLimit: Infinity, inputPercent: 0, outputPercent: 0, blocked: false, reason: "no quota set" };
|
|
29
|
+
}
|
|
30
|
+
const cutoff = now - this.windowMs;
|
|
31
|
+
const modelSamples = this.samples.filter((s) => s.model === model && s.timestamp >= cutoff);
|
|
32
|
+
const inputUsed = modelSamples.reduce((sum, s) => sum + s.inputTokens, 0);
|
|
33
|
+
const outputUsed = modelSamples.reduce((sum, s) => sum + s.outputTokens, 0);
|
|
34
|
+
const inputPct = (inputUsed / quota.maxInputTokensPerHour) * 100;
|
|
35
|
+
const outputPct = (outputUsed / quota.maxOutputTokensPerHour) * 100;
|
|
36
|
+
const blocked = inputUsed >= quota.maxInputTokensPerHour || outputUsed >= quota.maxOutputTokensPerHour;
|
|
37
|
+
return {
|
|
38
|
+
model,
|
|
39
|
+
inputTokensUsed: inputUsed,
|
|
40
|
+
outputTokensUsed: outputUsed,
|
|
41
|
+
inputLimit: quota.maxInputTokensPerHour,
|
|
42
|
+
outputLimit: quota.maxOutputTokensPerHour,
|
|
43
|
+
inputPercent: inputPct,
|
|
44
|
+
outputPercent: outputPct,
|
|
45
|
+
blocked,
|
|
46
|
+
reason: blocked
|
|
47
|
+
? `${inputUsed >= quota.maxInputTokensPerHour ? "input" : "output"} quota exceeded`
|
|
48
|
+
: "ok",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Check if reasoning should be blocked for a model. */
|
|
52
|
+
isBlocked(model, now = Date.now()) {
|
|
53
|
+
return this.getStatus(model, now).blocked;
|
|
54
|
+
}
|
|
55
|
+
/** Get status for all models with quotas. */
|
|
56
|
+
getAllStatuses(now = Date.now()) {
|
|
57
|
+
return [...this.quotas.keys()].map((m) => this.getStatus(m, now));
|
|
58
|
+
}
|
|
59
|
+
/** Format for TUI display. */
|
|
60
|
+
formatAll(now = Date.now()) {
|
|
61
|
+
const statuses = this.getAllStatuses(now);
|
|
62
|
+
if (statuses.length === 0)
|
|
63
|
+
return [" (no token quotas configured)"];
|
|
64
|
+
const lines = [];
|
|
65
|
+
for (const s of statuses) {
|
|
66
|
+
const icon = s.blocked ? "🔴" : s.inputPercent > 80 || s.outputPercent > 80 ? "🟡" : "🟢";
|
|
67
|
+
lines.push(` ${icon} ${s.model}: ${s.inputTokensUsed.toLocaleString()}/${s.inputLimit.toLocaleString()} in, ${s.outputTokensUsed.toLocaleString()}/${s.outputLimit.toLocaleString()} out (${Math.round(Math.max(s.inputPercent, s.outputPercent))}%)`);
|
|
68
|
+
}
|
|
69
|
+
return lines;
|
|
70
|
+
}
|
|
71
|
+
prune(now) {
|
|
72
|
+
const cutoff = now - this.windowMs * 2;
|
|
73
|
+
this.samples = this.samples.filter((s) => s.timestamp >= cutoff);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=token-quota.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WorkflowState } from "./workflow-engine.js";
|
|
2
|
+
export interface WorkflowChainEntry {
|
|
3
|
+
workflowName: string;
|
|
4
|
+
dependsOn: string[];
|
|
5
|
+
status: "pending" | "active" | "completed" | "failed";
|
|
6
|
+
}
|
|
7
|
+
export interface WorkflowChain {
|
|
8
|
+
name: string;
|
|
9
|
+
entries: WorkflowChainEntry[];
|
|
10
|
+
startedAt: number;
|
|
11
|
+
completedAt?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a workflow chain from a list of entries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createWorkflowChain(name: string, entries: Array<{
|
|
17
|
+
workflowName: string;
|
|
18
|
+
dependsOn?: string[];
|
|
19
|
+
}>, now?: number): WorkflowChain;
|
|
20
|
+
/**
|
|
21
|
+
* Advance the chain: activate workflows whose dependencies are met.
|
|
22
|
+
* Returns workflow names that should be started.
|
|
23
|
+
*/
|
|
24
|
+
export declare function advanceChain(chain: WorkflowChain, workflowStates: ReadonlyMap<string, WorkflowState>): {
|
|
25
|
+
activate: string[];
|
|
26
|
+
completed: boolean;
|
|
27
|
+
failed: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Format workflow chain for TUI display.
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatWorkflowChain(chain: WorkflowChain): string[];
|
|
33
|
+
//# sourceMappingURL=workflow-chain.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// workflow-chain.ts — chain workflows together with dependencies.
|
|
2
|
+
// a workflow chain is a sequence of workflows where each can depend on
|
|
3
|
+
// the completion of previous workflows before starting.
|
|
4
|
+
/**
|
|
5
|
+
* Create a workflow chain from a list of entries.
|
|
6
|
+
*/
|
|
7
|
+
export function createWorkflowChain(name, entries, now = Date.now()) {
|
|
8
|
+
return {
|
|
9
|
+
name,
|
|
10
|
+
entries: entries.map((e) => ({
|
|
11
|
+
workflowName: e.workflowName,
|
|
12
|
+
dependsOn: e.dependsOn ?? [],
|
|
13
|
+
status: "pending",
|
|
14
|
+
})),
|
|
15
|
+
startedAt: now,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Advance the chain: activate workflows whose dependencies are met.
|
|
20
|
+
* Returns workflow names that should be started.
|
|
21
|
+
*/
|
|
22
|
+
export function advanceChain(chain, workflowStates) {
|
|
23
|
+
const completedSet = new Set();
|
|
24
|
+
const failedSet = new Set();
|
|
25
|
+
for (const entry of chain.entries) {
|
|
26
|
+
const wfState = workflowStates.get(entry.workflowName);
|
|
27
|
+
if (entry.status === "completed" || wfState?.completedAt) {
|
|
28
|
+
entry.status = "completed";
|
|
29
|
+
completedSet.add(entry.workflowName);
|
|
30
|
+
}
|
|
31
|
+
if (entry.status === "failed") {
|
|
32
|
+
failedSet.add(entry.workflowName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// check for failed chains
|
|
36
|
+
if (failedSet.size > 0) {
|
|
37
|
+
return { activate: [], completed: false, failed: true };
|
|
38
|
+
}
|
|
39
|
+
const activate = [];
|
|
40
|
+
for (const entry of chain.entries) {
|
|
41
|
+
if (entry.status !== "pending")
|
|
42
|
+
continue;
|
|
43
|
+
const depsmet = entry.dependsOn.every((dep) => completedSet.has(dep));
|
|
44
|
+
if (depsmet) {
|
|
45
|
+
entry.status = "active";
|
|
46
|
+
activate.push(entry.workflowName);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const allDone = chain.entries.every((e) => e.status === "completed");
|
|
50
|
+
if (allDone)
|
|
51
|
+
chain.completedAt = Date.now();
|
|
52
|
+
return { activate, completed: allDone, failed: false };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Format workflow chain for TUI display.
|
|
56
|
+
*/
|
|
57
|
+
export function formatWorkflowChain(chain) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
const elapsed = chain.completedAt ? chain.completedAt - chain.startedAt : Date.now() - chain.startedAt;
|
|
60
|
+
const dur = elapsed < 60_000 ? `${Math.round(elapsed / 1000)}s` : `${Math.round(elapsed / 60_000)}m`;
|
|
61
|
+
lines.push(` Workflow chain: ${chain.name} (${dur})`);
|
|
62
|
+
for (const e of chain.entries) {
|
|
63
|
+
const icon = e.status === "completed" ? "✓" : e.status === "active" ? "▶" : e.status === "failed" ? "✗" : "○";
|
|
64
|
+
const deps = e.dependsOn.length > 0 ? ` ← [${e.dependsOn.join(", ")}]` : "";
|
|
65
|
+
lines.push(` ${icon} ${e.workflowName} (${e.status})${deps}`);
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=workflow-chain.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from "./workflow-engine.js";
|
|
2
|
+
export interface WorkflowCostForecast {
|
|
3
|
+
workflowName: string;
|
|
4
|
+
totalEstimatedCostUsd: number;
|
|
5
|
+
totalEstimatedHours: number;
|
|
6
|
+
stages: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
taskCount: number;
|
|
9
|
+
estimatedCostUsd: number;
|
|
10
|
+
estimatedHours: number;
|
|
11
|
+
}>;
|
|
12
|
+
confidence: "low" | "medium" | "high";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Estimate the total cost of running a workflow.
|
|
16
|
+
*/
|
|
17
|
+
export declare function forecastWorkflowCost(workflow: WorkflowDefinition, historicalCostPerHour?: number): WorkflowCostForecast;
|
|
18
|
+
/**
|
|
19
|
+
* Format workflow cost forecast for TUI display.
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatWorkflowCostForecast(forecast: WorkflowCostForecast): string[];
|
|
22
|
+
//# sourceMappingURL=workflow-cost-forecast.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// workflow-cost-forecast.ts — estimate total workflow cost before starting.
|
|
2
|
+
// uses per-stage task difficulty + historical cost patterns to project spend.
|
|
3
|
+
import { scoreDifficulty } from "./difficulty-scorer.js";
|
|
4
|
+
// rough cost per difficulty point per hour (based on typical LLM usage)
|
|
5
|
+
const COST_PER_DIFFICULTY_HOUR = 0.50; // $0.50/difficulty-point/hour
|
|
6
|
+
/**
|
|
7
|
+
* Estimate the total cost of running a workflow.
|
|
8
|
+
*/
|
|
9
|
+
export function forecastWorkflowCost(workflow, historicalCostPerHour) {
|
|
10
|
+
const costRate = historicalCostPerHour ?? COST_PER_DIFFICULTY_HOUR;
|
|
11
|
+
let totalCost = 0;
|
|
12
|
+
let totalHours = 0;
|
|
13
|
+
const stages = workflow.stages.map((stage) => {
|
|
14
|
+
let stageCost = 0;
|
|
15
|
+
let stageHours = 0;
|
|
16
|
+
for (const task of stage.tasks) {
|
|
17
|
+
const difficulty = scoreDifficulty(task.sessionTitle, task.goal);
|
|
18
|
+
const hours = difficulty.estimatedHours;
|
|
19
|
+
const cost = hours * costRate * (difficulty.score / 5); // scale by difficulty
|
|
20
|
+
stageCost += cost;
|
|
21
|
+
stageHours = Math.max(stageHours, hours); // parallel tasks: max duration
|
|
22
|
+
}
|
|
23
|
+
totalCost += stageCost;
|
|
24
|
+
totalHours += stageHours; // sequential stages: sum durations
|
|
25
|
+
return {
|
|
26
|
+
name: stage.name,
|
|
27
|
+
taskCount: stage.tasks.length,
|
|
28
|
+
estimatedCostUsd: Math.round(stageCost * 100) / 100,
|
|
29
|
+
estimatedHours: Math.round(stageHours * 10) / 10,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const confidence = historicalCostPerHour ? "medium" : "low";
|
|
33
|
+
return {
|
|
34
|
+
workflowName: workflow.name,
|
|
35
|
+
totalEstimatedCostUsd: Math.round(totalCost * 100) / 100,
|
|
36
|
+
totalEstimatedHours: Math.round(totalHours * 10) / 10,
|
|
37
|
+
stages,
|
|
38
|
+
confidence,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format workflow cost forecast for TUI display.
|
|
43
|
+
*/
|
|
44
|
+
export function formatWorkflowCostForecast(forecast) {
|
|
45
|
+
const conf = forecast.confidence === "high" ? "●" : forecast.confidence === "medium" ? "◐" : "○";
|
|
46
|
+
const lines = [];
|
|
47
|
+
lines.push(` ${conf} Workflow cost forecast: "${forecast.workflowName}"`);
|
|
48
|
+
lines.push(` Total: ~$${forecast.totalEstimatedCostUsd.toFixed(2)} over ~${forecast.totalEstimatedHours}h`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
for (const s of forecast.stages) {
|
|
51
|
+
lines.push(` ${s.name}: ${s.taskCount} task${s.taskCount !== 1 ? "s" : ""}, ~$${s.estimatedCostUsd.toFixed(2)}, ~${s.estimatedHours}h`);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=workflow-cost-forecast.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from "./workflow-engine.js";
|
|
2
|
+
export interface WorkflowTemplate {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
stages: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
taskGoals: string[];
|
|
8
|
+
}>;
|
|
9
|
+
tags: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const BUILTIN_WORKFLOW_TEMPLATES: WorkflowTemplate[];
|
|
12
|
+
/**
|
|
13
|
+
* Find a workflow template by name.
|
|
14
|
+
*/
|
|
15
|
+
export declare function findWorkflowTemplate(name: string): WorkflowTemplate | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Instantiate a workflow definition from a template.
|
|
18
|
+
* sessionPrefix is used to generate session titles for each task.
|
|
19
|
+
*/
|
|
20
|
+
export declare function instantiateWorkflow(template: WorkflowTemplate, sessionPrefix: string): WorkflowDefinition;
|
|
21
|
+
/**
|
|
22
|
+
* Format workflow template list for TUI display.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatWorkflowTemplateList(): string[];
|
|
25
|
+
//# sourceMappingURL=workflow-templates.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// workflow-templates.ts — pre-built workflow definitions for common patterns.
|
|
2
|
+
// each template produces a WorkflowDefinition ready to instantiate.
|
|
3
|
+
export const BUILTIN_WORKFLOW_TEMPLATES = [
|
|
4
|
+
{
|
|
5
|
+
name: "ci-cd",
|
|
6
|
+
description: "Build → Test → Deploy pipeline",
|
|
7
|
+
stages: [
|
|
8
|
+
{ name: "build", taskGoals: ["Build the project and fix any compilation errors"] },
|
|
9
|
+
{ name: "test", taskGoals: ["Run the full test suite and fix any failures"] },
|
|
10
|
+
{ name: "deploy", taskGoals: ["Deploy to staging/production and verify health checks"] },
|
|
11
|
+
],
|
|
12
|
+
tags: ["ci", "cd", "deploy"],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "feature-dev",
|
|
16
|
+
description: "Implement → Test → Review → Merge",
|
|
17
|
+
stages: [
|
|
18
|
+
{ name: "implement", taskGoals: ["Implement the feature according to the spec"] },
|
|
19
|
+
{ name: "test", taskGoals: ["Write unit and integration tests for the new feature", "Run existing tests to verify no regressions"] },
|
|
20
|
+
{ name: "review", taskGoals: ["Self-review: check for code quality, security, and documentation"] },
|
|
21
|
+
{ name: "merge", taskGoals: ["Create PR, address feedback, merge to main"] },
|
|
22
|
+
],
|
|
23
|
+
tags: ["feature", "development"],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "refactor",
|
|
27
|
+
description: "Analyze → Refactor → Test → Cleanup",
|
|
28
|
+
stages: [
|
|
29
|
+
{ name: "analyze", taskGoals: ["Identify code smells, duplications, and improvement opportunities"] },
|
|
30
|
+
{ name: "refactor", taskGoals: ["Apply refactoring changes incrementally with tests passing at each step"] },
|
|
31
|
+
{ name: "test", taskGoals: ["Run full test suite, fix any regressions from refactoring"] },
|
|
32
|
+
{ name: "cleanup", taskGoals: ["Remove dead code, update documentation, commit with clear message"] },
|
|
33
|
+
],
|
|
34
|
+
tags: ["refactor", "cleanup"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "incident-response",
|
|
38
|
+
description: "Triage → Fix → Test → Postmortem",
|
|
39
|
+
stages: [
|
|
40
|
+
{ name: "triage", taskGoals: ["Identify root cause from logs and error output"] },
|
|
41
|
+
{ name: "fix", taskGoals: ["Implement the fix with minimal blast radius"] },
|
|
42
|
+
{ name: "test", taskGoals: ["Verify fix resolves the issue, run regression tests"] },
|
|
43
|
+
{ name: "postmortem", taskGoals: ["Document what happened, why, and how to prevent recurrence"] },
|
|
44
|
+
],
|
|
45
|
+
tags: ["incident", "hotfix", "production"],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "multi-repo",
|
|
49
|
+
description: "Parallel work across multiple repositories",
|
|
50
|
+
stages: [
|
|
51
|
+
{ name: "parallel-work", taskGoals: ["Complete assigned work in each repository"] },
|
|
52
|
+
{ name: "integration", taskGoals: ["Verify cross-repo integration works correctly"] },
|
|
53
|
+
{ name: "release", taskGoals: ["Tag releases and update dependency references"] },
|
|
54
|
+
],
|
|
55
|
+
tags: ["multi-repo", "monorepo"],
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Find a workflow template by name.
|
|
60
|
+
*/
|
|
61
|
+
export function findWorkflowTemplate(name) {
|
|
62
|
+
return BUILTIN_WORKFLOW_TEMPLATES.find((t) => t.name.toLowerCase() === name.toLowerCase());
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Instantiate a workflow definition from a template.
|
|
66
|
+
* sessionPrefix is used to generate session titles for each task.
|
|
67
|
+
*/
|
|
68
|
+
export function instantiateWorkflow(template, sessionPrefix) {
|
|
69
|
+
let taskCounter = 0;
|
|
70
|
+
const stages = template.stages.map((s) => ({
|
|
71
|
+
name: s.name,
|
|
72
|
+
tasks: s.taskGoals.map((goal) => {
|
|
73
|
+
taskCounter++;
|
|
74
|
+
return { sessionTitle: `${sessionPrefix}-${s.name}-${taskCounter}`, goal };
|
|
75
|
+
}),
|
|
76
|
+
}));
|
|
77
|
+
return { name: `${sessionPrefix}-${template.name}`, stages };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format workflow template list for TUI display.
|
|
81
|
+
*/
|
|
82
|
+
export function formatWorkflowTemplateList() {
|
|
83
|
+
const lines = [];
|
|
84
|
+
lines.push(` Workflow templates (${BUILTIN_WORKFLOW_TEMPLATES.length} built-in):`);
|
|
85
|
+
for (const t of BUILTIN_WORKFLOW_TEMPLATES) {
|
|
86
|
+
const stageNames = t.stages.map((s) => s.name).join(" → ");
|
|
87
|
+
lines.push(` ${t.name.padEnd(18)} ${stageNames} [${t.tags.join(", ")}]`);
|
|
88
|
+
lines.push(` ${t.description}`);
|
|
89
|
+
}
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=workflow-templates.js.map
|