@kitsy/coop-ai 2.0.0 → 2.1.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.
package/dist/index.cjs CHANGED
@@ -30,18 +30,26 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CliAgentClient: () => CliAgentClient,
34
+ CliCompletionProvider: () => CliCompletionProvider,
35
+ buildIdeaRefinementPrompt: () => buildIdeaRefinementPrompt,
36
+ buildTaskRefinementPrompt: () => buildTaskRefinementPrompt,
33
37
  build_contract: () => build_contract,
34
38
  build_decomposition_prompt: () => build_decomposition_prompt,
35
39
  constraint_violation_reasons: () => constraint_violation_reasons,
36
40
  create_provider: () => create_provider,
37
41
  create_provider_agent_client: () => create_provider_agent_client,
38
42
  create_provider_idea_decomposer: () => create_provider_idea_decomposer,
43
+ create_provider_refinement_client: () => create_provider_refinement_client,
39
44
  create_run: () => create_run,
40
45
  decompose_idea_to_tasks: () => decompose_idea_to_tasks,
41
46
  enforce_constraints: () => enforce_constraints,
42
47
  execute_task: () => execute_task,
43
48
  finalize_run: () => finalize_run,
44
49
  log_step: () => log_step,
50
+ parseRefinementDraftResponse: () => parseRefinementDraftResponse,
51
+ refine_idea_to_draft: () => refine_idea_to_draft,
52
+ refine_task_to_draft: () => refine_task_to_draft,
45
53
  resolve_provider_config: () => resolve_provider_config,
46
54
  select_agent: () => select_agent,
47
55
  validate_command: () => validate_command,
@@ -101,6 +109,17 @@ function taskAcceptance(task) {
101
109
  const maybe = asRecord(task).acceptance;
102
110
  return asStringArray(maybe) ?? [];
103
111
  }
