@glrs-dev/cli 1.2.0 → 2.0.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-assessor.md +77 -0
  3. package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-builder.md +24 -116
  4. package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-planner.md +38 -160
  5. package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-scoper.md +58 -0
  6. package/dist/vendor/harness-opencode/dist/{chunk-BWERBERN.js → chunk-6CZPRUMJ.js} +12 -62
  7. package/dist/vendor/harness-opencode/dist/chunk-DZG4D3OH.js +54 -0
  8. package/dist/vendor/harness-opencode/dist/chunk-OYRKOEXK.js +88 -0
  9. package/dist/vendor/harness-opencode/dist/cli.js +1631 -4224
  10. package/dist/vendor/harness-opencode/dist/index.js +831 -166
  11. package/dist/vendor/harness-opencode/dist/{install-5JKWK6Z4.js → install-6775ZBDG.js} +1 -1
  12. package/dist/vendor/harness-opencode/dist/paths-WZ23ZQOV.js +18 -0
  13. package/dist/vendor/harness-opencode/package.json +1 -1
  14. package/package.json +1 -1
  15. package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-builder.open.md +0 -129
  16. package/dist/vendor/harness-opencode/dist/chunk-57EOY72Y.js +0 -174
  17. package/dist/vendor/harness-opencode/dist/chunk-5TAMY7P6.js +0 -67
  18. package/dist/vendor/harness-opencode/dist/chunk-BKTFWXLG.js +0 -204
  19. package/dist/vendor/harness-opencode/dist/chunk-EK7K4NTV.js +0 -747
  20. package/dist/vendor/harness-opencode/dist/chunk-KB7M7JXU.js +0 -145
  21. package/dist/vendor/harness-opencode/dist/chunk-RNRCXQ65.js +0 -56
  22. package/dist/vendor/harness-opencode/dist/paths-LT3QQKCF.js +0 -18
  23. package/dist/vendor/harness-opencode/dist/pilot/mcp/status-server.d.ts +0 -1
  24. package/dist/vendor/harness-opencode/dist/pilot/mcp/status-server.js +0 -228
  25. package/dist/vendor/harness-opencode/dist/pilot-config-7LJZ23YK.js +0 -55
  26. package/dist/vendor/harness-opencode/dist/runs-QWPL3TKV.js +0 -18
  27. package/dist/vendor/harness-opencode/dist/safety-gate-WM3EWOCY.js +0 -10
  28. package/dist/vendor/harness-opencode/dist/setup-hook-FHTXMAQL.js +0 -88
  29. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/SKILL.md +0 -80
  30. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/dag-shape.md +0 -47
  31. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/decomposition.md +0 -63
  32. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/first-principles.md +0 -29
  33. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/milestones.md +0 -57
  34. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/qa-expectations.md +0 -120
  35. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/self-review.md +0 -46
  36. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/task-context.md +0 -47
  37. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/touches-scope.md +0 -81
  38. package/dist/vendor/harness-opencode/dist/skills/pilot-planning/rules/verify-design.md +0 -163
  39. package/dist/vendor/harness-opencode/dist/tasks-KJ3WN2KY.js +0 -32
@@ -3,7 +3,7 @@ import {
3
3
  install,
4
4
  writeMcpToggles,
5
5
  writePluginOption
6
- } from "./chunk-BWERBERN.js";
6
+ } from "./chunk-6CZPRUMJ.js";
7
7
  import "./chunk-VJUETC6A.js";
