@damian87/omp 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.github/skills/weighted-consensus/SKILL.md +128 -0
  2. package/README.md +75 -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 +125 -1
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/council/config.d.ts +33 -0
  9. package/dist/src/council/config.js +89 -0
  10. package/dist/src/council/config.js.map +1 -0
  11. package/dist/src/council/engine.d.ts +21 -0
  12. package/dist/src/council/engine.js +216 -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 +172 -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 +98 -0
  24. package/dist/src/council/types.js +4 -0
  25. package/dist/src/council/types.js.map +1 -0
  26. package/package.json +3 -1
  27. package/dist/test/catalog.test.d.ts +0 -1
  28. package/dist/test/catalog.test.js +0 -21
  29. package/dist/test/catalog.test.js.map +0 -1
  30. package/dist/test/jira.test.d.ts +0 -1
  31. package/dist/test/jira.test.js +0 -26
  32. package/dist/test/jira.test.js.map +0 -1
  33. package/dist/test/lint.test.d.ts +0 -1
  34. package/dist/test/lint.test.js +0 -9
  35. package/dist/test/lint.test.js.map +0 -1
  36. package/dist/test/sync.test.d.ts +0 -1
  37. package/dist/test/sync.test.js +0 -15
  38. package/dist/test/sync.test.js.map +0 -1