112
+ function taskTestsRequired(task) {
113
+ const maybe = asRecord(task).tests_required;
114
+ return asStringArray(maybe) ?? [];
115
+ }
116
+ function taskOriginRefs(task) {
117
+ const origin = asRecord(asRecord(task).origin);
118
+ return {
119
+ authority_refs: asStringArray(origin.authority_refs) ?? [],
120
+ derived_refs: asStringArray(origin.derived_refs) ?? []
121
+ };
122
+ }
104
123
  function collectRelatedArtifacts(task, graph) {
105
124
  const context = asRecord(task.execution?.context ?? {});
106
125
  const relatedTaskIds = dedupe(asStringArray(context.tasks) ?? []);
@@ -195,9 +214,12 @@ function build_contract(task, graph, config) {
195
214
  const taskContextFiles = asStringArray(executionContext.files) ?? [];
196
215
  const taskContextTasks = asStringArray(executionContext.tasks) ?? [];
197
216
  const acceptance_criteria = taskAcceptance(task);
217
+ const tests_required = taskTestsRequired(task);
218
+ const origin_refs = taskOriginRefs(task);
198
219
  const related_task_artifacts = collectRelatedArtifacts(task, graph);
199
220
  const output_requirements = [
200
221
  ...DEFAULT_OUTPUT_REQUIREMENTS,
222
+ ...tests_required.length > 0 ? tests_required.map((entry) => `Required test: ${entry}`) : [],
201
223
  ...(task.artifacts?.produces ?? []).map((artifact) => {
202
224
  if (artifact.path) {
203
225
  return `Produce ${artifact.type} artifact at ${artifact.path}`;
@@ -229,6 +251,9 @@ function build_contract(task, graph, config) {
229
251
  files: taskContextFiles,
230
252
  tasks: taskContextTasks,
231
253
  acceptance_criteria,
254
+ tests_required,
255
+ authority_refs: origin_refs.authority_refs,
256
+ derived_refs: origin_refs.derived_refs,
232
257
  related_task_artifacts
233
258
  },
234
259
  output_requirements
@@ -628,7 +653,7 @@ function build_decomposition_prompt(input) {
628
653
  return [
629
654
  "You are a planning agent for COOP.",
630
655
  "Decompose the idea into 2-5 implementation tasks.",
631
- "Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), and body.",
656
+ "Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), body, acceptance_criteria, and tests_required.",
632
657
  `Idea ID: ${input.idea_id}`,
633
658
  `Title: ${input.title}`,
634
659
  "Body:",
@@ -647,12 +672,273 @@ async function decompose_idea_to_tasks(input, client) {
647
672
  return drafts;
648
673
  }
649
674
 
675
+ // src/refinement/refine.ts
676
+ function sentenceCase2(value) {
677
+ if (value.length === 0) return value;
678
+ return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
679
+ }
680
+ function nonEmptyLines2(input) {
681
+ return input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
682
+ }
683
+ function extractBulletIdeas2(body) {
684
+ return nonEmptyLines2(body).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean).slice(0, 5);
685
+ }
686
+ function taskBodySections(title) {
687
+ return [
688
+ "## Objective",
689
+ title,
690
+ "",
691
+ "## Scope",
692
+ "- Define implementation boundaries",
693
+ "",
694
+ "## Constraints",
695
+ "- Preserve existing behavior unless explicitly changed",
696
+ "",
697
+ "## References",
698
+ "- Add source refs during refinement/apply",
699
+ "",
700
+ "## Refinement Notes",
701
+ "- Generated by COOP refinement fallback"
702
+ ].join("\n");
703
+ }
704
+ function normalizeStringArray(value) {
705
+ if (!Array.isArray(value)) return void 0;
706
+ const entries = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
707
+ return entries.length > 0 ? entries : void 0;
708
+ }
709
+ function normalizeProposal(value) {
710
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
711
+ return null;
712
+ }
713
+ const record = value;
714
+ const title = typeof record.title === "string" ? record.title.trim() : "";
715
+ if (!title) return null;
716
+ const action = record.action === "update" ? "update" : "create";
717
+ return {
718
+ action,
719
+ id: typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0,
720
+ target_id: typeof record.target_id === "string" && record.target_id.trim() ? record.target_id.trim() : void 0,
721
+ title,
722
+ type: record.type === "feature" || record.type === "bug" || record.type === "chore" || record.type === "spike" || record.type === "epic" ? record.type : void 0,
723
+ status: record.status === "todo" || record.status === "blocked" || record.status === "in_progress" || record.status === "in_review" || record.status === "done" || record.status === "canceled" ? record.status : void 0,
724
+ track: typeof record.track === "string" ? record.track.trim() || void 0 : void 0,
725
+ priority: record.priority === "p0" || record.priority === "p1" || record.priority === "p2" || record.priority === "p3" ? record.priority : void 0,
726
+ depends_on: normalizeStringArray(record.depends_on),
727
+ acceptance: normalizeStringArray(record.acceptance),
728
+ tests_required: normalizeStringArray(record.tests_required),
729
+ authority_refs: normalizeStringArray(record.authority_refs),
730
+ derived_refs: normalizeStringArray(record.derived_refs),
731
+ body: typeof record.body === "string" ? record.body : void 0
732
+ };
733
+ }
734
+ function normalizeDraft(value, mode, sourceId, sourceTitle) {
735
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
736
+ return null;
737
+ }
738
+ const record = value;
739
+ const proposalsRaw = Array.isArray(record.proposals) ? record.proposals : Array.isArray(record.tasks) ? record.tasks : [];
740
+ const proposals = proposalsRaw.map((entry) => normalizeProposal(entry)).filter((entry) => Boolean(entry));
741
+ if (proposals.length === 0) return null;
742
+ const summary = typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : `Refined ${mode} ${sourceId}`;
743
+ return {
744
+ kind: "refinement_draft",
745
+ version: 1,
746
+ mode,
747
+ source: {
748
+ entity_type: mode,
749
+ id: sourceId,
750
+ title: sourceTitle
751
+ },
752
+ summary,
753
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
754
+ proposals
755
+ };
756
+ }
757
+ function parseJsonObject(text) {
758
+ const trimmed = text.trim();
759
+ const candidates = [trimmed];
760
+ const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
761
+ if (fenced?.[1]) {
762
+ candidates.push(fenced[1]);
763
+ }
764
+ const start = trimmed.indexOf("{");
765
+ const end = trimmed.lastIndexOf("}");
766
+ if (start >= 0 && end > start) {
767
+ candidates.push(trimmed.slice(start, end + 1));
768
+ }
769
+ for (const candidate of candidates) {
770
+ try {
771
+ const parsed = JSON.parse(candidate);
772
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
773
+ return parsed;
774
+ }
775
+ } catch {
776
+ }
777
+ }
778
+ return null;
779
+ }
780
+ function fallbackIdeaRefinement(input) {
781
+ const bullets = extractBulletIdeas2(input.body);
782
+ const tasks = bullets.length > 0 ? bullets.slice(0, 3).map((bullet, index) => ({
783
+ title: sentenceCase2(bullet),
784
+ body: taskBodySections(sentenceCase2(bullet)),
785
+ type: index === 0 ? "spike" : "feature",
786
+ priority: index === 0 ? "p1" : "p2",
787
+ track: "unassigned",
788
+ acceptance_criteria: [`${sentenceCase2(bullet)} is implemented as described`],
789
+ tests_required: ["Relevant integration or regression coverage is added"]
790
+ })) : [
791
+ {
792
+ title: `Define scope and acceptance for ${input.title}`,
793
+ body: taskBodySections(`Define scope and acceptance for ${input.title}`),
794
+ type: "spike",
795
+ priority: "p1",
796
+ track: "unassigned",
797
+ acceptance_criteria: [`Scope and acceptance are explicit for ${input.title}`],
798
+ tests_required: ["Planning review recorded"]
799
+ },
800
+ {
801
+ title: `Implement ${input.title}`,
802
+ body: taskBodySections(`Implement ${input.title}`),
803
+ type: "feature",
804
+ priority: "p1",
805
+ track: "unassigned",
806
+ acceptance_criteria: [`${input.title} is implemented end-to-end`],
807
+ tests_required: ["Automated test coverage added for the primary path"]
808
+ }
809
+ ];
810
+ return {
811
+ kind: "refinement_draft",
812
+ version: 1,
813
+ mode: "idea",
814
+ source: {
815
+ entity_type: "idea",
816
+ id: input.idea_id,
817
+ title: input.title
818
+ },
819
+ summary: `Refined idea ${input.idea_id} into ${tasks.length} proposed task(s).`,
820
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
821
+ proposals: tasks.map((task) => ({
822
+ action: "create",
823
+ title: task.title,
824
+ type: task.type,
825
+ status: "todo",
826
+ track: task.track ?? "unassigned",
827
+ priority: task.priority,
828
+ acceptance: task.acceptance_criteria,
829
+ tests_required: task.tests_required,
830
+ body: task.body
831
+ }))
832
+ };
833
+ }
834
+ function fallbackTaskRefinement(input) {
835
+ const authorityRefs = input.task.origin?.authority_refs ?? [];
836
+ const derivedRefs = input.task.origin?.derived_refs ?? [];
837
+ const acceptance = input.task.acceptance && input.task.acceptance.length > 0 ? input.task.acceptance : [`${input.task.title} is complete and reviewable against its defined scope`];
838
+ const testsRequired = input.task.tests_required && input.task.tests_required.length > 0 ? input.task.tests_required : ["Automated regression coverage for the changed behavior"];
839
+ return {
840
+ kind: "refinement_draft",
841
+ version: 1,
842
+ mode: "task",
843
+ source: {
844
+ entity_type: "task",
845
+ id: input.task.id,
846
+ title: input.task.title
847
+ },
848
+ summary: `Refined task ${input.task.id} into an execution-ready update proposal.`,
849
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
850
+ proposals: [
851
+ {
852
+ action: "update",
853
+ target_id: input.task.id,
854
+ title: input.task.title,
855
+ type: input.task.type,
856
+ status: input.task.status,
857
+ track: input.task.track,
858
+ priority: input.task.priority,
859
+ depends_on: input.task.depends_on,
860
+ acceptance,
861
+ tests_required: testsRequired,
862
+ authority_refs: authorityRefs,
863
+ derived_refs: derivedRefs,
864
+ body: input.body?.trim() || taskBodySections(input.task.title)
865
+ }
866
+ ]
867
+ };
868
+ }
869
+ function buildIdeaRefinementPrompt(input) {
870
+ return [
871
+ "You are a COOP planning agent.",
872
+ "Return ONLY JSON object with keys: summary, proposals.",
873
+ "proposals must be an array of task proposals.",
874
+ "Each proposal must include: action(create), title, type(feature|bug|chore|spike), priority(p0-p3), track, acceptance(string[]), tests_required(string[]), body.",
875
+ "Use depends_on only for existing task ids when necessary.",
876
+ `Idea ID: ${input.idea_id}`,
877
+ `Title: ${input.title}`,
878
+ "Body:",
879
+ input.body || "(empty)",
880
+ input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
881
+ ].filter(Boolean).join("\n");
882
+ }
883
+ function buildTaskRefinementPrompt(input) {
884
+ const authorityContext = (input.authority_context ?? []).map((entry) => `Ref: ${entry.ref}
885
+ ${entry.content}`).join("\n\n---\n\n");
886
+ return [
887
+ "You are a COOP planning/refinement agent.",
888
+ "Return ONLY JSON object with keys: summary, proposals.",
889
+ "proposals must be an array with one update proposal for the current task.",
890
+ "The proposal must include: action(update), target_id, title, type, status, track, priority, depends_on, acceptance(string[]), tests_required(string[]), authority_refs(string[]), derived_refs(string[]), body.",
891
+ `Task ID: ${input.task.id}`,
892
+ `Title: ${input.task.title}`,
893
+ `Status: ${input.task.status}`,
894
+ `Type: ${input.task.type}`,
895
+ `Priority: ${input.task.priority ?? "p2"}`,
896
+ `Track: ${input.task.track ?? "unassigned"}`,
897
+ `Depends On: ${(input.task.depends_on ?? []).join(", ") || "-"}`,
898
+ "Task Body:",
899
+ input.body || "(empty)",
900
+ authorityContext ? ["Authority Context:", authorityContext].join("\n") : "",
901
+ input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
902
+ ].filter(Boolean).join("\n");
903
+ }
904
+ async function refine_idea_to_draft(input, client) {
905
+ const prompt = buildIdeaRefinementPrompt(input);
906
+ if (!client) {
907
+ return fallbackIdeaRefinement(input);
908
+ }
909
+ const result = await client.refineIdea(prompt, input);
910
+ return result ?? fallbackIdeaRefinement(input);
911
+ }
912
+ async function refine_task_to_draft(input, client) {
913
+ const prompt = buildTaskRefinementPrompt(input);
914
+ if (!client) {
915
+ return fallbackTaskRefinement(input);
916
+ }
917
+ const result = await client.refineTask(prompt, input);
918
+ return result ?? fallbackTaskRefinement(input);
919
+ }
920
+ function parseRefinementDraftResponse(text, mode, sourceId, sourceTitle) {
921
+ const parsed = parseJsonObject(text);
922
+ if (!parsed) {
923
+ return null;
924
+ }
925
+ return normalizeDraft(parsed, mode, sourceId, sourceTitle);
926
+ }
927
+
650
928
  // src/providers/config.ts
651
929
  var DEFAULT_MODELS = {
652
930
  openai: "gpt-5-mini",
653
931
  anthropic: "claude-3-5-sonnet-latest",
654
932
  gemini: "gemini-2.0-flash",
655
- ollama: "llama3.2"
933
+ ollama: "llama3.2",
934
+ codex_cli: "gpt-5-codex",
935
+ claude_cli: "sonnet",
936
+ gemini_cli: "gemini-2.5-pro"
937
+ };
938
+ var DEFAULT_COMMAND = {
939
+ codex_cli: "codex",
940
+ claude_cli: "claude",
941
+ gemini_cli: "gemini"
656
942
  };
657
943
  var DEFAULT_KEY_ENV = {
658
944
  openai: "OPENAI_API_KEY",
@@ -682,7 +968,7 @@ function asFinite(value) {
682
968
  }
683
969
  function readProvider(value) {
684
970
  const normalized = asString(value)?.toLowerCase();
685
- if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock") {
971
+ if (normalized === "openai" || normalized === "anthropic" || normalized === "gemini" || normalized === "ollama" || normalized === "mock" || normalized === "codex_cli" || normalized === "claude_cli" || normalized === "gemini_cli") {
686
972
  return normalized;
687
973
  }
688
974
  return "mock";
@@ -703,15 +989,34 @@ function resolve_provider_config(config) {
703
989
  timeout_ms: 6e4
704
990
  };
705
991
  }
992
+ if (provider === "codex_cli" || provider === "claude_cli" || provider === "gemini_cli") {
993
+ const section2 = lookupProviderSection(ai, provider);
994
+ const model2 = asString(section2.model) ?? asString(ai.model) ?? DEFAULT_MODELS[provider];
995
+ const timeout_ms2 = asFinite(section2.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 3e5;
996
+ const command = asString(section2.command) ?? DEFAULT_COMMAND[provider];
997
+ const argsRaw = Array.isArray(section2.args) ? section2.args : Array.isArray(ai.args) ? ai.args : [];
998
+ const args = argsRaw.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
999
+ return {
1000
+ provider,
1001
+ model: model2,
1002
+ command,
1003
+ args,
1004
+ temperature: 0.2,
1005
+ max_output_tokens: 4096,
1006
+ timeout_ms: timeout_ms2
1007
+ };
1008
+ }
706
1009
  const section = lookupProviderSection(ai, provider);
707
- const model = asString(section.model) ?? asString(ai.model) ?? DEFAULT_MODELS[provider];
708
- const base_url = asString(section.base_url) ?? asString(ai.base_url) ?? DEFAULT_BASE_URL[provider];
1010
+ const cloudProvider = provider;
1011
+ const model = asString(section.model) ?? asString(ai.model) ?? DEFAULT_MODELS[cloudProvider];
1012
+ const base_url = asString(section.base_url) ?? asString(ai.base_url) ?? DEFAULT_BASE_URL[cloudProvider];
709
1013
  const temperature = asFinite(section.temperature) ?? asFinite(ai.temperature) ?? 0.2;
710
1014
  const max_output_tokens = asFinite(section.max_output_tokens) ?? asFinite(ai.max_output_tokens) ?? 1024;
711
1015
  const timeout_ms = asFinite(section.timeout_ms) ?? asFinite(ai.timeout_ms) ?? 6e4;
712
1016
  let api_key;
713
1017
  if (provider !== "ollama") {
714
- const envName = asString(section.api_key_env) ?? asString(ai.api_key_env) ?? DEFAULT_KEY_ENV[provider];
1018
+ const keyedProvider = provider;
1019
+ const envName = asString(section.api_key_env) ?? asString(ai.api_key_env) ?? DEFAULT_KEY_ENV[keyedProvider];
715
1020
  const envValue = envName ? asString(process.env[envName]) : null;
716
1021
  api_key = asString(section.api_key) ?? asString(ai.api_key) ?? envValue ?? void 0;
717
1022
  if (!api_key) {
@@ -731,6 +1036,250 @@ function resolve_provider_config(config) {
731
1036
  };
732
1037
  }
733
1038
 
1039
+ // src/providers/cli.ts
1040
+ var import_node_fs2 = __toESM(require("fs"), 1);
1041
+ var import_node_os = __toESM(require("os"), 1);
1042
+ var import_node_path3 = __toESM(require("path"), 1);
1043
+ var import_node_child_process2 = require("child_process");
1044
+ function defaultRunner(invocation) {
1045
+ const result = (0, import_node_child_process2.spawnSync)(invocation.command, invocation.args, {
1046
+ cwd: invocation.cwd,
1047
+ encoding: "utf8",
1048
+ input: invocation.input,
1049
+ timeout: invocation.timeout_ms,
1050
+ windowsHide: true
1051
+ });
1052
+ return {
1053
+ status: result.status,
1054
+ stdout: result.stdout ?? "",
1055
+ stderr: result.stderr ?? "",
1056
+ error: result.error
1057
+ };
1058
+ }
1059
+ function asText(result) {
1060
+ return [result.stdout, result.stderr].map((value) => value.trim()).filter(Boolean).join("\n").trim();
1061
+ }
1062
+ function requireSuccess(provider, result) {
1063
+ if ((result.status ?? 1) === 0 && !result.error) {
1064
+ return;
1065
+ }
1066
+ const details = asText(result) || result.error?.message || "unknown error";
1067
+ throw new Error(`${provider} execution failed: ${details}`);
1068
+ }
1069
+ function tempOutputFile(prefix) {
1070
+ return import_node_path3.default.join(import_node_os.default.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
1071
+ }
1072
+ function combinedPrompt(input) {
1073
+ return [input.system.trim(), input.prompt.trim()].filter(Boolean).join("\n\n");
1074
+ }
1075
+ function completionInvocation(config, input, cwd) {
1076
+ if (config.provider === "codex_cli") {
1077
+ const outputFile = tempOutputFile("coop-codex-complete");
1078
+ return {
1079
+ command: config.command ?? "codex",
1080
+ args: [
1081
+ "exec",
1082
+ "-C",
1083
+ cwd,
1084
+ "--skip-git-repo-check",
1085
+ "--sandbox",
1086
+ "read-only",
1087
+ ...config.model ? ["--model", config.model] : [],
1088
+ "--output-last-message",
1089
+ outputFile,
1090
+ ...config.args ?? [],
1091
+ "-"
1092
+ ],
1093
+ cwd,
1094
+ input: combinedPrompt(input),
1095
+ timeout_ms: config.timeout_ms
1096
+ };
1097
+ }
1098
+ if (config.provider === "claude_cli") {
1099
+ return {
1100
+ command: config.command ?? "claude",
1101
+ args: [
1102
+ "-p",
1103
+ combinedPrompt(input),
1104
+ "--output-format",
1105
+ "text",
1106
+ "--permission-mode",
1107
+ "plan",
1108
+ ...config.model ? ["--model", config.model] : [],
1109
+ ...config.args ?? []
1110
+ ],
1111
+ cwd,
1112
+ timeout_ms: config.timeout_ms
1113
+ };
1114
+ }
1115
+ return {
1116
+ command: config.command ?? "gemini",
1117
+ args: [
1118
+ "-p",
1119
+ combinedPrompt(input),
1120
+ "--output-format",
1121
+ "text",
1122
+ "--approval-mode",
1123
+ "plan",
1124
+ ...config.model ? ["--model", config.model] : [],
1125
+ ...config.args ?? []
1126
+ ],
1127
+ cwd,
1128
+ timeout_ms: config.timeout_ms
1129
+ };
1130
+ }
1131
+ function readCodexOutput(invocation) {
1132
+ const index = invocation.args.indexOf("--output-last-message");
1133
+ const outputFile = index >= 0 ? invocation.args[index + 1] : void 0;
1134
+ if (!outputFile) return null;
1135
+ if (!import_node_fs2.default.existsSync(outputFile)) return null;
1136
+ try {
1137
+ return import_node_fs2.default.readFileSync(outputFile, "utf8").trim();
1138
+ } finally {
1139
+ import_node_fs2.default.rmSync(outputFile, { force: true });
1140
+ }
1141
+ }
1142
+ var CliCompletionProvider = class {
1143
+ name;
1144
+ config;
1145
+ cwd;
1146
+ runner;
1147
+ constructor(config, options = {}) {
1148
+ this.name = config.provider;
1149
+ this.config = config;
1150
+ this.cwd = import_node_path3.default.resolve(options.cwd ?? process.cwd());
1151
+ this.runner = options.runner ?? defaultRunner;
1152
+ }
1153
+ async complete(input) {
1154
+ const invocation = completionInvocation(this.config, input, this.cwd);
1155
+ const result = this.runner(invocation);
1156
+ requireSuccess(this.name, result);
1157
+ const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
1158
+ return {
1159
+ text: codexOutput || result.stdout.trim() || result.stderr.trim()
1160
+ };
1161
+ }
1162
+ };
1163
+ function buildExecutionPrompt(task, contract, step, prompt, mode) {
1164
+ const sections = [
1165
+ mode === "execute" ? "You are executing a COOP task inside the repo. Make the necessary file changes, run allowed commands when needed, and finish with a concise summary." : "You are reviewing a COOP task inside the repo. Inspect the current workspace and return concise review findings only.",
1166
+ `Task: ${task.id} - ${task.title}`,
1167
+ `Goal: ${contract.goal}`,
1168
+ `Step: ${step.step} (${step.action})`,
1169
+ contract.context.acceptance_criteria.length > 0 ? `Acceptance: ${contract.context.acceptance_criteria.join(" | ")}` : "",
1170
+ contract.context.tests_required.length > 0 ? `Tests Required: ${contract.context.tests_required.join(" | ")}` : "",
1171
+ contract.context.authority_refs.length > 0 ? `Authority Refs: ${contract.context.authority_refs.join(" | ")}` : "",
1172
+ contract.context.derived_refs.length > 0 ? `Derived Refs: ${contract.context.derived_refs.join(" | ")}` : "",
1173
+ contract.permissions.read_paths.length > 0 ? `Allowed Read Paths: ${contract.permissions.read_paths.join(", ")}` : "",
1174
+ contract.permissions.write_paths.length > 0 ? `Allowed Write Paths: ${contract.permissions.write_paths.join(", ")}` : "",
1175
+ contract.permissions.allowed_commands.length > 0 ? `Allowed Commands: ${contract.permissions.allowed_commands.join(", ")}` : "",
1176
+ contract.permissions.forbidden_commands.length > 0 ? `Forbidden Commands: ${contract.permissions.forbidden_commands.join(", ")}` : "",
1177
+ prompt
1178
+ ].filter(Boolean);
1179
+ return sections.join("\n\n");
1180
+ }
1181
+ function countGitChanges(repoRoot, runner, timeoutMs) {
1182
+ const result = runner({
1183
+ command: "git",
1184
+ args: ["status", "--porcelain"],
1185
+ cwd: repoRoot,
1186
+ timeout_ms: timeoutMs
1187
+ });
1188
+ if ((result.status ?? 1) !== 0) return 0;
1189
+ return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).length;
1190
+ }
1191
+ function executionInvocation(config, prompt, repoRoot, mode) {
1192
+ if (config.provider === "codex_cli") {
1193
+ const outputFile = tempOutputFile("coop-codex-run");
1194
+ return {
1195
+ command: config.command ?? "codex",
1196
+ args: [
1197
+ "exec",
1198
+ "-C",
1199
+ repoRoot,
1200
+ "--skip-git-repo-check",
1201
+ "--sandbox",
1202
+ mode === "execute" ? "workspace-write" : "read-only",
1203
+ ...mode === "execute" ? ["--full-auto"] : [],
1204
+ ...config.model ? ["--model", config.model] : [],
1205
+ "--output-last-message",
1206
+ outputFile,
1207
+ ...config.args ?? [],
1208
+ "-"
1209
+ ],
1210
+ cwd: repoRoot,
1211
+ input: prompt,
1212
+ timeout_ms: config.timeout_ms
1213
+ };
1214
+ }
1215
+ if (config.provider === "claude_cli") {
1216
+ return {
1217
+ command: config.command ?? "claude",
1218
+ args: [
1219
+ "-p",
1220
+ prompt,
1221
+ "--output-format",
1222
+ "text",
1223
+ "--permission-mode",
1224
+ mode === "execute" ? "bypassPermissions" : "plan",
1225
+ ...config.model ? ["--model", config.model] : [],
1226
+ ...config.args ?? []
1227
+ ],
1228
+ cwd: repoRoot,
1229
+ timeout_ms: config.timeout_ms
1230
+ };
1231
+ }
1232
+ return {
1233
+ command: config.command ?? "gemini",
1234
+ args: [
1235
+ "-p",
1236
+ prompt,
1237
+ "--output-format",
1238
+ "text",
1239
+ "--approval-mode",
1240
+ mode === "execute" ? "yolo" : "plan",
1241
+ ...config.model ? ["--model", config.model] : [],
1242
+ ...config.args ?? []
1243
+ ],
1244
+ cwd: repoRoot,
1245
+ timeout_ms: config.timeout_ms
1246
+ };
1247
+ }
1248
+ var CliAgentClient = class {
1249
+ config;
1250
+ repoRoot;
1251
+ runner;
1252
+ constructor(config, options = {}) {
1253
+ this.config = config;
1254
+ this.repoRoot = import_node_path3.default.resolve(options.cwd ?? process.cwd());
1255
+ this.runner = options.runner ?? defaultRunner;
1256
+ }
1257
+ async generate(input) {
1258
+ const before = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
1259
+ const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "execute");
1260
+ const invocation = executionInvocation(this.config, prompt, this.repoRoot, "execute");
1261
+ const result = this.runner(invocation);
1262
+ requireSuccess(this.config.provider, result);
1263
+ const after = countGitChanges(this.repoRoot, this.runner, this.config.timeout_ms);
1264
+ const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
1265
+ return {
1266
+ summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Completed ${input.step.step}.`,
1267
+ file_changes: Math.max(0, after - before)
1268
+ };
1269
+ }
1270
+ async review(input) {
1271
+ const prompt = buildExecutionPrompt(input.task, input.contract, input.step, input.prompt, "review");
1272
+ const invocation = executionInvocation(this.config, prompt, this.repoRoot, "review");
1273
+ const result = this.runner(invocation);
1274
+ requireSuccess(this.config.provider, result);
1275
+ const codexOutput = this.config.provider === "codex_cli" ? readCodexOutput(invocation) : null;
1276
+ return {
1277
+ summary: codexOutput || result.stdout.trim() || result.stderr.trim() || `Reviewed ${input.step.step}.`,
1278
+ file_changes: 0
1279
+ };
1280
+ }
1281
+ };
1282
+
734
1283
  // src/providers/http.ts
735
1284
  async function post_json(url, init) {
736
1285
  const controller = new AbortController();
@@ -927,6 +1476,10 @@ function create_provider(config) {
927
1476
  return new GeminiProvider(resolved);
928
1477
  case "ollama":
929
1478
  return new OllamaProvider(resolved);
1479
+ case "codex_cli":
1480
+ case "claude_cli":
1481
+ case "gemini_cli":
1482
+ return new CliCompletionProvider(resolved);
930
1483
  case "mock":
931
1484
  default:
932
1485
  return new MockProvider();
@@ -978,13 +1531,23 @@ function toTaskDrafts(value) {
978
1531
  }
979
1532
  return out;
980
1533
  }
1534
+ function refinementSystemPrompt(mode) {
1535
+ if (mode === "idea") {
1536
+ return "Return ONLY JSON object with keys summary and proposals. proposals must be an array of task proposals with action(create), title, type, priority, track, acceptance, tests_required, body.";
1537
+ }
1538
+ return "Return ONLY JSON object with keys summary and proposals. proposals must be an array with one update proposal containing action(update), target_id, title, type, status, track, priority, depends_on, acceptance, tests_required, authority_refs, derived_refs, body.";
1539
+ }
981
1540
  function asAgentResponse(text, tokens) {
982
1541
  return {
983
1542
  summary: text.trim(),
984
1543
  tokens_used: tokens
985
1544
  };
986
1545
  }
987
- function create_provider_agent_client(config) {
1546
+ function create_provider_agent_client(config, runtime = {}) {
1547
+ const resolved = resolve_provider_config(config);
1548
+ if (resolved.provider === "codex_cli" || resolved.provider === "claude_cli" || resolved.provider === "gemini_cli") {
1549
+ return new CliAgentClient(resolved, runtime);
1550
+ }
988
1551
  const provider = create_provider(config);
989
1552
  return {
990
1553
  async generate(input) {
@@ -1019,20 +1582,47 @@ function create_provider_idea_decomposer(config) {
1019
1582
  }
1020
1583
  };
1021
1584
  }
1585
+ function create_provider_refinement_client(config) {
1586
+ const provider = create_provider(config);
1587
+ return {
1588
+ async refineIdea(prompt, input) {
1589
+ const result = await provider.complete({
1590
+ system: refinementSystemPrompt("idea"),
1591
+ prompt
1592
+ });
1593
+ return parseRefinementDraftResponse(result.text, "idea", input.idea_id, input.title);
1594
+ },
1595
+ async refineTask(prompt, input) {
1596
+ const result = await provider.complete({
1597
+ system: refinementSystemPrompt("task"),
1598
+ prompt
1599
+ });
1600
+ return parseRefinementDraftResponse(result.text, "task", input.task.id, input.task.title);
1601
+ }
1602
+ };
1603
+ }
1022
1604
  // Annotate the CommonJS export names for ESM import in node:
1023
1605
  0 && (module.exports = {
1606
+ CliAgentClient,
1607
+ CliCompletionProvider,
1608
+ buildIdeaRefinementPrompt,
1609
+ buildTaskRefinementPrompt,
1024
1610
  build_contract,
1025
1611
  build_decomposition_prompt,
1026
1612
  constraint_violation_reasons,
1027
1613
  create_provider,
1028
1614
  create_provider_agent_client,
1029
1615
  create_provider_idea_decomposer,
1616
+ create_provider_refinement_client,
1030
1617
  create_run,
1031
1618
  decompose_idea_to_tasks,
1032
1619
  enforce_constraints,
1033
1620
  execute_task,
1034
1621
  finalize_run,
1035
1622
  log_step,
1623
+ parseRefinementDraftResponse,
1624
+ refine_idea_to_draft,
1625
+ refine_task_to_draft,
1036
1626
  resolve_provider_config,
1037
1627
  select_agent,
1038
1628
  validate_command,