@caseyharalson/orrery 0.7.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/.devcontainer.example/Dockerfile +149 -0
- package/.devcontainer.example/devcontainer.json +61 -0
- package/.devcontainer.example/init-firewall.sh +175 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/agent/skills/discovery/SKILL.md +428 -0
- package/agent/skills/discovery/schemas/plan-schema.yaml +138 -0
- package/agent/skills/orrery-execute/SKILL.md +107 -0
- package/agent/skills/orrery-report/SKILL.md +119 -0
- package/agent/skills/orrery-review/SKILL.md +105 -0
- package/agent/skills/orrery-verify/SKILL.md +105 -0
- package/agent/skills/refine-plan/SKILL.md +291 -0
- package/agent/skills/simulate-plan/SKILL.md +244 -0
- package/bin/orrery.js +5 -0
- package/lib/cli/commands/help.js +21 -0
- package/lib/cli/commands/ingest-plan.js +56 -0
- package/lib/cli/commands/init.js +21 -0
- package/lib/cli/commands/install-devcontainer.js +97 -0
- package/lib/cli/commands/install-skills.js +182 -0
- package/lib/cli/commands/orchestrate.js +27 -0
- package/lib/cli/commands/resume.js +146 -0
- package/lib/cli/commands/status.js +137 -0
- package/lib/cli/commands/validate-plan.js +288 -0
- package/lib/cli/index.js +57 -0
- package/lib/orchestration/agent-invoker.js +595 -0
- package/lib/orchestration/condensed-plan.js +128 -0
- package/lib/orchestration/config.js +213 -0
- package/lib/orchestration/dependency-resolver.js +149 -0
- package/lib/orchestration/edit-invoker.js +115 -0
- package/lib/orchestration/index.js +1065 -0
- package/lib/orchestration/plan-loader.js +212 -0
- package/lib/orchestration/progress-tracker.js +208 -0
- package/lib/orchestration/report-format.js +80 -0
- package/lib/orchestration/review-invoker.js +305 -0
- package/lib/utils/agent-detector.js +47 -0
- package/lib/utils/git.js +297 -0
- package/lib/utils/paths.js +43 -0
- package/lib/utils/plan-detect.js +24 -0
- package/lib/utils/skill-copier.js +79 -0
- package/package.json +58 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: simulate-plan
|
|
3
|
+
description: >
|
|
4
|
+
Explore a plan through conversational dialogue before committing to execution.
|
|
5
|
+
Requires a plan file argument (e.g., /simulate-plan .agent-work/plans/my-plan.yaml, /simulate-plan my-plan).
|
|
6
|
+
Ask "what if" questions, trace dependencies, and build intuition about what you're building.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Simulate Skill
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
Use this skill when you have a plan and want to **think through it** before executing. Simulate is a thinking partner that helps you interrogate the plan, explore implications, and build confidence in your approach.
|
|
14
|
+
|
|
15
|
+
**Triggers:**
|
|
16
|
+
|
|
17
|
+
- "Let's think through this plan before we start"
|
|
18
|
+
- "What if..." questions about the plan
|
|
19
|
+
- Uncertainty about whether the plan is right
|
|
20
|
+
- Want to understand implications before committing
|
|
21
|
+
- "Walk me through this plan"
|
|
22
|
+
|
|
23
|
+
**Skip if:**
|
|
24
|
+
|
|
25
|
+
- No plan exists yet (use Discovery first)
|
|
26
|
+
- Ready to execute and confident in the approach
|
|
27
|
+
- Need to actually modify the plan (use Discovery skill instead)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## How to Do It
|
|
32
|
+
|
|
33
|
+
### Step 1: Load the Plan
|
|
34
|
+
|
|
35
|
+
Read and internalize the plan structure:
|
|
36
|
+
|
|
37
|
+
- Parse the YAML to understand steps, dependencies, criteria
|
|
38
|
+
- Note the outcomes (what success looks like)
|
|
39
|
+
- Identify risks and constraints mentioned in `risk_notes`
|
|
40
|
+
- Map the dependency graph (what blocks what)
|
|
41
|
+
|
|
42
|
+
Announce what you've loaded:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
I've loaded the [plan name]. It has [N] steps delivering [outcomes].
|
|
46
|
+
The critical path runs through [key steps]. What would you like to explore?
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Step 2: Enter Dialogue Mode
|
|
50
|
+
|
|
51
|
+
Answer questions conversationally. Don't wait for specific commands—respond to natural questions about the plan.
|
|
52
|
+
|
|
53
|
+
For each question:
|
|
54
|
+
|
|
55
|
+
1. Identify which part of the plan is relevant
|
|
56
|
+
2. Trace implications through the dependency graph
|
|
57
|
+
3. Reference specific steps, criteria, or requirements
|
|
58
|
+
4. Suggest follow-up questions if appropriate
|
|
59
|
+
|
|
60
|
+
### Step 3: Stay Read-Only
|
|
61
|
+
|
|
62
|
+
Never modify the plan file. If the user wants to make changes based on the simulation:
|
|
63
|
+
|
|
64
|
+
- Summarize the proposed changes
|
|
65
|
+
- Suggest exiting simulate and using the Discovery skill to revise
|
|
66
|
+
- Offer to continue exploring other aspects first
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Question Types
|
|
71
|
+
|
|
72
|
+
Guide users toward productive exploration. Handle these question patterns:
|
|
73
|
+
|
|
74
|
+
### Dependency Questions
|
|
75
|
+
|
|
76
|
+
- "What depends on step X?"
|
|
77
|
+
- "What's blocking the frontend work?"
|
|
78
|
+
- "If step 3 slips, what else is affected?"
|
|
79
|
+
- "Can we do A and B in parallel?"
|
|
80
|
+
|
|
81
|
+
**How to answer:** Trace the `deps` field. Show upstream (what this depends on) and downstream (what depends on this) relationships.
|
|
82
|
+
|
|
83
|
+
### Scope Questions
|
|
84
|
+
|
|
85
|
+
- "Do we really need all of this for an MVP?"
|
|
86
|
+
- "Which steps deliver the core value?"
|
|
87
|
+
- "What could we cut without breaking the outcomes?"
|
|
88
|
+
- "What's the simplest version that works?"
|
|
89
|
+
|
|
90
|
+
**How to answer:** Connect steps back to outcomes. Identify which steps are essential vs. nice-to-have based on the criteria and requirements.
|
|
91
|
+
|
|
92
|
+
### Risk Questions
|
|
93
|
+
|
|
94
|
+
- "What's the riskiest step?"
|
|
95
|
+
- "What happens if the API integration takes longer than expected?"
|
|
96
|
+
- "Where are we most likely to get stuck?"
|
|
97
|
+
- "What could go wrong?"
|
|
98
|
+
|
|
99
|
+
**How to answer:** Reference `risk_notes` from the plan. Consider steps with many dependencies (high blast radius) or external integrations (uncertainty).
|
|
100
|
+
|
|
101
|
+
### Alternative Questions
|
|
102
|
+
|
|
103
|
+
- "Could we do this a different way?"
|
|
104
|
+
- "What if we used X instead of Y?"
|
|
105
|
+
- "Is there a simpler path?"
|
|
106
|
+
- "What would change if we...?"
|
|
107
|
+
|
|
108
|
+
**How to answer:** Reason through how the alternative would affect other steps. Note which requirements or criteria would need to change.
|
|
109
|
+
|
|
110
|
+
### Validation Questions
|
|
111
|
+
|
|
112
|
+
- "Does this plan actually deliver what we said we wanted?"
|
|
113
|
+
- "Are there gaps between the outcomes and the steps?"
|
|
114
|
+
- "Have we forgotten anything?"
|
|
115
|
+
- "Is this plan complete?"
|
|
116
|
+
|
|
117
|
+
**How to answer:** Map steps to outcomes. Check if every outcome has steps that deliver it. Look for implicit requirements that aren't captured.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Response Style
|
|
122
|
+
|
|
123
|
+
### Be Conversational
|
|
124
|
+
|
|
125
|
+
Don't produce formal reports. Think out loud with the user.
|
|
126
|
+
|
|
127
|
+
**Bad:** "Analysis complete. The dependency graph shows..."
|
|
128
|
+
**Good:** "Let me trace that through. Step 3 needs step 2's output, so if you skip 2..."
|
|
129
|
+
|
|
130
|
+
### Trace Implications
|
|
131
|
+
|
|
132
|
+
Walk through cause and effect chains.
|
|
133
|
+
|
|
134
|
+
**Example:**
|
|
135
|
+
|
|
136
|
+
> "If you skip the caching step, the trends endpoint will still work, but you won't meet the 200ms response time criterion. That affects the 'responsive dashboard' requirement in step 4. The risk note mentions 'API response time under load'—without caching, that risk is higher."
|
|
137
|
+
|
|
138
|
+
### Ask Follow-Up Questions
|
|
139
|
+
|
|
140
|
+
Understand the user's underlying concern.
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
|
|
144
|
+
> "Are you asking because you're worried about scope, or because you're not sure caching is the right approach?"
|
|
145
|
+
|
|
146
|
+
### Reference the Plan
|
|
147
|
+
|
|
148
|
+
Quote specific steps, criteria, and requirements. Don't make up constraints.
|
|
149
|
+
|
|
150
|
+
**Example:**
|
|
151
|
+
|
|
152
|
+
> "Step 2 specifies 'Cache responses for 1 hour' in requirement 3. That's what we'd be cutting."
|
|
153
|
+
|
|
154
|
+
### Stay Grounded
|
|
155
|
+
|
|
156
|
+
Only discuss what's in the plan. If asked about implementation details not covered, acknowledge the gap.
|
|
157
|
+
|
|
158
|
+
**Example:**
|
|
159
|
+
|
|
160
|
+
> "The plan doesn't specify which caching library to use—that's an implementation detail for the Execute phase."
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Example Dialogue
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
User: /simulate .agent-work/plans/analytics-dashboard.yaml
|
|
168
|
+
|
|
169
|
+
Agent: I've loaded the analytics dashboard plan. It has 6 steps delivering
|
|
170
|
+
two outcomes: "Users can see usage trends" and "Admins can export reports."
|
|
171
|
+
|
|
172
|
+
The critical path is: API endpoint (step 2) → Caching (step 3) → Visualization (step 1).
|
|
173
|
+
Export functionality (steps 5-6) can run in parallel once the API is ready.
|
|
174
|
+
|
|
175
|
+
What would you like to explore?
|
|
176
|
+
|
|
177
|
+
User: What if we skip the caching step?
|
|
178
|
+
|
|
179
|
+
Agent: Step 3 (Query result caching) is a dependency for step 1 (Trend
|
|
180
|
+
visualization). If you skip caching:
|
|
181
|
+
|
|
182
|
+
- The trends endpoint will work, but may be slow (no 200ms target)
|
|
183
|
+
- Step 1's "responsive dashboard" criterion becomes harder to meet
|
|
184
|
+
- The risk note mentions "API response time under load"—without caching,
|
|
185
|
+
this risk materializes
|
|
186
|
+
|
|
187
|
+
The core functionality would work, but you'd be shipping a slower experience.
|
|
188
|
+
Is performance a hard requirement, or could caching be a fast-follow?
|
|
189
|
+
|
|
190
|
+
User: Could we do a simpler version of caching?
|
|
191
|
+
|
|
192
|
+
Agent: The plan currently specifies "Cache responses for 1 hour" in
|
|
193
|
+
requirement 3 of step 3. Some simpler approaches:
|
|
194
|
+
|
|
195
|
+
- In-memory cache (no Redis)—simpler but lost on restart
|
|
196
|
+
- Shorter TTL (5 min)—less stale data, more cache misses
|
|
197
|
+
- Cache only the expensive aggregations, not full responses
|
|
198
|
+
|
|
199
|
+
None of these are in the current plan. If you want to explore a variant,
|
|
200
|
+
we should exit simulate and revise the plan. Want to keep exploring first,
|
|
201
|
+
or make changes now?
|
|
202
|
+
|
|
203
|
+
User: Let's keep exploring. What's the riskiest step?
|
|
204
|
+
|
|
205
|
+
Agent: Looking at risk_notes and dependencies, I'd say step 2 (Create trends
|
|
206
|
+
API endpoint) is the riskiest:
|
|
207
|
+
|
|
208
|
+
1. It has the most downstream dependencies—steps 1, 3, 5, and 6 all need it
|
|
209
|
+
2. The risk note mentions "complex date aggregation logic"
|
|
210
|
+
3. If the data model doesn't support efficient range queries, you'll discover
|
|
211
|
+
it here
|
|
212
|
+
|
|
213
|
+
Step 3 (caching) is the second riskiest because it affects performance
|
|
214
|
+
criteria across multiple steps.
|
|
215
|
+
|
|
216
|
+
Want me to trace what happens if step 2 takes longer than expected?
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Boundaries
|
|
222
|
+
|
|
223
|
+
### Read-Only
|
|
224
|
+
|
|
225
|
+
Simulate never modifies the plan. The plan file is treated as immutable during simulation. If changes are needed, the user should exit and use the Discovery skill.
|
|
226
|
+
|
|
227
|
+
### Plan-Scoped
|
|
228
|
+
|
|
229
|
+
Answer based on plan content, not general coding advice. If asked "how should I implement caching?", redirect: "That's an Execute phase decision. In this plan, the caching step specifies [requirements]. Do you want to explore whether those requirements are right?"
|
|
230
|
+
|
|
231
|
+
### Suggests But Doesn't Decide
|
|
232
|
+
|
|
233
|
+
Present options and implications, but let the user decide what to do. Don't say "you should cut this step"—instead say "if you cut this step, here's what changes."
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Common Pitfalls
|
|
238
|
+
|
|
239
|
+
- **Inventing constraints:** Only discuss what's in the plan. Don't assume requirements or risks that aren't documented.
|
|
240
|
+
- **Formal analysis mode:** Keep it conversational. Users want to think out loud, not receive a report.
|
|
241
|
+
- **Skipping the dependency trace:** When asked "what if we skip X?", always trace through what depends on X. Don't just say "it would be faster."
|
|
242
|
+
- **Forgetting to reference the plan:** Quote specific steps and criteria. Vague answers aren't helpful.
|
|
243
|
+
- **Trying to modify the plan:** If changes are needed, explicitly suggest exiting simulate mode. Don't try to "fix" the plan inline.
|
|
244
|
+
- **No follow-up questions:** Understand what the user is really asking. A question about scope might really be about risk, or vice versa.
|
package/bin/orrery.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = function registerHelpCommand(program) {
|
|
2
|
+
program
|
|
3
|
+
.command("help [command]")
|
|
4
|
+
.description("Display help for a command")
|
|
5
|
+
.action((command) => {
|
|
6
|
+
if (!command) {
|
|
7
|
+
program.outputHelp();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const subcommand = program.commands.find((cmd) => cmd.name() === command);
|
|
12
|
+
if (!subcommand) {
|
|
13
|
+
console.error(`Unknown command: ${command}`);
|
|
14
|
+
program.outputHelp();
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
subcommand.outputHelp();
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const { getPlansDir } = require("../../utils/paths");
|
|
5
|
+
const { validatePlanStructure } = require("./validate-plan");
|
|
6
|
+
|
|
7
|
+
module.exports = function registerIngestPlanCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("ingest-plan")
|
|
10
|
+
.description("Validate and import a plan file into the plans directory")
|
|
11
|
+
.argument("<file>", "Path to the plan file to ingest")
|
|
12
|
+
.option("--force", "Overwrite existing plan file if it exists")
|
|
13
|
+
.action((file, options) => {
|
|
14
|
+
const sourcePath = path.resolve(file);
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(sourcePath)) {
|
|
17
|
+
console.error(`File not found: ${file}`);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { errors, warnings } = validatePlanStructure(sourcePath);
|
|
23
|
+
|
|
24
|
+
if (errors.length > 0) {
|
|
25
|
+
console.error("Validation errors:\n");
|
|
26
|
+
errors.forEach((err) => console.error(` - ${err}`));
|
|
27
|
+
console.error();
|
|
28
|
+
process.exitCode = 2;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (warnings.length > 0) {
|
|
33
|
+
console.warn("Warnings:\n");
|
|
34
|
+
warnings.forEach((warn) => console.warn(` - ${warn}`));
|
|
35
|
+
console.warn();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const plansDir = getPlansDir();
|
|
39
|
+
if (!fs.existsSync(plansDir)) {
|
|
40
|
+
fs.mkdirSync(plansDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const fileName = path.basename(sourcePath);
|
|
44
|
+
const destPath = path.join(plansDir, fileName);
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(destPath) && !options.force) {
|
|
47
|
+
console.error(`Plan already exists: ${destPath}`);
|
|
48
|
+
console.error("Use --force to overwrite");
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
54
|
+
console.log(`Plan ingested: ${destPath}`);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { installSkillsAction } = require("./install-skills");
|
|
2
|
+
|
|
3
|
+
function initAction(options) {
|
|
4
|
+
console.log("Initializing Orrery...\n");
|
|
5
|
+
|
|
6
|
+
// Install skills to detected agents
|
|
7
|
+
installSkillsAction(options);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = function registerInitCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command("init")
|
|
13
|
+
.description("Initialize Orrery: install skills to detected agents")
|
|
14
|
+
.option(
|
|
15
|
+
"--agent <agent>",
|
|
16
|
+
"Target agent (claude|codex|gemini|all); defaults to auto-detect"
|
|
17
|
+
)
|
|
18
|
+
.option("--force", "Overwrite existing skills")
|
|
19
|
+
.option("--dry-run", "Show what would be copied without writing files")
|
|
20
|
+
.action(initAction);
|
|
21
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
function getProjectRoot() {
|
|
5
|
+
return path.join(__dirname, "..", "..", "..");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getSourceDevcontainerDir() {
|
|
9
|
+
return path.join(getProjectRoot(), ".devcontainer.example");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function copyDirectory(src, dest, options = {}) {
|
|
13
|
+
const { dryRun = false } = options;
|
|
14
|
+
const copiedFiles = [];
|
|
15
|
+
|
|
16
|
+
if (!dryRun) {
|
|
17
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const srcPath = path.join(src, entry.name);
|
|
24
|
+
const destPath = path.join(dest, entry.name);
|
|
25
|
+
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
const subFiles = copyDirectory(srcPath, destPath, options);
|
|
28
|
+
copiedFiles.push(...subFiles);
|
|
29
|
+
} else {
|
|
30
|
+
if (!dryRun) {
|
|
31
|
+
fs.copyFileSync(srcPath, destPath);
|
|
32
|
+
}
|
|
33
|
+
copiedFiles.push(destPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return copiedFiles;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = function registerInstallDevcontainerCommand(program) {
|
|
41
|
+
program
|
|
42
|
+
.command("install-devcontainer")
|
|
43
|
+
.description("Copy the orrery devcontainer to a target directory")
|
|
44
|
+
.argument("[target]", "Target directory", process.cwd())
|
|
45
|
+
.option("--force", "Overwrite existing devcontainer")
|
|
46
|
+
.option("--dry-run", "Show what would be copied without writing files")
|
|
47
|
+
.action((target, options) => {
|
|
48
|
+
const sourceDir = getSourceDevcontainerDir();
|
|
49
|
+
if (!fs.existsSync(sourceDir)) {
|
|
50
|
+
console.error(`Source .devcontainer not found: ${sourceDir}`);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const targetDir = path.resolve(target);
|
|
56
|
+
const destDevcontainer = path.join(targetDir, ".devcontainer");
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(targetDir)) {
|
|
59
|
+
console.error(`Target directory does not exist: ${targetDir}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(destDevcontainer)) {
|
|
65
|
+
if (!options.force) {
|
|
66
|
+
console.error(
|
|
67
|
+
`Devcontainer already exists at: ${destDevcontainer}\nUse --force to overwrite.`
|
|
68
|
+
);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log("Overwriting existing devcontainer (--force).");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (options.dryRun) {
|
|
76
|
+
console.log("Dry run enabled. No files will be written.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`Source: ${sourceDir}`);
|
|
80
|
+
console.log(`Target: ${destDevcontainer}`);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const copiedFiles = copyDirectory(sourceDir, destDevcontainer, {
|
|
84
|
+
dryRun: options.dryRun
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log(
|
|
88
|
+
`${options.dryRun ? "Would copy" : "Copied"} ${copiedFiles.length} file${
|
|
89
|
+
copiedFiles.length === 1 ? "" : "s"
|
|
90
|
+
}.`
|
|
91
|
+
);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`Failed to copy devcontainer: ${error.message}`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
detectInstalledAgents,
|
|
6
|
+
getAgentSkillsDir
|
|
7
|
+
} = require("../../utils/agent-detector");
|
|
8
|
+
const { copySkills } = require("../../utils/skill-copier");
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_AGENTS = ["claude", "codex", "gemini"];
|
|
11
|
+
|
|
12
|
+
function getSourceSkillsDir() {
|
|
13
|
+
return path.join(__dirname, "..", "..", "..", "agent", "skills");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function listSkillDirectories(sourceDir) {
|
|
17
|
+
if (!fs.existsSync(sourceDir)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return fs
|
|
22
|
+
.readdirSync(sourceDir, { withFileTypes: true })
|
|
23
|
+
.filter((entry) => entry.isDirectory())
|
|
24
|
+
.map((entry) => entry.name)
|
|
25
|
+
.sort();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveRequestedAgents(agentOption) {
|
|
29
|
+
if (!agentOption) {
|
|
30
|
+
const detected = detectInstalledAgents();
|
|
31
|
+
return {
|
|
32
|
+
agents: detected,
|
|
33
|
+
reason:
|
|
34
|
+
detected.length === 0
|
|
35
|
+
? "No installed agents detected. Use --agent to override."
|
|
36
|
+
: null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const normalized = agentOption.toLowerCase();
|
|
41
|
+
if (normalized === "all") {
|
|
42
|
+
const detected = detectInstalledAgents();
|
|
43
|
+
return {
|
|
44
|
+
agents: detected,
|
|
45
|
+
reason:
|
|
46
|
+
detected.length === 0
|
|
47
|
+
? "No installed agents detected. Create an agent directory first."
|
|
48
|
+
: null
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!SUPPORTED_AGENTS.includes(normalized)) {
|
|
53
|
+
return {
|
|
54
|
+
agents: [],
|
|
55
|
+
reason: `Unknown agent: ${agentOption}. Expected one of ${SUPPORTED_AGENTS.join(
|
|
56
|
+
", "
|
|
57
|
+
)} or "all".`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { agents: [normalized], reason: null };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function filterMissingAgents(agents) {
|
|
65
|
+
const available = [];
|
|
66
|
+
const missing = [];
|
|
67
|
+
|
|
68
|
+
for (const agent of agents) {
|
|
69
|
+
const skillsDir = getAgentSkillsDir(agent);
|
|
70
|
+
if (!skillsDir) {
|
|
71
|
+
missing.push({ agent, reason: "Unsupported agent." });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const agentRoot = path.dirname(skillsDir);
|
|
76
|
+
if (!fs.existsSync(agentRoot)) {
|
|
77
|
+
missing.push({
|
|
78
|
+
agent,
|
|
79
|
+
reason: `Agent directory not found: ${agentRoot}`
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
available.push(agent);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { available, missing };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function installSkillsAction(options) {
|
|
91
|
+
const sourceDir = getSourceSkillsDir();
|
|
92
|
+
if (!fs.existsSync(sourceDir)) {
|
|
93
|
+
console.error(`Source skills directory not found: ${sourceDir}`);
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const skills = listSkillDirectories(sourceDir);
|
|
99
|
+
if (skills.length === 0) {
|
|
100
|
+
console.error(`No skills found in: ${sourceDir}`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { agents, reason } = resolveRequestedAgents(options.agent);
|
|
106
|
+
if (reason && agents.length === 0) {
|
|
107
|
+
console.warn(reason);
|
|
108
|
+
if (options.agent && options.agent !== "all") {
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { available, missing } = filterMissingAgents(agents);
|
|
115
|
+
if (missing.length > 0) {
|
|
116
|
+
for (const entry of missing) {
|
|
117
|
+
console.warn(`Skipping ${entry.agent}: ${entry.reason}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (available.length === 0) {
|
|
122
|
+
console.warn("No valid agent targets found.");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(
|
|
127
|
+
`Installing ${skills.length} skill${
|
|
128
|
+
skills.length === 1 ? "" : "s"
|
|
129
|
+
}: ${skills.join(", ")}`
|
|
130
|
+
);
|
|
131
|
+
if (options.dryRun) {
|
|
132
|
+
console.log("Dry run enabled. No files will be written.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let totalFiles = 0;
|
|
136
|
+
|
|
137
|
+
for (const agent of available) {
|
|
138
|
+
const targetDir = getAgentSkillsDir(agent);
|
|
139
|
+
console.log(`\nAgent: ${agent}`);
|
|
140
|
+
console.log(`Target: ${targetDir}`);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const copiedFiles = copySkills(sourceDir, targetDir, {
|
|
144
|
+
force: options.force,
|
|
145
|
+
dryRun: options.dryRun
|
|
146
|
+
});
|
|
147
|
+
totalFiles += copiedFiles.length;
|
|
148
|
+
console.log(
|
|
149
|
+
`${
|
|
150
|
+
options.dryRun ? "Would copy" : "Copied"
|
|
151
|
+
} ${copiedFiles.length} file${copiedFiles.length === 1 ? "" : "s"}.`
|
|
152
|
+
);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`Failed to install skills for ${agent}: ${error.message}`);
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(
|
|
160
|
+
`\nSummary: ${available.length} agent${
|
|
161
|
+
available.length === 1 ? "" : "s"
|
|
162
|
+
}, ${options.dryRun ? "would copy" : "copied"} ${totalFiles} file${
|
|
163
|
+
totalFiles === 1 ? "" : "s"
|
|
164
|
+
}.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function registerInstallSkillsCommand(program) {
|
|
169
|
+
program
|
|
170
|
+
.command("install-skills")
|
|
171
|
+
.description("Install orrery skills for supported agents")
|
|
172
|
+
.option(
|
|
173
|
+
"--agent <agent>",
|
|
174
|
+
"Target agent (claude|codex|gemini|all); defaults to auto-detect"
|
|
175
|
+
)
|
|
176
|
+
.option("--force", "Overwrite existing skills")
|
|
177
|
+
.option("--dry-run", "Show what would be copied without writing files")
|
|
178
|
+
.action(installSkillsAction);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = registerInstallSkillsCommand;
|
|
182
|
+
module.exports.installSkillsAction = installSkillsAction;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { orchestrate } = require("../../orchestration");
|
|
2
|
+
|
|
3
|
+
module.exports = function registerOrchestrateCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("orchestrate")
|
|
6
|
+
.alias("exec")
|
|
7
|
+
.description("Run plan orchestration for the current project")
|
|
8
|
+
.option("--plan <file>", "Process only a specific plan file")
|
|
9
|
+
.option("--dry-run", "Show what would be executed without running agents")
|
|
10
|
+
.option("--verbose", "Show detailed agent output")
|
|
11
|
+
.option("--resume", "Resume orchestration on the current work branch")
|
|
12
|
+
.option("--review", "Enable code review loop after each step")
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
try {
|
|
15
|
+
await orchestrate({
|
|
16
|
+
plan: options.plan,
|
|
17
|
+
dryRun: options.dryRun,
|
|
18
|
+
verbose: options.verbose,
|
|
19
|
+
resume: options.resume,
|
|
20
|
+
review: options.review
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(error && error.message ? error.message : error);
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|