@damian87/omp 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.
Files changed (57) hide show
  1. package/.github/skills/weighted-consensus/SKILL.md +120 -0
  2. package/README.md +59 -1
  3. package/catalog/capabilities.json +46 -0
  4. package/catalog/skills-general.json +29 -1
  5. package/dist/src/cli.d.ts +8 -0
  6. package/dist/src/cli.js +147 -3
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/council/config.d.ts +32 -0
  9. package/dist/src/council/config.js +84 -0
  10. package/dist/src/council/config.js.map +1 -0
  11. package/dist/src/council/engine.d.ts +19 -0
  12. package/dist/src/council/engine.js +193 -0
  13. package/dist/src/council/engine.js.map +1 -0
  14. package/dist/src/council/index.d.ts +20 -0
  15. package/dist/src/council/index.js +72 -0
  16. package/dist/src/council/index.js.map +1 -0
  17. package/dist/src/council/prompts.d.ts +32 -0
  18. package/dist/src/council/prompts.js +173 -0
  19. package/dist/src/council/prompts.js.map +1 -0
  20. package/dist/src/council/synth.d.ts +15 -0
  21. package/dist/src/council/synth.js +57 -0
  22. package/dist/src/council/synth.js.map +1 -0
  23. package/dist/src/council/types.d.ts +96 -0
  24. package/dist/src/council/types.js +4 -0
  25. package/dist/src/council/types.js.map +1 -0
  26. package/dist/src/mode-state/paths.d.ts +6 -0
  27. package/dist/src/mode-state/paths.js +12 -0
  28. package/dist/src/mode-state/paths.js.map +1 -1
  29. package/dist/src/team/api.d.ts +60 -1
  30. package/dist/src/team/api.js +101 -0
  31. package/dist/src/team/api.js.map +1 -1
  32. package/dist/src/team/config.d.ts +7 -0
  33. package/dist/src/team/config.js +16 -0
  34. package/dist/src/team/config.js.map +1 -0
  35. package/dist/src/team/mailbox.d.ts +30 -0
  36. package/dist/src/team/mailbox.js +188 -0
  37. package/dist/src/team/mailbox.js.map +1 -0
  38. package/dist/src/team/runtime.d.ts +9 -1
  39. package/dist/src/team/runtime.js +18 -12
  40. package/dist/src/team/runtime.js.map +1 -1
  41. package/dist/src/team/types.d.ts +25 -0
  42. package/dist/src/team/worker-bootstrap.js +29 -0
  43. package/dist/src/team/worker-bootstrap.js.map +1 -1
  44. package/dist/test/catalog.test.d.ts +1 -0
  45. package/dist/test/catalog.test.js +21 -0
  46. package/dist/test/catalog.test.js.map +1 -0
  47. package/dist/test/jira.test.d.ts +1 -0
  48. package/dist/test/jira.test.js +26 -0
  49. package/dist/test/jira.test.js.map +1 -0
  50. package/dist/test/lint.test.d.ts +1 -0
  51. package/dist/test/lint.test.js +9 -0
  52. package/dist/test/lint.test.js.map +1 -0
  53. package/dist/test/sync.test.d.ts +1 -0
  54. package/dist/test/sync.test.js +15 -0
  55. package/dist/test/sync.test.js.map +1 -0
  56. package/docs/plans/2026-05-29-team-messaging-and-nudge-gating-design.md +170 -0
  57. package/package.json +1 -1
