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 +21 -0
- package/README.md +78 -0
- package/bin/opencode-missions.js +29 -0
- package/dist/droid-missions/paths.d.ts +10 -0
- package/dist/droid-missions/prompts.d.ts +16 -0
- package/dist/droid-missions/runner.d.ts +28 -0
- package/dist/droid-missions/store.d.ts +53 -0
- package/dist/droid-missions/types.d.ts +158 -0
- package/dist/droid-missions/validation.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1414 -0
- package/opencode/AGENTS.md +25 -0
- package/opencode/agents/droid-mission-orchestrator.md +41 -0
- package/opencode/agents/droid-mission-scrutiny-validator.md +18 -0
- package/opencode/agents/droid-mission-user-testing-validator.md +18 -0
- package/opencode/agents/droid-mission-worker.md +28 -0
- package/opencode/commands/mission.md +10 -0
- package/opencode/skills/droid-mission-orchestrator/SKILL.md +19 -0
- package/package.json +55 -0
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>;
|
package/dist/index.d.ts
ADDED