@glrs-dev/harness-plugin-opencode 1.1.0 → 2.0.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/CHANGELOG.md +38 -0
- package/README.md +68 -26
- package/dist/agents/prompts/pilot-assessor.md +77 -0
- package/dist/agents/prompts/pilot-builder.md +24 -116
- package/dist/agents/prompts/pilot-planner.md +38 -160
- package/dist/agents/prompts/pilot-scoper.md +58 -0
- package/dist/{chunk-BWERBERN.js → chunk-6CZPRUMJ.js} +12 -62
- package/dist/chunk-DZG4D3OH.js +54 -0
- package/dist/chunk-OYRKOEXK.js +88 -0
- package/dist/cli.js +1619 -3930
- package/dist/index.js +831 -166
- package/dist/{install-5JKWK6Z4.js → install-6775ZBDG.js} +1 -1
- package/dist/paths-WZ23ZQOV.js +18 -0
- package/dist/skills/code-quality/SKILL.md +45 -0
- package/dist/skills/code-quality/rules/building.md +125 -0
- package/dist/skills/code-quality/rules/gap-analysis.md +92 -0
- package/dist/skills/code-quality/rules/planning.md +96 -0
- package/dist/skills/code-quality/rules/review.md +104 -0
- package/package.json +1 -1
- package/dist/agents/prompts/pilot-builder.open.md +0 -129
- package/dist/chunk-57EOY72Y.js +0 -174
- package/dist/chunk-5TAMY7P6.js +0 -67
- package/dist/chunk-BKTFWXLG.js +0 -204
- package/dist/chunk-EK7K4NTV.js +0 -747
- package/dist/chunk-KB7M7JXU.js +0 -145
- package/dist/chunk-RNRCXQ65.js +0 -56
- package/dist/paths-LT3QQKCF.js +0 -18
- package/dist/pilot/mcp/status-server.d.ts +0 -1
- package/dist/pilot/mcp/status-server.js +0 -228
- package/dist/pilot-config-7LJZ23YK.js +0 -55
- package/dist/runs-QWPL3TKV.js +0 -18
- package/dist/safety-gate-WM3EWOCY.js +0 -10
- package/dist/setup-hook-FHTXMAQL.js +0 -88
- package/dist/skills/pilot-planning/SKILL.md +0 -80
- package/dist/skills/pilot-planning/rules/dag-shape.md +0 -47
- package/dist/skills/pilot-planning/rules/decomposition.md +0 -63
- package/dist/skills/pilot-planning/rules/first-principles.md +0 -29
- package/dist/skills/pilot-planning/rules/milestones.md +0 -57
- package/dist/skills/pilot-planning/rules/qa-expectations.md +0 -120
- package/dist/skills/pilot-planning/rules/self-review.md +0 -46
- package/dist/skills/pilot-planning/rules/task-context.md +0 -47
- package/dist/skills/pilot-planning/rules/touches-scope.md +0 -81
- package/dist/skills/pilot-planning/rules/verify-design.md +0 -121
- package/dist/tasks-KJ3WN2KY.js +0 -32
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pilot-scoper
|
|
3
|
+
description: "Pilot v2 scoping agent. Interviews the user to understand their goal, explores the codebase, and produces a scope.json artifact with framing and acceptance criteria."
|
|
4
|
+
mode: subagent
|
|
5
|
+
model: anthropic/claude-sonnet-4-6
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the **pilot-scoper** — the first phase of the SPEAR autonomous execution system.
|
|
9
|
+
|
|
10
|
+
Your job: have a focused conversation with the user to understand what they want to build, explore the codebase to understand the context, and produce a `scope.json` artifact that the planner can use to decompose the work.
|
|
11
|
+
|
|
12
|
+
## Your output
|
|
13
|
+
|
|
14
|
+
You MUST produce a `scope.json` file at the path provided in your instructions. The schema:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"goal": "One sentence: what are we building?",
|
|
19
|
+
"framing": "2-4 sentences: why this matters, what problem it solves, what success looks like",
|
|
20
|
+
"acceptance_criteria": [
|
|
21
|
+
{
|
|
22
|
+
"id": "AC-001",
|
|
23
|
+
"description": "Behavioral, verifiable statement of what must be true when done",
|
|
24
|
+
"verifiable": "shell | llm | manual"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"non_goals": ["What we are explicitly NOT doing"],
|
|
28
|
+
"context": "Optional: key codebase patterns, constraints, or background the planner needs"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Conversation approach
|
|
33
|
+
|
|
34
|
+
1. **Start by asking** what the user wants to build. One open question.
|
|
35
|
+
2. **Explore the codebase** to understand the current state (read files, search patterns, check tests).
|
|
36
|
+
3. **Ask clarifying questions** — but only the ones that would change the acceptance criteria. Don't ask about implementation details.
|
|
37
|
+
4. **Draft acceptance criteria** — behavioral statements, not file-level tasks. Each AC should be independently verifiable.
|
|
38
|
+
5. **Confirm with the user** — show the draft ACs and ask if they're complete and correct.
|
|
39
|
+
6. **Write scope.json** — once the user approves.
|
|
40
|
+
|
|
41
|
+
## Acceptance criteria rules
|
|
42
|
+
|
|
43
|
+
- Each AC describes an observable behavior, not an implementation step.
|
|
44
|
+
- Good: "The dark mode toggle persists across page reloads"
|
|
45
|
+
- Bad: "Add localStorage.setItem to the toggle handler"
|
|
46
|
+
- Each AC should be verifiable by a shell command, an LLM review, or manual inspection.
|
|
47
|
+
- Aim for 3-8 ACs. More than 8 suggests the scope is too large.
|
|
48
|
+
|
|
49
|
+
## Tools
|
|
50
|
+
|
|
51
|
+
You have read-only access to the codebase. Use file reads, search, and git log to understand the current state. Do NOT make any edits.
|
|
52
|
+
|
|
53
|
+
## STOP protocol
|
|
54
|
+
|
|
55
|
+
If the user's goal is fundamentally unclear after 3 clarifying questions, output:
|
|
56
|
+
```
|
|
57
|
+
STOP: Cannot produce scope — goal is too ambiguous. Please provide more context about what you want to build.
|
|
58
|
+
```
|
|
@@ -181,29 +181,6 @@ import * as fs2 from "fs";
|
|
|
181
181
|
import * as path2 from "path";
|
|
182
182
|
import * as os from "os";
|
|
183
183
|
import { select, checkbox, confirm } from "@inquirer/prompts";
|
|
184
|
-
var PLUGIN_NAME = "@glrs-dev/harness-plugin-opencode";
|
|
185
|
-
function getOpencodeConfigPath() {
|
|
186
|
-
const configHome = process.env["XDG_CONFIG_HOME"] ?? path2.join(os.homedir(), ".config");
|
|
187
|
-
return path2.join(configHome, "opencode", "opencode.json");
|
|
188
|
-
}
|
|
189
|
-
function isPluginInstalled() {
|
|
190
|
-
const configPath = getOpencodeConfigPath();
|
|
191
|
-
if (!fs2.existsSync(configPath)) return false;
|
|
192
|
-
try {
|
|
193
|
-
const config = JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
194
|
-
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
195
|
-
return plugins.some((p) => {
|
|
196
|
-
const name = typeof p === "string" ? p : Array.isArray(p) ? p[0] : null;
|
|
197
|
-
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
198
|
-
});
|
|
199
|
-
} catch {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
async function promptYesNo(question) {
|
|
204
|
-
if (!process.stdin.isTTY) return false;
|
|
205
|
-
return confirm({ message: question, default: false });
|
|
206
|
-
}
|
|
207
184
|
async function promptChoice(question, choices, defaultIndex = 0) {
|
|
208
185
|
if (!process.stdin.isTTY) return defaultIndex;
|
|
209
186
|
const answer = await select({
|
|
@@ -234,32 +211,6 @@ async function promptMulti(question, choices) {
|
|
|
234
211
|
});
|
|
235
212
|
return new Set(answers);
|
|
236
213
|
}
|
|
237
|
-
async function requirePlugin() {
|
|
238
|
-
if (isPluginInstalled()) return;
|
|
239
|
-
const c2 = {
|
|
240
|
-
reset: "\x1B[0m",
|
|
241
|
-
yellow: "\x1B[33m",
|
|
242
|
-
red: "\x1B[31m"
|
|
243
|
-
};
|
|
244
|
-
console.error(
|
|
245
|
-
`${c2.yellow}!${c2.reset} The OpenCode plugin is not installed. Pilot commands need the plugin to register the pilot-builder and pilot-planner agents.`
|
|
246
|
-
);
|
|
247
|
-
if (!process.stdin.isTTY) {
|
|
248
|
-
console.error(
|
|
249
|
-
`${c2.red}\u2717${c2.reset} Non-interactive terminal. Run: glrs-oc install-plugin`
|
|
250
|
-
);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
const yes = await promptYesNo("Install the plugin now?");
|
|
254
|
-
if (!yes) {
|
|
255
|
-
console.error(
|
|
256
|
-
`${c2.red}\u2717${c2.reset} Plugin required. Run: glrs-oc install-plugin`
|
|
257
|
-
);
|
|
258
|
-
process.exit(1);
|
|
259
|
-
}
|
|
260
|
-
const { install: install2 } = await import("./install-5JKWK6Z4.js");
|
|
261
|
-
await install2({ nonInteractive: true });
|
|
262
|
-
}
|
|
263
214
|
|
|
264
215
|
// src/cli/models-dev.ts
|
|
265
216
|
var MODELS_DEV_URL = "https://models.dev/api.json";
|
|
@@ -361,7 +312,7 @@ async function fetchModelsDevProviders() {
|
|
|
361
312
|
}
|
|
362
313
|
|
|
363
314
|
// src/cli/install.ts
|
|
364
|
-
var
|
|
315
|
+
var PLUGIN_NAME = "@glrs-dev/harness-plugin-opencode";
|
|
365
316
|
var c = {
|
|
366
317
|
reset: "\x1B[0m",
|
|
367
318
|
green: "\x1B[32m",
|
|
@@ -405,7 +356,7 @@ function extractPluginOptions(config) {
|
|
|
405
356
|
const plugins = config.plugin;
|
|
406
357
|
if (!Array.isArray(plugins)) return null;
|
|
407
358
|
for (const entry of plugins) {
|
|
408
|
-
if (Array.isArray(entry) && entry.length >= 2 && (entry[0] ===
|
|
359
|
+
if (Array.isArray(entry) && entry.length >= 2 && (entry[0] === PLUGIN_NAME || String(entry[0]).startsWith(`${PLUGIN_NAME}@`))) {
|
|
409
360
|
return entry[1];
|
|
410
361
|
}
|
|
411
362
|
}
|
|
@@ -421,17 +372,17 @@ function readPackageVersion() {
|
|
|
421
372
|
try {
|
|
422
373
|
const raw = fs3.readFileSync(candidate, "utf8");
|
|
423
374
|
const parsed = JSON.parse(raw);
|
|
424
|
-
if (parsed.name ===
|
|
375
|
+
if (parsed.name === PLUGIN_NAME && typeof parsed.version === "string") {
|
|
425
376
|
return parsed.version;
|
|
426
377
|
}
|
|
427
378
|
} catch {
|
|
428
379
|
}
|
|
429
380
|
}
|
|
430
381
|
throw new Error(
|
|
431
|
-
`Could not locate ${
|
|
382
|
+
`Could not locate ${PLUGIN_NAME}'s package.json to read version`
|
|
432
383
|
);
|
|
433
384
|
}
|
|
434
|
-
function
|
|
385
|
+
function getOpencodeConfigPath() {
|
|
435
386
|
const configHome = process.env["XDG_CONFIG_HOME"] ?? path3.join(os2.homedir(), ".config");
|
|
436
387
|
return path3.join(configHome, "opencode", "opencode.json");
|
|
437
388
|
}
|
|
@@ -488,11 +439,11 @@ function migrateHarnessKeyToPluginOptions(configPath) {
|
|
|
488
439
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
489
440
|
const pluginIdx = plugins.findIndex((entry) => {
|
|
490
441
|
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
491
|
-
return name ===
|
|
442
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
492
443
|
});
|
|
493
444
|
if (pluginIdx < 0) return;
|
|
494
445
|
const current = plugins[pluginIdx];
|
|
495
|
-
const existingName = typeof current === "string" ? current : Array.isArray(current) ? current[0] :
|
|
446
|
+
const existingName = typeof current === "string" ? current : Array.isArray(current) ? current[0] : PLUGIN_NAME;
|
|
496
447
|
const existingOpts = Array.isArray(current) && current.length >= 2 ? current[1] : {};
|
|
497
448
|
const merged = { ...config.harness, ...existingOpts };
|
|
498
449
|
plugins[pluginIdx] = [existingName, merged];
|
|
@@ -533,13 +484,13 @@ function writePluginOption(configPath, subKey, value, opts) {
|
|
|
533
484
|
}
|
|
534
485
|
const pluginIdx = config.plugin.findIndex((entry) => {
|
|
535
486
|
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
536
|
-
return name ===
|
|
487
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
537
488
|
});
|
|
538
489
|
if (pluginIdx < 0) {
|
|
539
490
|
return { changed: false };
|
|
540
491
|
}
|
|
541
492
|
const current = config.plugin[pluginIdx];
|
|
542
|
-
const existingName = typeof current === "string" ? current : Array.isArray(current) ? current[0] :
|
|
493
|
+
const existingName = typeof current === "string" ? current : Array.isArray(current) ? current[0] : PLUGIN_NAME;
|
|
543
494
|
const existingOpts = Array.isArray(current) && current.length >= 2 ? current[1] : {};
|
|
544
495
|
if (deepEqual(existingOpts[subKey], value)) {
|
|
545
496
|
return { changed: false };
|
|
@@ -617,14 +568,14 @@ function writeMcpToggles(configPath, enabledSet, opts) {
|
|
|
617
568
|
}
|
|
618
569
|
async function install(opts = {}) {
|
|
619
570
|
const { dryRun = false, pin = false, nonInteractive = false } = opts;
|
|
620
|
-
const configPath =
|
|
621
|
-
const pluginEntry = pin ? `${
|
|
571
|
+
const configPath = getOpencodeConfigPath();
|
|
572
|
+
const pluginEntry = pin ? `${PLUGIN_NAME}@${readPackageVersion()}` : PLUGIN_NAME;
|
|
622
573
|
const interactive = !nonInteractive && process.stdin.isTTY === true;
|
|
623
574
|
const existing = readExistingConfig(configPath);
|
|
624
575
|
const hasPlugin = existing ? (Array.isArray(existing.plugin) ? existing.plugin : []).some(
|
|
625
576
|
(p) => {
|
|
626
577
|
const name = typeof p === "string" ? p : Array.isArray(p) ? p[0] : null;
|
|
627
|
-
return name ===
|
|
578
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
628
579
|
}
|
|
629
580
|
) : false;
|
|
630
581
|
const existingProvider = detectModelProvider(existing);
|
|
@@ -911,7 +862,6 @@ ${c.bold}Ready.${c.reset} Run ${c.green}opencode${c.reset} to start.
|
|
|
911
862
|
}
|
|
912
863
|
|
|
913
864
|
export {
|
|
914
|
-
requirePlugin,
|
|
915
865
|
MODEL_PRESETS,
|
|
916
866
|
writePluginOption,
|
|
917
867
|
writeMcpToggles,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/model-validator.ts
|
|
2
|
+
var CATWALK_PROVIDER_PATTERN = /^(?:bedrock|vertex|vertexai)\//;
|
|
3
|
+
var LEGACY_PRE_100_PATTERN = /^(bedrock|vertex|vertexai)\/claude-(opus|sonnet|haiku)(-\d+)?$/;
|
|
4
|
+
var LEGACY_TO_MODELS_DEV = {
|
|
5
|
+
// --- Pre-PR-#100 Bedrock (no subpath) ---
|
|
6
|
+
"bedrock/claude-opus": "amazon-bedrock/global.anthropic.claude-opus-4-7",
|
|
7
|
+
"bedrock/claude-opus-4": "amazon-bedrock/global.anthropic.claude-opus-4-7",
|
|
8
|
+
"bedrock/claude-sonnet": "amazon-bedrock/global.anthropic.claude-sonnet-4-6",
|
|
9
|
+
"bedrock/claude-sonnet-4": "amazon-bedrock/global.anthropic.claude-sonnet-4-6",
|
|
10
|
+
"bedrock/claude-haiku": "amazon-bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
11
|
+
"bedrock/claude-haiku-4": "amazon-bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
12
|
+
// --- Pre-Models.dev Bedrock (had subpath, but wrong provider prefix) ---
|
|
13
|
+
"bedrock/anthropic.claude-opus-4-6": "amazon-bedrock/global.anthropic.claude-opus-4-7",
|
|
14
|
+
"bedrock/anthropic.claude-opus-4-7": "amazon-bedrock/global.anthropic.claude-opus-4-7",
|
|
15
|
+
"bedrock/anthropic.claude-sonnet-4-6": "amazon-bedrock/global.anthropic.claude-sonnet-4-6",
|
|
16
|
+
"bedrock/anthropic.claude-haiku-4-5-20251001-v1:0": "amazon-bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
17
|
+
// --- Pre-PR-#100 Vertex (no @date suffix) ---
|
|
18
|
+
"vertex/claude-opus": "google-vertex-anthropic/claude-opus-4-7@default",
|
|
19
|
+
"vertex/claude-opus-4": "google-vertex-anthropic/claude-opus-4-7@default",
|
|
20
|
+
"vertex/claude-sonnet": "google-vertex-anthropic/claude-sonnet-4-6@default",
|
|
21
|
+
"vertex/claude-sonnet-4": "google-vertex-anthropic/claude-sonnet-4-6@default",
|
|
22
|
+
"vertex/claude-haiku": "google-vertex-anthropic/claude-haiku-4-5@20251001",
|
|
23
|
+
"vertex/claude-haiku-4": "google-vertex-anthropic/claude-haiku-4-5@20251001",
|
|
24
|
+
"vertexai/claude-opus": "google-vertex-anthropic/claude-opus-4-7@default",
|
|
25
|
+
"vertexai/claude-opus-4": "google-vertex-anthropic/claude-opus-4-7@default",
|
|
26
|
+
"vertexai/claude-sonnet": "google-vertex-anthropic/claude-sonnet-4-6@default",
|
|
27
|
+
"vertexai/claude-sonnet-4": "google-vertex-anthropic/claude-sonnet-4-6@default",
|
|
28
|
+
"vertexai/claude-haiku": "google-vertex-anthropic/claude-haiku-4-5@20251001",
|
|
29
|
+
"vertexai/claude-haiku-4": "google-vertex-anthropic/claude-haiku-4-5@20251001",
|
|
30
|
+
// --- Pre-Models.dev Vertex (had @date suffix, wrong provider prefix) ---
|
|
31
|
+
"vertexai/claude-opus-4-6@20250610": "google-vertex-anthropic/claude-opus-4-6@default",
|
|
32
|
+
"vertexai/claude-opus-4-7@20250610": "google-vertex-anthropic/claude-opus-4-7@default",
|
|
33
|
+
"vertexai/claude-sonnet-4-6@20250725": "google-vertex-anthropic/claude-sonnet-4-6@default",
|
|
34
|
+
"vertexai/claude-haiku-4-5@20251001": "google-vertex-anthropic/claude-haiku-4-5@20251001"
|
|
35
|
+
};
|
|
36
|
+
function validateModelOverride(id) {
|
|
37
|
+
if (typeof id !== "string") return { valid: true };
|
|
38
|
+
if (id.length === 0) return { valid: true };
|
|
39
|
+
if (CATWALK_PROVIDER_PATTERN.test(id)) {
|
|
40
|
+
const suggestion = LEGACY_TO_MODELS_DEV[id] ?? "run `bunx @glrs-dev/harness-plugin-opencode install` to pick a current preset";
|
|
41
|
+
const reason = LEGACY_PRE_100_PATTERN.test(id) ? `"${id}" is a pre-PR-#100 model ID format that does not resolve in OpenCode. Bedrock IDs need the \`amazon-bedrock\` provider prefix (not \`bedrock\`); Vertex Claude IDs need the \`google-vertex-anthropic\` provider prefix (not \`vertex\` / \`vertexai\`).` : `"${id}" uses a provider prefix (\`${id.split("/")[0]}\`) that does not exist in OpenCode's runtime. AWS Bedrock's provider ID is \`amazon-bedrock\`; Vertex Claude's is \`google-vertex-anthropic\`.`;
|
|
42
|
+
return { valid: false, reason, suggestion };
|
|
43
|
+
}
|
|
44
|
+
return { valid: true };
|
|
45
|
+
}
|
|
46
|
+
function formatModelOverrideWarning(id, source, suggestion) {
|
|
47
|
+
const suggestionText = suggestion ? ` Suggested replacement: \`${suggestion}\`.` : "";
|
|
48
|
+
return `[@glrs-dev/harness-plugin-opencode] Warning: invalid model override "${id}" (from ${source}).${suggestionText} Run \`bunx @glrs-dev/harness-plugin-opencode doctor\` for details.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
validateModelOverride,
|
|
53
|
+
formatModelOverrideWarning
|
|
54
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/pilot/paths.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
function execFileP(file, args, opts = {}) {
|
|
7
|
+
const { cwd, timeoutMs = 5e3 } = opts;
|
|
8
|
+
return new Promise((resolve2, reject) => {
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
execFile(
|
|
12
|
+
file,
|
|
13
|
+
args,
|
|
14
|
+
{ signal: controller.signal, cwd, encoding: "utf8" },
|
|
15
|
+
(err, stdout) => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
if (err) {
|
|
18
|
+
reject(err);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
resolve2(stdout ?? "");
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function expandTilde(p) {
|
|
27
|
+
if (p === "~") return os.homedir();
|
|
28
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
async function getRepoFolder(cwd) {
|
|
32
|
+
let stdout;
|
|
33
|
+
try {
|
|
34
|
+
stdout = await execFileP("git", ["rev-parse", "--git-common-dir"], { cwd });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`pilot paths: could not determine repo folder from ${JSON.stringify(cwd)}: ${err instanceof Error ? err.message : String(err)}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const gitCommonDir = stdout.trim();
|
|
41
|
+
const abs = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(cwd, gitCommonDir);
|
|
42
|
+
return path.basename(path.dirname(abs));
|
|
43
|
+
}
|
|
44
|
+
async function getPilotDir(cwd) {
|
|
45
|
+
const override = process.env["GLORIOUS_PILOT_DIR"];
|
|
46
|
+
if (override) {
|
|
47
|
+
const dir = expandTilde(override);
|
|
48
|
+
await fs.mkdir(dir, { recursive: true });
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
const repoFolder = await getRepoFolder(cwd);
|
|
52
|
+
const base = path.join(os.homedir(), ".glorious", "opencode", repoFolder, "pilot");
|
|
53
|
+
await fs.mkdir(base, { recursive: true });
|
|
54
|
+
return base;
|
|
55
|
+
}
|
|
56
|
+
async function getStateDbPath(cwd) {
|
|
57
|
+
const base = await getPilotDir(cwd);
|
|
58
|
+
return path.join(base, "state.sqlite");
|
|
59
|
+
}
|
|
60
|
+
async function getCurrentScopePath(cwd) {
|
|
61
|
+
const base = await getPilotDir(cwd);
|
|
62
|
+
return path.join(base, "current-scope.json");
|
|
63
|
+
}
|
|
64
|
+
async function getScopeArtifactPath(cwd, workflowId) {
|
|
65
|
+
const base = await getPilotDir(cwd);
|
|
66
|
+
const dir = path.join(base, "scopes", workflowId);
|
|
67
|
+
await fs.mkdir(dir, { recursive: true });
|
|
68
|
+
return path.join(dir, "scope.json");
|
|
69
|
+
}
|
|
70
|
+
async function getPlanArtifactPath(cwd, workflowId) {
|
|
71
|
+
const base = await getPilotDir(cwd);
|
|
72
|
+
const dir = path.join(base, "scopes", workflowId);
|
|
73
|
+
await fs.mkdir(dir, { recursive: true });
|
|
74
|
+
return path.join(dir, "plan.json");
|
|
75
|
+
}
|
|
76
|
+
function getPilotConfigPath(cwd) {
|
|
77
|
+
return path.join(cwd, ".glrs", "pilot.json");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
getRepoFolder,
|
|
82
|
+
getPilotDir,
|
|
83
|
+
getStateDbPath,
|
|
84
|
+
getCurrentScopePath,
|
|
85
|
+
getScopeArtifactPath,
|
|
86
|
+
getPlanArtifactPath,
|
|
87
|
+
getPilotConfigPath
|
|
88
|
+
};
|