@@ -0,0 +1,32 @@
1
+ import type { CouncilMemberSpec, CouncilTaskSpec, ResolvedCouncilConfig } from "./types.js";
2
+ export declare const DEFAULT_MIN_SURVIVORS = 2;
3
+ export declare const DEFAULT_PER_MEMBER_TIMEOUT_MS = 120000;
4
+ export declare const DEFAULT_MAX_CONCURRENCY = 4;
5
+ /**
6
+ * Built-in default roster. Uses the free/included lightweight models verified
7
+ * available via the Task 0 spike (gpt-5-mini, gpt-4.1) so the council runs
8
+ * without consuming premium model quota. Users override these in
9
+ * .omp/config.json `council` or inline via --models (e.g. add a Claude member
10
+ * for cross-provider diversity if their plan includes it).
11
+ */
12
+ export declare const DEFAULT_MEMBERS: CouncilMemberSpec[];
13
+ export declare const DEFAULT_SYNTHESIZER = "gpt-4.1";
14
+ interface PartialCouncilFileConfig {
15
+ members?: unknown;
16
+ synthesizer?: unknown;
17
+ minSurvivors?: unknown;
18
+ perMemberTimeoutMs?: unknown;
19
+ maxConcurrency?: unknown;
20
+ probe?: unknown;
21
+ }
22
+ export interface LoadCouncilConfigOptions {
23
+ cwd?: string;
24
+ }
25
+ /** Read the `council` block from .omp/config.json; tolerate missing/malformed. */
26
+ export declare function readCouncilFileConfig(options?: LoadCouncilConfigOptions): PartialCouncilFileConfig;
27
+ /**
28
+ * Resolve the effective council configuration.
29
+ * Precedence: spec override > .omp/config.json council block > built-in default.
30
+ */
31
+ export declare function loadCouncilConfig(spec?: CouncilTaskSpec, options?: LoadCouncilConfigOptions): ResolvedCouncilConfig;
32
+ export {};
@@ -0,0 +1,84 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export const DEFAULT_MIN_SURVIVORS = 2;
4
+ export const DEFAULT_PER_MEMBER_TIMEOUT_MS = 120000;
5
+ export const DEFAULT_MAX_CONCURRENCY = 4;
6
+ /**
7
+ * Built-in default roster. Uses the free/included lightweight models verified
8
+ * available via the Task 0 spike (gpt-5-mini, gpt-4.1) so the council runs
9
+ * without consuming premium model quota. Users override these in
10
+ * .omp/config.json `council` or inline via --models (e.g. add a Claude member
11
+ * for cross-provider diversity if their plan includes it).
12
+ */
13
+ export const DEFAULT_MEMBERS = [
14
+ { model: "gpt-5-mini", role: "critic", weight: 0.4 },
15
+ { model: "gpt-4.1", role: "architect", weight: 0.35 },
16
+ { model: "gpt-5-mini", role: "pragmatist", weight: 0.25 },
17
+ ];
18
+ export const DEFAULT_SYNTHESIZER = "gpt-4.1";
19
+ function isValidMember(value) {
20
+ if (typeof value !== "object" || value === null)
21
+ return false;
22
+ const m = value;
23
+ return (typeof m.model === "string" &&
24
+ m.model.length > 0 &&
25
+ typeof m.role === "string" &&
26
+ m.role.length > 0 &&
27
+ typeof m.weight === "number" &&
28
+ Number.isFinite(m.weight) &&
29
+ m.weight > 0);
30
+ }
31
+ /** Read the `council` block from .omp/config.json; tolerate missing/malformed. */
32
+ export function readCouncilFileConfig(options = {}) {
33
+ const cwd = options.cwd ?? process.cwd();
34
+ const configFile = join(cwd, ".omp", "config.json");
35
+ if (!existsSync(configFile))
36
+ return {};
37
+ try {
38
+ const parsed = JSON.parse(readFileSync(configFile, "utf8"));
39
+ return parsed.council ?? {};
40
+ }
41
+ catch {
42
+ return {}; // unreadable/invalid config -> treat as absent
43
+ }
44
+ }
45
+ /**
46
+ * Resolve the effective council configuration.
47
+ * Precedence: spec override > .omp/config.json council block > built-in default.
48
+ */
49
+ export function loadCouncilConfig(spec = { question: "" }, options = {}) {
50
+ const file = readCouncilFileConfig(options);
51
+ const fileMembers = Array.isArray(file.members)
52
+ ? file.members.filter(isValidMember)
53
+ : [];
54
+ const members = spec.members && spec.members.length > 0
55
+ ? spec.members
56
+ : fileMembers.length > 0
57
+ ? fileMembers
58
+ : DEFAULT_MEMBERS;
59
+ const synthesizerModel = spec.synthesizerModel ??
60
+ (typeof file.synthesizer === "string" && file.synthesizer.length > 0
61
+ ? file.synthesizer
62
+ : DEFAULT_SYNTHESIZER);
63
+ const minSurvivors = spec.minSurvivors ??
64
+ (typeof file.minSurvivors === "number" ? file.minSurvivors : DEFAULT_MIN_SURVIVORS);
65
+ const perMemberTimeoutMs = spec.perMemberTimeoutMs ??
66
+ (typeof file.perMemberTimeoutMs === "number"
67
+ ? file.perMemberTimeoutMs
68
+ : DEFAULT_PER_MEMBER_TIMEOUT_MS);
69
+ const maxConcurrency = spec.maxConcurrency ??
70
+ (typeof file.maxConcurrency === "number"
71
+ ? file.maxConcurrency
72
+ : DEFAULT_MAX_CONCURRENCY);
73
+ // probe precedence: spec.probe ?? config.probe ?? false
74
+ const probe = spec.probe ?? (typeof file.probe === "boolean" ? file.probe : false);
75
+ return {
76
+ members,
77
+ synthesizerModel,
78
+ minSurvivors,
79
+ perMemberTimeoutMs,
80
+ maxConcurrency: maxConcurrency > 0 ? maxConcurrency : DEFAULT_MAX_CONCURRENCY,
81
+ probe,
82
+ };
83
+ }
84
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/council/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type LoadCouncilConfigOptions } from "./config.js";
2
+ import type { CouncilDeps, CouncilRunResult, CouncilTaskSpec } from "./types.js";
3
+ /**
4
+ * Run `worker` over `items` with at most `limit` in flight. The slot is released
5
+ * in a finally block so a rejected/errored worker frees its slot too (otherwise
6
+ * a rejection would deadlock the queue for subsequent items). Results preserve
7
+ * input order. Worker rejections are reflected as rejected result promises, so
8
+ * callers should make `worker` non-throwing (we do).
9
+ */
10
+ export declare function runWithConcurrency<T, R>(items: T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
11
+ export interface RunCouncilOptions extends LoadCouncilConfigOptions {
12
+ }
13
+ /**
14
+ * Fan a question out to the council, parse + classify each member, then
15
+ * synthesize from survivors. Degrades gracefully: drops timed-out / unavailable /
16
+ * errored / unparseable members and synthesizes from the rest; fails only if
17
+ * fewer than `minSurvivors` survive.
18
+ */
19
+ export declare function runCouncil(spec: CouncilTaskSpec, deps: CouncilDeps, options?: RunCouncilOptions): Promise<CouncilRunResult>;
@@ -0,0 +1,193 @@
1
+ import { tmpdir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { loadCouncilConfig } from "./config.js";
4
+ import { buildMemberPrompt, parseMemberOutput } from "./prompts.js";
5
+ import { synthesize } from "./synth.js";
6
+ /** stderr signature emitted by copilot when a --model slug is not entitled. */
7
+ const UNAVAILABLE_SIGNATURE = /is not available/i;
8
+ function isUnavailable(res) {
9
+ return res.exitCode !== 0 && !res.timedOut && UNAVAILABLE_SIGNATURE.test(res.stderr);
10
+ }
11
+ /**
12
+ * Run `worker` over `items` with at most `limit` in flight. The slot is released
13
+ * in a finally block so a rejected/errored worker frees its slot too (otherwise
14
+ * a rejection would deadlock the queue for subsequent items). Results preserve
15
+ * input order. Worker rejections are reflected as rejected result promises, so
16
+ * callers should make `worker` non-throwing (we do).
17
+ */
18
+ export async function runWithConcurrency(items, limit, worker) {
19
+ const results = new Array(items.length);
20
+ let next = 0;
21
+ const effectiveLimit = Math.max(1, Math.min(limit, items.length || 1));
22
+ async function runner() {
23
+ while (true) {
24
+ const current = next++;
25
+ if (current >= items.length)
26
+ return;
27
+ try {
28
+ results[current] = await worker(items[current], current);
29
+ }
30
+ finally {
31
+ // slot is freed simply by looping to the next item
32
+ }
33
+ }
34
+ }
35
+ const runners = [];
36
+ for (let i = 0; i < effectiveLimit; i++)
37
+ runners.push(runner());
38
+ await Promise.all(runners);
39
+ return results;
40
+ }
41
+ function classify(res) {
42
+ if (res.timedOut)
43
+ return { status: "timeout", dropReason: "member timed out" };
44
+ if (isUnavailable(res)) {
45
+ return { status: "unavailable", dropReason: "model not available to this Copilot plan" };
46
+ }
47
+ if (res.exitCode !== 0) {
48
+ return { status: "error", dropReason: `member exited ${res.exitCode}` };
49
+ }
50
+ return { status: "ok" };
51
+ }
52
+ async function runMember(spec, index, taskSpec, config, deps, tmpDir) {
53
+ const prompt = buildMemberPrompt(taskSpec, spec);
54
+ const started = (deps.now ?? Date.now)();
55
+ let res;
56
+ try {
57
+ res = await deps.spawn({ model: spec.model, prompt, timeoutMs: config.perMemberTimeoutMs });
58
+ }
59
+ catch (err) {
60
+ return {
61
+ spec,
62
+ status: "error",
63
+ durationMs: 0,
64
+ dropReason: `spawn failed: ${String(err)}`,
65
+ };
66
+ }
67
+ const durationMs = (deps.now ?? Date.now)() - started;
68
+ let { status, dropReason } = classify(res);
69
+ let output;
70
+ if (status === "ok") {
71
+ const parsed = parseMemberOutput(res.stdout);
72
+ if (parsed) {
73
+ output = parsed;
74
+ }
75
+ else {
76
+ status = "unparseable";
77
+ dropReason = "no schema-valid JSON in output";
78
+ }
79
+ }
80
+ const jsonPath = join(tmpDir, `m${index}.json`);
81
+ const artifact = output ?? { rawStdout: res.stdout, rawStderr: res.stderr, status };
82
+ if (deps.writeArtifact) {
83
+ try {
84
+ deps.writeArtifact(jsonPath, JSON.stringify(artifact, null, 2));
85
+ }
86
+ catch {
87
+ // artifact write is best-effort; ignore failures
88
+ }
89
+ }
90
+ return {
91
+ spec,
92
+ status,
93
+ output,
94
+ rawStdout: res.stdout,
95
+ rawStderr: res.stderr,
96
+ exitCode: res.exitCode,
97
+ durationMs,
98
+ dropReason,
99
+ jsonPath,
100
+ };
101
+ }
102
+ /**
103
+ * Opt-in preflight probe: cheaply ping each DISTINCT model and prune only those
104
+ * that report "unavailable" (exit + stderr signature). A slow/timed-out probe is
105
+ * NOT treated as unavailable, so reachable-but-slow models are kept.
106
+ */
107
+ async function probeRoster(members, config, deps) {
108
+ const distinct = Array.from(new Set(members.map((m) => m.model)));
109
+ const probeTimeout = Math.min(15000, config.perMemberTimeoutMs);
110
+ const verdicts = await runWithConcurrency(distinct, config.maxConcurrency, async (model) => {
111
+ try {
112
+ const res = await deps.spawn({ model, prompt: "Reply with: ok", timeoutMs: probeTimeout });
113
+ return { model, unavailable: isUnavailable(res) };
114
+ }
115
+ catch {
116
+ return { model, unavailable: false }; // spawn error != entitlement problem
117
+ }
118
+ });
119
+ const unavailable = new Set(verdicts.filter((v) => v.unavailable).map((v) => v.model));
120
+ const kept = members.filter((m) => !unavailable.has(m.model));
121
+ const pruned = members
122
+ .filter((m) => unavailable.has(m.model))
123
+ .map((m) => ({ model: m.model, reason: "model not available to this Copilot plan" }));
124
+ return { kept, pruned };
125
+ }
126
+ /**
127
+ * Fan a question out to the council, parse + classify each member, then
128
+ * synthesize from survivors. Degrades gracefully: drops timed-out / unavailable /
129
+ * errored / unparseable members and synthesizes from the rest; fails only if
130
+ * fewer than `minSurvivors` survive.
131
+ */
132
+ export async function runCouncil(spec, deps, options = {}) {
133
+ const config = loadCouncilConfig(spec, options);
134
+ const now = deps.now ?? Date.now;
135
+ const tmpDir = spec.tmpDir ?? join(tmpdir(), `council-${now()}`);
136
+ let roster = config.members;
137
+ const prunedResults = [];
138
+ if (config.probe) {
139
+ const { kept, pruned } = await probeRoster(roster, config, deps);
140
+ roster = kept;
141
+ for (const p of pruned) {
142
+ const spc = config.members.find((m) => m.model === p.model);
143
+ if (spc) {
144
+ prunedResults.push({
145
+ spec: spc,
146
+ status: "unavailable",
147
+ durationMs: 0,
148
+ dropReason: p.reason,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ const ran = await runWithConcurrency(roster, config.maxConcurrency, (member, i) => runMember(member, i, spec, config, deps, tmpDir));
154
+ const members = [...ran, ...prunedResults];
155
+ const survivors = members.filter((m) => m.status === "ok");
156
+ const dropped = members.length - survivors.length;
157
+ if (survivors.length < config.minSurvivors) {
158
+ const unavailableModels = members
159
+ .filter((m) => m.status === "unavailable")
160
+ .map((m) => m.spec.model);
161
+ const detail = unavailableModels.length > 0
162
+ ? ` Unavailable models: ${unavailableModels.join(", ")}.`
163
+ : "";
164
+ return {
165
+ ok: false,
166
+ members,
167
+ survivors: survivors.length,
168
+ dropped,
169
+ tmpDir,
170
+ error: `too few surviving members (${survivors.length}/${config.minSurvivors}).${detail}`,
171
+ };
172
+ }
173
+ const synthResult = await synthesize(config, spec, members, deps);
174
+ if (!synthResult.ok || !synthResult.synth) {
175
+ return {
176
+ ok: false,
177
+ members,
178
+ survivors: survivors.length,
179
+ dropped,
180
+ tmpDir,
181
+ error: synthResult.error ?? "synthesis failed",
182
+ };
183
+ }
184
+ return {
185
+ ok: true,
186
+ synth: synthResult.synth,
187
+ members,
188
+ survivors: survivors.length,
189
+ dropped,
190
+ tmpDir,
191
+ };
192
+ }
193
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/council/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type RunCouncilOptions } from "./engine.js";
2
+ import type { CouncilDeps, CouncilRunResult, CouncilSpawn, CouncilTaskSpec } from "./types.js";
3
+ export * from "./types.js";
4
+ export { loadCouncilConfig, readCouncilFileConfig, DEFAULT_MEMBERS, DEFAULT_SYNTHESIZER, DEFAULT_MIN_SURVIVORS, DEFAULT_PER_MEMBER_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, } from "./config.js";
5
+ export { buildMemberPrompt, buildSynthPrompt, parseMemberOutput, parseSynthOutput, extractJsonCandidates, balancedBlocks, isValidMemberOutput, isValidSynthOutput, } from "./prompts.js";
6
+ export { runCouncil, runWithConcurrency } from "./engine.js";
7
+ export { synthesize } from "./synth.js";
8
+ /**
9
+ * Default member invoker: spawn `copilot --model <model> -p <prompt> --allow-all-tools`
10
+ * with PIPED stdio so we can capture stdout. Uses resolveCopilotBin() so the
11
+ * OMP_COPILOT_BIN stub-bin test seam works. Does NOT route through launchCopilot
12
+ * (that tmux-wraps and inherits stdio, which would discard the model's output).
13
+ */
14
+ export declare function createDefaultSpawn(bin?: string): CouncilSpawn;
15
+ /** Default deps: real spawn + fs artifact writer (mkdir -p on demand). */
16
+ export declare function createDefaultDeps(bin?: string): CouncilDeps;
17
+ /** Convenience entry for the CLI: run the council with the real default deps. */
18
+ export declare function runCouncilWithDefaults(spec: CouncilTaskSpec, options?: RunCouncilOptions & {
19
+ bin?: string;
20
+ }): Promise<CouncilRunResult>;
@@ -0,0 +1,72 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { resolveCopilotBin } from "../copilot/launch.js";
5
+ import { runCouncil } from "./engine.js";
6
+ export * from "./types.js";
7
+ export { loadCouncilConfig, readCouncilFileConfig, DEFAULT_MEMBERS, DEFAULT_SYNTHESIZER, DEFAULT_MIN_SURVIVORS, DEFAULT_PER_MEMBER_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, } from "./config.js";
8
+ export { buildMemberPrompt, buildSynthPrompt, parseMemberOutput, parseSynthOutput, extractJsonCandidates, balancedBlocks, isValidMemberOutput, isValidSynthOutput, } from "./prompts.js";
9
+ export { runCouncil, runWithConcurrency } from "./engine.js";
10
+ export { synthesize } from "./synth.js";
11
+ /**
12
+ * Default member invoker: spawn `copilot --model <model> -p <prompt> --allow-all-tools`
13
+ * with PIPED stdio so we can capture stdout. Uses resolveCopilotBin() so the
14
+ * OMP_COPILOT_BIN stub-bin test seam works. Does NOT route through launchCopilot
15
+ * (that tmux-wraps and inherits stdio, which would discard the model's output).
16
+ */
17
+ export function createDefaultSpawn(bin) {
18
+ const copilotBin = resolveCopilotBin(bin);
19
+ return (req) => new Promise((resolveFn) => {
20
+ const child = spawn(copilotBin, ["--model", req.model, "-p", req.prompt, "--allow-all-tools"], { stdio: ["ignore", "pipe", "pipe"] });
21
+ let stdout = "";
22
+ let stderr = "";
23
+ let timedOut = false;
24
+ let settled = false;
25
+ const timer = setTimeout(() => {
26
+ timedOut = true;
27
+ child.kill("SIGTERM");
28
+ }, req.timeoutMs);
29
+ child.stdout?.on("data", (d) => {
30
+ stdout += d.toString();
31
+ });
32
+ child.stderr?.on("data", (d) => {
33
+ stderr += d.toString();
34
+ });
35
+ child.on("error", () => {
36
+ if (settled)
37
+ return;
38
+ settled = true;
39
+ clearTimeout(timer);
40
+ resolveFn({ stdout, stderr, exitCode: 127, timedOut });
41
+ });
42
+ child.on("close", (code) => {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ clearTimeout(timer);
47
+ resolveFn({
48
+ stdout,
49
+ stderr,
50
+ exitCode: typeof code === "number" ? code : timedOut ? 124 : 1,
51
+ timedOut,
52
+ });
53
+ });
54
+ });
55
+ }
56
+ /** Default deps: real spawn + fs artifact writer (mkdir -p on demand). */
57
+ export function createDefaultDeps(bin) {
58
+ return {
59
+ spawn: createDefaultSpawn(bin),
60
+ now: () => Date.now(),
61
+ writeArtifact: (path, data) => {
62
+ mkdirSync(dirname(path), { recursive: true });
63
+ writeFileSync(path, data, "utf8");
64
+ },
65
+ };
66
+ }
67
+ /** Convenience entry for the CLI: run the council with the real default deps. */
68
+ export async function runCouncilWithDefaults(spec, options = {}) {
69
+ const { bin, ...rest } = options;
70
+ return runCouncil(spec, createDefaultDeps(bin), rest);
71
+ }
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/council/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { CouncilMemberOutput, CouncilMemberResult, CouncilMemberSpec, CouncilSynthOutput, CouncilTaskSpec, ResolvedCouncilConfig } from "./types.js";
2
+ /**
3
+ * Build a concise member prompt. Role is injected via {{COUNCIL_ROLE}}.
4
+ * Members are told to wrap their JSON in sentinels so the engine can extract it
5
+ * reliably from fence/banner noise (verified necessary in the Task 0 spike).
6
+ * NOTE: sentinels are NOT a security boundary — a hostile --context could embed
7
+ * the markers; the engine schema-validates extracted blocks to mitigate.
8
+ */
9
+ export declare function buildMemberPrompt(spec: CouncilTaskSpec, member: CouncilMemberSpec): string;
10
+ /** Build the low-context synthesizer prompt from surviving members. */
11
+ export declare function buildSynthPrompt(config: ResolvedCouncilConfig, spec: CouncilTaskSpec, survivors: CouncilMemberResult[]): string;
12
+ /**
13
+ * Enumerate top-level balanced {...} blocks in text (brace-depth scan).
14
+ * Returns the raw substrings in order of appearance.
15
+ */
16
+ export declare function balancedBlocks(text: string): string[];
17
+ /**
18
+ * Extract candidate JSON strings from raw stdout. Sentinel-wrapped content is
19
+ * preferred (primary contract); otherwise fall back to all balanced {...} blocks.
20
+ */
21
+ export declare function extractJsonCandidates(stdout: string): string[];
22
+ export declare function isValidMemberOutput(value: unknown): value is CouncilMemberOutput;
23
+ export declare function isValidSynthOutput(value: unknown): value is CouncilSynthOutput;
24
+ /**
25
+ * Parse a member's stdout into a validated CouncilMemberOutput.
26
+ * Strategy: enumerate candidate blocks, JSON.parse each, return the FIRST that
27
+ * passes schema validation. Tie-break = schema validity, not size/position.
28
+ * Returns null when no candidate validates (caller marks "unparseable").
29
+ */
30
+ export declare function parseMemberOutput(stdout: string): CouncilMemberOutput | null;
31
+ /** Parse the synthesizer's stdout into a validated CouncilSynthOutput. */
32
+ export declare function parseSynthOutput(stdout: string): CouncilSynthOutput | null;
@@ -0,0 +1,173 @@
1
+ const SENTINEL_START = "<<<JSON>>>";
2
+ const SENTINEL_END = "<<<END>>>";
3
+ /**
4
+ * Build a concise member prompt. Role is injected via {{COUNCIL_ROLE}}.
5
+ * Members are told to wrap their JSON in sentinels so the engine can extract it
6
+ * reliably from fence/banner noise (verified necessary in the Task 0 spike).
7
+ * NOTE: sentinels are NOT a security boundary — a hostile --context could embed
8
+ * the markers; the engine schema-validates extracted blocks to mitigate.
9
+ */
10
+ export function buildMemberPrompt(spec, member) {
11
+ const role = member.role;
12
+ const parts = [];
13
+ parts.push(`You are acting as the ${role} on an independent review council. ` +
14
+ `Bring YOUR OWN angle as the ${role}; you cannot see other members' answers.`);
15
+ parts.push(`Question: ${spec.question}`);
16
+ if (spec.context && spec.context.trim().length > 0) {
17
+ parts.push(`Context:\n${spec.context}`);
18
+ }
19
+ if (spec.rubric && spec.rubric.trim().length > 0) {
20
+ parts.push(`Evaluation rubric:\n${spec.rubric}`);
21
+ }
22
+ parts.push(`Respond with ONLY a JSON object wrapped EXACTLY in ${SENTINEL_START} and ${SENTINEL_END} markers, ` +
23
+ `no prose outside them. Shape: ` +
24
+ `${SENTINEL_START}{"verdict":"<short answer>","confidence":<0..1>,"rationale":"<concise, evidence-first>","risks":["..."],"dissent":"<where you expect to disagree>"}${SENTINEL_END}`);
25
+ return parts.join("\n\n");
26
+ }
27
+ /** Build the low-context synthesizer prompt from surviving members. */
28
+ export function buildSynthPrompt(config, spec, survivors) {
29
+ const memberBlocks = survivors
30
+ .map((r, i) => {
31
+ const o = r.output;
32
+ return (`Member ${i + 1} [role=${r.spec.role}, model=${r.spec.model}, weight=${r.spec.weight}]:\n` +
33
+ ` verdict: ${o.verdict}\n` +
34
+ ` confidence: ${o.confidence}\n` +
35
+ ` rationale: ${o.rationale}` +
36
+ (o.risks && o.risks.length ? `\n risks: ${o.risks.join("; ")}` : "") +
37
+ (o.dissent ? `\n dissent: ${o.dissent}` : ""));
38
+ })
39
+ .join("\n\n");
40
+ return [
41
+ `You are the synthesizer for a model council. Original question: ${spec.question}`,
42
+ spec.context ? `Shared context:\n${spec.context}` : "",
43
+ `Council members (independent, did not see each other):\n\n${memberBlocks}`,
44
+ `Treat each member's weight as a PRIOR/hint (e.g. trust the higher-weight member more), ` +
45
+ `but let EVIDENCE QUALITY override weight — do NOT do a simple majority vote or average. ` +
46
+ `Surface any well-reasoned dissent as a minority report so dangerous edge cases are not voted away.`,
47
+ `Respond with ONLY a JSON object wrapped EXACTLY in ${SENTINEL_START} and ${SENTINEL_END}. Shape: ` +
48
+ `${SENTINEL_START}{"verdict":"<final>","confidence":<0..1>,"rationale":"<your reasoning>","minority_report":"<notable dissent or empty string>",` +
49
+ `"per_member_summary":[{"model":"...","role":"...","weight":<n>,"verdict":"...","confidence":<0..1>,"dropped":false}]}${SENTINEL_END}`,
50
+ ]
51
+ .filter((s) => s.length > 0)
52
+ .join("\n\n");
53
+ }
54
+ /**
55
+ * Enumerate top-level balanced {...} blocks in text (brace-depth scan).
56
+ * Returns the raw substrings in order of appearance.
57
+ */
58
+ export function balancedBlocks(text) {
59
+ const blocks = [];
60
+ let depth = 0;
61
+ let start = -1;
62
+ let inString = false;
63
+ let escaped = false;
64
+ for (let i = 0; i < text.length; i++) {
65
+ const ch = text[i];
66
+ if (inString) {
67
+ if (escaped) {
68
+ escaped = false;
69
+ }
70
+ else if (ch === "\\") {
71
+ escaped = true;
72
+ }
73
+ else if (ch === '"') {
74
+ inString = false;
75
+ }
76
+ continue;
77
+ }
78
+ if (ch === '"') {
79
+ inString = true;
80
+ }
81
+ else if (ch === "{") {
82
+ if (depth === 0)
83
+ start = i;
84
+ depth++;
85
+ }
86
+ else if (ch === "}") {
87
+ if (depth > 0) {
88
+ depth--;
89
+ if (depth === 0 && start >= 0) {
90
+ blocks.push(text.slice(start, i + 1));
91
+ start = -1;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return blocks;
97
+ }
98
+ /**
99
+ * Extract candidate JSON strings from raw stdout. Sentinel-wrapped content is
100
+ * preferred (primary contract); otherwise fall back to all balanced {...} blocks.
101
+ */
102
+ export function extractJsonCandidates(stdout) {
103
+ const candidates = [];
104
+ const sentinelRe = new RegExp(`${SENTINEL_START}([\\s\\S]*?)${SENTINEL_END}`, "g");
105
+ let match;
106
+ while ((match = sentinelRe.exec(stdout)) !== null) {
107
+ candidates.push(match[1].trim());
108
+ }
109
+ // Fallback: balanced blocks (may include the inner object of a sentinel match,
110
+ // which is harmless — schema validation decides the winner).
111
+ for (const block of balancedBlocks(stdout)) {
112
+ candidates.push(block);
113
+ }
114
+ return candidates;
115
+ }
116
+ export function isValidMemberOutput(value) {
117
+ if (typeof value !== "object" || value === null)
118
+ return false;
119
+ const o = value;
120
+ return (typeof o.verdict === "string" &&
121
+ typeof o.confidence === "number" &&
122
+ Number.isFinite(o.confidence) &&
123
+ typeof o.rationale === "string");
124
+ }
125
+ export function isValidSynthOutput(value) {
126
+ if (typeof value !== "object" || value === null)
127
+ return false;
128
+ const o = value;
129
+ return (typeof o.verdict === "string" &&
130
+ typeof o.confidence === "number" &&
131
+ Number.isFinite(o.confidence) &&
132
+ typeof o.rationale === "string");
133
+ }
134
+ /**
135
+ * Parse a member's stdout into a validated CouncilMemberOutput.
136
+ * Strategy: enumerate candidate blocks, JSON.parse each, return the FIRST that
137
+ * passes schema validation. Tie-break = schema validity, not size/position.
138
+ * Returns null when no candidate validates (caller marks "unparseable").
139
+ */
140
+ export function parseMemberOutput(stdout) {
141
+ for (const candidate of extractJsonCandidates(stdout)) {
142
+ try {
143
+ const parsed = JSON.parse(candidate);
144
+ if (isValidMemberOutput(parsed))
145
+ return parsed;
146
+ }
147
+ catch {
148
+ // try next candidate
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ /** Parse the synthesizer's stdout into a validated CouncilSynthOutput. */
154
+ export function parseSynthOutput(stdout) {
155
+ for (const candidate of extractJsonCandidates(stdout)) {
156
+ try {
157
+ const parsed = JSON.parse(candidate);
158
+ if (isValidSynthOutput(parsed)) {
159
+ const o = parsed;
160
+ if (typeof o.minority_report !== "string")
161
+ o.minority_report = "";
162
+ if (!Array.isArray(o.per_member_summary))
163
+ o.per_member_summary = [];
164
+ return o;
165
+ }
166
+ }
167
+ catch {
168
+ // try next candidate
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ //# sourceMappingURL=prompts.js.map