opencode-empirical-plan 0.2.0 → 0.3.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/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { type LifecycleState, STATE_FILENAME, validateLifecycleState } from "../schema.ts";
|
|
3
|
+
import { type LifecyclePhase, type LifecycleState, STATE_FILENAME, validateLifecycleState } from "../schema.ts";
|
|
4
|
+
|
|
5
|
+
const PHASE_ORDER: LifecyclePhase[] = ["plan", "execute", "reflect", "done"];
|
|
6
|
+
|
|
7
|
+
export async function assertPhaseAtLeast(
|
|
8
|
+
runDir: string,
|
|
9
|
+
required: LifecyclePhase,
|
|
10
|
+
): Promise<{ ok: true } | { ok: false; reason: string }> {
|
|
11
|
+
const state = await readLifecycleState(runDir);
|
|
12
|
+
if (!state) return { ok: true };
|
|
13
|
+
const current = PHASE_ORDER.indexOf(state.phase);
|
|
14
|
+
const needed = PHASE_ORDER.indexOf(required);
|
|
15
|
+
if (current < needed) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
reason: `lifecycle phase is "${state.phase}", expected at least "${required}"`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return { ok: true };
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
export async function readLifecycleState(runDir: string): Promise<LifecycleState | null> {
|
|
6
25
|
const stateFile = path.join(runDir, STATE_FILENAME);
|
|
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { atomicWrite } from "../state/frontmatter.ts";
|
|
5
5
|
import { ensureExecutionMd } from "../hooks/tool-after.ts";
|
|
6
|
+
import { assertPhaseAtLeast } from "../state/lifecycle-state.ts";
|
|
6
7
|
|
|
7
8
|
const SUMMARY_MARKER = "<!-- EXECUTION:SUMMARY -->";
|
|
8
9
|
|
|
@@ -25,8 +26,9 @@ export const lifecycleRecordExecutionTool = tool({
|
|
|
25
26
|
execution.md is the dedicated execution record file — separate from plan.md (which stays read-only after Phase 1-3).
|
|
26
27
|
|
|
27
28
|
Guards (hard-block):
|
|
28
|
-
-
|
|
29
|
-
-
|
|
29
|
+
- lifecycle-state.json phase must be >= "execute" (fail-open if state file missing)
|
|
30
|
+
- plan.md must exist
|
|
31
|
+
- execution.md must exist (auto-created if missing)
|
|
30
32
|
- <!-- EXECUTION:SUMMARY --> must NOT already exist in execution.md (idempotent guard)
|
|
31
33
|
|
|
32
34
|
On success: appends the execution_summary section into execution.md and updates its Phase frontmatter field to "summarized".
|
|
@@ -47,33 +49,33 @@ The execution_summary should compare each Plan Step against what actually happen
|
|
|
47
49
|
const planFile = path.join(runDir, "plan.md");
|
|
48
50
|
const executionFile = path.join(runDir, "execution.md");
|
|
49
51
|
|
|
50
|
-
const result = await doRecord(planFile, executionFile, args.execution_summary);
|
|
52
|
+
const result = await doRecord(runDir, planFile, executionFile, args.execution_summary);
|
|
51
53
|
return JSON.stringify(result);
|
|
52
54
|
},
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
async function doRecord(
|
|
58
|
+
runDir: string,
|
|
56
59
|
planFile: string,
|
|
57
60
|
executionFile: string,
|
|
58
61
|
executionSummary: string,
|
|
59
62
|
): Promise<RecordResult> {
|
|
60
|
-
|
|
61
|
-
if (!
|
|
63
|
+
const phaseCheck = await assertPhaseAtLeast(runDir, "execute");
|
|
64
|
+
if (!phaseCheck.ok) {
|
|
62
65
|
return {
|
|
63
66
|
success: false,
|
|
64
67
|
code: "PHASE_NOT_READY",
|
|
65
|
-
reason:
|
|
68
|
+
reason: phaseCheck.reason,
|
|
66
69
|
next_action: "Complete the PLAN phase first — run empirical-plan skill to produce plan.md",
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
if (!planContent.includes("## Plan Steps")) {
|
|
73
|
+
if (!(await fileExists(planFile))) {
|
|
72
74
|
return {
|
|
73
75
|
success: false,
|
|
74
76
|
code: "PHASE_NOT_READY",
|
|
75
|
-
reason:
|
|
76
|
-
next_action: "Complete the PLAN phase
|
|
77
|
+
reason: `plan.md not found at ${planFile}`,
|
|
78
|
+
next_action: "Complete the PLAN phase first — run empirical-plan skill to produce plan.md",
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
|
|
@@ -2,8 +2,8 @@ import { tool } from "@opencode-ai/plugin";
|
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { applyFrontmatterLifecycle, atomicWrite } from "../state/frontmatter.ts";
|
|
5
|
+
import { assertPhaseAtLeast } from "../state/lifecycle-state.ts";
|
|
5
6
|
|
|
6
|
-
const EXECUTION_SUMMARY_MARKER = "<!-- EXECUTION:SUMMARY -->";
|
|
7
7
|
const REFLECTION_MARKER = "<!-- LIFECYCLE:REFLECTION -->";
|
|
8
8
|
|
|
9
9
|
async function fileExists(p: string): Promise<boolean> {
|
|
@@ -19,7 +19,8 @@ export const lifecycleRecordReflectionTool = tool({
|
|
|
19
19
|
description: `Record Phase 5 (Reflection & Optimization) into plan.md.
|
|
20
20
|
|
|
21
21
|
Guards (hard-block):
|
|
22
|
-
-
|
|
22
|
+
- lifecycle-state.json phase must be >= "reflect" (fail-open if state file missing)
|
|
23
|
+
- execution.md must exist
|
|
23
24
|
- reflect.md must exist in run_dir (REFLECT phase complete)
|
|
24
25
|
- <!-- LIFECYCLE:REFLECTION --> must NOT already exist in plan.md (idempotent guard)
|
|
25
26
|
|
|
@@ -40,7 +41,16 @@ The reflection_record should contain: execution quality assessment, plan design
|
|
|
40
41
|
const executionFile = path.join(runDir, "execution.md");
|
|
41
42
|
const reflectFile = path.join(runDir, "reflect.md");
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
const phaseCheck = await assertPhaseAtLeast(runDir, "reflect");
|
|
45
|
+
if (!phaseCheck.ok) {
|
|
46
|
+
return JSON.stringify({
|
|
47
|
+
success: false,
|
|
48
|
+
code: "PHASE_NOT_READY",
|
|
49
|
+
reason: phaseCheck.reason,
|
|
50
|
+
next_action: "Complete EXECUTE phase and call lifecycle_record_execution first",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
if (!(await fileExists(planFile))) {
|
|
45
55
|
return JSON.stringify({
|
|
46
56
|
success: false,
|
|
@@ -50,7 +60,7 @@ The reflection_record should contain: execution quality assessment, plan design
|
|
|
50
60
|
});
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
// Guard 2: execution.md must exist
|
|
63
|
+
// Guard 2: execution.md must exist
|
|
54
64
|
if (!(await fileExists(executionFile))) {
|
|
55
65
|
return JSON.stringify({
|
|
56
66
|
success: false,
|
|
@@ -60,16 +70,6 @@ The reflection_record should contain: execution quality assessment, plan design
|
|
|
60
70
|
});
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
const executionContent = await fs.readFile(executionFile, "utf-8");
|
|
64
|
-
if (!executionContent.includes(EXECUTION_SUMMARY_MARKER)) {
|
|
65
|
-
return JSON.stringify({
|
|
66
|
-
success: false,
|
|
67
|
-
code: "EXECUTION_NOT_RECORDED",
|
|
68
|
-
reason: "Phase 4 summary (EXECUTION:SUMMARY) not yet recorded in execution.md",
|
|
69
|
-
next_action: "Call lifecycle_record_execution first to record Phase 4",
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
73
|
// Guard 3: reflect.md must exist
|
|
74
74
|
if (!(await fileExists(reflectFile))) {
|
|
75
75
|
return JSON.stringify({
|