oh-my-opencode 4.18.1 → 4.19.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/.agents/skills/codex-qa/scripts/lsp-e2e.sh +39 -16
- package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +111 -28
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +4 -4
- package/README.ru.md +2 -2
- package/README.zh-cn.md +2 -2
- package/bin/AGENTS.md +33 -0
- package/dist/agents/atlas/agent.d.ts +6 -5
- package/dist/agents/sisyphus/index.d.ts +5 -0
- package/dist/agents/sisyphus/kimi-k3.d.ts +19 -0
- package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
- package/dist/agents/sisyphus-junior/agent.d.ts +8 -4
- package/dist/agents/sisyphus-junior/index.d.ts +2 -0
- package/dist/agents/sisyphus-junior/kimi-k3.d.ts +12 -0
- package/dist/agents/types.d.ts +2 -2
- package/dist/cli/index.js +690 -139
- package/dist/cli-node/index.js +690 -139
- package/dist/config/index.d.ts +1 -1
- package/dist/config/schema/browser-automation.d.ts +1 -0
- package/dist/config/schema/commands.d.ts +1 -3
- package/dist/config/schema/default-mode.d.ts +1 -1
- package/dist/config/schema/goal.d.ts +7 -0
- package/dist/config/schema/hooks.d.ts +1 -1
- package/dist/config/schema/oh-my-opencode-config.d.ts +6 -10
- package/dist/config/schema/ralph-loop.d.ts +1 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/create-hooks.d.ts +1 -1
- package/dist/features/builtin-commands/templates/goal.d.ts +1 -0
- package/dist/features/builtin-commands/templates/stop-continuation.d.ts +1 -1
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
- package/dist/hooks/auto-slash-command/executor.d.ts +1 -0
- package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
- package/dist/hooks/comment-checker/hook.d.ts +8 -1
- package/dist/hooks/goal/command-arguments.d.ts +13 -0
- package/dist/hooks/goal/controller.d.ts +26 -0
- package/dist/hooks/goal/index.d.ts +23 -0
- package/dist/hooks/goal/prompt.d.ts +3 -0
- package/dist/hooks/goal/store.d.ts +8 -0
- package/dist/hooks/goal/tools.d.ts +10 -0
- package/dist/hooks/goal/types.d.ts +93 -0
- package/dist/hooks/goal/validation.d.ts +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/keyword-detector/hook.d.ts +1 -2
- package/dist/hooks/ralph-loop/types.d.ts +1 -1
- package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
- package/dist/index.js +3706 -4329
- package/dist/oh-my-opencode.schema.json +23 -19
- package/dist/plugin/chat-message/loop-commands.d.ts +1 -1
- package/dist/plugin/chat-message/types.d.ts +23 -10
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +3 -2
- package/dist/plugin/hooks/create-transform-hooks.d.ts +0 -2
- package/dist/plugin/messages-transform.d.ts +1 -0
- package/dist/plugin/stop-continuation.d.ts +2 -2
- package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
- package/dist/shared/system-directive.d.ts +0 -1
- package/dist/skills/ulw-plan/SKILL.md +11 -7
- package/dist/skills/ulw-plan/references/full-workflow.md +117 -6
- package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/dist/skills/ulw-research/SKILL.md +3 -3
- package/dist/tools/delegate-task/types.d.ts +0 -1
- package/dist/tui.js +115 -28
- package/package.json +14 -14
- package/packages/git-bash-mcp/dist/cli.js +81 -19
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +4 -4
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +118 -1
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +1 -1
- package/packages/lsp-daemon/dist/cli.js +262 -63
- package/packages/lsp-daemon/dist/client.js +194 -50
- package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -2
- package/packages/lsp-daemon/dist/daemon-client.js +26 -2
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +5 -4
- package/packages/lsp-daemon/dist/ensure-daemon.js +79 -18
- package/packages/lsp-daemon/dist/index.js +262 -63
- package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
- package/packages/lsp-daemon/dist/proxy.js +79 -23
- package/packages/lsp-tools-mcp/dist/cli.js +82 -20
- package/packages/lsp-tools-mcp/dist/mcp.js +82 -20
- package/packages/lsp-tools-mcp/dist/tools.js +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/.mcp.json +2 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +352 -17
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +7 -6
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +195 -56
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
- package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
- package/packages/omo-codex/plugin/components/lsp/AGENTS.md +1 -0
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -31
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +1 -0
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/AGENTS.md +8 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +148 -61
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +4 -72
- package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/src/plan-checklist.ts +171 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +213 -24
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +4 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +25 -4
- package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md +15 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -10
- package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +11 -7
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
- package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +32 -28
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +17 -45
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +455 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +13 -20
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +11 -7
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +2 -2
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +6 -6
- package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +3 -3
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +62 -0
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +271 -14
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +38 -22
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +0 -5
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +293 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +10 -10
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +353 -18
- package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +39 -2
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -7
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +117 -6
- package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
- package/dist/features/builtin-commands/templates/ralph-loop.d.ts +0 -3
- package/dist/skills/ultraresearch/SKILL.md +0 -10
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +0 -273
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -12
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -107
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +0 -128
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +0 -11
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +0 -268
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +0 -11
- package/packages/shared-skills/skills/ultraresearch/SKILL.md +0 -10
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/agents/openai.yaml +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/SKILL.md +0 -0
- /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/agents/openai.yaml +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export type PlanChecklist = {
|
|
4
|
+
readonly completed: number;
|
|
5
|
+
readonly remaining: number;
|
|
6
|
+
readonly total: number;
|
|
7
|
+
readonly nextTaskLabel: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const TODO_HEADING_PATTERN = /^##[ \t]+TODOs(?:[ \t]+#+)?[ \t]*$/i;
|
|
11
|
+
const FINAL_VERIFICATION_HEADING_PATTERN = /^##[ \t]+Final Verification Wave(?:[ \t]+#+)?[ \t]*$/i;
|
|
12
|
+
const SECTION_BOUNDARY_HEADING_PATTERN = /^#{1,2}(?:[ \t]+|$)/;
|
|
13
|
+
const FENCE_PATTERN = /^[ \t]{0,3}(`{3,}|~{3,})(.*)$/;
|
|
14
|
+
const SIMPLE_CHECKBOX_PATTERN = /^[-*][ \t]*\[[ \t]*([xX]?)[ \t]*\][ \t]+(.+)$/;
|
|
15
|
+
const TODO_CHECKBOX_PATTERN = /^- \[([ xX])\] ([1-9]\d*\. .+)$/;
|
|
16
|
+
const FINAL_WAVE_CHECKBOX_PATTERN = /^- \[([ xX])\] (F[1-9]\d*\. .+)$/i;
|
|
17
|
+
|
|
18
|
+
type ChecklistSection = "todo" | "final-wave" | "other";
|
|
19
|
+
|
|
20
|
+
type ParsedCheckbox = {
|
|
21
|
+
readonly checked: boolean;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type MarkdownFence = {
|
|
26
|
+
readonly marker: "`" | "~";
|
|
27
|
+
readonly length: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function getPlanChecklist(planPath: string): PlanChecklist {
|
|
31
|
+
if (!existsSync(planPath)) return emptyChecklist();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
return parsePlanChecklist(readFileSync(planPath, "utf8"));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof Error) return emptyChecklist();
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parsePlanChecklist(markdown: string): PlanChecklist {
|
|
42
|
+
const lines = markdown.split(/\r?\n/);
|
|
43
|
+
if (!hasStructuredSection(lines)) return parseSimpleChecklist(lines);
|
|
44
|
+
|
|
45
|
+
let completed = 0;
|
|
46
|
+
let remaining = 0;
|
|
47
|
+
let nextTaskLabel: string | null = null;
|
|
48
|
+
let section: ChecklistSection = "other";
|
|
49
|
+
let fence: MarkdownFence | null = null;
|
|
50
|
+
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
if (fence !== null) {
|
|
53
|
+
if (isClosingFence(line, fence)) fence = null;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const openingFence = parseOpeningFence(line);
|
|
57
|
+
if (openingFence !== null) {
|
|
58
|
+
fence = openingFence;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (SECTION_BOUNDARY_HEADING_PATTERN.test(line)) {
|
|
63
|
+
section = parseStructuredSectionHeading(line);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (section === "other") continue;
|
|
67
|
+
|
|
68
|
+
const checkbox = parseStructuredCheckbox(line, section);
|
|
69
|
+
if (checkbox === null) continue;
|
|
70
|
+
if (checkbox.checked) completed += 1;
|
|
71
|
+
else {
|
|
72
|
+
remaining += 1;
|
|
73
|
+
nextTaskLabel = nextTaskLabel ?? checkbox.label;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { completed, remaining, total: completed + remaining, nextTaskLabel };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasStructuredSection(lines: readonly string[]): boolean {
|
|
81
|
+
let fence: MarkdownFence | null = null;
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (fence !== null) {
|
|
84
|
+
if (isClosingFence(line, fence)) fence = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const openingFence = parseOpeningFence(line);
|
|
88
|
+
if (openingFence !== null) {
|
|
89
|
+
fence = openingFence;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (parseStructuredSectionHeading(line) !== "other") return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseSimpleChecklist(lines: readonly string[]): PlanChecklist {
|
|
98
|
+
let completed = 0;
|
|
99
|
+
let remaining = 0;
|
|
100
|
+
let nextTaskLabel: string | null = null;
|
|
101
|
+
let fence: MarkdownFence | null = null;
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (fence !== null) {
|
|
105
|
+
if (isClosingFence(line, fence)) fence = null;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const openingFence = parseOpeningFence(line);
|
|
109
|
+
if (openingFence !== null) {
|
|
110
|
+
fence = openingFence;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const checkbox = parseSimpleTopLevelCheckbox(line);
|
|
115
|
+
if (checkbox === null) continue;
|
|
116
|
+
if (checkbox.checked) completed += 1;
|
|
117
|
+
else {
|
|
118
|
+
remaining += 1;
|
|
119
|
+
nextTaskLabel = nextTaskLabel ?? checkbox.label;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { completed, remaining, total: completed + remaining, nextTaskLabel };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseStructuredSectionHeading(line: string): ChecklistSection {
|
|
127
|
+
if (TODO_HEADING_PATTERN.test(line)) return "todo";
|
|
128
|
+
if (FINAL_VERIFICATION_HEADING_PATTERN.test(line)) return "final-wave";
|
|
129
|
+
return "other";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseStructuredCheckbox(line: string, section: "todo" | "final-wave"): ParsedCheckbox | null {
|
|
133
|
+
const pattern = section === "todo" ? TODO_CHECKBOX_PATTERN : FINAL_WAVE_CHECKBOX_PATTERN;
|
|
134
|
+
const match = line.match(pattern);
|
|
135
|
+
const marker = match?.[1];
|
|
136
|
+
const label = match?.[2];
|
|
137
|
+
if (marker === undefined || label === undefined) return null;
|
|
138
|
+
return { checked: marker.toLowerCase() === "x", label };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseSimpleTopLevelCheckbox(line: string): ParsedCheckbox | null {
|
|
142
|
+
const match = line.match(SIMPLE_CHECKBOX_PATTERN);
|
|
143
|
+
const marker = match?.[1];
|
|
144
|
+
const label = match?.[2];
|
|
145
|
+
if (marker === undefined || label === undefined) return null;
|
|
146
|
+
return { checked: marker.toLowerCase() === "x", label };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function parseOpeningFence(line: string): MarkdownFence | null {
|
|
150
|
+
const match = line.match(FENCE_PATTERN);
|
|
151
|
+
const run = match?.[1];
|
|
152
|
+
const info = match?.[2];
|
|
153
|
+
const marker = run?.charAt(0);
|
|
154
|
+
if (
|
|
155
|
+
run === undefined ||
|
|
156
|
+
info === undefined ||
|
|
157
|
+
(marker !== "`" && marker !== "~") ||
|
|
158
|
+
(marker === "`" && info.includes("`"))
|
|
159
|
+
)
|
|
160
|
+
return null;
|
|
161
|
+
return { marker, length: run.length };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isClosingFence(line: string, fence: MarkdownFence): boolean {
|
|
165
|
+
const run = line.match(/^[ \t]{0,3}(`{3,}|~{3,})[ \t]*$/)?.[1];
|
|
166
|
+
return run?.charAt(0) === fence.marker && run.length >= fence.length;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function emptyChecklist(): PlanChecklist {
|
|
170
|
+
return { completed: 0, remaining: 0, total: 0, nextTaskLabel: null };
|
|
171
|
+
}
|
package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
5
|
import { getPlanChecklist, readContinuationState } from "../src/boulder-reader.js";
|
|
6
6
|
|
|
7
7
|
const cleanupRoots: string[] = [];
|
|
8
|
+
const SCAFFOLD_PLAN_MARKDOWN = readFileSync(new URL("./fixtures/plan-scaffold.md", import.meta.url), "utf8");
|
|
8
9
|
|
|
9
10
|
afterEach(() => {
|
|
10
11
|
for (const root of cleanupRoots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
describe("start-work plan checklist consumption", () => {
|
|
14
|
-
it("#given
|
|
15
|
+
it("#given scaffold headings and canonical numbered rows #when parsed #then structured totals and next label match", () => {
|
|
15
16
|
// given
|
|
16
|
-
const planPath = createPlan(
|
|
17
|
-
["# Plan", "", "## TODOs", "- [ ] First", "- [x] Done", "- [X] Also done", "- [ ] Second"].join("\n"),
|
|
18
|
-
);
|
|
17
|
+
const planPath = createPlan(SCAFFOLD_PLAN_MARKDOWN);
|
|
19
18
|
|
|
20
19
|
// when
|
|
21
20
|
const checklist = getPlanChecklist(planPath);
|
|
22
21
|
|
|
23
22
|
// then
|
|
24
|
-
expect(checklist).toEqual({
|
|
23
|
+
expect(checklist).toEqual({
|
|
24
|
+
completed: 2,
|
|
25
|
+
remaining: 2,
|
|
26
|
+
total: 4,
|
|
27
|
+
nextTaskLabel: "1. Implement checklist parser parity",
|
|
28
|
+
});
|
|
25
29
|
});
|
|
26
30
|
|
|
27
31
|
it("#given nested checkboxes #when parsed #then ignores non-column-zero items", () => {
|
|
28
32
|
// given
|
|
29
33
|
const planPath = createPlan(
|
|
30
|
-
["## TODOs", "- [ ] Top-level", " - [ ] Nested", "\t- [ ] Tab nested", "- [x] Complete"].join("\n"),
|
|
34
|
+
["## TODOs", "- [ ] 1. Top-level", " - [ ] Nested", "\t- [ ] Tab nested", "- [x] 2. Complete"].join("\n"),
|
|
31
35
|
);
|
|
32
36
|
|
|
33
37
|
// when
|
|
34
38
|
const checklist = getPlanChecklist(planPath);
|
|
35
39
|
|
|
36
40
|
// then
|
|
37
|
-
expect(checklist).toEqual({ completed: 1, remaining: 1, total: 2, nextTaskLabel: "Top-level" });
|
|
41
|
+
expect(checklist).toEqual({ completed: 1, remaining: 1, total: 2, nextTaskLabel: "1. Top-level" });
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
it("#given checkboxes outside counted sections #when parsed #then ignores unrelated top-level tasks", () => {
|
|
@@ -44,12 +48,12 @@ describe("start-work plan checklist consumption", () => {
|
|
|
44
48
|
"# Plan",
|
|
45
49
|
"- [ ] Preamble task",
|
|
46
50
|
"## TODOs",
|
|
47
|
-
"- [ ] Build hook",
|
|
51
|
+
"- [ ] 1. Build hook",
|
|
48
52
|
"## Acceptance Criteria",
|
|
49
53
|
"- [ ] Acceptance item",
|
|
50
54
|
"## Final Verification Wave",
|
|
51
|
-
"- [x] Run tests",
|
|
52
|
-
"- [ ] Run smoke",
|
|
55
|
+
"- [x] F1. Run tests",
|
|
56
|
+
"- [ ] F2. Run smoke",
|
|
53
57
|
].join("\n"),
|
|
54
58
|
);
|
|
55
59
|
|
|
@@ -57,18 +61,184 @@ describe("start-work plan checklist consumption", () => {
|
|
|
57
61
|
const checklist = getPlanChecklist(planPath);
|
|
58
62
|
|
|
59
63
|
// then
|
|
60
|
-
expect(checklist).toEqual({ completed: 1, remaining: 2, total: 3, nextTaskLabel: "Build hook" });
|
|
64
|
+
expect(checklist).toEqual({ completed: 1, remaining: 2, total: 3, nextTaskLabel: "1. Build hook" });
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
it("#given all top-level tasks complete #when parsed #then next task is null", () => {
|
|
64
68
|
// given
|
|
65
|
-
const planPath = createPlan(
|
|
69
|
+
const planPath = createPlan(
|
|
70
|
+
["## Todos", "- [x] 1. First", "- [X] 2. Second", "## Final verification wave", "- [x] F1. Final"].join("\n"),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// when
|
|
74
|
+
const checklist = getPlanChecklist(planPath);
|
|
75
|
+
|
|
76
|
+
// then
|
|
77
|
+
expect(checklist).toEqual({ completed: 3, remaining: 0, total: 3, nextTaskLabel: null });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("#given no structured headings #when parsed #then legacy top-level checkbox fallback remains", () => {
|
|
81
|
+
// given
|
|
82
|
+
const planPath = createPlan(["# Plan", "- [ ] First", "- [x] Done", " - [ ] Nested"].join("\n"));
|
|
83
|
+
|
|
84
|
+
// when
|
|
85
|
+
const checklist = getPlanChecklist(planPath);
|
|
86
|
+
|
|
87
|
+
// then
|
|
88
|
+
expect(checklist).toEqual({ completed: 1, remaining: 1, total: 2, nextTaskLabel: "First" });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("#given a heading-free legacy star checklist #when parsed #then fallback behavior is preserved", () => {
|
|
92
|
+
// given
|
|
93
|
+
const planPath = createPlan(["# Plan", "* [ ] First", "* [x] Done", " * [ ] Nested"].join("\n"));
|
|
94
|
+
|
|
95
|
+
// when
|
|
96
|
+
const checklist = getPlanChecklist(planPath);
|
|
97
|
+
|
|
98
|
+
// then
|
|
99
|
+
expect(checklist).toEqual({ completed: 1, remaining: 1, total: 2, nextTaskLabel: "First" });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("#given noncanonical structured rows #when parsed #then only exact positive-number grammar is counted", () => {
|
|
103
|
+
// given
|
|
104
|
+
const planPath = createPlan(
|
|
105
|
+
[
|
|
106
|
+
"## Todos",
|
|
107
|
+
"- [ ] 0. Zero is invalid",
|
|
108
|
+
"- [ ] 01. Leading zero is invalid",
|
|
109
|
+
"* [ ] 2. Star marker is invalid",
|
|
110
|
+
"-[ ] 3. Missing spaces are invalid",
|
|
111
|
+
"- [ ] 4. Canonical implementation",
|
|
112
|
+
"## Final verification wave",
|
|
113
|
+
"- [ ] F0. Zero final verifier is invalid",
|
|
114
|
+
"- [ ] F01. Leading zero final verifier is invalid",
|
|
115
|
+
"- [x] F2. Canonical final verifier",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
);
|
|
66
118
|
|
|
67
119
|
// when
|
|
68
120
|
const checklist = getPlanChecklist(planPath);
|
|
69
121
|
|
|
70
122
|
// then
|
|
71
|
-
expect(checklist).toEqual({
|
|
123
|
+
expect(checklist).toEqual({
|
|
124
|
+
completed: 1,
|
|
125
|
+
remaining: 1,
|
|
126
|
+
total: 2,
|
|
127
|
+
nextTaskLabel: "4. Canonical implementation",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("#given fenced examples and a higher-level heading #when parsed #then section scope excludes them", () => {
|
|
132
|
+
// given
|
|
133
|
+
const planPath = createPlan(
|
|
134
|
+
[
|
|
135
|
+
"## Todos",
|
|
136
|
+
"- [ ] 1. Counted implementation",
|
|
137
|
+
"```md",
|
|
138
|
+
"- [ ] 2. Fenced example",
|
|
139
|
+
"```",
|
|
140
|
+
"# Appendix",
|
|
141
|
+
"- [ ] 3. Appendix checkbox",
|
|
142
|
+
"## Final verification wave",
|
|
143
|
+
"- [x] F1. Counted verifier",
|
|
144
|
+
].join("\n"),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// when
|
|
148
|
+
const checklist = getPlanChecklist(planPath);
|
|
149
|
+
|
|
150
|
+
// then
|
|
151
|
+
expect(checklist).toEqual({
|
|
152
|
+
completed: 1,
|
|
153
|
+
remaining: 1,
|
|
154
|
+
total: 2,
|
|
155
|
+
nextTaskLabel: "1. Counted implementation",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("#given a child heading inside TODOs #when parsed #then canonical rows remain in the parent section", () => {
|
|
160
|
+
// given
|
|
161
|
+
const planPath = createPlan(["## TODOs", "- [x] 1. First task", "### Notes", "- [ ] 2. Second task"].join("\n"));
|
|
162
|
+
|
|
163
|
+
// when
|
|
164
|
+
const checklist = getPlanChecklist(planPath);
|
|
165
|
+
|
|
166
|
+
// then
|
|
167
|
+
expect(checklist).toEqual({ completed: 1, remaining: 1, total: 2, nextTaskLabel: "2. Second task" });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("#given a four-backtick fence containing triple-backtick examples #when parsed #then shorter fences do not close it", () => {
|
|
171
|
+
// given
|
|
172
|
+
const planPath = createPlan(
|
|
173
|
+
[
|
|
174
|
+
"## Todos",
|
|
175
|
+
"- [x] 1. Counted implementation",
|
|
176
|
+
"````md",
|
|
177
|
+
"```ts",
|
|
178
|
+
"- [ ] 2. Fenced example",
|
|
179
|
+
"```",
|
|
180
|
+
"````",
|
|
181
|
+
"## Final verification wave",
|
|
182
|
+
"- [ ] F1. Counted verifier",
|
|
183
|
+
].join("\n"),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// when
|
|
187
|
+
const checklist = getPlanChecklist(planPath);
|
|
188
|
+
|
|
189
|
+
// then
|
|
190
|
+
expect(checklist).toEqual({
|
|
191
|
+
completed: 1,
|
|
192
|
+
remaining: 1,
|
|
193
|
+
total: 2,
|
|
194
|
+
nextTaskLabel: "F1. Counted verifier",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("#given structured headings with ATX closing markers #when parsed #then canonical tasks remain structured", () => {
|
|
199
|
+
// given
|
|
200
|
+
const planPath = createPlan(
|
|
201
|
+
["## TODOs ##", "- [ ] 1. Implement", "## Final Verification Wave ###", "- [ ] F1. Verify"].join("\n"),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// when
|
|
205
|
+
const checklist = getPlanChecklist(planPath);
|
|
206
|
+
|
|
207
|
+
// then
|
|
208
|
+
expect(checklist).toEqual({
|
|
209
|
+
completed: 0,
|
|
210
|
+
remaining: 2,
|
|
211
|
+
total: 2,
|
|
212
|
+
nextTaskLabel: "1. Implement",
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("#given inline backtick code before a canonical task #when parsed #then it does not open a fence", () => {
|
|
217
|
+
// given
|
|
218
|
+
const planPath = createPlan(["## TODOs", "```example```", "- [ ] 1. Implement"].join("\n"));
|
|
219
|
+
|
|
220
|
+
// when
|
|
221
|
+
const checklist = getPlanChecklist(planPath);
|
|
222
|
+
|
|
223
|
+
// then
|
|
224
|
+
expect(checklist.total).toBe(1);
|
|
225
|
+
expect(checklist.nextTaskLabel).toBe("1. Implement");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("#given a heading-free fenced checkbox example #when parsed #then legacy fallback ignores it", () => {
|
|
229
|
+
// given
|
|
230
|
+
const planPath = createPlan(["````md", "- [ ] 1. Example only", "````"].join("\n"));
|
|
231
|
+
|
|
232
|
+
// when
|
|
233
|
+
const checklist = getPlanChecklist(planPath);
|
|
234
|
+
|
|
235
|
+
// then
|
|
236
|
+
expect(checklist).toEqual({
|
|
237
|
+
completed: 0,
|
|
238
|
+
remaining: 0,
|
|
239
|
+
total: 0,
|
|
240
|
+
nextTaskLabel: null,
|
|
241
|
+
});
|
|
72
242
|
});
|
|
73
243
|
});
|
|
74
244
|
|
|
@@ -77,7 +247,7 @@ describe("start-work boulder state reader", () => {
|
|
|
77
247
|
// given
|
|
78
248
|
const workspace = createWorkspace({
|
|
79
249
|
boulderJson: createBoulderJson({ status: "active", sessionIds: ["codex:sess_abc"] }),
|
|
80
|
-
planMarkdown:
|
|
250
|
+
planMarkdown: SCAFFOLD_PLAN_MARKDOWN,
|
|
81
251
|
});
|
|
82
252
|
|
|
83
253
|
// when
|
|
@@ -90,7 +260,12 @@ describe("start-work boulder state reader", () => {
|
|
|
90
260
|
boulderPath: join(workspace, ".omo", "boulder.json"),
|
|
91
261
|
ledgerPath: join(workspace, ".omo", "start-work", "ledger.jsonl"),
|
|
92
262
|
worktreePath: null,
|
|
93
|
-
checklist: {
|
|
263
|
+
checklist: {
|
|
264
|
+
completed: 2,
|
|
265
|
+
remaining: 2,
|
|
266
|
+
total: 4,
|
|
267
|
+
nextTaskLabel: "1. Implement checklist parser parity",
|
|
268
|
+
},
|
|
94
269
|
});
|
|
95
270
|
});
|
|
96
271
|
|
|
@@ -98,7 +273,7 @@ describe("start-work boulder state reader", () => {
|
|
|
98
273
|
// given
|
|
99
274
|
const workspace = createWorkspace({
|
|
100
275
|
boulderJson: createBoulderJson({ status: "completed", sessionIds: ["codex:sess_abc"] }),
|
|
101
|
-
planMarkdown: "# Plan\n\n## TODOs\n- [ ] First\n",
|
|
276
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [ ] 1. First\n",
|
|
102
277
|
});
|
|
103
278
|
|
|
104
279
|
// when
|
|
@@ -112,7 +287,7 @@ describe("start-work boulder state reader", () => {
|
|
|
112
287
|
// given
|
|
113
288
|
const workspace = createWorkspace({
|
|
114
289
|
boulderJson: createBoulderJson({ status: "paused", sessionIds: ["codex:sess_abc"] }),
|
|
115
|
-
planMarkdown: "# Plan\n\n## TODOs\n- [ ] First\n",
|
|
290
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [ ] 1. First\n",
|
|
116
291
|
});
|
|
117
292
|
|
|
118
293
|
// when
|
|
@@ -120,14 +295,28 @@ describe("start-work boulder state reader", () => {
|
|
|
120
295
|
|
|
121
296
|
// then
|
|
122
297
|
expect(state?.planName).toBe("launch-plan");
|
|
123
|
-
expect(state?.checklist).toEqual({ completed: 0, remaining: 1, total: 1, nextTaskLabel: "First" });
|
|
298
|
+
expect(state?.checklist).toEqual({ completed: 0, remaining: 1, total: 1, nextTaskLabel: "1. First" });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("#given active codex work with no remaining checklist items #when state is read #then final gate continuation remains present", () => {
|
|
302
|
+
// given
|
|
303
|
+
const workspace = createWorkspace({
|
|
304
|
+
boulderJson: createBoulderJson({ status: "active", sessionIds: ["codex:sess_abc"] }),
|
|
305
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [x] 1. First\n",
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// when
|
|
309
|
+
const state = readContinuationState(workspace, "sess_abc");
|
|
310
|
+
|
|
311
|
+
// then
|
|
312
|
+
expect(state?.checklist).toEqual({ completed: 1, remaining: 0, total: 1, nextTaskLabel: null });
|
|
124
313
|
});
|
|
125
314
|
|
|
126
|
-
it("#given no
|
|
315
|
+
it("#given active codex work with no readable checklist #when state is read #then continuation remains absent", () => {
|
|
127
316
|
// given
|
|
128
317
|
const workspace = createWorkspace({
|
|
129
318
|
boulderJson: createBoulderJson({ status: "active", sessionIds: ["codex:sess_abc"] }),
|
|
130
|
-
planMarkdown: "# Plan\n\
|
|
319
|
+
planMarkdown: "# Plan\n\nNo checklist yet.\n",
|
|
131
320
|
});
|
|
132
321
|
|
|
133
322
|
// when
|
|
@@ -141,7 +330,7 @@ describe("start-work boulder state reader", () => {
|
|
|
141
330
|
// given
|
|
142
331
|
const workspace = createWorkspace({
|
|
143
332
|
boulderJson: "{",
|
|
144
|
-
planMarkdown: "# Plan\n\n## TODOs\n- [ ] First\n",
|
|
333
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [ ] 1. First\n",
|
|
145
334
|
});
|
|
146
335
|
|
|
147
336
|
// when
|
|
@@ -155,7 +344,7 @@ describe("start-work boulder state reader", () => {
|
|
|
155
344
|
// given
|
|
156
345
|
const workspace = createWorkspace({
|
|
157
346
|
boulderJson: createBoulderJson({ status: "active", sessionIds: ["sess_abc"] }),
|
|
158
|
-
planMarkdown: "# Plan\n\n## TODOs\n- [ ] First\n",
|
|
347
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [ ] 1. First\n",
|
|
159
348
|
});
|
|
160
349
|
|
|
161
350
|
// when
|
|
@@ -187,7 +376,7 @@ describe("start-work boulder state reader", () => {
|
|
|
187
376
|
started_at: "2026-06-12T00:00:00.000Z",
|
|
188
377
|
session_ids: ["codex:sess_abc"],
|
|
189
378
|
}),
|
|
190
|
-
planMarkdown: "# Plan\n\n## TODOs\n- [ ] First\n",
|
|
379
|
+
planMarkdown: "# Plan\n\n## TODOs\n- [ ] 1. First\n",
|
|
191
380
|
});
|
|
192
381
|
|
|
193
382
|
// when
|
|
@@ -6,6 +6,7 @@ import { execPath } from "node:process";
|
|
|
6
6
|
import { afterEach, describe, expect, it } from "vitest";
|
|
7
7
|
|
|
8
8
|
const cleanupRoots: string[] = [];
|
|
9
|
+
const SCAFFOLD_PLAN_MARKDOWN = readFileSync(new URL("./fixtures/plan-scaffold.md", import.meta.url), "utf8");
|
|
9
10
|
|
|
10
11
|
afterEach(() => {
|
|
11
12
|
for (const root of cleanupRoots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
@@ -25,8 +26,8 @@ describe("start-work continuation CLI", () => {
|
|
|
25
26
|
expect(result.status).toBe(0);
|
|
26
27
|
const output = parseStopHookOutput(result.stdout);
|
|
27
28
|
expect(output.decision).toBe("block");
|
|
28
|
-
expect(output.reason).toContain("Remaining top-level checkboxes: `2` of `
|
|
29
|
-
expect(output.reason).toContain("Next incomplete task: `
|
|
29
|
+
expect(output.reason).toContain("Remaining top-level checkboxes: `2` of `4`");
|
|
30
|
+
expect(output.reason).toContain("Next incomplete task: `1. Implement checklist parser parity`");
|
|
30
31
|
expect(output.reason).toContain("Your session id in boulder.json: `codex:s1`");
|
|
31
32
|
});
|
|
32
33
|
|
|
@@ -106,7 +107,7 @@ function createWorkspace(sessionIds: readonly string[]): string {
|
|
|
106
107
|
const root = mkdtempSync(join(tmpdir(), "codex-continuation-cli-"));
|
|
107
108
|
cleanupRoots.push(root);
|
|
108
109
|
mkdirSync(join(root, ".omo", "plans"), { recursive: true });
|
|
109
|
-
writeFileSync(join(root, ".omo", "plans", "plan.md"),
|
|
110
|
+
writeFileSync(join(root, ".omo", "plans", "plan.md"), SCAFFOLD_PLAN_MARKDOWN);
|
|
110
111
|
const work = {
|
|
111
112
|
work_id: "w1",
|
|
112
113
|
active_plan: ".omo/plans/plan.md",
|
package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
@@ -8,6 +8,7 @@ import type { ReadonlyFileSystem, StopInput } from "../src/types.js";
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_WORKSPACE = "/repo";
|
|
10
10
|
const cleanupRoots: string[] = [];
|
|
11
|
+
const SCAFFOLD_PLAN_MARKDOWN = readFileSync(new URL("./fixtures/plan-scaffold.md", import.meta.url), "utf8");
|
|
11
12
|
|
|
12
13
|
afterEach(() => {
|
|
13
14
|
for (const root of cleanupRoots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
@@ -49,7 +50,7 @@ describe("start-work Stop hook", () => {
|
|
|
49
50
|
status: "active",
|
|
50
51
|
worktreePath: "/tmp/worktree",
|
|
51
52
|
}),
|
|
52
|
-
planMarkdown:
|
|
53
|
+
planMarkdown: SCAFFOLD_PLAN_MARKDOWN,
|
|
53
54
|
});
|
|
54
55
|
const fs = createMemoryFs();
|
|
55
56
|
|
|
@@ -62,13 +63,33 @@ describe("start-work Stop hook", () => {
|
|
|
62
63
|
expect(parsed.reason).toContain("- Plan: `launch-plan`");
|
|
63
64
|
expect(parsed.reason).toContain(`- Plan file: \`${join(workspace, ".omo", "plans", "plan.md")}\``);
|
|
64
65
|
expect(parsed.reason).toContain(`- Boulder state: \`${join(workspace, ".omo", "boulder.json")}\``);
|
|
65
|
-
expect(parsed.reason).toContain("- Remaining top-level checkboxes: `2` of `
|
|
66
|
-
expect(parsed.reason).toContain("- Next incomplete task: `
|
|
66
|
+
expect(parsed.reason).toContain("- Remaining top-level checkboxes: `2` of `4`");
|
|
67
|
+
expect(parsed.reason).toContain("- Next incomplete task: `1. Implement checklist parser parity`");
|
|
67
68
|
expect(parsed.reason).toContain("- Worktree: `/tmp/worktree`");
|
|
68
69
|
expect(parsed.reason).toContain(`- Ledger: \`${join(workspace, ".omo", "start-work", "ledger.jsonl")}\``);
|
|
69
70
|
expect(parsed.reason).toContain("- Your session id in boulder.json: `codex:sess_abc`");
|
|
70
71
|
});
|
|
71
72
|
|
|
73
|
+
it("#given active codex work with zero remaining tasks #when hook runs #then blocks for the final gate", () => {
|
|
74
|
+
// given
|
|
75
|
+
const workspace = createWorkspace({
|
|
76
|
+
boulderJson: createBoulderJson({ sessionIds: ["codex:sess_abc"], status: "active" }),
|
|
77
|
+
planMarkdown: ["# Plan", "", "## TODOs", "- [x] 1. Done"].join("\n"),
|
|
78
|
+
});
|
|
79
|
+
const fs = createMemoryFs();
|
|
80
|
+
|
|
81
|
+
// when
|
|
82
|
+
const output = runStopHook(createStopInput(workspace), fs);
|
|
83
|
+
|
|
84
|
+
// then
|
|
85
|
+
const parsed = parseBlockOutput(output);
|
|
86
|
+
expect(parsed.decision).toBe("block");
|
|
87
|
+
expect(parsed.reason).toContain("- Remaining top-level checkboxes: `0` of `1`");
|
|
88
|
+
expect(parsed.reason).toContain("- Next incomplete task: `none (final gate pending)`");
|
|
89
|
+
expect(parsed.reason).toContain("When the remaining count is `0`, skip checkbox execution");
|
|
90
|
+
expect(parsed.reason).toContain("re-read the ledger record and verify the exact lane/SHA pair");
|
|
91
|
+
});
|
|
92
|
+
|
|
72
93
|
it("#given context-window pressure in transcript #when hook runs #then it does not inject continuation text", () => {
|
|
73
94
|
// given
|
|
74
95
|
const transcriptPath = "/repo/transcript.jsonl";
|
package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Scaffold Parser Parity
|
|
2
|
+
|
|
3
|
+
## Todos
|
|
4
|
+
- [ ] 1. Implement checklist parser parity
|
|
5
|
+
- [ ] Nested acceptance detail
|
|
6
|
+
- [x] 2. Preserve completed implementation rows
|
|
7
|
+
- [ ] Missing numeric prefix must be ignored
|
|
8
|
+
|
|
9
|
+
## Acceptance Criteria
|
|
10
|
+
- [ ] Outside tracked sections must be ignored
|
|
11
|
+
|
|
12
|
+
## Final verification wave
|
|
13
|
+
- [ ] F1. Exercise the Codex Stop surface
|
|
14
|
+
- [X] F2. Preserve completed final verification rows
|
|
15
|
+
- [ ] 3. Wrong final-wave label must be ignored
|