@h-rig/planning-plugin 0.0.6-alpha.135 → 0.0.6-alpha.136
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/src/index.js +72 -0
- package/dist/src/planning.d.ts +4 -0
- package/dist/src/planning.js +72 -0
- package/package.json +3 -3
package/dist/src/index.js
CHANGED
|
@@ -12,6 +12,76 @@ async function plan(planSpec, ports) {
|
|
|
12
12
|
async function specClarifyPlan(input, context, ports) {
|
|
13
13
|
return plan(await clarify(await spec(input, ports), context, ports), ports);
|
|
14
14
|
}
|
|
15
|
+
var PLAN_SPEC_SYSTEM = "You are an engineering architect. Convert a PRD into a structured spec. Capture WHAT and WHY, not the tech stack. Surface every ambiguity as an open question. Decompose into ordered, dependency-aware tasks. Reply ONLY with a single JSON object.";
|
|
16
|
+
function planSpecUser(input) {
|
|
17
|
+
return [
|
|
18
|
+
`PRD title: ${input.title}`,
|
|
19
|
+
`PRD:
|
|
20
|
+
"""${input.body}"""`,
|
|
21
|
+
"",
|
|
22
|
+
'Reply with JSON of exactly this shape: { "userStories": [{"id":"US1","title":"...","priority":1}], "functionalRequirements": [{"id":"FR-001","text":"..."}], "openQuestions": [{"id":"Q1","question":"..."}], "tasks": [{"localId":"task-1","title":"...","description":"...","acceptance":["..."],"scope":["path"],"validationKeys":[],"dependsOn":[],"parent":null,"parallelizable":true}] }',
|
|
23
|
+
"Mark as open any underspecified data shape, edge case, non-functional limit, or integration point. Use dependsOn (localIds) to order tasks. The task graph must be acyclic."
|
|
24
|
+
].join(`
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
function extractJsonObject(text) {
|
|
28
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
29
|
+
const raw = fenced?.[1] ?? text;
|
|
30
|
+
const start = raw.indexOf("{");
|
|
31
|
+
const end = raw.lastIndexOf("}");
|
|
32
|
+
if (start < 0 || end <= start)
|
|
33
|
+
throw new Error("model output contained no JSON object");
|
|
34
|
+
return JSON.parse(raw.slice(start, end + 1));
|
|
35
|
+
}
|
|
36
|
+
function createLlmPlanningProvider(chat, now) {
|
|
37
|
+
return {
|
|
38
|
+
spec: async (input) => {
|
|
39
|
+
const parsed = extractJsonObject(await chat(PLAN_SPEC_SYSTEM, planSpecUser(input)));
|
|
40
|
+
const stories = Array.isArray(parsed.userStories) ? parsed.userStories : [];
|
|
41
|
+
const reqs = Array.isArray(parsed.functionalRequirements) ? parsed.functionalRequirements : [];
|
|
42
|
+
const questions = Array.isArray(parsed.openQuestions) ? parsed.openQuestions : [];
|
|
43
|
+
const tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
44
|
+
return {
|
|
45
|
+
prdTitle: input.title,
|
|
46
|
+
userStories: stories.filter((s) => s.title).map((s, i) => ({ id: s.id ?? `US${i + 1}`, title: String(s.title), priority: typeof s.priority === "number" ? s.priority : i + 1 })),
|
|
47
|
+
functionalRequirements: reqs.filter((r) => r.text).map((r, i) => ({ id: r.id ?? `FR-${String(i + 1).padStart(3, "0")}`, text: String(r.text) })),
|
|
48
|
+
openQuestions: questions.filter((q) => q.question).map((q, i) => ({ id: q.id ?? `Q${i + 1}`, question: String(q.question), status: "open", resolution: null })),
|
|
49
|
+
tasks: tasks.filter((t) => typeof t.title === "string").map((t, i) => ({
|
|
50
|
+
localId: typeof t.localId === "string" ? t.localId : `task-${i + 1}`,
|
|
51
|
+
title: String(t.title),
|
|
52
|
+
description: typeof t.description === "string" ? t.description : "",
|
|
53
|
+
acceptance: Array.isArray(t.acceptance) ? t.acceptance.map(String) : [],
|
|
54
|
+
scope: Array.isArray(t.scope) ? t.scope.map(String) : [],
|
|
55
|
+
validationKeys: Array.isArray(t.validationKeys) ? t.validationKeys.map(String) : [],
|
|
56
|
+
dependsOn: Array.isArray(t.dependsOn) ? t.dependsOn.map(String) : [],
|
|
57
|
+
parent: typeof t.parent === "string" ? t.parent : null,
|
|
58
|
+
parallelizable: t.parallelizable !== false
|
|
59
|
+
})),
|
|
60
|
+
generatedAt: now()
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
clarify: (planSpec) => planSpec,
|
|
64
|
+
plan: (planSpec) => planSpec
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function openAiChat(apiKey, model = "gpt-4o-mini") {
|
|
68
|
+
return async (system, user) => {
|
|
69
|
+
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
model,
|
|
74
|
+
messages: [{ role: "system", content: system }, { role: "user", content: user }],
|
|
75
|
+
temperature: 0.2,
|
|
76
|
+
response_format: { type: "json_object" }
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
throw new Error(`OpenAI ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
return data.choices?.[0]?.message?.content ?? "{}";
|
|
83
|
+
};
|
|
84
|
+
}
|
|
15
85
|
function taskByLocalId(tasks) {
|
|
16
86
|
return new Map(tasks.map((task) => [task.localId, task]));
|
|
17
87
|
}
|
|
@@ -138,7 +208,9 @@ export {
|
|
|
138
208
|
spec,
|
|
139
209
|
planningPlugin,
|
|
140
210
|
plan,
|
|
211
|
+
openAiChat,
|
|
141
212
|
materialize,
|
|
213
|
+
createLlmPlanningProvider,
|
|
142
214
|
clarify,
|
|
143
215
|
PLANNING_PLUGIN_NAME
|
|
144
216
|
};
|
package/dist/src/planning.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export declare function spec(input: PrdInput, ports: Pick<PlanningProviderPorts,
|
|
|
28
28
|
export declare function clarify(planSpec: PlanSpec, context: ClarificationContext, ports: Pick<PlanningProviderPorts, "clarify">): Promise<PlanSpec>;
|
|
29
29
|
export declare function plan(planSpec: PlanSpec, ports: Pick<PlanningProviderPorts, "plan">): Promise<PlanSpec>;
|
|
30
30
|
export declare function specClarifyPlan(input: PrdInput, context: ClarificationContext, ports: PlanningProviderPorts): Promise<PlanSpec>;
|
|
31
|
+
export type ChatComplete = (system: string, user: string) => Promise<string>;
|
|
32
|
+
export declare function createLlmPlanningProvider(chat: ChatComplete, now: () => string): PlanningProviderPorts;
|
|
33
|
+
/** OpenAI chat-completions port via fetch (no SDK). */
|
|
34
|
+
export declare function openAiChat(apiKey: string, model?: string): ChatComplete;
|
|
31
35
|
export declare function materialize(planSpec: PlanSpec, source: Pick<RegisteredTaskSource, "list" | "create">, options?: {
|
|
32
36
|
readonly planId?: string;
|
|
33
37
|
}): Promise<MaterializePlanResult>;
|
package/dist/src/planning.js
CHANGED
|
@@ -12,6 +12,76 @@ async function plan(planSpec, ports) {
|
|
|
12
12
|
async function specClarifyPlan(input, context, ports) {
|
|
13
13
|
return plan(await clarify(await spec(input, ports), context, ports), ports);
|
|
14
14
|
}
|
|
15
|
+
var PLAN_SPEC_SYSTEM = "You are an engineering architect. Convert a PRD into a structured spec. Capture WHAT and WHY, not the tech stack. Surface every ambiguity as an open question. Decompose into ordered, dependency-aware tasks. Reply ONLY with a single JSON object.";
|
|
16
|
+
function planSpecUser(input) {
|
|
17
|
+
return [
|
|
18
|
+
`PRD title: ${input.title}`,
|
|
19
|
+
`PRD:
|
|
20
|
+
"""${input.body}"""`,
|
|
21
|
+
"",
|
|
22
|
+
'Reply with JSON of exactly this shape: { "userStories": [{"id":"US1","title":"...","priority":1}], "functionalRequirements": [{"id":"FR-001","text":"..."}], "openQuestions": [{"id":"Q1","question":"..."}], "tasks": [{"localId":"task-1","title":"...","description":"...","acceptance":["..."],"scope":["path"],"validationKeys":[],"dependsOn":[],"parent":null,"parallelizable":true}] }',
|
|
23
|
+
"Mark as open any underspecified data shape, edge case, non-functional limit, or integration point. Use dependsOn (localIds) to order tasks. The task graph must be acyclic."
|
|
24
|
+
].join(`
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
function extractJsonObject(text) {
|
|
28
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
29
|
+
const raw = fenced?.[1] ?? text;
|
|
30
|
+
const start = raw.indexOf("{");
|
|
31
|
+
const end = raw.lastIndexOf("}");
|
|
32
|
+
if (start < 0 || end <= start)
|
|
33
|
+
throw new Error("model output contained no JSON object");
|
|
34
|
+
return JSON.parse(raw.slice(start, end + 1));
|
|
35
|
+
}
|
|
36
|
+
function createLlmPlanningProvider(chat, now) {
|
|
37
|
+
return {
|
|
38
|
+
spec: async (input) => {
|
|
39
|
+
const parsed = extractJsonObject(await chat(PLAN_SPEC_SYSTEM, planSpecUser(input)));
|
|
40
|
+
const stories = Array.isArray(parsed.userStories) ? parsed.userStories : [];
|
|
41
|
+
const reqs = Array.isArray(parsed.functionalRequirements) ? parsed.functionalRequirements : [];
|
|
42
|
+
const questions = Array.isArray(parsed.openQuestions) ? parsed.openQuestions : [];
|
|
43
|
+
const tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
44
|
+
return {
|
|
45
|
+
prdTitle: input.title,
|
|
46
|
+
userStories: stories.filter((s) => s.title).map((s, i) => ({ id: s.id ?? `US${i + 1}`, title: String(s.title), priority: typeof s.priority === "number" ? s.priority : i + 1 })),
|
|
47
|
+
functionalRequirements: reqs.filter((r) => r.text).map((r, i) => ({ id: r.id ?? `FR-${String(i + 1).padStart(3, "0")}`, text: String(r.text) })),
|
|
48
|
+
openQuestions: questions.filter((q) => q.question).map((q, i) => ({ id: q.id ?? `Q${i + 1}`, question: String(q.question), status: "open", resolution: null })),
|
|
49
|
+
tasks: tasks.filter((t) => typeof t.title === "string").map((t, i) => ({
|
|
50
|
+
localId: typeof t.localId === "string" ? t.localId : `task-${i + 1}`,
|
|
51
|
+
title: String(t.title),
|
|
52
|
+
description: typeof t.description === "string" ? t.description : "",
|
|
53
|
+
acceptance: Array.isArray(t.acceptance) ? t.acceptance.map(String) : [],
|
|
54
|
+
scope: Array.isArray(t.scope) ? t.scope.map(String) : [],
|
|
55
|
+
validationKeys: Array.isArray(t.validationKeys) ? t.validationKeys.map(String) : [],
|
|
56
|
+
dependsOn: Array.isArray(t.dependsOn) ? t.dependsOn.map(String) : [],
|
|
57
|
+
parent: typeof t.parent === "string" ? t.parent : null,
|
|
58
|
+
parallelizable: t.parallelizable !== false
|
|
59
|
+
})),
|
|
60
|
+
generatedAt: now()
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
clarify: (planSpec) => planSpec,
|
|
64
|
+
plan: (planSpec) => planSpec
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function openAiChat(apiKey, model = "gpt-4o-mini") {
|
|
68
|
+
return async (system, user) => {
|
|
69
|
+
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
model,
|
|
74
|
+
messages: [{ role: "system", content: system }, { role: "user", content: user }],
|
|
75
|
+
temperature: 0.2,
|
|
76
|
+
response_format: { type: "json_object" }
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
throw new Error(`OpenAI ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
return data.choices?.[0]?.message?.content ?? "{}";
|
|
83
|
+
};
|
|
84
|
+
}
|
|
15
85
|
function taskByLocalId(tasks) {
|
|
16
86
|
return new Map(tasks.map((task) => [task.localId, task]));
|
|
17
87
|
}
|
|
@@ -119,6 +189,8 @@ export {
|
|
|
119
189
|
specClarifyPlan,
|
|
120
190
|
spec,
|
|
121
191
|
plan,
|
|
192
|
+
openAiChat,
|
|
122
193
|
materialize,
|
|
194
|
+
createLlmPlanningProvider,
|
|
123
195
|
clarify
|
|
124
196
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/planning-plugin",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.136",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "First-party PRD-to-plan plugin for Rig task sources.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"module": "./dist/src/index.js",
|
|
30
30
|
"types": "./dist/src/index.d.ts",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
33
|
-
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.
|
|
32
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.136",
|
|
33
|
+
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.136"
|
|
34
34
|
}
|
|
35
35
|
}
|