@@ -0,0 +1,172 @@
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
+ `Council members (independent, did not see each other):\n\n${memberBlocks}`,
43
+ `Treat each member's weight as a PRIOR/hint (e.g. trust the higher-weight member more), ` +
44
+ `but let EVIDENCE QUALITY override weight — do NOT do a simple majority vote or average. ` +
45
+ `Surface any well-reasoned dissent as a minority report so dangerous edge cases are not voted away.`,
46
+ `Respond with ONLY a JSON object wrapped EXACTLY in ${SENTINEL_START} and ${SENTINEL_END}. Shape: ` +
47
+ `${SENTINEL_START}{"verdict":"<final>","confidence":<0..1>,"rationale":"<your reasoning>","minority_report":"<notable dissent or empty string>",` +
48
+ `"per_member_summary":[{"model":"...","role":"...","weight":<n>,"verdict":"...","confidence":<0..1>,"dropped":false}]}${SENTINEL_END}`,
49
+ ]
50
+ .filter((s) => s.length > 0)
51
+ .join("\n\n");
52
+ }
53
+ /**
54
+ * Enumerate top-level balanced {...} blocks in text (brace-depth scan).
55
+ * Returns the raw substrings in order of appearance.
56
+ */
57
+ export function balancedBlocks(text) {
58
+ const blocks = [];
59
+ let depth = 0;
60
+ let start = -1;
61
+ let inString = false;
62
+ let escaped = false;
63
+ for (let i = 0; i < text.length; i++) {
64
+ const ch = text[i];
65
+ if (inString) {
66
+ if (escaped) {
67
+ escaped = false;
68
+ }
69
+ else if (ch === "\\") {
70
+ escaped = true;
71
+ }
72
+ else if (ch === '"') {
73
+ inString = false;
74
+ }
75
+ continue;
76
+ }
77
+ if (ch === '"') {
78
+ inString = true;
79
+ }
80
+ else if (ch === "{") {
81
+ if (depth === 0)
82
+ start = i;
83
+ depth++;
84
+ }
85
+ else if (ch === "}") {
86
+ if (depth > 0) {
87
+ depth--;
88
+ if (depth === 0 && start >= 0) {
89
+ blocks.push(text.slice(start, i + 1));
90
+ start = -1;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return blocks;
96
+ }
97
+ /**
98
+ * Extract candidate JSON strings from raw stdout. Sentinel-wrapped content is
99
+ * preferred (primary contract); otherwise fall back to all balanced {...} blocks.
100
+ */
101
+ export function extractJsonCandidates(stdout) {
102
+ const candidates = [];
103
+ const sentinelRe = new RegExp(`${SENTINEL_START}([\\s\\S]*?)${SENTINEL_END}`, "g");
104
+ let match;
105
+ while ((match = sentinelRe.exec(stdout)) !== null) {
106
+ candidates.push(match[1].trim());
107
+ }
108
+ // Fallback: balanced blocks (may include the inner object of a sentinel match,
109
+ // which is harmless — schema validation decides the winner).
110
+ for (const block of balancedBlocks(stdout)) {
111
+ candidates.push(block);
112
+ }
113
+ return candidates;
114
+ }
115
+ export function isValidMemberOutput(value) {
116
+ if (typeof value !== "object" || value === null)
117
+ return false;
118
+ const o = value;
119
+ return (typeof o.verdict === "string" &&
120
+ typeof o.confidence === "number" &&
121
+ Number.isFinite(o.confidence) &&
122
+ typeof o.rationale === "string");
123
+ }
124
+ export function isValidSynthOutput(value) {
125
+ if (typeof value !== "object" || value === null)
126
+ return false;
127
+ const o = value;
128
+ return (typeof o.verdict === "string" &&
129
+ typeof o.confidence === "number" &&
130
+ Number.isFinite(o.confidence) &&
131
+ typeof o.rationale === "string");
132
+ }
133
+ /**
134
+ * Parse a member's stdout into a validated CouncilMemberOutput.
135
+ * Strategy: enumerate candidate blocks, JSON.parse each, return the FIRST that
136
+ * passes schema validation. Tie-break = schema validity, not size/position.
137
+ * Returns null when no candidate validates (caller marks "unparseable").
138
+ */
139
+ export function parseMemberOutput(stdout) {
140
+ for (const candidate of extractJsonCandidates(stdout)) {
141
+ try {
142
+ const parsed = JSON.parse(candidate);
143
+ if (isValidMemberOutput(parsed))
144
+ return parsed;
145
+ }
146
+ catch {
147
+ // try next candidate
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+ /** Parse the synthesizer's stdout into a validated CouncilSynthOutput. */
153
+ export function parseSynthOutput(stdout) {
154
+ for (const candidate of extractJsonCandidates(stdout)) {
155
+ try {
156
+ const parsed = JSON.parse(candidate);
157
+ if (isValidSynthOutput(parsed)) {
158
+ const o = parsed;
159
+ if (typeof o.minority_report !== "string")
160
+ o.minority_report = "";
161
+ if (!Array.isArray(o.per_member_summary))
162
+ o.per_member_summary = [];
163
+ return o;
164
+ }
165
+ }
166
+ catch {
167
+ // try next candidate
168
+ }
169
+ }
170
+ return null;
171
+ }
172
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/council/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { CouncilDeps, CouncilMemberResult, CouncilSynthOutput, CouncilTaskSpec, ResolvedCouncilConfig } from "./types.js";
2
+ export interface SynthesizeResult {
3
+ ok: boolean;
4
+ synth?: CouncilSynthOutput;
5
+ rawStdout?: string;
6
+ rawStderr?: string;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Run the synthesizer over surviving members. Never fabricates a verdict: if the
11
+ * synth call fails or is unparseable, returns ok:false with the raw output.
12
+ * The returned synth's per_member_summary is authoritatively rebuilt from ALL
13
+ * members (including dropped ones) so the model can't omit a dropped member.
14
+ */
15
+ export declare function synthesize(config: ResolvedCouncilConfig, spec: CouncilTaskSpec, allMembers: CouncilMemberResult[], deps: CouncilDeps): Promise<SynthesizeResult>;
@@ -0,0 +1,57 @@
1
+ import { buildSynthPrompt, parseSynthOutput } from "./prompts.js";
2
+ function buildPerMemberSummary(members) {
3
+ return members.map((r) => ({
4
+ model: r.spec.model,
5
+ role: r.spec.role,
6
+ weight: r.spec.weight,
7
+ verdict: r.output?.verdict ?? "",
8
+ confidence: r.output?.confidence ?? 0,
9
+ dropped: r.status !== "ok",
10
+ dropReason: r.status !== "ok" ? r.dropReason : undefined,
11
+ }));
12
+ }
13
+ /**
14
+ * Run the synthesizer over surviving members. Never fabricates a verdict: if the
15
+ * synth call fails or is unparseable, returns ok:false with the raw output.
16
+ * The returned synth's per_member_summary is authoritatively rebuilt from ALL
17
+ * members (including dropped ones) so the model can't omit a dropped member.
18
+ */
19
+ export async function synthesize(config, spec, allMembers, deps) {
20
+ const survivors = allMembers.filter((m) => m.status === "ok");
21
+ const prompt = buildSynthPrompt(config, spec, survivors);
22
+ let res;
23
+ try {
24
+ res = await deps.spawn({
25
+ model: config.synthesizerModel,
26
+ prompt,
27
+ timeoutMs: config.synthTimeoutMs,
28
+ });
29
+ }
30
+ catch (err) {
31
+ return { ok: false, error: `synth spawn failed: ${String(err)}` };
32
+ }
33
+ if (res.timedOut) {
34
+ return { ok: false, rawStdout: res.stdout, rawStderr: res.stderr, error: "synth timed out" };
35
+ }
36
+ if (res.exitCode !== 0) {
37
+ return {
38
+ ok: false,
39
+ rawStdout: res.stdout,
40
+ rawStderr: res.stderr,
41
+ error: `synth exited ${res.exitCode}`,
42
+ };
43
+ }
44
+ const parsed = parseSynthOutput(res.stdout);
45
+ if (!parsed) {
46
+ return {
47
+ ok: false,
48
+ rawStdout: res.stdout,
49
+ rawStderr: res.stderr,
50
+ error: "synth output unparseable",
51
+ };
52
+ }
53
+ // Authoritative summary from engine-side member records.
54
+ parsed.per_member_summary = buildPerMemberSummary(allMembers);
55
+ return { ok: true, synth: parsed, rawStdout: res.stdout, rawStderr: res.stderr };
56
+ }
57
+ //# sourceMappingURL=synth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synth.js","sourceRoot":"","sources":["../../../src/council/synth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAkBlE,SAAS,qBAAqB,CAC5B,OAA8B;IAE9B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK;QACnB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI;QACjB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM;QACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE;QAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC;QACrC,OAAO,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI;QAC1B,UAAU,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;KACzD,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAA6B,EAC7B,IAAqB,EACrB,UAAiC,EACjC,IAAiB;IAEjB,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAEzD,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;YACrB,KAAK,EAAE,MAAM,CAAC,gBAAgB;YAC9B,MAAM;YACN,SAAS,EAAE,MAAM,CAAC,cAAc;SACjC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACpE,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC/F,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,KAAK,EAAE,gBAAgB,GAAG,CAAC,QAAQ,EAAE;SACtC,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,EAAE,EAAE,KAAK;YACT,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,KAAK,EAAE,0BAA0B;SAClC,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,MAAM,CAAC,kBAAkB,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC9D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;AACnF,CAAC"}
@@ -0,0 +1,98 @@
1
+ /** One council member: a model running under a role with a prior weight. */
2
+ export interface CouncilMemberSpec {
3
+ model: string;
4
+ role: string;
5
+ weight: number;
6
+ }
7
+ /** The task the council answers. Generic so /code-review etc. can reuse it. */
8
+ export interface CouncilTaskSpec {
9
+ question: string;
10
+ context?: string;
11
+ rubric?: string;
12
+ rolePack?: string;
13
+ members?: CouncilMemberSpec[];
14
+ minSurvivors?: number;
15
+ perMemberTimeoutMs?: number;
16
+ synthTimeoutMs?: number;
17
+ synthesizerModel?: string;
18
+ probe?: boolean;
19
+ maxConcurrency?: number;
20
+ tmpDir?: string;
21
+ }
22
+ /** What each member MUST emit (the member JSON contract). */
23
+ export interface CouncilMemberOutput {
24
+ verdict: string;
25
+ confidence: number;
26
+ rationale: string;
27
+ risks?: string[];
28
+ dissent?: string;
29
+ }
30
+ /** Per-member runtime classification. "unavailable" is distinct from "error". */
31
+ export type CouncilMemberStatus = "ok" | "timeout" | "error" | "unavailable" | "unparseable";
32
+ /** Per-member runtime result (engine-internal, surfaced in --json). */
33
+ export interface CouncilMemberResult {
34
+ spec: CouncilMemberSpec;
35
+ status: CouncilMemberStatus;
36
+ output?: CouncilMemberOutput;
37
+ rawStdout?: string;
38
+ rawStderr?: string;
39
+ exitCode?: number;
40
+ durationMs: number;
41
+ dropReason?: string;
42
+ jsonPath?: string;
43
+ }
44
+ export interface CouncilPerMemberSummary {
45
+ model: string;
46
+ role: string;
47
+ weight: number;
48
+ verdict: string;
49
+ confidence: number;
50
+ dropped: boolean;
51
+ dropReason?: string;
52
+ }
53
+ /** Synthesizer output schema (final verdict). */
54
+ export interface CouncilSynthOutput {
55
+ verdict: string;
56
+ confidence: number;
57
+ rationale: string;
58
+ minority_report: string;
59
+ per_member_summary: CouncilPerMemberSummary[];
60
+ }
61
+ /** Top-level engine result (what runCouncil returns / cli prints). */
62
+ export interface CouncilRunResult {
63
+ ok: boolean;
64
+ synth?: CouncilSynthOutput;
65
+ members: CouncilMemberResult[];
66
+ survivors: number;
67
+ dropped: number;
68
+ tmpDir: string;
69
+ error?: string;
70
+ }
71
+ /** Resolved configuration after merging spec > config > built-in default. */
72
+ export interface ResolvedCouncilConfig {
73
+ members: CouncilMemberSpec[];
74
+ synthesizerModel: string;
75
+ minSurvivors: number;
76
+ perMemberTimeoutMs: number;
77
+ synthTimeoutMs: number;
78
+ maxConcurrency: number;
79
+ probe: boolean;
80
+ }
81
+ export interface SpawnRequest {
82
+ model: string;
83
+ prompt: string;
84
+ timeoutMs: number;
85
+ }
86
+ export interface SpawnResponse {
87
+ stdout: string;
88
+ stderr: string;
89
+ exitCode: number;
90
+ timedOut: boolean;
91
+ }
92
+ /** Real impl spawns `copilot --model <model> -p <prompt>`; tests inject a fake. */
93
+ export type CouncilSpawn = (req: SpawnRequest) => Promise<SpawnResponse>;
94
+ export interface CouncilDeps {
95
+ spawn: CouncilSpawn;
96
+ now?: () => number;
97
+ writeArtifact?: (path: string, data: string) => void;
98
+ }
@@ -0,0 +1,4 @@
1
+ // Type contracts for the weighted-consensus model council.
2
+ // See docs/plans / .omc/plans/weighted-consensus.md for the authoritative design.
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/council/types.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,kFAAkF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damian87/omp",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "GitHub Copilot project skills catalog and Jira handoff tools.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -24,6 +24,8 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "tsc -p tsconfig.json",
27
+ "release": "bash tools/release.sh",
28
+ "release:publish-only": "bash tools/release.sh --publish-only",
27
29
  "check:catalog": "npm run build && node dist/src/cli.js catalog validate",
28
30
  "catalog:list": "npm run build && node dist/src/cli.js catalog list",
29
31
  "project:inspect": "npm run build && node dist/src/cli.js project inspect",
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
- import { loadCapabilityCatalog, loadSkillCatalog, validateCatalog } from '../src/catalog.js';
4
- test('catalog contains approved MVP skills and valid projections', () => {
5
- const catalog = loadSkillCatalog();
6
- assert.deepEqual(validateCatalog(), []);
7
- const names = catalog.skills.map((skill) => skill.name);
8
- assert.deepEqual(names, ['grill', 'verify', 'jira-ticket', 'code-review', 'qa']);
9
- assert.ok(catalog.projectionCommands.includes('grill-me'));
10
- assert.ok(catalog.projectionCommands.includes('team'));
11
- assert.ok(catalog.projectionCommands.includes('ralph'));
12
- });
13
- test('team and ralph stay thin handoff capabilities for Copilot', () => {
14
- const capabilities = loadCapabilityCatalog();
15
- for (const name of ['team', 'ralph']) {
16
- const capability = capabilities.capabilities.find((entry) => entry.name === name);
17
- assert.ok(capability, `${name} capability exists`);
18
- assert.equal(capability.support.copilot, 'thin-handoff');
19
- }
20
- });
21
- //# sourceMappingURL=catalog.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalog.test.js","sourceRoot":"","sources":["../../test/catalog.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE7F,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;IACtE,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,MAAM,CAAC,SAAS,CAAC,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IACjF,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACrE,MAAM,YAAY,GAAG,qBAAqB,EAAE,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAClF,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,IAAI,oBAAoB,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,26 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
- import { commentPayload, createIssuePayload, discoverJiraConfig, isJiraConfigured, linkFallbackPayload, safeUpdatePayload, transitionFallbackPayload } from '../src/jira.js';
4
- test('jira config discovery supports env and configuration detection', () => {
5
- const config = discoverJiraConfig('/tmp/no-such-root', {
6
- JIRA_SITE_URL: 'https://example.atlassian.net',
7
- JIRA_EMAIL: 'agent@example.com',
8
- JIRA_API_TOKEN: 'secret-token',
9
- JIRA_PROJECT_KEY: 'OMC'
10
- });
11
- assert.equal(config.siteUrl, 'https://example.atlassian.net');
12
- assert.equal(config.projectKey, 'OMC');
13
- assert.equal(isJiraConfigured(config), true);
14
- });
15
- test('jira adapter renders create, comment, update, and fallback payloads', () => {
16
- const config = discoverJiraConfig('/tmp/no-such-root', { JIRA_PROJECT_KEY: 'OMC' });
17
- const create = createIssuePayload(config, { summary: 'Implement slice', description: 'Body' });
18
- assert.equal(create.operation, 'create');
19
- assert.equal(create.configured, false);
20
- assert.match(JSON.stringify(create.body), /Implement slice/);
21
- assert.equal(commentPayload(config, 'OMC-1', 'Evidence').operation, 'comment');
22
- assert.equal(safeUpdatePayload(config, 'OMC-1', { summary: 'New' }).method, 'PUT');
23
- assert.equal(transitionFallbackPayload(config, 'OMC-1', 'Done').configured, false);
24
- assert.equal(linkFallbackPayload(config, 'OMC-1', 'OMC-2').operation, 'link-fallback');
25
- });
26
- //# sourceMappingURL=jira.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"jira.test.js","sourceRoot":"","sources":["../../test/jira.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAE7K,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC1E,MAAM,MAAM,GAAG,kBAAkB,CAAC,mBAAmB,EAAE;QACrD,aAAa,EAAE,+BAA+B;QAC9C,UAAU,EAAE,mBAAmB;QAC/B,cAAc,EAAE,cAAc;QAC9B,gBAAgB,EAAE,KAAK;KACxB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,+BAA+B,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC/E,MAAM,MAAM,GAAG,kBAAkB,CAAC,mBAAmB,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpF,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/F,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAE7D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC/E,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnF,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACnF,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AACzF,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,9 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
- import { lintSkills } from '../src/lint.js';
4
- import { workspaceRoot } from '../src/project.js';
5
- test('workspace skills satisfy Phase 1 catalog lint', () => {
6
- const issues = lintSkills(workspaceRoot('..'));
7
- assert.deepEqual(issues.filter((issue) => issue.level === 'error'), []);
8
- });
9
- //# sourceMappingURL=lint.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lint.test.js","sourceRoot":"","sources":["../../test/lint.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;IACzD,MAAM,MAAM,GAAG,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,15 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
- import { projectCopilotCommands } from '../src/sync.js';
4
- test('dry-run projects skill aliases and thin runtime handoffs', () => {
5
- const files = projectCopilotCommands();
6
- const paths = files.map((file) => file.path);
7
- assert.ok(paths.includes('.github/copilot/commands/grill.md'));
8
- assert.ok(paths.includes('.github/copilot/commands/grill-me.md'));
9
- assert.ok(paths.includes('.github/copilot/commands/team.md'));
10
- assert.ok(paths.includes('.github/copilot/commands/ralph.md'));
11
- const team = files.find((file) => file.path.endsWith('/team.md'));
12
- assert.ok(team?.content.includes('thin capability handoff'));
13
- assert.ok(team?.content.includes('not a Copilot-native durable runtime'));
14
- });
15
- //# sourceMappingURL=sync.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../../test/sync.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACpE,MAAM,KAAK,GAAG,sBAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE7C,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,kCAAkC,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC"}