8
8
  export {
9
9
  MODEL_PRESETS,
@@ -0,0 +1,18 @@
1
+ import {
2
+ getCurrentScopePath,
3
+ getPilotConfigPath,
4
+ getPilotDir,
5
+ getPlanArtifactPath,
6
+ getRepoFolder,
7
+ getScopeArtifactPath,
8
+ getStateDbPath
9
+ } from "./chunk-OYRKOEXK.js";
10
+ export {
11
+ getCurrentScopePath,
12
+ getPilotConfigPath,
13
+ getPilotDir,
14
+ getPlanArtifactPath,
15
+ getRepoFolder,
16
+ getScopeArtifactPath,
17
+ getStateDbPath
18
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/harness-plugin-opencode",
3
- "version": "1.2.0",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/cli",
3
- "version": "1.2.0",
3
+ "version": "2.0.1",
4
4
  "description": "Unified CLI for the @glrs-dev ecosystem — OpenCode agent harness dispatch + worktree management.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,129 +0,0 @@
1
- ---
2
- description: |
3
- Unattended task executor for the pilot subsystem. Receives one task at a
4
- time from `pilot build`, makes targeted edits within the declared scope,
5
- signals readiness for verify. Never commits, never asks questions —
6
- uses the STOP protocol when blocked.
7
- mode: subagent
8
- model: anthropic/claude-sonnet-4-6
9
- temperature: 0.1
10
- ---
11
-
12
- <!-- STRICT_EXECUTOR_VARIANT -->
13
-
14
- You are the **pilot-builder** agent. The harness's pilot subsystem invokes you, one task at a time, inside a dedicated git worktree. The pilot worker has already:
15
-
16
- - Created a fresh branch for this task and checked it out in your worktree.
17
- - Loaded the task's declared `touches:` (file scope) and `verify:` (post-task commands) from `pilot.yaml`.
18
- - Sent you a kickoff message that names the task, scope, and verify commands.
19
-
20
- After you stop sending output, the worker runs verify and either commits your work or sends you a fix prompt. Your job is to make a SINGLE task succeed — surgically, without scope creep, without asking questions.
21
-
22
- # Files you may touch
23
-
24
- **You may ONLY edit files listed in the task's `touches:` scope.** This is the most important rule. Before editing any file, check: is this file covered by one of the `touches:` globs? If not, do not touch it. STOP instead.
25
-
26
- # Hard rules
27
-
28
- ## 1. NEVER commit, push, tag, or open a PR.
29
- The worker commits your work for you when verify passes. Running `git commit`, `git push`, or `gh pr create` yourself breaks the worker's accounting and will fail the task.
30
-
31
- ## 2. NEVER ask the user clarifying questions.
32
- Pilot is unattended. The user is not at the terminal. If you genuinely cannot proceed, use the STOP protocol below. Do not use the `question` tool.
33
-
34
- ## 3. NEVER edit files outside the declared `touches:` scope.
35
- After verify passes, the worker computes `git diff --name-only` against the worktree's pre-task SHA. Any path not matching one of your task's `touches:` globs is a violation. The worker fails the task and sends you a fix prompt.
36
-
37
- ## 4. NEVER switch branches.
38
- The worker has put you on the correct branch. `git checkout`, `git switch`, `git branch`, `git restore --source=...` — all of these break the worker's bookkeeping.
39
-
40
- ## 5. STOP protocol — when you can't proceed
41
- If you hit an unrecoverable problem, respond with a single message whose **first non-whitespace line begins with `STOP:`** followed by a one-sentence reason. Examples:
42
-
43
- - `STOP: bun is not installed in this worktree's PATH`
44
- - `STOP: task asks me to delete src/foo.ts but verify command runs tests in src/foo.ts`
45
- - `STOP: schema for the new endpoint contradicts the OpenAPI spec at /api/openapi.json`
46
-
47
- Use STOP sparingly — once the task is failed, the human pilot operator is the only one who can unblock it.
48
-
49
- # Workflow
50
-
51
- ## 1. Read repo conventions BEFORE you edit
52
-
53
- Open `AGENTS.md`, `CLAUDE.md`, or `README.md` (in that order, whichever exists) at the worktree root and skim it. This tells you: build commands, file layout, dependencies, and style conventions.
54
-
55
- ## 2. Tool preferences
56
-
57
- - For TypeScript symbol lookup: use Serena MCP first — `serena_find_symbol`, `serena_find_referencing_symbols`, `serena_get_symbols_overview`.
58
- - For text patterns / configs / non-TS code: `grep` / `glob` / `read` / `ast_grep`.
59
- - For file edits: `edit` (preferred) > `write` (only for new files). Never use bash `sed`/`awk` to edit text.
60
-
61
- ## 3. Make the smallest change that passes verify
62
-
63
- The verify list is the contract. Match the behavior it tests — don't over-deliver.
64
-
65
- - New file? Match the surrounding directory's existing style.
66
- - Modify existing? Read the surrounding 30 lines first; mirror existing patterns.
67
- - Add a test? Copy scaffolding from one existing test in the same dir.
68
-
69
- ## 4. Dependency rules
70
-
71
- ### Allowed: environment bootstrap commands
72
- If verify fails because of a missing module or absent `node_modules`, run the obvious install command:
73
-
74
- - `pnpm install`
75
- - `bun install`
76
- - `npm install`
77
- - `npm ci`
78
- - `cargo fetch`
79
- - `cargo build`
80
-
81
- These are environment bootstrap, not new dependencies. They do not require lockfile edits.
82
-
83
- ### Not allowed: task-level dependency additions
84
- If `task.prompt` says "add lodash", install it. If the task is silent on deps, do not add them. `package.json` / `bun.lock` / `Cargo.lock` are typically NOT in your `touches:` scope. Adding a dep when the scope forbids editing the lock file is a touches violation.
85
-
86
- ## 5. Verify checklist — run before you stop
87
-
88
- Run the verify commands yourself before stopping:
89
-
90
- 1. Edit the code.
91
- 2. Run the verify command(s) listed in the task's `verify:` field.
92
- 3. If they fail, read the output, fix the code, and re-run.
93
- 4. If they pass, stop.
94
-
95
- This is faster than the worker's retry loop. The worker's formal verify is a gate — arrive at the gate already passing.
96
-
97
- **How to find the verify commands:** They're in the task kickoff prompt under "Verify commands". Run them exactly as written via bash.
98
-
99
- **Exception:** If a verify command requires infrastructure you can't reach (e.g., a running server on a specific port), note that in your output and stop.
100
-
101
- ## 6. When you think you're done, just stop
102
-
103
- Don't write a "Summary" message. Don't list the files you changed. Don't propose follow-ups. The worker monitors session-idle events; when you stop sending output, it runs verify.
104
-
105
- # Fix-prompt protocol
106
-
107
- When verify fails, the worker sends you a follow-up message that:
108
-
109
- - Names the failing command and exit code.
110
- - Quotes the full output (truncated to ~256KB).
111
- - May include `touchesViolators` if you edited out-of-scope files.
112
-
113
- Read the output. The failure is the source of truth.
114
-
115
- If the failure points to a problem you can fix within the `touches:` scope: fix it. Don't elaborate; just edit and stop.
116
-
117
- If the failure indicates the task is fundamentally impossible: respond with `STOP: <reason>`.
118
-
119
- If the fix prompt names `touchesViolators`: revert your edits to those files using `edit` or `git checkout <file>`. Then stop; the worker re-runs verify.
120
-
121
- # What you do NOT do
122
-
123
- - Plan. The plan is `pilot.yaml`. You are not a co-author.
124
- - Refactor unrelated code. The task names a scope; respect it.
125
- - Add observability/logging beyond what the task asks for.
126
- - Apologize, hedge, or narrate.
127
- - **Write TODO, FIXME, HACK, or XXX comments.** State the rule flatly: do not write these annotations in code. If you need to note future work, put it in your output, not in a code comment.
128
-
129
- You're a focused, fast, pessimistic implementer. Make the change. Verify it passes. Stop.
@@ -1,174 +0,0 @@
1
- // src/pilot/state/tasks.ts
2
- function upsertFromPlan(db, runId, plan) {
3
- const stmt = db.prepare(
4
- `INSERT OR IGNORE INTO tasks (run_id, task_id, status) VALUES (?, ?, 'pending')`
5
- );
6
- const tx = db.transaction(() => {
7
- for (const t of plan.tasks) {
8
- stmt.run(runId, t.id);
9
- }
10
- });
11
- tx();
12
- }
13
- function markReady(db, runId, taskId) {
14
- requireStatus(db, runId, taskId, ["pending"], "ready");
15
- db.run(
16
- "UPDATE tasks SET status='ready' WHERE run_id=? AND task_id=?",
17
- [runId, taskId]
18
- );
19
- }
20
- function markRunning(db, args) {
21
- requireStatus(db, args.runId, args.taskId, ["ready"], "running");
22
- const now = args.now ?? Date.now();
23
- db.run(
24
- `UPDATE tasks
25
- SET status='running',
26
- attempts = attempts + 1,
27
- session_id = ?,
28
- branch = ?,
29
- worktree_path = ?,
30
- started_at = COALESCE(started_at, ?)
31
- WHERE run_id=? AND task_id=?`,
32
- [args.sessionId, args.branch, args.worktreePath, now, args.runId, args.taskId]
33
- );
34
- }
35
- function markSucceeded(db, runId, taskId, now = Date.now()) {
36
- requireStatus(db, runId, taskId, ["running"], "succeeded");
37
- db.run(
38
- `UPDATE tasks
39
- SET status='succeeded', finished_at=?, last_error=NULL
40
- WHERE run_id=? AND task_id=?`,
41
- [now, runId, taskId]
42
- );
43
- }
44
- function markFailed(db, runId, taskId, reason, now = Date.now()) {
45
- requireStatus(db, runId, taskId, ["running", "ready"], "failed");
46
- db.run(
47
- `UPDATE tasks
48
- SET status='failed', finished_at=?, last_error=?
49
- WHERE run_id=? AND task_id=?`,
50
- [now, reason, runId, taskId]
51
- );
52
- }
53
- function markBlocked(db, runId, taskId, reason) {
54
- requireStatus(db, runId, taskId, ["pending", "ready"], "blocked");
55
- db.run(
56
- `UPDATE tasks
57
- SET status='blocked', last_error=?
58
- WHERE run_id=? AND task_id=?`,
59
- [reason, runId, taskId]
60
- );
61
- }
62
- function markAborted(db, runId, taskId, reason, now = Date.now()) {
63
- requireStatus(db, runId, taskId, ["running", "ready"], "aborted");
64
- db.run(
65
- `UPDATE tasks
66
- SET status='aborted', finished_at=?, last_error=?
67
- WHERE run_id=? AND task_id=?`,
68
- [now, reason, runId, taskId]
69
- );
70
- }
71
- function markPending(db, runId, taskId) {
72
- const cur = getTask(db, runId, taskId);
73
- if (!cur) {
74
- throw new Error(
75
- `markPending: task ${JSON.stringify(taskId)} not found in run ${JSON.stringify(runId)}`
76
- );
77
- }
78
- db.run(
79
- `UPDATE tasks
80
- SET status='pending',
81
- session_id=NULL,
82
- branch=NULL,
83
- worktree_path=NULL,
84
- started_at=NULL,
85
- finished_at=NULL,
86
- last_error=NULL
87
- WHERE run_id=? AND task_id=?`,
88
- [runId, taskId]
89
- );
90
- }
91
- function setCostUsd(db, runId, taskId, costUsd) {
92
- if (!Number.isFinite(costUsd) || costUsd < 0) {
93
- throw new RangeError(`setCostUsd: invalid cost ${costUsd}`);
94
- }
95
- db.run(
96
- "UPDATE tasks SET cost_usd=? WHERE run_id=? AND task_id=?",
97
- [costUsd, runId, taskId]
98
- );
99
- }
100
- function getTask(db, runId, taskId) {
101
- return db.query("SELECT * FROM tasks WHERE run_id=? AND task_id=?").get(runId, taskId);
102
- }
103
- function listTasks(db, runId) {
104
- return db.query("SELECT * FROM tasks WHERE run_id=? ORDER BY task_id").all(runId);
105
- }
106
- function readyTasks(db, runId) {
107
- return db.query("SELECT * FROM tasks WHERE run_id=? AND status='ready' ORDER BY task_id").all(runId);
108
- }
109
- function countByStatus(db, runId) {
110
- const rows = db.query("SELECT status, COUNT(*) as n FROM tasks WHERE run_id=? GROUP BY status").all(runId);
111
- const out = {
112
- pending: 0,
113
- ready: 0,
114
- running: 0,
115
- succeeded: 0,
116
- failed: 0,
117
- blocked: 0,
118
- aborted: 0
119
- };
120
- for (const r of rows) out[r.status] = r.n;
121
- return out;
122
- }
123
- function resetTasksForResume(db, runId) {
124
- const rows = listTasks(db, runId);
125
- const resettable = rows.filter((r) => r.status !== "succeeded");
126
- if (resettable.length === 0) return [];
127
- const stmt = db.prepare(
128
- `UPDATE tasks
129
- SET status='pending',
130
- attempts=0,
131
- session_id=NULL,
132
- last_error=NULL,
133
- started_at=NULL,
134
- finished_at=NULL,
135
- branch=NULL,
136
- worktree_path=NULL
137
- WHERE run_id=? AND task_id=? AND status != 'succeeded'`
138
- );
139
- const tx = db.transaction(() => {
140
- for (const r of resettable) stmt.run(runId, r.task_id);
141
- });
142
- tx();
143
- return resettable.map((r) => r.task_id);
144
- }
145
- function requireStatus(db, runId, taskId, expected, intended) {
146
- const row = getTask(db, runId, taskId);
147
- if (!row) {
148
- throw new Error(
149
- `task ${JSON.stringify(taskId)} not found in run ${JSON.stringify(runId)}`
150
- );
151
- }
152
- if (!expected.includes(row.status)) {
153
- throw new Error(
154
- `cannot move task ${JSON.stringify(taskId)} from ${row.status} to ${intended} (expected one of: ${expected.join(", ")})`
155
- );
156
- }
157
- }
158
-
159
- export {
160
- upsertFromPlan,
161
- markReady,
162
- markRunning,
163
- markSucceeded,
164
- markFailed,
165
- markBlocked,
166
- markAborted,
167
- markPending,
168
- setCostUsd,
169
- getTask,
170
- listTasks,
171
- readyTasks,
172
- countByStatus,
173
- resetTasksForResume
174
- };
@@ -1,67 +0,0 @@
1
- // src/pilot/state/runs.ts
2
- import { ulid } from "ulid";
3
- function createRun(db, args) {
4
- const id = ulid();
5
- const now = args.now ?? Date.now();
6
- db.run(
7
- `INSERT INTO runs (id, plan_path, plan_slug, started_at, status)
8
- VALUES (?, ?, ?, ?, 'pending')`,
9
- [id, args.planPath, args.slug, now]
10
- );
11
- void args.plan;
12
- return id;
13
- }
14
- function markRunRunning(db, runId) {
15
- const cur = getRun(db, runId);
16
- if (!cur) throw new Error(`markRunRunning: run ${JSON.stringify(runId)} not found`);
17
- if (cur.status === "running") return;
18
- if (cur.status !== "pending") {
19
- throw new Error(
20
- `markRunRunning: cannot move run ${JSON.stringify(runId)} from ${cur.status} to running`
21
- );
22
- }
23
- db.run("UPDATE runs SET status='running' WHERE id=?", [runId]);
24
- }
25
- function markRunFinished(db, runId, status, now = Date.now()) {
26
- if (status !== "completed" && status !== "aborted" && status !== "failed") {
27
- throw new Error(
28
- `markRunFinished: ${JSON.stringify(status)} is not a terminal status`
29
- );
30
- }
31
- const cur = getRun(db, runId);
32
- if (!cur) {
33
- throw new Error(`markRunFinished: run ${JSON.stringify(runId)} not found`);
34
- }
35
- db.run("UPDATE runs SET status=?, finished_at=? WHERE id=?", [status, now, runId]);
36
- }
37
- function markRunResumed(db, runId) {
38
- const cur = getRun(db, runId);
39
- if (!cur) throw new Error(`markRunResumed: run ${JSON.stringify(runId)} not found`);
40
- if (cur.status === "completed") {
41
- throw new Error(
42
- `markRunResumed: run ${JSON.stringify(runId)} is already completed; nothing to resume`
43
- );
44
- }
45
- db.run("UPDATE runs SET status='running', finished_at=NULL WHERE id=?", [runId]);
46
- }
47
- function getRun(db, runId) {
48
- const row = db.query("SELECT * FROM runs WHERE id=?").get(runId);
49
- return row;
50
- }
51
- function listRuns(db, limit = 100) {
52
- return db.query("SELECT * FROM runs ORDER BY started_at DESC LIMIT ?").all(limit);
53
- }
54
- function latestRun(db) {
55
- const row = db.query("SELECT * FROM runs ORDER BY started_at DESC LIMIT 1").get();
56
- return row;
57
- }
58
-
59
- export {
60
- createRun,
61
- markRunRunning,
62
- markRunFinished,
63
- markRunResumed,
64
- getRun,
65
- listRuns,
66
- latestRun
67
- };
@@ -1,204 +0,0 @@
1
- // src/pilot/paths.ts
2
- import { promises as fs2 } from "fs";
3
- import * as os2 from "os";
4
- import * as path2 from "path";
5
-
6
- // src/plan-paths.ts
7
- import { execFile } from "child_process";
8
- import * as fs from "fs/promises";
9
- import * as os from "os";
10
- import * as path from "path";
11
- function execFileP(file, args, opts = {}) {
12
- const { cwd, timeoutMs = 5e3 } = opts;
13
- return new Promise((resolve2, reject) => {
14
- const controller = new AbortController();
15
- const timer = setTimeout(() => controller.abort(), timeoutMs);
16
- execFile(
17
- file,
18
- args,
19
- { signal: controller.signal, cwd, encoding: "utf8" },
20
- (err, stdout) => {
21
- clearTimeout(timer);
22
- if (err) {
23
- reject(err);
24
- return;
25
- }
26
- resolve2(stdout ?? "");
27
- }
28
- );
29
- });
30
- }
31
- function expandTilde(p) {
32
- if (p === "~") return os.homedir();
33
- if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
34
- return p;
35
- }
36
- async function getRepoFolder(worktreeDir) {
37
- let stdout;
38
- try {
39
- stdout = await execFileP(
40
- "git",
41
- ["rev-parse", "--git-common-dir"],
42
- { cwd: worktreeDir }
43
- );
44
- } catch (err) {
45
- const msg = err instanceof Error ? err.message : "unknown error running `git rev-parse --git-common-dir`";
46
- throw new Error(
47
- `getRepoFolder: failed to resolve git-common-dir for ${worktreeDir}: ${msg}`
48
- );
49
- }
50
- const gitCommonDir = stdout.trim();
51
- if (!gitCommonDir) {
52
- throw new Error(
53
- `getRepoFolder: \`git rev-parse --git-common-dir\` returned empty for ${worktreeDir}`
54
- );
55
- }
56
- const absCommonDir = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(worktreeDir, gitCommonDir);
57
- const repoRoot = path.dirname(absCommonDir);
58
- return path.basename(repoRoot);
59
- }
60
- async function getPlanDir(worktreeDir) {
61
- const override = process.env.GLORIOUS_PLAN_DIR;
62
- const base = override ? expandTilde(override) : path.join(os.homedir(), ".glorious", "opencode");
63
- const repoFolder = await getRepoFolder(worktreeDir);
64
- const planDir = path.join(base, repoFolder, "plans");
65
- await fs.mkdir(planDir, { recursive: true });
66
- return planDir;
67
- }
68
- async function migratePlans(worktreeDir, planDir) {
69
- const oldDir = path.join(worktreeDir, ".agent", "plans");
70
- const marker = path.join(oldDir, ".migrated");
71
- try {
72
- await fs.stat(oldDir);
73
- } catch {
74
- return;
75
- }
76
- try {
77
- await fs.stat(marker);
78
- return;
79
- } catch {
80
- }
81
- let entries;
82
- try {
83
- entries = await fs.readdir(oldDir);
84
- } catch {
85
- return;
86
- }
87
- const planFiles = entries.filter(
88
- (name) => name.endsWith(".md") && !name.startsWith(".")
89
- );
90
- await fs.mkdir(planDir, { recursive: true });
91
- for (const name of planFiles) {
92
- const src = path.join(oldDir, name);
93
- const dst = path.join(planDir, name);
94
- let dstExists = false;
95
- try {
96
- await fs.stat(dst);
97
- dstExists = true;
98
- } catch {
99
- dstExists = false;
100
- }
101
- if (!dstExists) {
102
- await fs.rename(src, dst);
103
- continue;
104
- }
105
- const [srcBuf, dstBuf] = await Promise.all([
106
- fs.readFile(src),
107
- fs.readFile(dst)
108
- ]);
109
- if (srcBuf.equals(dstBuf)) {
110
- await fs.unlink(src);
111
- continue;
112
- }
113
- process.stderr.write(
114
- `[harness-opencode] migratePlans: conflict on ${name} \u2014 destination ${dst} exists with different content; leaving source ${src} in place. Resolve manually.
115
- `
116
- );
117
- }
118
- await fs.writeFile(marker, "");
119
- }
120
-
121
- // src/pilot/paths.ts
122
- function expandTilde2(p) {
123
- if (p === "~") return os2.homedir();
124
- if (p.startsWith("~/")) return path2.join(os2.homedir(), p.slice(2));
125
- return p;
126
- }
127
- function resolveBaseDir() {
128
- const pilotEnv = process.env.GLORIOUS_PILOT_DIR;
129
- if (pilotEnv) return expandTilde2(pilotEnv);
130
- const planEnv = process.env.GLORIOUS_PLAN_DIR;
131
- if (planEnv) {
132
- return path2.dirname(expandTilde2(planEnv));
133
- }
134
- return path2.join(os2.homedir(), ".glorious", "opencode");
135
- }
136
- async function getPilotDir(cwd) {
137
- const base = resolveBaseDir();
138
- const repoFolder = await getRepoFolder(cwd);
139
- const dir = path2.join(base, repoFolder, "pilot");
140
- await fs2.mkdir(dir, { recursive: true });
141
- return dir;
142
- }
143
- async function getPlansDir(cwd) {
144
- const pilot = await getPilotDir(cwd);
145
- const dir = path2.join(pilot, "plans");
146
- await fs2.mkdir(dir, { recursive: true });
147
- return dir;
148
- }
149
- async function getRunDir(cwd, runId) {
150
- if (!isSafeRunId(runId)) {
151
- throw new Error(
152
- `getRunDir: runId ${JSON.stringify(runId)} is not a safe filesystem segment`
153
- );
154
- }
155
- const pilot = await getPilotDir(cwd);
156
- const dir = path2.join(pilot, "runs", runId);
157
- await fs2.mkdir(dir, { recursive: true });
158
- return dir;
159
- }
160
- async function getStateDbPath(cwd, runId) {
161
- const runDir = await getRunDir(cwd, runId);
162
- return path2.join(runDir, "state.db");
163
- }
164
- async function getWorkerJsonlPath(cwd, runId, n) {
165
- const runDir = await getRunDir(cwd, runId);
166
- const workersDir = path2.join(runDir, "workers");
167
- await fs2.mkdir(workersDir, { recursive: true });
168
- const padded = n.toString().padStart(2, "0");
169
- return path2.join(workersDir, `${padded}.jsonl`);
170
- }
171
- async function getTaskJsonlPath(cwd, runId, taskId) {
172
- if (!isSafeRunId(runId)) {
173
- throw new Error(
174
- `getTaskJsonlPath: runId ${JSON.stringify(runId)} is not a safe filesystem segment`
175
- );
176
- }
177
- if (!isSafeTaskId(taskId)) {
178
- throw new Error(
179
- `getTaskJsonlPath: taskId ${JSON.stringify(taskId)} is not a safe filesystem segment`
180
- );
181
- }
182
- const runDir = await getRunDir(cwd, runId);
183
- const taskDir = path2.join(runDir, "tasks", taskId);
184
- await fs2.mkdir(taskDir, { recursive: true });
185
- return path2.join(taskDir, "session.jsonl");
186
- }
187
- function isSafeRunId(runId) {
188
- return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(runId);
189
- }
190
- function isSafeTaskId(taskId) {
191
- return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(taskId);
192
- }
193
-
194
- export {
195
- getPlanDir,
196
- migratePlans,
197
- resolveBaseDir,
198
- getPilotDir,
199
- getPlansDir,
200
- getRunDir,
201
- getStateDbPath,
202
- getWorkerJsonlPath,
203
- getTaskJsonlPath
204
- };