opencode-missions 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jared Boynton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # opencode-missions
2
+
3
+ OpenCode plugin for project-local mission planning, delegated feature execution, structured worker handoffs, and milestone validation. It re-implements the mission lifecycle locally and does not require Droid CLI, Factory runtime files, or any external mission service.
4
+
5
+ ## Install
6
+
7
+ Add the npm plugin to `opencode.json`:
8
+
9
+ ```json
10
+ {
11
+ "$schema": "https://opencode.ai/config.json",
12
+ "plugin": ["opencode-missions"]
13
+ }
14
+ ```
15
+
16
+ OpenCode installs npm plugins automatically with Bun at startup.
17
+
18
+ Optional agent, command, and skill assets are included under `opencode/`. To install them into the current project:
19
+
20
+ ```bash
21
+ bunx opencode-missions install
22
+ ```
23
+
24
+ That copies:
25
+
26
+ - `opencode/agents/*` to `.opencode/agents/`
27
+ - `opencode/commands/*` to `.opencode/commands/`
28
+ - `opencode/skills/*` to `.opencode/skills/`
29
+
30
+ ## Usage
31
+
32
+ After enabling the plugin, use the mission tools directly or install the included `/mission` command and mission agents:
33
+
34
+ ```text
35
+ /mission Build and validate the requested feature through mission mode.
36
+ ```
37
+
38
+ The orchestrator creates a mission workspace, writes mission artifacts and feature plans, starts one worker per feature, receives structured handoffs, injects milestone validators, and refuses completion until implementation and validation work are complete.
39
+
40
+ Mission state is stored under:
41
+
42
+ ```text
43
+ .opencode/droid-missions/missions/
44
+ ```
45
+
46
+ The storage path intentionally remains stable for compatibility with the recovered mission lifecycle.
47
+
48
+ ## Tools
49
+
50
+ - `droid_mission_create`
51
+ - `droid_mission_write_artifact`
52
+ - `droid_mission_write_features`
53
+ - `droid_mission_start`
54
+ - `droid_mission_end_feature`
55
+ - `droid_mission_pause`
56
+ - `droid_mission_status`
57
+ - `droid_mission_complete`
58
+ - `droid_mission_dismiss_handoff_items`
59
+ - `droid_mission_list`
60
+
61
+ ## Development
62
+
63
+ ```bash
64
+ bun install
65
+ bun run test
66
+ bun run typecheck
67
+ bun run build
68
+ bun run check:no-droid
69
+ bun run ci
70
+ ```
71
+
72
+ ## Release
73
+
74
+ ```bash
75
+ npm version patch
76
+ git push --follow-tags
77
+ npm publish --access public
78
+ ```
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { cp, mkdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const command = process.argv[2];
7
+
8
+ if (command !== "install") {
9
+ console.log("Usage: opencode-missions install [project-directory]");
10
+ process.exit(command ? 1 : 0);
11
+ }
12
+
13
+ const packageRoot = path.resolve(
14
+ path.dirname(fileURLToPath(import.meta.url)),
15
+ "..",
16
+ );
17
+ const projectRoot = path.resolve(process.argv[3] ?? process.cwd());
18
+ const sourceRoot = path.join(packageRoot, "opencode");
19
+ const targetRoot = path.join(projectRoot, ".opencode");
20
+
21
+ for (const dir of ["agents", "commands", "skills"]) {
22
+ await mkdir(path.join(targetRoot, dir), { recursive: true });
23
+ await cp(path.join(sourceRoot, dir), path.join(targetRoot, dir), {
24
+ recursive: true,
25
+ force: true,
26
+ });
27
+ }
28
+
29
+ console.log(`Installed opencode-missions assets into ${targetRoot}`);
@@ -0,0 +1,10 @@
1
+ export declare const STORAGE_DIR: string;
2
+ export declare const MISSIONS_DIR = "missions";
3
+ export declare function missionStorageRoot(projectRoot: string): string;
4
+ export declare function missionsRoot(projectRoot: string): string;
5
+ export declare function sanitizeMissionId(input: string): string;
6
+ export declare function createMissionId(title: string, now: string): string;
7
+ export declare function missionDir(projectRoot: string, missionId: string): string;
8
+ export declare function toMissionRelativePath(relativePath: string): string;
9
+ export declare function assertInside(parent: string, child: string): void;
10
+ export declare function isMissionSystemFile(relativePath: string): boolean;
@@ -0,0 +1,16 @@
1
+ import type { MissionFeature, MissionState } from "./types";
2
+ export declare function buildWorkerPrompt(input: {
3
+ state: MissionState;
4
+ feature: MissionFeature;
5
+ missionDir: string;
6
+ message?: string;
7
+ }): string;
8
+ export declare function buildCompactionContext(input: {
9
+ state: MissionState;
10
+ features: MissionFeature[];
11
+ }): string;
12
+ export declare function buildResumePrompt(input: {
13
+ state: MissionState;
14
+ feature: MissionFeature;
15
+ missionDir: string;
16
+ }): string;
@@ -0,0 +1,28 @@
1
+ import { MissionStore } from "./store";
2
+ import type { EndFeatureRunArgs, MissionFeature, MissionState, StartMissionArgs, StartMissionResult } from "./types";
3
+ type OpenCodeClientLike = {
4
+ session?: {
5
+ create?: (options: any) => Promise<unknown>;
6
+ prompt?: (options: any) => Promise<unknown>;
7
+ abort?: (options: any) => Promise<unknown>;
8
+ };
9
+ };
10
+ export declare class MissionRunner {
11
+ private readonly store;
12
+ private readonly client;
13
+ constructor(store: MissionStore, client: OpenCodeClientLike);
14
+ startMission(args: StartMissionArgs): Promise<StartMissionResult>;
15
+ endFeatureRun(args: EndFeatureRunArgs): Promise<{
16
+ feature: MissionFeature;
17
+ validationInserted: string[];
18
+ }>;
19
+ pauseMission(missionId: string, reason?: string, now?: string): Promise<MissionState>;
20
+ private resumeWorker;
21
+ completeMission(missionId: string, now?: string): Promise<MissionState>;
22
+ private injectMilestoneValidators;
23
+ private injectEligibleMilestoneValidators;
24
+ private injectValidatorsForMilestone;
25
+ private getUnresolvedHandoffItems;
26
+ private getValidationStateBlockers;
27
+ }
28
+ export {};
@@ -0,0 +1,53 @@
1
+ import type { FeatureHandoff, MissionCreateArgs, MissionFeature, MissionSnapshot, MissionState, ProgressEntry, StoredHandoff } from "./types";
2
+ export declare class MissionStore {
3
+ readonly projectRoot: string;
4
+ constructor(projectRoot: string);
5
+ storageRoot(): string;
6
+ missionsRoot(): string;
7
+ missionDir(missionId: string): string;
8
+ createMission(args: MissionCreateArgs): Promise<{
9
+ dir: string;
10
+ state: MissionState;
11
+ features: MissionFeature[];
12
+ }>;
13
+ removeMission(missionId: string): Promise<void>;
14
+ listMissions(): Promise<MissionState[]>;
15
+ readState(missionId: string): Promise<MissionState>;
16
+ writeState(state: MissionState): Promise<void>;
17
+ readFeatures(missionId: string): Promise<MissionFeature[]>;
18
+ writeFeatureRecords(missionId: string, features: MissionFeature[]): Promise<void>;
19
+ writeFeatures(missionId: string, inputs: unknown[]): Promise<MissionFeature[]>;
20
+ listMissionSkills(missionId: string): Promise<Set<string>>;
21
+ writeArtifact(missionId: string, args: {
22
+ relativePath: string;
23
+ content: string;
24
+ }): Promise<string>;
25
+ readProgress(missionId: string): Promise<ProgressEntry[]>;
26
+ appendProgress(missionId: string, entry: ProgressEntry): Promise<void>;
27
+ readHandoffs(missionId: string): Promise<StoredHandoff[]>;
28
+ writeHandoffs(missionId: string, handoffs: StoredHandoff[]): Promise<void>;
29
+ appendHandoff(missionId: string, args: {
30
+ featureId: string;
31
+ workerSessionId?: string;
32
+ successState: StoredHandoff["successState"];
33
+ returnToOrchestrator: boolean;
34
+ validatorsPassed?: boolean;
35
+ commitId?: string;
36
+ repoPath?: string;
37
+ handoff: FeatureHandoff;
38
+ now?: string;
39
+ }): Promise<StoredHandoff>;
40
+ appendTranscriptSkeleton(missionId: string, args: {
41
+ workerSessionId?: string;
42
+ featureId: string;
43
+ milestone?: string;
44
+ skeleton: string;
45
+ createdAt?: string;
46
+ }): Promise<void>;
47
+ readValidationState(missionId: string): Promise<unknown>;
48
+ dismissHandoffItems(missionId: string, dismissals: Array<{
49
+ id: string;
50
+ reason: string;
51
+ }>, now?: string): Promise<StoredHandoff[]>;
52
+ snapshot(missionId: string): Promise<MissionSnapshot>;
53
+ }
@@ -0,0 +1,158 @@
1
+ export type MissionStatus = "awaiting_input" | "initializing" | "running" | "paused" | "orchestrator_turn" | "completed";
2
+ export type FeatureStatus = "pending" | "in_progress" | "completed" | "cancelled";
3
+ export type SuccessState = "success" | "partial" | "failure";
4
+ export type ValidationKind = "scrutiny" | "user-testing";
5
+ export type MissionState = {
6
+ missionId: string;
7
+ title: string;
8
+ goal: string;
9
+ status: MissionStatus;
10
+ workingDirectory: string;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ completedAt?: string;
14
+ activeFeatureId?: string;
15
+ activeWorkerSessionId?: string;
16
+ pauseReason?: string;
17
+ lastMessage?: string;
18
+ };
19
+ export type FeaturePlanInput = {
20
+ id: string;
21
+ description: string;
22
+ skillName: string;
23
+ milestone: string;
24
+ preconditions?: string[];
25
+ expectedBehavior: string;
26
+ verificationSteps?: string[];
27
+ fulfills?: string[];
28
+ };
29
+ export type MissionFeature = FeaturePlanInput & {
30
+ preconditions: string[];
31
+ verificationSteps: string[];
32
+ fulfills: string[];
33
+ status: FeatureStatus;
34
+ workerSessionId?: string;
35
+ startedAt?: string;
36
+ completedAt?: string;
37
+ isValidation?: boolean;
38
+ validatesMilestone?: string;
39
+ validationKind?: ValidationKind;
40
+ };
41
+ export type VerificationCommand = {
42
+ command: string;
43
+ exitCode: number;
44
+ observation: string;
45
+ };
46
+ export type InteractiveCheck = {
47
+ action: string;
48
+ observed: string;
49
+ };
50
+ export type HandoffVerification = {
51
+ commandsRun: VerificationCommand[];
52
+ interactiveChecks?: InteractiveCheck[];
53
+ };
54
+ export type TestCase = {
55
+ name: string;
56
+ verifies: string;
57
+ };
58
+ export type TestFile = {
59
+ file: string;
60
+ cases: TestCase[];
61
+ };
62
+ export type HandoffTests = {
63
+ added: TestFile[];
64
+ updated?: string[];
65
+ coverage: string;
66
+ };
67
+ export type DiscoveredIssue = {
68
+ severity: "blocking" | "non_blocking" | "suggestion";
69
+ description: string;
70
+ suggestedFix?: string;
71
+ };
72
+ export type SkillDeviation = {
73
+ step: string;
74
+ whatIDidInstead: string;
75
+ why: string;
76
+ };
77
+ export type SkillFeedback = {
78
+ followedProcedure: boolean;
79
+ deviations: SkillDeviation[];
80
+ suggestedChanges?: string[];
81
+ };
82
+ export type FeatureHandoff = {
83
+ salientSummary: string;
84
+ whatWasImplemented: string;
85
+ whatWasLeftUndone: string;
86
+ verification: HandoffVerification;
87
+ tests: HandoffTests;
88
+ discoveredIssues: DiscoveredIssue[];
89
+ skillFeedback?: SkillFeedback;
90
+ };
91
+ export type StoredHandoff = FeatureHandoff & {
92
+ id: string;
93
+ missionId: string;
94
+ featureId: string;
95
+ workerSessionId?: string;
96
+ successState: SuccessState;
97
+ returnToOrchestrator: boolean;
98
+ validatorsPassed?: boolean;
99
+ commitId?: string;
100
+ repoPath?: string;
101
+ handoffFile: string;
102
+ createdAt: string;
103
+ dismissedAt?: string;
104
+ dismissalReason?: string;
105
+ };
106
+ export type ProgressEntry = {
107
+ type: "mission_accepted" | "mission_paused" | "mission_resumed" | "mission_run_started" | "worker_started" | "worker_selected_feature" | "worker_completed" | "worker_failed" | "worker_paused" | "handoff_items_dismissed" | "milestone_validation_triggered" | "mission_completed" | "features_written" | "artifact_written";
108
+ at: string;
109
+ missionId: string;
110
+ featureId?: string;
111
+ workerSessionId?: string;
112
+ summary?: string;
113
+ details?: Record<string, unknown>;
114
+ };
115
+ export type MissionSnapshot = {
116
+ dir: string;
117
+ state: MissionState;
118
+ features: MissionFeature[];
119
+ handoffs: StoredHandoff[];
120
+ progress: ProgressEntry[];
121
+ };
122
+ export type MissionCreateArgs = {
123
+ missionId?: string;
124
+ title: string;
125
+ goal: string;
126
+ workingDirectory?: string;
127
+ now?: string;
128
+ };
129
+ export type StartMissionArgs = {
130
+ missionId: string;
131
+ parentSessionId?: string;
132
+ message?: string;
133
+ resumeWorkerSessionId?: string;
134
+ restartFeature?: boolean;
135
+ now?: string;
136
+ };
137
+ export type StartMissionResult = {
138
+ started: boolean;
139
+ completed: boolean;
140
+ missionId: string;
141
+ featureId?: string;
142
+ workerSessionId?: string;
143
+ blockedReason?: string;
144
+ systemMessage: string;
145
+ };
146
+ export type EndFeatureRunArgs = {
147
+ missionId: string;
148
+ featureId: string;
149
+ workerSessionId?: string;
150
+ successState: SuccessState;
151
+ returnToOrchestrator?: boolean;
152
+ validatorsPassed?: boolean;
153
+ codeChanged?: boolean;
154
+ commitId?: string;
155
+ repoPath?: string;
156
+ handoff: FeatureHandoff;
157
+ now?: string;
158
+ };
@@ -0,0 +1,4 @@
1
+ import type { MissionFeature } from "./types";
2
+ export declare function validateFeaturePlans(inputs: unknown[], skillNames: Set<string>): MissionFeature[];
3
+ export declare function validateArtifactPath(relativePath: string): string;
4
+ export declare function missionSkillExists(missionDir: string, skillName: string): Promise<boolean>;
@@ -0,0 +1,3 @@
1
+ import { type Plugin } from "@opencode-ai/plugin";
2
+ export declare const DroidMissionsPlugin: Plugin;
3
+ export default DroidMissionsPlugin;