@kitsy/coop-ai 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +308 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +302 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -30,18 +30,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
buildIdeaRefinementPrompt: () => buildIdeaRefinementPrompt,
|
|
34
|
+
buildTaskRefinementPrompt: () => buildTaskRefinementPrompt,
|
|
33
35
|
build_contract: () => build_contract,
|
|
34
36
|
build_decomposition_prompt: () => build_decomposition_prompt,
|
|
35
37
|
constraint_violation_reasons: () => constraint_violation_reasons,
|
|
36
38
|
create_provider: () => create_provider,
|
|
37
39
|
create_provider_agent_client: () => create_provider_agent_client,
|
|
38
40
|
create_provider_idea_decomposer: () => create_provider_idea_decomposer,
|
|
41
|
+
create_provider_refinement_client: () => create_provider_refinement_client,
|
|
39
42
|
create_run: () => create_run,
|
|
40
43
|
decompose_idea_to_tasks: () => decompose_idea_to_tasks,
|
|
41
44
|
enforce_constraints: () => enforce_constraints,
|
|
42
45
|
execute_task: () => execute_task,
|
|
43
46
|
finalize_run: () => finalize_run,
|
|
44
47
|
log_step: () => log_step,
|
|
48
|
+
parseRefinementDraftResponse: () => parseRefinementDraftResponse,
|
|
49
|
+
refine_idea_to_draft: () => refine_idea_to_draft,
|
|
50
|
+
refine_task_to_draft: () => refine_task_to_draft,
|
|
45
51
|
resolve_provider_config: () => resolve_provider_config,
|
|
46
52
|
select_agent: () => select_agent,
|
|
47
53
|
validate_command: () => validate_command,
|
|
@@ -101,6 +107,17 @@ function taskAcceptance(task) {
|
|
|
101
107
|
const maybe = asRecord(task).acceptance;
|
|
102
108
|
return asStringArray(maybe) ?? [];
|
|
103
109
|
}
|
|
110
|
+
function taskTestsRequired(task) {
|
|
111
|
+
const maybe = asRecord(task).tests_required;
|
|
112
|
+
return asStringArray(maybe) ?? [];
|
|
113
|
+
}
|
|
114
|
+
function taskOriginRefs(task) {
|
|
115
|
+
const origin = asRecord(asRecord(task).origin);
|
|
116
|
+
return {
|
|
117
|
+
authority_refs: asStringArray(origin.authority_refs) ?? [],
|
|
118
|
+
derived_refs: asStringArray(origin.derived_refs) ?? []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
104
121
|
function collectRelatedArtifacts(task, graph) {
|
|
105
122
|
const context = asRecord(task.execution?.context ?? {});
|
|
106
123
|
const relatedTaskIds = dedupe(asStringArray(context.tasks) ?? []);
|
|
@@ -195,9 +212,12 @@ function build_contract(task, graph, config) {
|
|
|
195
212
|
const taskContextFiles = asStringArray(executionContext.files) ?? [];
|
|
196
213
|
const taskContextTasks = asStringArray(executionContext.tasks) ?? [];
|
|
197
214
|
const acceptance_criteria = taskAcceptance(task);
|
|
215
|
+
const tests_required = taskTestsRequired(task);
|
|
216
|
+
const origin_refs = taskOriginRefs(task);
|
|
198
217
|
const related_task_artifacts = collectRelatedArtifacts(task, graph);
|
|
199
218
|
const output_requirements = [
|
|
200
219
|
...DEFAULT_OUTPUT_REQUIREMENTS,
|
|
220
|
+
...tests_required.length > 0 ? tests_required.map((entry) => `Required test: ${entry}`) : [],
|
|
201
221
|
...(task.artifacts?.produces ?? []).map((artifact) => {
|
|
202
222
|
if (artifact.path) {
|
|
203
223
|
return `Produce ${artifact.type} artifact at ${artifact.path}`;
|
|
@@ -229,6 +249,9 @@ function build_contract(task, graph, config) {
|
|
|
229
249
|
files: taskContextFiles,
|
|
230
250
|
tasks: taskContextTasks,
|
|
231
251
|
acceptance_criteria,
|
|
252
|
+
tests_required,
|
|
253
|
+
authority_refs: origin_refs.authority_refs,
|
|
254
|
+
derived_refs: origin_refs.derived_refs,
|
|
232
255
|
related_task_artifacts
|
|
233
256
|
},
|
|
234
257
|
output_requirements
|
|
@@ -628,7 +651,7 @@ function build_decomposition_prompt(input) {
|
|
|
628
651
|
return [
|
|
629
652
|
"You are a planning agent for COOP.",
|
|
630
653
|
"Decompose the idea into 2-5 implementation tasks.",
|
|
631
|
-
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), and
|
|
654
|
+
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), body, acceptance_criteria, and tests_required.",
|
|
632
655
|
`Idea ID: ${input.idea_id}`,
|
|
633
656
|
`Title: ${input.title}`,
|
|
634
657
|
"Body:",
|
|
@@ -647,6 +670,259 @@ async function decompose_idea_to_tasks(input, client) {
|
|
|
647
670
|
return drafts;
|
|
648
671
|
}
|
|
649
672
|
|
|
673
|
+
// src/refinement/refine.ts
|
|
674
|
+
function sentenceCase2(value) {
|
|
675
|
+
if (value.length === 0) return value;
|
|
676
|
+
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
677
|
+
}
|
|
678
|
+
function nonEmptyLines2(input) {
|
|
679
|
+
return input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
680
|
+
}
|
|
681
|
+
function extractBulletIdeas2(body) {
|
|
682
|
+
return nonEmptyLines2(body).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean).slice(0, 5);
|
|
683
|
+
}
|
|
684
|
+
function taskBodySections(title) {
|
|
685
|
+
return [
|
|
686
|
+
"## Objective",
|
|
687
|
+
title,
|
|
688
|
+
"",
|
|
689
|
+
"## Scope",
|
|
690
|
+
"- Define implementation boundaries",
|
|
691
|
+
"",
|
|
692
|
+
"## Constraints",
|
|
693
|
+
"- Preserve existing behavior unless explicitly changed",
|
|
694
|
+
"",
|
|
695
|
+
"## References",
|
|
696
|
+
"- Add source refs during refinement/apply",
|
|
697
|
+
"",
|
|
698
|
+
"## Refinement Notes",
|
|
699
|
+
"- Generated by COOP refinement fallback"
|
|
700
|
+
].join("\n");
|
|
701
|
+
}
|
|
702
|
+
function normalizeStringArray(value) {
|
|
703
|
+
if (!Array.isArray(value)) return void 0;
|
|
704
|
+
const entries = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
705
|
+
return entries.length > 0 ? entries : void 0;
|
|
706
|
+
}
|
|
707
|
+
function normalizeProposal(value) {
|
|
708
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
const record = value;
|
|
712
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
713
|
+
if (!title) return null;
|
|
714
|
+
const action = record.action === "update" ? "update" : "create";
|
|
715
|
+
return {
|
|
716
|
+
action,
|
|
717
|
+
id: typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0,
|
|
718
|
+
target_id: typeof record.target_id === "string" && record.target_id.trim() ? record.target_id.trim() : void 0,
|
|
719
|
+
title,
|
|
720
|
+
type: record.type === "feature" || record.type === "bug" || record.type === "chore" || record.type === "spike" || record.type === "epic" ? record.type : void 0,
|
|
721
|
+
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,
|
|
722
|
+
track: typeof record.track === "string" ? record.track.trim() || void 0 : void 0,
|
|
723
|
+
priority: record.priority === "p0" || record.priority === "p1" || record.priority === "p2" || record.priority === "p3" ? record.priority : void 0,
|
|
724
|
+
depends_on: normalizeStringArray(record.depends_on),
|
|
725
|
+
acceptance: normalizeStringArray(record.acceptance),
|
|
726
|
+
tests_required: normalizeStringArray(record.tests_required),
|
|
727
|
+
authority_refs: normalizeStringArray(record.authority_refs),
|
|
728
|
+
derived_refs: normalizeStringArray(record.derived_refs),
|
|
729
|
+
body: typeof record.body === "string" ? record.body : void 0
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function normalizeDraft(value, mode, sourceId, sourceTitle) {
|
|
733
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
const record = value;
|
|
737
|
+
const proposalsRaw = Array.isArray(record.proposals) ? record.proposals : Array.isArray(record.tasks) ? record.tasks : [];
|
|
738
|
+
const proposals = proposalsRaw.map((entry) => normalizeProposal(entry)).filter((entry) => Boolean(entry));
|
|
739
|
+
if (proposals.length === 0) return null;
|
|
740
|
+
const summary = typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : `Refined ${mode} ${sourceId}`;
|
|
741
|
+
return {
|
|
742
|
+
kind: "refinement_draft",
|
|
743
|
+
version: 1,
|
|
744
|
+
mode,
|
|
745
|
+
source: {
|
|
746
|
+
entity_type: mode,
|
|
747
|
+
id: sourceId,
|
|
748
|
+
title: sourceTitle
|
|
749
|
+
},
|
|
750
|
+
summary,
|
|
751
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
752
|
+
proposals
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function parseJsonObject(text) {
|
|
756
|
+
const trimmed = text.trim();
|
|
757
|
+
const candidates = [trimmed];
|
|
758
|
+
const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
|
|
759
|
+
if (fenced?.[1]) {
|
|
760
|
+
candidates.push(fenced[1]);
|
|
761
|
+
}
|
|
762
|
+
const start = trimmed.indexOf("{");
|
|
763
|
+
const end = trimmed.lastIndexOf("}");
|
|
764
|
+
if (start >= 0 && end > start) {
|
|
765
|
+
candidates.push(trimmed.slice(start, end + 1));
|
|
766
|
+
}
|
|
767
|
+
for (const candidate of candidates) {
|
|
768
|
+
try {
|
|
769
|
+
const parsed = JSON.parse(candidate);
|
|
770
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
771
|
+
return parsed;
|
|
772
|
+
}
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
function fallbackIdeaRefinement(input) {
|
|
779
|
+
const bullets = extractBulletIdeas2(input.body);
|
|
780
|
+
const tasks = bullets.length > 0 ? bullets.slice(0, 3).map((bullet, index) => ({
|
|
781
|
+
title: sentenceCase2(bullet),
|
|
782
|
+
body: taskBodySections(sentenceCase2(bullet)),
|
|
783
|
+
type: index === 0 ? "spike" : "feature",
|
|
784
|
+
priority: index === 0 ? "p1" : "p2",
|
|
785
|
+
track: "unassigned",
|
|
786
|
+
acceptance_criteria: [`${sentenceCase2(bullet)} is implemented as described`],
|
|
787
|
+
tests_required: ["Relevant integration or regression coverage is added"]
|
|
788
|
+
})) : [
|
|
789
|
+
{
|
|
790
|
+
title: `Define scope and acceptance for ${input.title}`,
|
|
791
|
+
body: taskBodySections(`Define scope and acceptance for ${input.title}`),
|
|
792
|
+
type: "spike",
|
|
793
|
+
priority: "p1",
|
|
794
|
+
track: "unassigned",
|
|
795
|
+
acceptance_criteria: [`Scope and acceptance are explicit for ${input.title}`],
|
|
796
|
+
tests_required: ["Planning review recorded"]
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
title: `Implement ${input.title}`,
|
|
800
|
+
body: taskBodySections(`Implement ${input.title}`),
|
|
801
|
+
type: "feature",
|
|
802
|
+
priority: "p1",
|
|
803
|
+
track: "unassigned",
|
|
804
|
+
acceptance_criteria: [`${input.title} is implemented end-to-end`],
|
|
805
|
+
tests_required: ["Automated test coverage added for the primary path"]
|
|
806
|
+
}
|
|
807
|
+
];
|
|
808
|
+
return {
|
|
809
|
+
kind: "refinement_draft",
|
|
810
|
+
version: 1,
|
|
811
|
+
mode: "idea",
|
|
812
|
+
source: {
|
|
813
|
+
entity_type: "idea",
|
|
814
|
+
id: input.idea_id,
|
|
815
|
+
title: input.title
|
|
816
|
+
},
|
|
817
|
+
summary: `Refined idea ${input.idea_id} into ${tasks.length} proposed task(s).`,
|
|
818
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
819
|
+
proposals: tasks.map((task) => ({
|
|
820
|
+
action: "create",
|
|
821
|
+
title: task.title,
|
|
822
|
+
type: task.type,
|
|
823
|
+
status: "todo",
|
|
824
|
+
track: task.track ?? "unassigned",
|
|
825
|
+
priority: task.priority,
|
|
826
|
+
acceptance: task.acceptance_criteria,
|
|
827
|
+
tests_required: task.tests_required,
|
|
828
|
+
body: task.body
|
|
829
|
+
}))
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function fallbackTaskRefinement(input) {
|
|
833
|
+
const authorityRefs = input.task.origin?.authority_refs ?? [];
|
|
834
|
+
const derivedRefs = input.task.origin?.derived_refs ?? [];
|
|
835
|
+
const acceptance = input.task.acceptance && input.task.acceptance.length > 0 ? input.task.acceptance : [`${input.task.title} is complete and reviewable against its defined scope`];
|
|
836
|
+
const testsRequired = input.task.tests_required && input.task.tests_required.length > 0 ? input.task.tests_required : ["Automated regression coverage for the changed behavior"];
|
|
837
|
+
return {
|
|
838
|
+
kind: "refinement_draft",
|
|
839
|
+
version: 1,
|
|
840
|
+
mode: "task",
|
|
841
|
+
source: {
|
|
842
|
+
entity_type: "task",
|
|
843
|
+
id: input.task.id,
|
|
844
|
+
title: input.task.title
|
|
845
|
+
},
|
|
846
|
+
summary: `Refined task ${input.task.id} into an execution-ready update proposal.`,
|
|
847
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
848
|
+
proposals: [
|
|
849
|
+
{
|
|
850
|
+
action: "update",
|
|
851
|
+
target_id: input.task.id,
|
|
852
|
+
title: input.task.title,
|
|
853
|
+
type: input.task.type,
|
|
854
|
+
status: input.task.status,
|
|
855
|
+
track: input.task.track,
|
|
856
|
+
priority: input.task.priority,
|
|
857
|
+
depends_on: input.task.depends_on,
|
|
858
|
+
acceptance,
|
|
859
|
+
tests_required: testsRequired,
|
|
860
|
+
authority_refs: authorityRefs,
|
|
861
|
+
derived_refs: derivedRefs,
|
|
862
|
+
body: input.body?.trim() || taskBodySections(input.task.title)
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function buildIdeaRefinementPrompt(input) {
|
|
868
|
+
return [
|
|
869
|
+
"You are a COOP planning agent.",
|
|
870
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
871
|
+
"proposals must be an array of task proposals.",
|
|
872
|
+
"Each proposal must include: action(create), title, type(feature|bug|chore|spike), priority(p0-p3), track, acceptance(string[]), tests_required(string[]), body.",
|
|
873
|
+
"Use depends_on only for existing task ids when necessary.",
|
|
874
|
+
`Idea ID: ${input.idea_id}`,
|
|
875
|
+
`Title: ${input.title}`,
|
|
876
|
+
"Body:",
|
|
877
|
+
input.body || "(empty)",
|
|
878
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
879
|
+
].filter(Boolean).join("\n");
|
|
880
|
+
}
|
|
881
|
+
function buildTaskRefinementPrompt(input) {
|
|
882
|
+
const authorityContext = (input.authority_context ?? []).map((entry) => `Ref: ${entry.ref}
|
|
883
|
+
${entry.content}`).join("\n\n---\n\n");
|
|
884
|
+
return [
|
|
885
|
+
"You are a COOP planning/refinement agent.",
|
|
886
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
887
|
+
"proposals must be an array with one update proposal for the current task.",
|
|
888
|
+
"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.",
|
|
889
|
+
`Task ID: ${input.task.id}`,
|
|
890
|
+
`Title: ${input.task.title}`,
|
|
891
|
+
`Status: ${input.task.status}`,
|
|
892
|
+
`Type: ${input.task.type}`,
|
|
893
|
+
`Priority: ${input.task.priority ?? "p2"}`,
|
|
894
|
+
`Track: ${input.task.track ?? "unassigned"}`,
|
|
895
|
+
`Depends On: ${(input.task.depends_on ?? []).join(", ") || "-"}`,
|
|
896
|
+
"Task Body:",
|
|
897
|
+
input.body || "(empty)",
|
|
898
|
+
authorityContext ? ["Authority Context:", authorityContext].join("\n") : "",
|
|
899
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
900
|
+
].filter(Boolean).join("\n");
|
|
901
|
+
}
|
|
902
|
+
async function refine_idea_to_draft(input, client) {
|
|
903
|
+
const prompt = buildIdeaRefinementPrompt(input);
|
|
904
|
+
if (!client) {
|
|
905
|
+
return fallbackIdeaRefinement(input);
|
|
906
|
+
}
|
|
907
|
+
const result = await client.refineIdea(prompt, input);
|
|
908
|
+
return result ?? fallbackIdeaRefinement(input);
|
|
909
|
+
}
|
|
910
|
+
async function refine_task_to_draft(input, client) {
|
|
911
|
+
const prompt = buildTaskRefinementPrompt(input);
|
|
912
|
+
if (!client) {
|
|
913
|
+
return fallbackTaskRefinement(input);
|
|
914
|
+
}
|
|
915
|
+
const result = await client.refineTask(prompt, input);
|
|
916
|
+
return result ?? fallbackTaskRefinement(input);
|
|
917
|
+
}
|
|
918
|
+
function parseRefinementDraftResponse(text, mode, sourceId, sourceTitle) {
|
|
919
|
+
const parsed = parseJsonObject(text);
|
|
920
|
+
if (!parsed) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
return normalizeDraft(parsed, mode, sourceId, sourceTitle);
|
|
924
|
+
}
|
|
925
|
+
|
|
650
926
|
// src/providers/config.ts
|
|
651
927
|
var DEFAULT_MODELS = {
|
|
652
928
|
openai: "gpt-5-mini",
|
|
@@ -978,6 +1254,12 @@ function toTaskDrafts(value) {
|
|
|
978
1254
|
}
|
|
979
1255
|
return out;
|
|
980
1256
|
}
|
|
1257
|
+
function refinementSystemPrompt(mode) {
|
|
1258
|
+
if (mode === "idea") {
|
|
1259
|
+
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.";
|
|
1260
|
+
}
|
|
1261
|
+
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.";
|
|
1262
|
+
}
|
|
981
1263
|
function asAgentResponse(text, tokens) {
|
|
982
1264
|
return {
|
|
983
1265
|
summary: text.trim(),
|
|
@@ -1019,20 +1301,45 @@ function create_provider_idea_decomposer(config) {
|
|
|
1019
1301
|
}
|
|
1020
1302
|
};
|
|
1021
1303
|
}
|
|
1304
|
+
function create_provider_refinement_client(config) {
|
|
1305
|
+
const provider = create_provider(config);
|
|
1306
|
+
return {
|
|
1307
|
+
async refineIdea(prompt, input) {
|
|
1308
|
+
const result = await provider.complete({
|
|
1309
|
+
system: refinementSystemPrompt("idea"),
|
|
1310
|
+
prompt
|
|
1311
|
+
});
|
|
1312
|
+
return parseRefinementDraftResponse(result.text, "idea", input.idea_id, input.title);
|
|
1313
|
+
},
|
|
1314
|
+
async refineTask(prompt, input) {
|
|
1315
|
+
const result = await provider.complete({
|
|
1316
|
+
system: refinementSystemPrompt("task"),
|
|
1317
|
+
prompt
|
|
1318
|
+
});
|
|
1319
|
+
return parseRefinementDraftResponse(result.text, "task", input.task.id, input.task.title);
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1022
1323
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1023
1324
|
0 && (module.exports = {
|
|
1325
|
+
buildIdeaRefinementPrompt,
|
|
1326
|
+
buildTaskRefinementPrompt,
|
|
1024
1327
|
build_contract,
|
|
1025
1328
|
build_decomposition_prompt,
|
|
1026
1329
|
constraint_violation_reasons,
|
|
1027
1330
|
create_provider,
|
|
1028
1331
|
create_provider_agent_client,
|
|
1029
1332
|
create_provider_idea_decomposer,
|
|
1333
|
+
create_provider_refinement_client,
|
|
1030
1334
|
create_run,
|
|
1031
1335
|
decompose_idea_to_tasks,
|
|
1032
1336
|
enforce_constraints,
|
|
1033
1337
|
execute_task,
|
|
1034
1338
|
finalize_run,
|
|
1035
1339
|
log_step,
|
|
1340
|
+
parseRefinementDraftResponse,
|
|
1341
|
+
refine_idea_to_draft,
|
|
1342
|
+
refine_task_to_draft,
|
|
1036
1343
|
resolve_provider_config,
|
|
1037
1344
|
select_agent,
|
|
1038
1345
|
validate_command,
|
package/dist/index.d.cts
CHANGED
|
@@ -25,6 +25,9 @@ interface AgentContractContext {
|
|
|
25
25
|
files: string[];
|
|
26
26
|
tasks: string[];
|
|
27
27
|
acceptance_criteria: string[];
|
|
28
|
+
tests_required: string[];
|
|
29
|
+
authority_refs: string[];
|
|
30
|
+
derived_refs: string[];
|
|
28
31
|
related_task_artifacts: AgentContractRelatedArtifacts[];
|
|
29
32
|
}
|
|
30
33
|
interface AgentContract {
|
|
@@ -141,6 +144,8 @@ interface DecomposedTaskDraft {
|
|
|
141
144
|
type?: "feature" | "bug" | "chore" | "spike";
|
|
142
145
|
priority?: "p0" | "p1" | "p2" | "p3";
|
|
143
146
|
track?: string;
|
|
147
|
+
acceptance_criteria?: string[];
|
|
148
|
+
tests_required?: string[];
|
|
144
149
|
}
|
|
145
150
|
interface IdeaDecomposerClient {
|
|
146
151
|
decompose(prompt: string, input: IdeaDecompositionInput): Promise<DecomposedTaskDraft[]>;
|
|
@@ -156,6 +161,61 @@ declare function build_decomposition_prompt(input: IdeaDecompositionInput): stri
|
|
|
156
161
|
*/
|
|
157
162
|
declare function decompose_idea_to_tasks(input: IdeaDecompositionInput, client?: IdeaDecomposerClient): Promise<DecomposedTaskDraft[]>;
|
|
158
163
|
|
|
164
|
+
type RefinementProposalAction = "create" | "update";
|
|
165
|
+
interface RefinementTaskProposal {
|
|
166
|
+
action: RefinementProposalAction;
|
|
167
|
+
id?: string;
|
|
168
|
+
target_id?: string;
|
|
169
|
+
title: string;
|
|
170
|
+
type?: Task["type"];
|
|
171
|
+
status?: Task["status"];
|
|
172
|
+
track?: string;
|
|
173
|
+
priority?: "p0" | "p1" | "p2" | "p3";
|
|
174
|
+
depends_on?: string[];
|
|
175
|
+
acceptance?: string[];
|
|
176
|
+
tests_required?: string[];
|
|
177
|
+
authority_refs?: string[];
|
|
178
|
+
derived_refs?: string[];
|
|
179
|
+
body?: string;
|
|
180
|
+
}
|
|
181
|
+
interface RefinementDraft {
|
|
182
|
+
kind: "refinement_draft";
|
|
183
|
+
version: 1;
|
|
184
|
+
mode: "idea" | "task";
|
|
185
|
+
source: {
|
|
186
|
+
entity_type: "idea" | "task";
|
|
187
|
+
id: string;
|
|
188
|
+
title: string;
|
|
189
|
+
};
|
|
190
|
+
summary: string;
|
|
191
|
+
generated_at: string;
|
|
192
|
+
proposals: RefinementTaskProposal[];
|
|
193
|
+
}
|
|
194
|
+
interface IdeaRefinementInput {
|
|
195
|
+
idea_id: string;
|
|
196
|
+
title: string;
|
|
197
|
+
body: string;
|
|
198
|
+
supplemental_context?: string;
|
|
199
|
+
}
|
|
200
|
+
interface TaskRefinementInput {
|
|
201
|
+
task: Task;
|
|
202
|
+
body: string;
|
|
203
|
+
supplemental_context?: string;
|
|
204
|
+
authority_context?: Array<{
|
|
205
|
+
ref: string;
|
|
206
|
+
content: string;
|
|
207
|
+
}>;
|
|
208
|
+
}
|
|
209
|
+
interface RefinementClient {
|
|
210
|
+
refineIdea(prompt: string, input: IdeaRefinementInput): Promise<RefinementDraft | null>;
|
|
211
|
+
refineTask(prompt: string, input: TaskRefinementInput): Promise<RefinementDraft | null>;
|
|
212
|
+
}
|
|
213
|
+
declare function buildIdeaRefinementPrompt(input: IdeaRefinementInput): string;
|
|
214
|
+
declare function buildTaskRefinementPrompt(input: TaskRefinementInput): string;
|
|
215
|
+
declare function refine_idea_to_draft(input: IdeaRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
216
|
+
declare function refine_task_to_draft(input: TaskRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
217
|
+
declare function parseRefinementDraftResponse(text: string, mode: "idea" | "task", sourceId: string, sourceTitle: string): RefinementDraft | null;
|
|
218
|
+
|
|
159
219
|
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama";
|
|
160
220
|
interface ProviderConfig {
|
|
161
221
|
provider: AiProviderName;
|
|
@@ -201,5 +261,6 @@ declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
|
201
261
|
* [SPEC: Architecture v2.0 §Phase 4]
|
|
202
262
|
*/
|
|
203
263
|
declare function create_provider_idea_decomposer(config: unknown): IdeaDecomposerClient;
|
|
264
|
+
declare function create_provider_refinement_client(config: unknown): RefinementClient;
|
|
204
265
|
|
|
205
|
-
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type LlmProvider, type ProviderConfig, type RunResult, type SandboxRunState, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
|
266
|
+
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ interface AgentContractContext {
|
|
|
25
25
|
files: string[];
|
|
26
26
|
tasks: string[];
|
|
27
27
|
acceptance_criteria: string[];
|
|
28
|
+
tests_required: string[];
|
|
29
|
+
authority_refs: string[];
|
|
30
|
+
derived_refs: string[];
|
|
28
31
|
related_task_artifacts: AgentContractRelatedArtifacts[];
|
|
29
32
|
}
|
|
30
33
|
interface AgentContract {
|
|
@@ -141,6 +144,8 @@ interface DecomposedTaskDraft {
|
|
|
141
144
|
type?: "feature" | "bug" | "chore" | "spike";
|
|
142
145
|
priority?: "p0" | "p1" | "p2" | "p3";
|
|
143
146
|
track?: string;
|
|
147
|
+
acceptance_criteria?: string[];
|
|
148
|
+
tests_required?: string[];
|
|
144
149
|
}
|
|
145
150
|
interface IdeaDecomposerClient {
|
|
146
151
|
decompose(prompt: string, input: IdeaDecompositionInput): Promise<DecomposedTaskDraft[]>;
|
|
@@ -156,6 +161,61 @@ declare function build_decomposition_prompt(input: IdeaDecompositionInput): stri
|
|
|
156
161
|
*/
|
|
157
162
|
declare function decompose_idea_to_tasks(input: IdeaDecompositionInput, client?: IdeaDecomposerClient): Promise<DecomposedTaskDraft[]>;
|
|
158
163
|
|
|
164
|
+
type RefinementProposalAction = "create" | "update";
|
|
165
|
+
interface RefinementTaskProposal {
|
|
166
|
+
action: RefinementProposalAction;
|
|
167
|
+
id?: string;
|
|
168
|
+
target_id?: string;
|
|
169
|
+
title: string;
|
|
170
|
+
type?: Task["type"];
|
|
171
|
+
status?: Task["status"];
|
|
172
|
+
track?: string;
|
|
173
|
+
priority?: "p0" | "p1" | "p2" | "p3";
|
|
174
|
+
depends_on?: string[];
|
|
175
|
+
acceptance?: string[];
|
|
176
|
+
tests_required?: string[];
|
|
177
|
+
authority_refs?: string[];
|
|
178
|
+
derived_refs?: string[];
|
|
179
|
+
body?: string;
|
|
180
|
+
}
|
|
181
|
+
interface RefinementDraft {
|
|
182
|
+
kind: "refinement_draft";
|
|
183
|
+
version: 1;
|
|
184
|
+
mode: "idea" | "task";
|
|
185
|
+
source: {
|
|
186
|
+
entity_type: "idea" | "task";
|
|
187
|
+
id: string;
|
|
188
|
+
title: string;
|
|
189
|
+
};
|
|
190
|
+
summary: string;
|
|
191
|
+
generated_at: string;
|
|
192
|
+
proposals: RefinementTaskProposal[];
|
|
193
|
+
}
|
|
194
|
+
interface IdeaRefinementInput {
|
|
195
|
+
idea_id: string;
|
|
196
|
+
title: string;
|
|
197
|
+
body: string;
|
|
198
|
+
supplemental_context?: string;
|
|
199
|
+
}
|
|
200
|
+
interface TaskRefinementInput {
|
|
201
|
+
task: Task;
|
|
202
|
+
body: string;
|
|
203
|
+
supplemental_context?: string;
|
|
204
|
+
authority_context?: Array<{
|
|
205
|
+
ref: string;
|
|
206
|
+
content: string;
|
|
207
|
+
}>;
|
|
208
|
+
}
|
|
209
|
+
interface RefinementClient {
|
|
210
|
+
refineIdea(prompt: string, input: IdeaRefinementInput): Promise<RefinementDraft | null>;
|
|
211
|
+
refineTask(prompt: string, input: TaskRefinementInput): Promise<RefinementDraft | null>;
|
|
212
|
+
}
|
|
213
|
+
declare function buildIdeaRefinementPrompt(input: IdeaRefinementInput): string;
|
|
214
|
+
declare function buildTaskRefinementPrompt(input: TaskRefinementInput): string;
|
|
215
|
+
declare function refine_idea_to_draft(input: IdeaRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
216
|
+
declare function refine_task_to_draft(input: TaskRefinementInput, client?: RefinementClient): Promise<RefinementDraft>;
|
|
217
|
+
declare function parseRefinementDraftResponse(text: string, mode: "idea" | "task", sourceId: string, sourceTitle: string): RefinementDraft | null;
|
|
218
|
+
|
|
159
219
|
type AiProviderName = "mock" | "openai" | "anthropic" | "gemini" | "ollama";
|
|
160
220
|
interface ProviderConfig {
|
|
161
221
|
provider: AiProviderName;
|
|
@@ -201,5 +261,6 @@ declare function create_provider_agent_client(config: unknown): AgentClient;
|
|
|
201
261
|
* [SPEC: Architecture v2.0 §Phase 4]
|
|
202
262
|
*/
|
|
203
263
|
declare function create_provider_idea_decomposer(config: unknown): IdeaDecomposerClient;
|
|
264
|
+
declare function create_provider_refinement_client(config: unknown): RefinementClient;
|
|
204
265
|
|
|
205
|
-
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type LlmProvider, type ProviderConfig, type RunResult, type SandboxRunState, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
|
266
|
+
export { type AgentClient, type AgentContract, type AgentContractConstraints, type AgentContractContext, type AgentContractPermissions, type AgentContractRelatedArtifacts, type AgentResponse, type AiProviderName, type CompletionInput, type CompletionResult, type DecomposedTaskDraft, type ExecuteTaskOptions, type FileAccessMode, type IdeaDecomposerClient, type IdeaDecompositionInput, type IdeaRefinementInput, type LlmProvider, type ProviderConfig, type RefinementClient, type RefinementDraft, type RefinementProposalAction, type RefinementTaskProposal, type RunResult, type SandboxRunState, type TaskRefinementInput, buildIdeaRefinementPrompt, buildTaskRefinementPrompt, build_contract, build_decomposition_prompt, constraint_violation_reasons, create_provider, create_provider_agent_client, create_provider_idea_decomposer, create_provider_refinement_client, create_run, decompose_idea_to_tasks, enforce_constraints, execute_task, finalize_run, log_step, parseRefinementDraftResponse, refine_idea_to_draft, refine_task_to_draft, resolve_provider_config, select_agent, validate_command, validate_file_access, write_run };
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,17 @@ function taskAcceptance(task) {
|
|
|
49
49
|
const maybe = asRecord(task).acceptance;
|
|
50
50
|
return asStringArray(maybe) ?? [];
|
|
51
51
|
}
|
|
52
|
+
function taskTestsRequired(task) {
|
|
53
|
+
const maybe = asRecord(task).tests_required;
|
|
54
|
+
return asStringArray(maybe) ?? [];
|
|
55
|
+
}
|
|
56
|
+
function taskOriginRefs(task) {
|
|
57
|
+
const origin = asRecord(asRecord(task).origin);
|
|
58
|
+
return {
|
|
59
|
+
authority_refs: asStringArray(origin.authority_refs) ?? [],
|
|
60
|
+
derived_refs: asStringArray(origin.derived_refs) ?? []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
52
63
|
function collectRelatedArtifacts(task, graph) {
|
|
53
64
|
const context = asRecord(task.execution?.context ?? {});
|
|
54
65
|
const relatedTaskIds = dedupe(asStringArray(context.tasks) ?? []);
|
|
@@ -143,9 +154,12 @@ function build_contract(task, graph, config) {
|
|
|
143
154
|
const taskContextFiles = asStringArray(executionContext.files) ?? [];
|
|
144
155
|
const taskContextTasks = asStringArray(executionContext.tasks) ?? [];
|
|
145
156
|
const acceptance_criteria = taskAcceptance(task);
|
|
157
|
+
const tests_required = taskTestsRequired(task);
|
|
158
|
+
const origin_refs = taskOriginRefs(task);
|
|
146
159
|
const related_task_artifacts = collectRelatedArtifacts(task, graph);
|
|
147
160
|
const output_requirements = [
|
|
148
161
|
...DEFAULT_OUTPUT_REQUIREMENTS,
|
|
162
|
+
...tests_required.length > 0 ? tests_required.map((entry) => `Required test: ${entry}`) : [],
|
|
149
163
|
...(task.artifacts?.produces ?? []).map((artifact) => {
|
|
150
164
|
if (artifact.path) {
|
|
151
165
|
return `Produce ${artifact.type} artifact at ${artifact.path}`;
|
|
@@ -177,6 +191,9 @@ function build_contract(task, graph, config) {
|
|
|
177
191
|
files: taskContextFiles,
|
|
178
192
|
tasks: taskContextTasks,
|
|
179
193
|
acceptance_criteria,
|
|
194
|
+
tests_required,
|
|
195
|
+
authority_refs: origin_refs.authority_refs,
|
|
196
|
+
derived_refs: origin_refs.derived_refs,
|
|
180
197
|
related_task_artifacts
|
|
181
198
|
},
|
|
182
199
|
output_requirements
|
|
@@ -576,7 +593,7 @@ function build_decomposition_prompt(input) {
|
|
|
576
593
|
return [
|
|
577
594
|
"You are a planning agent for COOP.",
|
|
578
595
|
"Decompose the idea into 2-5 implementation tasks.",
|
|
579
|
-
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), and
|
|
596
|
+
"Each task needs: title, type(feature|bug|chore|spike), priority(p0-p3), body, acceptance_criteria, and tests_required.",
|
|
580
597
|
`Idea ID: ${input.idea_id}`,
|
|
581
598
|
`Title: ${input.title}`,
|
|
582
599
|
"Body:",
|
|
@@ -595,6 +612,259 @@ async function decompose_idea_to_tasks(input, client) {
|
|
|
595
612
|
return drafts;
|
|
596
613
|
}
|
|
597
614
|
|
|
615
|
+
// src/refinement/refine.ts
|
|
616
|
+
function sentenceCase2(value) {
|
|
617
|
+
if (value.length === 0) return value;
|
|
618
|
+
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
619
|
+
}
|
|
620
|
+
function nonEmptyLines2(input) {
|
|
621
|
+
return input.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
622
|
+
}
|
|
623
|
+
function extractBulletIdeas2(body) {
|
|
624
|
+
return nonEmptyLines2(body).filter((line) => /^[-*]\s+/.test(line)).map((line) => line.replace(/^[-*]\s+/, "").trim()).filter(Boolean).slice(0, 5);
|
|
625
|
+
}
|
|
626
|
+
function taskBodySections(title) {
|
|
627
|
+
return [
|
|
628
|
+
"## Objective",
|
|
629
|
+
title,
|
|
630
|
+
"",
|
|
631
|
+
"## Scope",
|
|
632
|
+
"- Define implementation boundaries",
|
|
633
|
+
"",
|
|
634
|
+
"## Constraints",
|
|
635
|
+
"- Preserve existing behavior unless explicitly changed",
|
|
636
|
+
"",
|
|
637
|
+
"## References",
|
|
638
|
+
"- Add source refs during refinement/apply",
|
|
639
|
+
"",
|
|
640
|
+
"## Refinement Notes",
|
|
641
|
+
"- Generated by COOP refinement fallback"
|
|
642
|
+
].join("\n");
|
|
643
|
+
}
|
|
644
|
+
function normalizeStringArray(value) {
|
|
645
|
+
if (!Array.isArray(value)) return void 0;
|
|
646
|
+
const entries = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
647
|
+
return entries.length > 0 ? entries : void 0;
|
|
648
|
+
}
|
|
649
|
+
function normalizeProposal(value) {
|
|
650
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
const record = value;
|
|
654
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
655
|
+
if (!title) return null;
|
|
656
|
+
const action = record.action === "update" ? "update" : "create";
|
|
657
|
+
return {
|
|
658
|
+
action,
|
|
659
|
+
id: typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0,
|
|
660
|
+
target_id: typeof record.target_id === "string" && record.target_id.trim() ? record.target_id.trim() : void 0,
|
|
661
|
+
title,
|
|
662
|
+
type: record.type === "feature" || record.type === "bug" || record.type === "chore" || record.type === "spike" || record.type === "epic" ? record.type : void 0,
|
|
663
|
+
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,
|
|
664
|
+
track: typeof record.track === "string" ? record.track.trim() || void 0 : void 0,
|
|
665
|
+
priority: record.priority === "p0" || record.priority === "p1" || record.priority === "p2" || record.priority === "p3" ? record.priority : void 0,
|
|
666
|
+
depends_on: normalizeStringArray(record.depends_on),
|
|
667
|
+
acceptance: normalizeStringArray(record.acceptance),
|
|
668
|
+
tests_required: normalizeStringArray(record.tests_required),
|
|
669
|
+
authority_refs: normalizeStringArray(record.authority_refs),
|
|
670
|
+
derived_refs: normalizeStringArray(record.derived_refs),
|
|
671
|
+
body: typeof record.body === "string" ? record.body : void 0
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function normalizeDraft(value, mode, sourceId, sourceTitle) {
|
|
675
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
const record = value;
|
|
679
|
+
const proposalsRaw = Array.isArray(record.proposals) ? record.proposals : Array.isArray(record.tasks) ? record.tasks : [];
|
|
680
|
+
const proposals = proposalsRaw.map((entry) => normalizeProposal(entry)).filter((entry) => Boolean(entry));
|
|
681
|
+
if (proposals.length === 0) return null;
|
|
682
|
+
const summary = typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : `Refined ${mode} ${sourceId}`;
|
|
683
|
+
return {
|
|
684
|
+
kind: "refinement_draft",
|
|
685
|
+
version: 1,
|
|
686
|
+
mode,
|
|
687
|
+
source: {
|
|
688
|
+
entity_type: mode,
|
|
689
|
+
id: sourceId,
|
|
690
|
+
title: sourceTitle
|
|
691
|
+
},
|
|
692
|
+
summary,
|
|
693
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
694
|
+
proposals
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function parseJsonObject(text) {
|
|
698
|
+
const trimmed = text.trim();
|
|
699
|
+
const candidates = [trimmed];
|
|
700
|
+
const fenced = trimmed.match(/```json\s*([\s\S]*?)```/i);
|
|
701
|
+
if (fenced?.[1]) {
|
|
702
|
+
candidates.push(fenced[1]);
|
|
703
|
+
}
|
|
704
|
+
const start = trimmed.indexOf("{");
|
|
705
|
+
const end = trimmed.lastIndexOf("}");
|
|
706
|
+
if (start >= 0 && end > start) {
|
|
707
|
+
candidates.push(trimmed.slice(start, end + 1));
|
|
708
|
+
}
|
|
709
|
+
for (const candidate of candidates) {
|
|
710
|
+
try {
|
|
711
|
+
const parsed = JSON.parse(candidate);
|
|
712
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
713
|
+
return parsed;
|
|
714
|
+
}
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
function fallbackIdeaRefinement(input) {
|
|
721
|
+
const bullets = extractBulletIdeas2(input.body);
|
|
722
|
+
const tasks = bullets.length > 0 ? bullets.slice(0, 3).map((bullet, index) => ({
|
|
723
|
+
title: sentenceCase2(bullet),
|
|
724
|
+
body: taskBodySections(sentenceCase2(bullet)),
|
|
725
|
+
type: index === 0 ? "spike" : "feature",
|
|
726
|
+
priority: index === 0 ? "p1" : "p2",
|
|
727
|
+
track: "unassigned",
|
|
728
|
+
acceptance_criteria: [`${sentenceCase2(bullet)} is implemented as described`],
|
|
729
|
+
tests_required: ["Relevant integration or regression coverage is added"]
|
|
730
|
+
})) : [
|
|
731
|
+
{
|
|
732
|
+
title: `Define scope and acceptance for ${input.title}`,
|
|
733
|
+
body: taskBodySections(`Define scope and acceptance for ${input.title}`),
|
|
734
|
+
type: "spike",
|
|
735
|
+
priority: "p1",
|
|
736
|
+
track: "unassigned",
|
|
737
|
+
acceptance_criteria: [`Scope and acceptance are explicit for ${input.title}`],
|
|
738
|
+
tests_required: ["Planning review recorded"]
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
title: `Implement ${input.title}`,
|
|
742
|
+
body: taskBodySections(`Implement ${input.title}`),
|
|
743
|
+
type: "feature",
|
|
744
|
+
priority: "p1",
|
|
745
|
+
track: "unassigned",
|
|
746
|
+
acceptance_criteria: [`${input.title} is implemented end-to-end`],
|
|
747
|
+
tests_required: ["Automated test coverage added for the primary path"]
|
|
748
|
+
}
|
|
749
|
+
];
|
|
750
|
+
return {
|
|
751
|
+
kind: "refinement_draft",
|
|
752
|
+
version: 1,
|
|
753
|
+
mode: "idea",
|
|
754
|
+
source: {
|
|
755
|
+
entity_type: "idea",
|
|
756
|
+
id: input.idea_id,
|
|
757
|
+
title: input.title
|
|
758
|
+
},
|
|
759
|
+
summary: `Refined idea ${input.idea_id} into ${tasks.length} proposed task(s).`,
|
|
760
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
761
|
+
proposals: tasks.map((task) => ({
|
|
762
|
+
action: "create",
|
|
763
|
+
title: task.title,
|
|
764
|
+
type: task.type,
|
|
765
|
+
status: "todo",
|
|
766
|
+
track: task.track ?? "unassigned",
|
|
767
|
+
priority: task.priority,
|
|
768
|
+
acceptance: task.acceptance_criteria,
|
|
769
|
+
tests_required: task.tests_required,
|
|
770
|
+
body: task.body
|
|
771
|
+
}))
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function fallbackTaskRefinement(input) {
|
|
775
|
+
const authorityRefs = input.task.origin?.authority_refs ?? [];
|
|
776
|
+
const derivedRefs = input.task.origin?.derived_refs ?? [];
|
|
777
|
+
const acceptance = input.task.acceptance && input.task.acceptance.length > 0 ? input.task.acceptance : [`${input.task.title} is complete and reviewable against its defined scope`];
|
|
778
|
+
const testsRequired = input.task.tests_required && input.task.tests_required.length > 0 ? input.task.tests_required : ["Automated regression coverage for the changed behavior"];
|
|
779
|
+
return {
|
|
780
|
+
kind: "refinement_draft",
|
|
781
|
+
version: 1,
|
|
782
|
+
mode: "task",
|
|
783
|
+
source: {
|
|
784
|
+
entity_type: "task",
|
|
785
|
+
id: input.task.id,
|
|
786
|
+
title: input.task.title
|
|
787
|
+
},
|
|
788
|
+
summary: `Refined task ${input.task.id} into an execution-ready update proposal.`,
|
|
789
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
790
|
+
proposals: [
|
|
791
|
+
{
|
|
792
|
+
action: "update",
|
|
793
|
+
target_id: input.task.id,
|
|
794
|
+
title: input.task.title,
|
|
795
|
+
type: input.task.type,
|
|
796
|
+
status: input.task.status,
|
|
797
|
+
track: input.task.track,
|
|
798
|
+
priority: input.task.priority,
|
|
799
|
+
depends_on: input.task.depends_on,
|
|
800
|
+
acceptance,
|
|
801
|
+
tests_required: testsRequired,
|
|
802
|
+
authority_refs: authorityRefs,
|
|
803
|
+
derived_refs: derivedRefs,
|
|
804
|
+
body: input.body?.trim() || taskBodySections(input.task.title)
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function buildIdeaRefinementPrompt(input) {
|
|
810
|
+
return [
|
|
811
|
+
"You are a COOP planning agent.",
|
|
812
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
813
|
+
"proposals must be an array of task proposals.",
|
|
814
|
+
"Each proposal must include: action(create), title, type(feature|bug|chore|spike), priority(p0-p3), track, acceptance(string[]), tests_required(string[]), body.",
|
|
815
|
+
"Use depends_on only for existing task ids when necessary.",
|
|
816
|
+
`Idea ID: ${input.idea_id}`,
|
|
817
|
+
`Title: ${input.title}`,
|
|
818
|
+
"Body:",
|
|
819
|
+
input.body || "(empty)",
|
|
820
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
821
|
+
].filter(Boolean).join("\n");
|
|
822
|
+
}
|
|
823
|
+
function buildTaskRefinementPrompt(input) {
|
|
824
|
+
const authorityContext = (input.authority_context ?? []).map((entry) => `Ref: ${entry.ref}
|
|
825
|
+
${entry.content}`).join("\n\n---\n\n");
|
|
826
|
+
return [
|
|
827
|
+
"You are a COOP planning/refinement agent.",
|
|
828
|
+
"Return ONLY JSON object with keys: summary, proposals.",
|
|
829
|
+
"proposals must be an array with one update proposal for the current task.",
|
|
830
|
+
"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.",
|
|
831
|
+
`Task ID: ${input.task.id}`,
|
|
832
|
+
`Title: ${input.task.title}`,
|
|
833
|
+
`Status: ${input.task.status}`,
|
|
834
|
+
`Type: ${input.task.type}`,
|
|
835
|
+
`Priority: ${input.task.priority ?? "p2"}`,
|
|
836
|
+
`Track: ${input.task.track ?? "unassigned"}`,
|
|
837
|
+
`Depends On: ${(input.task.depends_on ?? []).join(", ") || "-"}`,
|
|
838
|
+
"Task Body:",
|
|
839
|
+
input.body || "(empty)",
|
|
840
|
+
authorityContext ? ["Authority Context:", authorityContext].join("\n") : "",
|
|
841
|
+
input.supplemental_context?.trim() ? ["Supplemental Context:", input.supplemental_context].join("\n") : ""
|
|
842
|
+
].filter(Boolean).join("\n");
|
|
843
|
+
}
|
|
844
|
+
async function refine_idea_to_draft(input, client) {
|
|
845
|
+
const prompt = buildIdeaRefinementPrompt(input);
|
|
846
|
+
if (!client) {
|
|
847
|
+
return fallbackIdeaRefinement(input);
|
|
848
|
+
}
|
|
849
|
+
const result = await client.refineIdea(prompt, input);
|
|
850
|
+
return result ?? fallbackIdeaRefinement(input);
|
|
851
|
+
}
|
|
852
|
+
async function refine_task_to_draft(input, client) {
|
|
853
|
+
const prompt = buildTaskRefinementPrompt(input);
|
|
854
|
+
if (!client) {
|
|
855
|
+
return fallbackTaskRefinement(input);
|
|
856
|
+
}
|
|
857
|
+
const result = await client.refineTask(prompt, input);
|
|
858
|
+
return result ?? fallbackTaskRefinement(input);
|
|
859
|
+
}
|
|
860
|
+
function parseRefinementDraftResponse(text, mode, sourceId, sourceTitle) {
|
|
861
|
+
const parsed = parseJsonObject(text);
|
|
862
|
+
if (!parsed) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
return normalizeDraft(parsed, mode, sourceId, sourceTitle);
|
|
866
|
+
}
|
|
867
|
+
|
|
598
868
|
// src/providers/config.ts
|
|
599
869
|
var DEFAULT_MODELS = {
|
|
600
870
|
openai: "gpt-5-mini",
|
|
@@ -926,6 +1196,12 @@ function toTaskDrafts(value) {
|
|
|
926
1196
|
}
|
|
927
1197
|
return out;
|
|
928
1198
|
}
|
|
1199
|
+
function refinementSystemPrompt(mode) {
|
|
1200
|
+
if (mode === "idea") {
|
|
1201
|
+
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.";
|
|
1202
|
+
}
|
|
1203
|
+
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.";
|
|
1204
|
+
}
|
|
929
1205
|
function asAgentResponse(text, tokens) {
|
|
930
1206
|
return {
|
|
931
1207
|
summary: text.trim(),
|
|
@@ -967,19 +1243,44 @@ function create_provider_idea_decomposer(config) {
|
|
|
967
1243
|
}
|
|
968
1244
|
};
|
|
969
1245
|
}
|
|
1246
|
+
function create_provider_refinement_client(config) {
|
|
1247
|
+
const provider = create_provider(config);
|
|
1248
|
+
return {
|
|
1249
|
+
async refineIdea(prompt, input) {
|
|
1250
|
+
const result = await provider.complete({
|
|
1251
|
+
system: refinementSystemPrompt("idea"),
|
|
1252
|
+
prompt
|
|
1253
|
+
});
|
|
1254
|
+
return parseRefinementDraftResponse(result.text, "idea", input.idea_id, input.title);
|
|
1255
|
+
},
|
|
1256
|
+
async refineTask(prompt, input) {
|
|
1257
|
+
const result = await provider.complete({
|
|
1258
|
+
system: refinementSystemPrompt("task"),
|
|
1259
|
+
prompt
|
|
1260
|
+
});
|
|
1261
|
+
return parseRefinementDraftResponse(result.text, "task", input.task.id, input.task.title);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
970
1265
|
export {
|
|
1266
|
+
buildIdeaRefinementPrompt,
|
|
1267
|
+
buildTaskRefinementPrompt,
|
|
971
1268
|
build_contract,
|
|
972
1269
|
build_decomposition_prompt,
|
|
973
1270
|
constraint_violation_reasons,
|
|
974
1271
|
create_provider,
|
|
975
1272
|
create_provider_agent_client,
|
|
976
1273
|
create_provider_idea_decomposer,
|
|
1274
|
+
create_provider_refinement_client,
|
|
977
1275
|
create_run,
|
|
978
1276
|
decompose_idea_to_tasks,
|
|
979
1277
|
enforce_constraints,
|
|
980
1278
|
execute_task,
|
|
981
1279
|
finalize_run,
|
|
982
1280
|
log_step,
|
|
1281
|
+
parseRefinementDraftResponse,
|
|
1282
|
+
refine_idea_to_draft,
|
|
1283
|
+
refine_task_to_draft,
|
|
983
1284
|
resolve_provider_config,
|
|
984
1285
|
select_agent,
|
|
985
1286
|
validate_command,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/coop-ai",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@kitsy/coop-core": "1.0
|
|
20
|
+
"@kitsy/coop-core": "2.1.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^24.12.0",
|