@structured-vibe-coding/core 0.18.0 → 0.19.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/defaults/teams/research/roles/researcher.md +29 -0
- package/defaults/teams/research/team.yaml +7 -0
- package/defaults/teams/software/roles/architect.md +31 -0
- package/defaults/teams/software/roles/dev-lead.md +32 -0
- package/defaults/teams/software/roles/developer.md +30 -0
- package/defaults/teams/software/roles/product-manager.md +32 -0
- package/defaults/teams/software/roles/product-owner.md +30 -0
- package/defaults/teams/software/roles/qa.md +35 -0
- package/defaults/teams/software/routing.yaml +28 -0
- package/defaults/teams/software/team.yaml +24 -0
- package/defaults/teams/solo/roles/developer.md +28 -0
- package/defaults/teams/solo/team.yaml +7 -0
- package/defaults/templates/plan.md +17 -0
- package/defaults/templates/review.md +11 -0
- package/defaults/templates/story.md +13 -0
- package/defaults/templates/task.md +11 -0
- package/defaults/workflows/team-software.yaml +153 -0
- package/dist/{agents-AKSUXQHN.js → agents-PSIOQEYH.js} +3 -3
- package/dist/{change-context-REE7UKU7.js → change-context-EZJMSGSP.js} +1 -1
- package/dist/{chunk-4PIRWYRA.js → chunk-26YG6A4M.js} +166 -81
- package/dist/{chunk-LHSR7JG2.js → chunk-2FJBFYVJ.js} +306 -31
- package/dist/{chunk-QGP43XCN.js → chunk-H55RFAEI.js} +1 -1
- package/dist/{chunk-QHV5KZKX.js → chunk-HG7TPW5V.js} +93 -8
- package/dist/{chunk-44LQ5BKZ.js → chunk-TGRV6WGS.js} +812 -4
- package/dist/{gather-ZNV2NAUN.js → gather-7TSVKQO2.js} +1 -1
- package/dist/{greenfield-I5S5OS2D.js → greenfield-QFA5WVHE.js} +3 -3
- package/dist/index.d.ts +804 -10
- package/dist/index.js +2765 -524
- package/package.json +2 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConfigSchema,
|
|
3
|
+
defaultTeamsDir,
|
|
3
4
|
getStage,
|
|
4
5
|
isStageSkipped,
|
|
5
6
|
shouldAutoSkip
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HG7TPW5V.js";
|
|
7
8
|
import {
|
|
8
9
|
SddError
|
|
9
10
|
} from "./chunk-MHODNFIL.js";
|
|
@@ -15,11 +16,16 @@ import {
|
|
|
15
16
|
ensureDir,
|
|
16
17
|
guidancePaths,
|
|
17
18
|
indexDbPath,
|
|
19
|
+
listDirs,
|
|
20
|
+
listFiles,
|
|
18
21
|
memoryPathRel,
|
|
19
22
|
pathExists,
|
|
23
|
+
readText,
|
|
20
24
|
readYaml,
|
|
25
|
+
removePath,
|
|
21
26
|
sddRoot,
|
|
22
27
|
searchConfigPath,
|
|
28
|
+
writeText,
|
|
23
29
|
writeYaml
|
|
24
30
|
} from "./chunk-O5SP6X5R.js";
|
|
25
31
|
|
|
@@ -101,6 +107,55 @@ async function getHeadSha(projectRoot, runGit = runGitHead) {
|
|
|
101
107
|
return sha.toLowerCase();
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
// src/role/types.ts
|
|
111
|
+
function emptyRoleLists() {
|
|
112
|
+
return {
|
|
113
|
+
owns: [],
|
|
114
|
+
inputs: [],
|
|
115
|
+
outputs: [],
|
|
116
|
+
collaborates: [],
|
|
117
|
+
reviews: [],
|
|
118
|
+
approvers: [],
|
|
119
|
+
never: [],
|
|
120
|
+
askHumanWhen: [],
|
|
121
|
+
skills: [],
|
|
122
|
+
rules: [],
|
|
123
|
+
goals: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var WORK_TYPES = [
|
|
127
|
+
"route",
|
|
128
|
+
"classify",
|
|
129
|
+
"check",
|
|
130
|
+
"draft",
|
|
131
|
+
"design",
|
|
132
|
+
"code",
|
|
133
|
+
"review"
|
|
134
|
+
];
|
|
135
|
+
function isWorkType(value) {
|
|
136
|
+
return WORK_TYPES.includes(value);
|
|
137
|
+
}
|
|
138
|
+
function workKind(workType) {
|
|
139
|
+
return workType === "route" || workType === "classify" || workType === "check" ? "decide" : "reason";
|
|
140
|
+
}
|
|
141
|
+
var ROLE_TEXT = {
|
|
142
|
+
needsJob: "A role needs a job.",
|
|
143
|
+
notFound: "Role not found.",
|
|
144
|
+
exists: "Role already exists.",
|
|
145
|
+
badName: "A role name is not valid.",
|
|
146
|
+
self: "A role cannot review itself.",
|
|
147
|
+
x: "X is not a role tool.",
|
|
148
|
+
needsSection: "This needs an accepted section before a draft.",
|
|
149
|
+
noModel: "No role model is configured.",
|
|
150
|
+
cannot: "A role cannot do that.",
|
|
151
|
+
askFirst: "Ask first. This stays stopped.",
|
|
152
|
+
choice: "Allow once, Always allow, or Deny.",
|
|
153
|
+
unavailable: "This action is not available in this slice.",
|
|
154
|
+
expired: "Expired. The action did not run.",
|
|
155
|
+
runNotFound: "Run not found.",
|
|
156
|
+
badYaml: "role.yaml is invalid"
|
|
157
|
+
};
|
|
158
|
+
|
|
104
159
|
// src/search/tokenize.ts
|
|
105
160
|
import { encode } from "gpt-tokenizer/encoding/cl100k_base";
|
|
106
161
|
function countTokens(text) {
|
|
@@ -111,8 +166,8 @@ function ftsTokens(query) {
|
|
|
111
166
|
let current = "";
|
|
112
167
|
for (const ch of query) {
|
|
113
168
|
const code = ch.codePointAt(0) ?? 0;
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
169
|
+
const ok3 = code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code > 127;
|
|
170
|
+
if (ok3) {
|
|
116
171
|
current += ch;
|
|
117
172
|
} else if (current) {
|
|
118
173
|
tokens.push(current);
|
|
@@ -2416,6 +2471,719 @@ async function executeSearch(projectRoot, config, query, scope, budget) {
|
|
|
2416
2471
|
return provider.search(query, options);
|
|
2417
2472
|
}
|
|
2418
2473
|
|
|
2474
|
+
// src/role/models/schema.ts
|
|
2475
|
+
import { join as join6 } from "pathe";
|
|
2476
|
+
import { parse as parseYaml } from "yaml";
|
|
2477
|
+
import { z as z2 } from "zod";
|
|
2478
|
+
var idSchema = z2.string().trim().min(1);
|
|
2479
|
+
var envName = z2.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
|
|
2480
|
+
var confidence = z2.number().gte(0).lte(1);
|
|
2481
|
+
var WorkTypeSchema = z2.enum(WORK_TYPES);
|
|
2482
|
+
var workSlots = z2.record(WorkTypeSchema, idSchema);
|
|
2483
|
+
var ModelEntrySchema = z2.discriminatedUnion("provider", [
|
|
2484
|
+
z2.object({
|
|
2485
|
+
kind: z2.literal("decide"),
|
|
2486
|
+
provider: z2.literal("rules")
|
|
2487
|
+
}).strict(),
|
|
2488
|
+
z2.object({
|
|
2489
|
+
kind: z2.enum(["decide", "reason"]),
|
|
2490
|
+
provider: z2.literal("command"),
|
|
2491
|
+
command_env: envName,
|
|
2492
|
+
min_confidence: confidence.optional()
|
|
2493
|
+
}).strict(),
|
|
2494
|
+
z2.object({
|
|
2495
|
+
kind: z2.literal("decide"),
|
|
2496
|
+
provider: z2.literal("ollama"),
|
|
2497
|
+
model: z2.string().trim().min(1),
|
|
2498
|
+
base_url_env: envName,
|
|
2499
|
+
min_confidence: confidence.optional()
|
|
2500
|
+
}).strict()
|
|
2501
|
+
]);
|
|
2502
|
+
var ModelsConfigSchema = z2.object({
|
|
2503
|
+
version: z2.literal(1),
|
|
2504
|
+
models: z2.record(idSchema, ModelEntrySchema),
|
|
2505
|
+
defaults: z2.object({
|
|
2506
|
+
by_work_type: workSlots,
|
|
2507
|
+
fallback_reason: idSchema,
|
|
2508
|
+
fallback_decide: idSchema
|
|
2509
|
+
}).strict(),
|
|
2510
|
+
overrides: z2.object({
|
|
2511
|
+
bots: z2.record(idSchema, workSlots).optional(),
|
|
2512
|
+
stages: z2.record(idSchema, workSlots).optional(),
|
|
2513
|
+
roles: z2.record(idSchema, workSlots).optional()
|
|
2514
|
+
}).strict().optional()
|
|
2515
|
+
}).strict().superRefine((config, ctx) => {
|
|
2516
|
+
if (Object.keys(config.models).length === 0) {
|
|
2517
|
+
ctx.addIssue({ code: "custom", path: ["models"], message: "needs a model" });
|
|
2518
|
+
}
|
|
2519
|
+
expectSlot(ctx, config.models, config.defaults.fallback_reason, "reason", [
|
|
2520
|
+
"defaults",
|
|
2521
|
+
"fallback_reason"
|
|
2522
|
+
]);
|
|
2523
|
+
expectSlot(ctx, config.models, config.defaults.fallback_decide, "decide", [
|
|
2524
|
+
"defaults",
|
|
2525
|
+
"fallback_decide"
|
|
2526
|
+
]);
|
|
2527
|
+
expectMap(ctx, config.models, config.defaults.by_work_type, ["defaults", "by_work_type"]);
|
|
2528
|
+
expectNamed(ctx, config.models, config.overrides?.bots, ["overrides", "bots"]);
|
|
2529
|
+
expectNamed(ctx, config.models, config.overrides?.stages, ["overrides", "stages"]);
|
|
2530
|
+
expectNamed(ctx, config.models, config.overrides?.roles, ["overrides", "roles"]);
|
|
2531
|
+
});
|
|
2532
|
+
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2533
|
+
var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=\S+$/;
|
|
2534
|
+
function expectSlot(ctx, models, id, kind, path) {
|
|
2535
|
+
if (!id) return;
|
|
2536
|
+
const model = models[id];
|
|
2537
|
+
if (!model) {
|
|
2538
|
+
ctx.addIssue({ code: "custom", path, message: "unknown model" });
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
if (model.kind !== kind) {
|
|
2542
|
+
ctx.addIssue({ code: "custom", path, message: `kind must be ${kind}` });
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
function expectMap(ctx, models, map, path) {
|
|
2546
|
+
if (!map) return;
|
|
2547
|
+
for (const [work, id] of Object.entries(map)) {
|
|
2548
|
+
if (!isWorkType(work)) continue;
|
|
2549
|
+
expectSlot(ctx, models, id, workKind(work), [...path, work]);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
function expectNamed(ctx, models, named, path) {
|
|
2553
|
+
if (!named) return;
|
|
2554
|
+
for (const [name, map] of Object.entries(named)) {
|
|
2555
|
+
expectMap(ctx, models, map, [...path, name]);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
function formatModelsError(error) {
|
|
2559
|
+
const issue = error.issues[0];
|
|
2560
|
+
const path = issue?.path.join(".") || "models";
|
|
2561
|
+
const message = issue?.message ?? "invalid";
|
|
2562
|
+
return `models.yaml is invalid: ${path} ${message}`;
|
|
2563
|
+
}
|
|
2564
|
+
function looksSecret(value) {
|
|
2565
|
+
if (value.includes("-----BEGIN")) return true;
|
|
2566
|
+
if (!/\s/.test(value) && ASSIGNMENT.test(value)) return true;
|
|
2567
|
+
if (!/\s/.test(value) && value.length >= 32 && !ENV_NAME.test(value)) return true;
|
|
2568
|
+
return false;
|
|
2569
|
+
}
|
|
2570
|
+
function containsSecret(value) {
|
|
2571
|
+
if (typeof value === "string") return looksSecret(value);
|
|
2572
|
+
if (Array.isArray(value)) return value.some(containsSecret);
|
|
2573
|
+
if (value && typeof value === "object") {
|
|
2574
|
+
return Object.entries(value).some(([key, entry]) => {
|
|
2575
|
+
if (key === "model" && typeof entry === "string") {
|
|
2576
|
+
if (entry.includes("-----BEGIN")) return true;
|
|
2577
|
+
if (!/\s/.test(entry) && ASSIGNMENT.test(entry)) return true;
|
|
2578
|
+
return false;
|
|
2579
|
+
}
|
|
2580
|
+
return containsSecret(entry);
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
return false;
|
|
2584
|
+
}
|
|
2585
|
+
function modelsPath(root) {
|
|
2586
|
+
return join6(root, ".sdd", "models.yaml");
|
|
2587
|
+
}
|
|
2588
|
+
async function loadModels(root) {
|
|
2589
|
+
const path = modelsPath(root);
|
|
2590
|
+
if (!await pathExists(path)) return null;
|
|
2591
|
+
let raw;
|
|
2592
|
+
try {
|
|
2593
|
+
raw = parseYaml(await readText(path));
|
|
2594
|
+
} catch {
|
|
2595
|
+
return { error: "models.yaml is invalid: models could not be parsed" };
|
|
2596
|
+
}
|
|
2597
|
+
if (containsSecret(raw)) return { error: "models.yaml has a secret value." };
|
|
2598
|
+
const parsed = ModelsConfigSchema.safeParse(raw);
|
|
2599
|
+
if (!parsed.success) return { error: formatModelsError(parsed.error) };
|
|
2600
|
+
return { config: parsed.data };
|
|
2601
|
+
}
|
|
2602
|
+
async function checkModels(root) {
|
|
2603
|
+
const loaded = await loadModels(root);
|
|
2604
|
+
if (loaded === null) return { ok: true };
|
|
2605
|
+
if ("error" in loaded) return { ok: false, message: loaded.error };
|
|
2606
|
+
return { ok: true };
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
// src/role/profile.ts
|
|
2610
|
+
import { readFile as readFile4, writeFile } from "fs/promises";
|
|
2611
|
+
import { join as join7 } from "pathe";
|
|
2612
|
+
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
2613
|
+
var NAME_LIMIT = 80;
|
|
2614
|
+
function canonicalRoleName(input) {
|
|
2615
|
+
const name = input.trim();
|
|
2616
|
+
if (!name || name === "." || name === "..") return null;
|
|
2617
|
+
if (name.length > NAME_LIMIT) return null;
|
|
2618
|
+
if (name.includes("/") || name.includes("\\") || name.includes("\0")) return null;
|
|
2619
|
+
return name;
|
|
2620
|
+
}
|
|
2621
|
+
function roleFilePath(root, name) {
|
|
2622
|
+
return join7(root, ".sdd", "roles", `${name}.md`);
|
|
2623
|
+
}
|
|
2624
|
+
function conversationDir(root, name) {
|
|
2625
|
+
return join7(root, ".sdd", "role-runs", "conversation", name);
|
|
2626
|
+
}
|
|
2627
|
+
function memoryDir(root, name, team = false) {
|
|
2628
|
+
if (team) return join7(root, ".sdd", "agents", name);
|
|
2629
|
+
return join7(root, ".sdd", "role-runs", "memory", name);
|
|
2630
|
+
}
|
|
2631
|
+
function allowFilePath(root) {
|
|
2632
|
+
return join7(root, ".sdd", "role-runs", "allow.json");
|
|
2633
|
+
}
|
|
2634
|
+
function ok(stdout = "") {
|
|
2635
|
+
return { exitCode: 0, stdout };
|
|
2636
|
+
}
|
|
2637
|
+
function fail(stdout) {
|
|
2638
|
+
return { exitCode: 1, stdout };
|
|
2639
|
+
}
|
|
2640
|
+
function asText(value) {
|
|
2641
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2642
|
+
}
|
|
2643
|
+
var LIST_FIELDS = [
|
|
2644
|
+
["owns", "owns"],
|
|
2645
|
+
["inputs", "inputs"],
|
|
2646
|
+
["outputs", "outputs"],
|
|
2647
|
+
["collaborates", "collaborates"],
|
|
2648
|
+
["reviews", "reviews"],
|
|
2649
|
+
["approvers", "approvers"],
|
|
2650
|
+
["never", "never"],
|
|
2651
|
+
["askHumanWhen", "ask_human_when"],
|
|
2652
|
+
["skills", "skills"],
|
|
2653
|
+
["rules", "rules"],
|
|
2654
|
+
["goals", "goals"]
|
|
2655
|
+
];
|
|
2656
|
+
function stringList(value) {
|
|
2657
|
+
if (value === void 0) return [];
|
|
2658
|
+
if (!Array.isArray(value)) return null;
|
|
2659
|
+
const out = [];
|
|
2660
|
+
for (const item of value) {
|
|
2661
|
+
if (typeof item !== "string" || !item.trim()) return null;
|
|
2662
|
+
out.push(item.trim());
|
|
2663
|
+
}
|
|
2664
|
+
return out;
|
|
2665
|
+
}
|
|
2666
|
+
function parseRoleDocument(name, raw) {
|
|
2667
|
+
const text = raw.replace(/^\uFEFF/, "");
|
|
2668
|
+
const nl = text.indexOf("\n");
|
|
2669
|
+
if (nl < 0 || text.slice(0, nl).trim() !== "---") return null;
|
|
2670
|
+
const rest = text.slice(nl + 1);
|
|
2671
|
+
const close = /\r?\n---\s*(?:\r?\n|$)/.exec(rest);
|
|
2672
|
+
if (!close) return null;
|
|
2673
|
+
let data;
|
|
2674
|
+
try {
|
|
2675
|
+
data = parseYaml2(rest.slice(0, close.index));
|
|
2676
|
+
} catch {
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
2680
|
+
const rec = data;
|
|
2681
|
+
const lists = emptyRoleLists();
|
|
2682
|
+
for (const [field2, key] of LIST_FIELDS) {
|
|
2683
|
+
const parsed = stringList(rec[key]);
|
|
2684
|
+
if (!parsed) return null;
|
|
2685
|
+
lists[field2] = parsed;
|
|
2686
|
+
}
|
|
2687
|
+
const body = rest.slice(close.index + close[0].length).trim();
|
|
2688
|
+
return {
|
|
2689
|
+
name,
|
|
2690
|
+
label: asText(rec.label),
|
|
2691
|
+
job: asText(rec.job),
|
|
2692
|
+
description: body,
|
|
2693
|
+
askFirst: rec.ask_first === true,
|
|
2694
|
+
pinned: rec.pinned === true,
|
|
2695
|
+
hidden: rec.hidden === true,
|
|
2696
|
+
...lists
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
function renderRole(role) {
|
|
2700
|
+
const record = {
|
|
2701
|
+
name: role.name,
|
|
2702
|
+
label: role.label,
|
|
2703
|
+
job: role.job,
|
|
2704
|
+
ask_first: role.askFirst,
|
|
2705
|
+
pinned: role.pinned,
|
|
2706
|
+
hidden: role.hidden
|
|
2707
|
+
};
|
|
2708
|
+
for (const [field2, key] of LIST_FIELDS) {
|
|
2709
|
+
if (role[field2].length > 0) record[key] = role[field2];
|
|
2710
|
+
}
|
|
2711
|
+
const front = stringifyYaml(record, { lineWidth: 100 }).trimEnd();
|
|
2712
|
+
const description = role.description.trim();
|
|
2713
|
+
if (!description) return `---
|
|
2714
|
+
${front}
|
|
2715
|
+
---
|
|
2716
|
+
`;
|
|
2717
|
+
return `---
|
|
2718
|
+
${front}
|
|
2719
|
+
---
|
|
2720
|
+
${description}
|
|
2721
|
+
`;
|
|
2722
|
+
}
|
|
2723
|
+
async function readRole(root, name) {
|
|
2724
|
+
const canonical = canonicalRoleName(name);
|
|
2725
|
+
if (!canonical) return null;
|
|
2726
|
+
const path = roleFilePath(root, canonical);
|
|
2727
|
+
if (!await pathExists(path)) return null;
|
|
2728
|
+
try {
|
|
2729
|
+
return parseRoleDocument(canonical, await readText(path));
|
|
2730
|
+
} catch {
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
async function writeRole(root, role) {
|
|
2735
|
+
await writeText(roleFilePath(root, role.name), renderRole(role));
|
|
2736
|
+
}
|
|
2737
|
+
async function createRole(root, input) {
|
|
2738
|
+
const name = canonicalRoleName(input.name);
|
|
2739
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2740
|
+
const job = input.job.trim();
|
|
2741
|
+
if (!job) return fail(ROLE_TEXT.needsJob);
|
|
2742
|
+
if (await pathExists(roleFilePath(root, name))) return fail(ROLE_TEXT.exists);
|
|
2743
|
+
const lists = emptyRoleLists();
|
|
2744
|
+
for (const [field2] of LIST_FIELDS) {
|
|
2745
|
+
lists[field2] = input[field2] ?? [];
|
|
2746
|
+
}
|
|
2747
|
+
await writeRole(root, {
|
|
2748
|
+
name,
|
|
2749
|
+
label: input.label.trim(),
|
|
2750
|
+
job,
|
|
2751
|
+
description: (input.description ?? "").trim(),
|
|
2752
|
+
askFirst: input.askFirst === true,
|
|
2753
|
+
pinned: false,
|
|
2754
|
+
hidden: false,
|
|
2755
|
+
...lists
|
|
2756
|
+
});
|
|
2757
|
+
return ok();
|
|
2758
|
+
}
|
|
2759
|
+
async function editRole(root, nameInput, patch) {
|
|
2760
|
+
const name = canonicalRoleName(nameInput);
|
|
2761
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2762
|
+
const role = await readRole(root, name);
|
|
2763
|
+
if (!role) return fail(ROLE_TEXT.notFound);
|
|
2764
|
+
if (patch.job !== void 0 && !patch.job.trim()) return fail(ROLE_TEXT.needsJob);
|
|
2765
|
+
if (patch.label !== void 0) role.label = patch.label.trim();
|
|
2766
|
+
if (patch.job !== void 0) role.job = patch.job.trim();
|
|
2767
|
+
if (patch.description !== void 0) role.description = patch.description.trim();
|
|
2768
|
+
if (patch.askFirst !== void 0) role.askFirst = patch.askFirst;
|
|
2769
|
+
await writeRole(root, role);
|
|
2770
|
+
return ok();
|
|
2771
|
+
}
|
|
2772
|
+
async function setFlag(root, nameInput, flag) {
|
|
2773
|
+
const name = canonicalRoleName(nameInput);
|
|
2774
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2775
|
+
const role = await readRole(root, name);
|
|
2776
|
+
if (!role) return fail(ROLE_TEXT.notFound);
|
|
2777
|
+
role[flag] = true;
|
|
2778
|
+
await writeRole(root, role);
|
|
2779
|
+
return ok();
|
|
2780
|
+
}
|
|
2781
|
+
function pinRole(root, name) {
|
|
2782
|
+
return setFlag(root, name, "pinned");
|
|
2783
|
+
}
|
|
2784
|
+
function hideRole(root, name) {
|
|
2785
|
+
return setFlag(root, name, "hidden");
|
|
2786
|
+
}
|
|
2787
|
+
async function showRoles(root, opts = {}) {
|
|
2788
|
+
if (opts.name !== void 0) {
|
|
2789
|
+
const name = canonicalRoleName(opts.name);
|
|
2790
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2791
|
+
const path = roleFilePath(root, name);
|
|
2792
|
+
if (!await pathExists(path) || !await readRole(root, name)) return fail(ROLE_TEXT.notFound);
|
|
2793
|
+
return ok((await readText(path)).replace(/\s*$/, ""));
|
|
2794
|
+
}
|
|
2795
|
+
const dir = join7(root, ".sdd", "roles");
|
|
2796
|
+
if (!await pathExists(dir)) return ok();
|
|
2797
|
+
const roles = [];
|
|
2798
|
+
for (const file of await listFiles(dir)) {
|
|
2799
|
+
if (!file.endsWith(".md")) continue;
|
|
2800
|
+
const role = await readRole(root, file.slice(0, -".md".length));
|
|
2801
|
+
if (!role) continue;
|
|
2802
|
+
if (role.hidden && !opts.all) continue;
|
|
2803
|
+
roles.push(role);
|
|
2804
|
+
}
|
|
2805
|
+
roles.sort((a, b) => {
|
|
2806
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
2807
|
+
if (a.name < b.name) return -1;
|
|
2808
|
+
if (a.name > b.name) return 1;
|
|
2809
|
+
return 0;
|
|
2810
|
+
});
|
|
2811
|
+
return ok(roles.map((role) => `${role.name} ${role.label}`).join("\n"));
|
|
2812
|
+
}
|
|
2813
|
+
async function duplicateRole(root, nameInput) {
|
|
2814
|
+
const name = canonicalRoleName(nameInput);
|
|
2815
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2816
|
+
const source = await readRole(root, name);
|
|
2817
|
+
if (!source) return fail(ROLE_TEXT.notFound);
|
|
2818
|
+
const copyName = canonicalRoleName(`${name} copy`);
|
|
2819
|
+
if (!copyName) return fail(ROLE_TEXT.badName);
|
|
2820
|
+
if (await pathExists(roleFilePath(root, copyName))) return fail(ROLE_TEXT.exists);
|
|
2821
|
+
await writeRole(root, { ...source, name: copyName });
|
|
2822
|
+
return ok();
|
|
2823
|
+
}
|
|
2824
|
+
async function deleteRole(root, nameInput) {
|
|
2825
|
+
const name = canonicalRoleName(nameInput);
|
|
2826
|
+
if (!name) return fail(ROLE_TEXT.badName);
|
|
2827
|
+
const path = roleFilePath(root, name);
|
|
2828
|
+
if (!await pathExists(path)) return fail(ROLE_TEXT.notFound);
|
|
2829
|
+
await removePath(path);
|
|
2830
|
+
await removePath(conversationDir(root, name));
|
|
2831
|
+
await removeBotBrain(root, name);
|
|
2832
|
+
await removePath(memoryDir(root, name));
|
|
2833
|
+
await removeAllowKey(root, name);
|
|
2834
|
+
return ok();
|
|
2835
|
+
}
|
|
2836
|
+
async function removeBotBrain(root, roleName) {
|
|
2837
|
+
const teamPath = join7(root, ".sdd", "team.yaml");
|
|
2838
|
+
if (!await pathExists(teamPath)) return;
|
|
2839
|
+
let raw;
|
|
2840
|
+
try {
|
|
2841
|
+
raw = parseYaml2(await readText(teamPath));
|
|
2842
|
+
} catch {
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
2846
|
+
const bots = raw.bots;
|
|
2847
|
+
if (!Array.isArray(bots)) return;
|
|
2848
|
+
for (const bot of bots) {
|
|
2849
|
+
if (!bot || typeof bot !== "object" || Array.isArray(bot)) continue;
|
|
2850
|
+
const rec = bot;
|
|
2851
|
+
const botName = typeof rec.name === "string" ? rec.name : "";
|
|
2852
|
+
const botRole = typeof rec.role === "string" ? rec.role : "";
|
|
2853
|
+
if (botName !== roleName && botRole !== roleName) continue;
|
|
2854
|
+
if (!canonicalRoleName(botName)) continue;
|
|
2855
|
+
const dir = memoryDir(root, botName, true);
|
|
2856
|
+
await removePath(join7(dir, "memory.md"));
|
|
2857
|
+
await removePath(join7(dir, "lessons.jsonl"));
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
async function removeAllowKey(root, name) {
|
|
2861
|
+
const path = allowFilePath(root);
|
|
2862
|
+
if (!await pathExists(path)) return;
|
|
2863
|
+
let data;
|
|
2864
|
+
try {
|
|
2865
|
+
data = JSON.parse(await readText(path));
|
|
2866
|
+
} catch {
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return;
|
|
2870
|
+
const rec = data;
|
|
2871
|
+
if (!(name in rec)) return;
|
|
2872
|
+
delete rec[name];
|
|
2873
|
+
await writeFile(path, `${JSON.stringify(rec, null, 2)}
|
|
2874
|
+
`, "utf8");
|
|
2875
|
+
}
|
|
2876
|
+
async function listConversationRoles(root) {
|
|
2877
|
+
return listDirs(join7(root, ".sdd", "role-runs", "conversation"));
|
|
2878
|
+
}
|
|
2879
|
+
async function readAllowFile(root) {
|
|
2880
|
+
const path = allowFilePath(root);
|
|
2881
|
+
if (!await pathExists(path)) return null;
|
|
2882
|
+
try {
|
|
2883
|
+
const data = JSON.parse(await readFile4(path, "utf8"));
|
|
2884
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
2885
|
+
return data;
|
|
2886
|
+
} catch {
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// src/role/schema.ts
|
|
2892
|
+
import { z as z3 } from "zod";
|
|
2893
|
+
var nameSchema = z3.string().trim().min(1);
|
|
2894
|
+
var botSchema = z3.object({
|
|
2895
|
+
name: nameSchema,
|
|
2896
|
+
role: nameSchema,
|
|
2897
|
+
wip: z3.number().int().positive()
|
|
2898
|
+
}).strict();
|
|
2899
|
+
var humanSchema = z3.object({
|
|
2900
|
+
name: nameSchema
|
|
2901
|
+
}).strict();
|
|
2902
|
+
var TeamSchema = z3.object({
|
|
2903
|
+
template: nameSchema.optional(),
|
|
2904
|
+
branch: z3.string().trim().min(1),
|
|
2905
|
+
rooms: z3.object({
|
|
2906
|
+
commit: z3.boolean()
|
|
2907
|
+
}).strict(),
|
|
2908
|
+
bots: z3.array(botSchema).min(1),
|
|
2909
|
+
humans: z3.array(humanSchema).default([]),
|
|
2910
|
+
role_wip: z3.record(z3.string().trim().min(1), z3.number().int().positive()).optional(),
|
|
2911
|
+
limits: z3.object({
|
|
2912
|
+
loop_max_steps: z3.number().int().positive().optional(),
|
|
2913
|
+
room_rounds: z3.number().int().positive().optional()
|
|
2914
|
+
}).strict().refine((limits) => limits.loop_max_steps !== void 0 || limits.room_rounds !== void 0, {
|
|
2915
|
+
message: "needs a limit"
|
|
2916
|
+
}).optional()
|
|
2917
|
+
}).strict().superRefine((team, ctx) => {
|
|
2918
|
+
if (!team.branch.includes("{bot}")) {
|
|
2919
|
+
ctx.addIssue({
|
|
2920
|
+
code: "custom",
|
|
2921
|
+
path: ["branch"],
|
|
2922
|
+
message: "must include {bot}"
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
const names = /* @__PURE__ */ new Set();
|
|
2926
|
+
for (const [index, bot] of team.bots.entries()) {
|
|
2927
|
+
if (names.has(bot.name)) {
|
|
2928
|
+
ctx.addIssue({
|
|
2929
|
+
code: "custom",
|
|
2930
|
+
path: ["bots", index, "name"],
|
|
2931
|
+
message: "bot name is repeated"
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
names.add(bot.name);
|
|
2935
|
+
}
|
|
2936
|
+
});
|
|
2937
|
+
function formatTeamError(error) {
|
|
2938
|
+
const issue = error.issues[0];
|
|
2939
|
+
const path = issue?.path.join(".") || "team";
|
|
2940
|
+
const message = issue?.message ?? "invalid";
|
|
2941
|
+
return `team.yaml is invalid: ${path} ${message}`;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// src/role/team.ts
|
|
2945
|
+
import { spawnSync } from "child_process";
|
|
2946
|
+
import { join as join8 } from "pathe";
|
|
2947
|
+
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
2948
|
+
function ok2(stdout = "") {
|
|
2949
|
+
return { exitCode: 0, stdout };
|
|
2950
|
+
}
|
|
2951
|
+
function fail2(stdout) {
|
|
2952
|
+
return { exitCode: 1, stdout };
|
|
2953
|
+
}
|
|
2954
|
+
function gitUserName(root) {
|
|
2955
|
+
const result = spawnSync("git", ["-C", root, "config", "user.name"], { encoding: "utf8" });
|
|
2956
|
+
if (result.status !== 0) return null;
|
|
2957
|
+
const name = result.stdout.trim();
|
|
2958
|
+
return name || null;
|
|
2959
|
+
}
|
|
2960
|
+
async function listTeamTemplates() {
|
|
2961
|
+
return (await listDirs(defaultTeamsDir())).sort();
|
|
2962
|
+
}
|
|
2963
|
+
async function templateRoleFiles(template) {
|
|
2964
|
+
const dir = join8(defaultTeamsDir(), template, "roles");
|
|
2965
|
+
if (!await pathExists(dir)) return null;
|
|
2966
|
+
const files = (await listFiles(dir)).filter((file) => file.endsWith(".md")).sort();
|
|
2967
|
+
const roles = [];
|
|
2968
|
+
for (const file of files) {
|
|
2969
|
+
roles.push({ name: file.slice(0, -".md".length), raw: await readText(join8(dir, file)) });
|
|
2970
|
+
}
|
|
2971
|
+
return roles;
|
|
2972
|
+
}
|
|
2973
|
+
async function listRoleTemplates() {
|
|
2974
|
+
const ids = [];
|
|
2975
|
+
for (const template of await listTeamTemplates()) {
|
|
2976
|
+
const roles = await templateRoleFiles(template);
|
|
2977
|
+
if (!roles) continue;
|
|
2978
|
+
for (const role of roles) ids.push(`${template}/${role.name}`);
|
|
2979
|
+
}
|
|
2980
|
+
return ids;
|
|
2981
|
+
}
|
|
2982
|
+
async function loadTemplateTeam(template) {
|
|
2983
|
+
const path = join8(defaultTeamsDir(), template, "team.yaml");
|
|
2984
|
+
if (!await pathExists(path)) return `Team template "${template}" was not found.`;
|
|
2985
|
+
let raw;
|
|
2986
|
+
try {
|
|
2987
|
+
raw = parseYaml3(await readText(path));
|
|
2988
|
+
} catch {
|
|
2989
|
+
return `Team template "${template}" is invalid.`;
|
|
2990
|
+
}
|
|
2991
|
+
const parsed = TeamSchema.safeParse(raw);
|
|
2992
|
+
if (!parsed.success) return `Team template "${template}" is invalid.`;
|
|
2993
|
+
return parsed.data;
|
|
2994
|
+
}
|
|
2995
|
+
function availableTeams(names) {
|
|
2996
|
+
return names.join(", ") || "(none)";
|
|
2997
|
+
}
|
|
2998
|
+
async function initTeam(root, template, gitUser) {
|
|
2999
|
+
const templates = await listTeamTemplates();
|
|
3000
|
+
if (!template.trim()) {
|
|
3001
|
+
return fail2(`Pass --template. Available: ${availableTeams(templates)}.`);
|
|
3002
|
+
}
|
|
3003
|
+
if (!templates.includes(template)) {
|
|
3004
|
+
return fail2(
|
|
3005
|
+
`Team template "${template}" was not found. Available: ${availableTeams(templates)}.`
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
const loaded = await loadTemplateTeam(template);
|
|
3009
|
+
if (typeof loaded === "string") return fail2(loaded);
|
|
3010
|
+
const roles = await templateRoleFiles(template);
|
|
3011
|
+
if (!roles || roles.length === 0) return fail2(`Team template "${template}" is invalid.`);
|
|
3012
|
+
for (const role of roles) {
|
|
3013
|
+
const parsed = parseRoleDocument(role.name, role.raw);
|
|
3014
|
+
if (!parsed || !parsed.job) return fail2(`Team template "${template}" is invalid.`);
|
|
3015
|
+
}
|
|
3016
|
+
const botRoles = new Set(loaded.bots.map((bot) => bot.role));
|
|
3017
|
+
for (const roleName of botRoles) {
|
|
3018
|
+
if (!roles.some((role) => role.name === roleName)) {
|
|
3019
|
+
return fail2(`Team template "${template}" is invalid.`);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
const teamPath = join8(root, ".sdd", "team.yaml");
|
|
3023
|
+
if (await pathExists(teamPath)) {
|
|
3024
|
+
return fail2("Team config already exists at .sdd/team.yaml. Nothing was written.");
|
|
3025
|
+
}
|
|
3026
|
+
const routingSrc = join8(defaultTeamsDir(), template, "routing.yaml");
|
|
3027
|
+
const routingDest = join8(root, ".sdd", "routing.yaml");
|
|
3028
|
+
const copyRouting = await pathExists(routingSrc);
|
|
3029
|
+
if (copyRouting && await pathExists(routingDest)) {
|
|
3030
|
+
return fail2("Routing config already exists at .sdd/routing.yaml. Nothing was written.");
|
|
3031
|
+
}
|
|
3032
|
+
const existing = [];
|
|
3033
|
+
for (const role of roles) {
|
|
3034
|
+
if (await pathExists(roleFilePath(root, role.name))) existing.push(role.name);
|
|
3035
|
+
}
|
|
3036
|
+
if (existing.length > 0) {
|
|
3037
|
+
return fail2(`Role already exists: ${existing.join(", ")}. Nothing was written.`);
|
|
3038
|
+
}
|
|
3039
|
+
const humans = loaded.humans.length > 0 ? loaded.humans : gitUser ? [{ name: gitUser }] : [];
|
|
3040
|
+
const team = { ...loaded, template, humans };
|
|
3041
|
+
const checked = TeamSchema.safeParse(team);
|
|
3042
|
+
if (!checked.success) return fail2(formatTeamError(checked.error));
|
|
3043
|
+
for (const role of roles) {
|
|
3044
|
+
await writeText(join8(root, ".sdd", "roles", `${role.name}.md`), role.raw);
|
|
3045
|
+
}
|
|
3046
|
+
await writeText(teamPath, stringifyYaml2(checked.data, { lineWidth: 100 }));
|
|
3047
|
+
if (copyRouting) await writeText(routingDest, await readText(routingSrc));
|
|
3048
|
+
await syncRoomIgnore(root, checked.data);
|
|
3049
|
+
return ok2(`Wrote .sdd/team.yaml from ${template}.`);
|
|
3050
|
+
}
|
|
3051
|
+
async function readTeamConfig(root) {
|
|
3052
|
+
const path = join8(root, ".sdd", "team.yaml");
|
|
3053
|
+
if (!await pathExists(path)) {
|
|
3054
|
+
return { error: "No team config. Run sdd team init --template software." };
|
|
3055
|
+
}
|
|
3056
|
+
let raw;
|
|
3057
|
+
try {
|
|
3058
|
+
raw = parseYaml3(await readText(path));
|
|
3059
|
+
} catch {
|
|
3060
|
+
return { error: "team.yaml is invalid: team could not be parsed" };
|
|
3061
|
+
}
|
|
3062
|
+
const parsed = TeamSchema.safeParse(raw);
|
|
3063
|
+
if (!parsed.success) return { error: formatTeamError(parsed.error) };
|
|
3064
|
+
await syncRoomIgnore(root, parsed.data);
|
|
3065
|
+
return { team: parsed.data };
|
|
3066
|
+
}
|
|
3067
|
+
function formatTeam(team, gitUser) {
|
|
3068
|
+
const lines = [
|
|
3069
|
+
`template: ${team.template ?? ""}`,
|
|
3070
|
+
`branch: ${team.branch}`,
|
|
3071
|
+
`rooms.commit: ${team.rooms.commit}`,
|
|
3072
|
+
"bots:"
|
|
3073
|
+
];
|
|
3074
|
+
for (const bot of team.bots) {
|
|
3075
|
+
lines.push(`- ${bot.name} role ${bot.role} wip ${bot.wip}`);
|
|
3076
|
+
}
|
|
3077
|
+
lines.push("humans:");
|
|
3078
|
+
if (team.humans.length === 0) lines.push("- (none)");
|
|
3079
|
+
for (const human of team.humans) {
|
|
3080
|
+
const you = gitUser !== null && human.name === gitUser ? " (you)" : "";
|
|
3081
|
+
lines.push(`- ${human.name}${you}`);
|
|
3082
|
+
}
|
|
3083
|
+
if (team.role_wip && Object.keys(team.role_wip).length > 0) {
|
|
3084
|
+
lines.push("role_wip:");
|
|
3085
|
+
for (const name of Object.keys(team.role_wip).sort()) {
|
|
3086
|
+
lines.push(`- ${name} ${team.role_wip[name]}`);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
if (team.limits?.loop_max_steps) {
|
|
3090
|
+
lines.push(`limits.loop_max_steps: ${team.limits.loop_max_steps}`);
|
|
3091
|
+
}
|
|
3092
|
+
if (team.limits?.room_rounds) {
|
|
3093
|
+
lines.push(`limits.room_rounds: ${team.limits.room_rounds}`);
|
|
3094
|
+
}
|
|
3095
|
+
return lines.join("\n");
|
|
3096
|
+
}
|
|
3097
|
+
async function syncRoomIgnore(root, team) {
|
|
3098
|
+
const file = join8(root, ".sdd", ".gitignore");
|
|
3099
|
+
const existed = await pathExists(file);
|
|
3100
|
+
const original = existed ? await readText(file) : "";
|
|
3101
|
+
const lines = original.split(/\r?\n/);
|
|
3102
|
+
if (!team.rooms.commit) {
|
|
3103
|
+
if (lines.some((line) => line.trim() === "rooms/")) return;
|
|
3104
|
+
const next = original.length > 0 && !original.endsWith("\n") ? `${original}
|
|
3105
|
+
` : original;
|
|
3106
|
+
await writeText(file, `${next}rooms/
|
|
3107
|
+
`);
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
if (!existed) return;
|
|
3111
|
+
const kept = lines.filter((line) => line.trim() !== "rooms/");
|
|
3112
|
+
if (kept.length === lines.length) return;
|
|
3113
|
+
const body = kept.join("\n");
|
|
3114
|
+
await writeText(file, body.length > 0 && !body.endsWith("\n") ? `${body}
|
|
3115
|
+
` : body);
|
|
3116
|
+
}
|
|
3117
|
+
function resolveBot(team, name) {
|
|
3118
|
+
const byName = team.bots.find((bot) => bot.name === name);
|
|
3119
|
+
if (byName) return byName;
|
|
3120
|
+
const byRole = team.bots.filter((bot) => bot.role === name);
|
|
3121
|
+
if (byRole.length === 1) return byRole[0];
|
|
3122
|
+
if (byRole.length > 1) return { error: "Name the bot." };
|
|
3123
|
+
return { error: "Bot not found." };
|
|
3124
|
+
}
|
|
3125
|
+
async function showTeam(root, gitUser) {
|
|
3126
|
+
const loaded = await readTeamConfig(root);
|
|
3127
|
+
if ("error" in loaded) return fail2(loaded.error);
|
|
3128
|
+
return ok2(formatTeam(loaded.team, gitUser));
|
|
3129
|
+
}
|
|
3130
|
+
async function checkTeam(root) {
|
|
3131
|
+
const loaded = await readTeamConfig(root);
|
|
3132
|
+
if ("error" in loaded) return fail2(loaded.error);
|
|
3133
|
+
const models = await checkModels(root);
|
|
3134
|
+
if (!models.ok) return fail2(models.message);
|
|
3135
|
+
return ok2("Team config is valid.");
|
|
3136
|
+
}
|
|
3137
|
+
async function resolveRoleTemplate(template) {
|
|
3138
|
+
const ids = await listRoleTemplates();
|
|
3139
|
+
const trimmed = template.trim();
|
|
3140
|
+
if (!trimmed) {
|
|
3141
|
+
return { error: `Pass --template. Available: ${ids.join(", ") || "(none)"}.` };
|
|
3142
|
+
}
|
|
3143
|
+
if (ids.includes(trimmed)) {
|
|
3144
|
+
const [team, role] = trimmed.split("/");
|
|
3145
|
+
const roles = await templateRoleFiles(team);
|
|
3146
|
+
const found = roles?.find((item) => item.name === role);
|
|
3147
|
+
if (!found) return { error: `Role template "${trimmed}" was not found.` };
|
|
3148
|
+
return { id: trimmed, raw: found.raw };
|
|
3149
|
+
}
|
|
3150
|
+
const matches = ids.filter((id) => id.endsWith(`/${trimmed}`));
|
|
3151
|
+
if (matches.length === 1) {
|
|
3152
|
+
const resolved = await resolveRoleTemplate(matches[0]);
|
|
3153
|
+
return resolved;
|
|
3154
|
+
}
|
|
3155
|
+
if (matches.length > 1) {
|
|
3156
|
+
return {
|
|
3157
|
+
error: `Role template "${trimmed}" matches ${matches.join(" and ")}. Pass one of those.`
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
return { error: `Role template "${trimmed}" was not found. Available: ${ids.join(", ")}.` };
|
|
3161
|
+
}
|
|
3162
|
+
async function createRoleFromTemplate(root, input) {
|
|
3163
|
+
const resolved = await resolveRoleTemplate(input.template);
|
|
3164
|
+
if ("error" in resolved) return fail2(resolved.error);
|
|
3165
|
+
const parsed = parseRoleDocument(input.name, resolved.raw);
|
|
3166
|
+
if (!parsed || !parsed.job) return fail2(`Role template "${resolved.id}" is invalid.`);
|
|
3167
|
+
return createRole(root, {
|
|
3168
|
+
name: input.name,
|
|
3169
|
+
label: input.label?.trim() || parsed.label,
|
|
3170
|
+
job: input.job?.trim() || parsed.job,
|
|
3171
|
+
description: input.description?.trim() || parsed.description,
|
|
3172
|
+
askFirst: parsed.askFirst,
|
|
3173
|
+
owns: parsed.owns,
|
|
3174
|
+
inputs: parsed.inputs,
|
|
3175
|
+
outputs: parsed.outputs,
|
|
3176
|
+
collaborates: parsed.collaborates,
|
|
3177
|
+
reviews: parsed.reviews,
|
|
3178
|
+
approvers: parsed.approvers,
|
|
3179
|
+
never: parsed.never,
|
|
3180
|
+
askHumanWhen: parsed.askHumanWhen,
|
|
3181
|
+
skills: parsed.skills,
|
|
3182
|
+
rules: parsed.rules,
|
|
3183
|
+
goals: parsed.goals
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
|
|
2419
3187
|
// src/agent-handoff.ts
|
|
2420
3188
|
function formatStatus(ctx) {
|
|
2421
3189
|
const lines = [];
|
|
@@ -2472,6 +3240,11 @@ async function buildAgentPrompt(ctx, config, projectRoot, budget, runtime) {
|
|
|
2472
3240
|
}
|
|
2473
3241
|
parts.push(`- Workflow: ${ctx.meta.workflow}`);
|
|
2474
3242
|
parts.push(`- Change path: ${ctx.displayPath}`);
|
|
3243
|
+
if (stage?.owner) parts.push(`- Owner: ${stage.owner}`);
|
|
3244
|
+
if (stage?.collaborates?.length) parts.push(`- Works with: ${stage.collaborates.join(", ")}`);
|
|
3245
|
+
if (stage?.gate?.approvers?.length) {
|
|
3246
|
+
parts.push(`- Approvers: ${stage.gate.approvers.join(", ")}`);
|
|
3247
|
+
}
|
|
2475
3248
|
parts.push("");
|
|
2476
3249
|
const paths = guidancePaths(config);
|
|
2477
3250
|
parts.push(`## Constraints`);
|
|
@@ -2530,7 +3303,7 @@ async function buildAgentPrompt(ctx, config, projectRoot, budget, runtime) {
|
|
|
2530
3303
|
}
|
|
2531
3304
|
try {
|
|
2532
3305
|
const { loadMcpConfig } = await import("./sources-3TXUPU6V.js");
|
|
2533
|
-
const { gatherMcpContextForChange, formatMcpContextForHandoff } = await import("./gather-
|
|
3306
|
+
const { gatherMcpContextForChange, formatMcpContextForHandoff } = await import("./gather-7TSVKQO2.js");
|
|
2534
3307
|
const mcpCfg = await loadMcpConfig(projectRoot);
|
|
2535
3308
|
if (mcpCfg.auto_fetch_on_handoff && mcpCfg.sources.length) {
|
|
2536
3309
|
const fetched = await gatherMcpContextForChange(projectRoot, ctx);
|
|
@@ -2580,6 +3353,10 @@ export {
|
|
|
2580
3353
|
defaultConfig,
|
|
2581
3354
|
mergeConfigDefaults,
|
|
2582
3355
|
getHeadSha,
|
|
3356
|
+
WORK_TYPES,
|
|
3357
|
+
isWorkType,
|
|
3358
|
+
workKind,
|
|
3359
|
+
ROLE_TEXT,
|
|
2583
3360
|
countTokens,
|
|
2584
3361
|
ftsTokens,
|
|
2585
3362
|
SearchConfigError,
|
|
@@ -2617,12 +3394,43 @@ export {
|
|
|
2617
3394
|
keywordHits,
|
|
2618
3395
|
listScopeHits,
|
|
2619
3396
|
rankScope,
|
|
3397
|
+
searchProject,
|
|
2620
3398
|
registerSearchProvider,
|
|
2621
3399
|
clearRegisteredSearchProviders,
|
|
2622
3400
|
fallbackWarning,
|
|
2623
3401
|
resolveSearchProvider,
|
|
2624
3402
|
buildRetrievedInner,
|
|
2625
3403
|
executeSearch,
|
|
3404
|
+
ModelsConfigSchema,
|
|
3405
|
+
loadModels,
|
|
3406
|
+
checkModels,
|
|
3407
|
+
canonicalRoleName,
|
|
3408
|
+
roleFilePath,
|
|
3409
|
+
conversationDir,
|
|
3410
|
+
memoryDir,
|
|
3411
|
+
allowFilePath,
|
|
3412
|
+
parseRoleDocument,
|
|
3413
|
+
readRole,
|
|
3414
|
+
createRole,
|
|
3415
|
+
editRole,
|
|
3416
|
+
pinRole,
|
|
3417
|
+
hideRole,
|
|
3418
|
+
showRoles,
|
|
3419
|
+
duplicateRole,
|
|
3420
|
+
deleteRole,
|
|
3421
|
+
listConversationRoles,
|
|
3422
|
+
readAllowFile,
|
|
3423
|
+
TeamSchema,
|
|
3424
|
+
gitUserName,
|
|
3425
|
+
listTeamTemplates,
|
|
3426
|
+
listRoleTemplates,
|
|
3427
|
+
initTeam,
|
|
3428
|
+
readTeamConfig,
|
|
3429
|
+
formatTeam,
|
|
3430
|
+
resolveBot,
|
|
3431
|
+
showTeam,
|
|
3432
|
+
checkTeam,
|
|
3433
|
+
createRoleFromTemplate,
|
|
2626
3434
|
formatStatus,
|
|
2627
3435
|
buildAgentPrompt
|
|
2628
3436
|
};
|