@glrs-dev/cli 1.2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +2 -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 +1622 -4226
  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
@@ -1,10 +1,7 @@
1
1
  import {
2
- AGENT_TIERS,
3
- createAgents,
4
2
  formatModelOverrideWarning,
5
- getStrictPrompt,
6
3
  validateModelOverride
7
- } from "./chunk-EK7K4NTV.js";
4
+ } from "./chunk-DZG4D3OH.js";
8
5
  import {
9
6
  PACKAGE_NAME,
10
7
  readOurPackageVersion,
@@ -16,35 +13,770 @@ import * as fs from "fs";
16
13
  import * as path from "path";
17
14
  import * as os from "os";
18
15
 
19
- // src/commands/index.ts
16
+ // src/agents/shared/index.ts
20
17
  import { readFileSync } from "fs";
21
18
  import { fileURLToPath } from "url";
22
19
  import { dirname, join } from "path";
23
20
  var HERE = dirname(fileURLToPath(import.meta.url));
21
+ function readMd(name) {
22
+ const candidates = [
23
+ join(HERE, name),
24
+ // dev: src/agents/shared/
25
+ join(HERE, "agents", "shared", name),
26
+ // dist: dist/ → dist/agents/shared/
27
+ join(HERE, "..", "..", "..", "src", "agents", "shared", name)
28
+ // fallback dev
29
+ ];
30
+ for (const p of candidates) {
31
+ try {
32
+ return readFileSync(p, "utf8");
33
+ } catch {
34
+ }
35
+ }
36
+ throw new Error(`Could not find shared file: ${name}`);
37
+ }
38
+ var WORKFLOW_MECHANICS_RULE = readMd("workflow-mechanics.md");
39
+
40
+ // src/agents/index.ts
41
+ import { readFileSync as readFileSync2 } from "fs";
42
+ import { fileURLToPath as fileURLToPath2 } from "url";
43
+ import { dirname as dirname2, join as join2 } from "path";
44
+ var HERE2 = dirname2(fileURLToPath2(import.meta.url));
24
45
  function readPrompt(name) {
25
46
  const candidates = [
26
- join(HERE, "prompts", name),
47
+ join2(HERE2, "prompts", name),
48
+ // dev: src/agents/prompts/
49
+ join2(HERE2, "agents", "prompts", name),
50
+ // dist: dist/ → dist/agents/prompts/
51
+ join2(HERE2, "..", "..", "src", "agents", "prompts", name)
52
+ // fallback dev
53
+ ];
54
+ for (const p of candidates) {
55
+ try {
56
+ return readFileSync2(p, "utf8");
57
+ } catch {
58
+ }
59
+ }
60
+ throw new Error(`Could not find prompt file: ${name}`);
61
+ }
62
+ var primePrompt = readPrompt("prime.md");
63
+ var planPrompt = readPrompt("plan.md");
64
+ var buildPrompt = readPrompt("build.md");
65
+ var buildOpenPrompt = readPrompt("build.open.md");
66
+ var qaReviewerPrompt = readPrompt("qa-reviewer.md");
67
+ var qaReviewerOpenPrompt = readPrompt("qa-reviewer.open.md");
68
+ var qaThoroughPrompt = readPrompt("qa-thorough.md");
69
+ var planReviewerPrompt = readPrompt("plan-reviewer.md");
70
+ var codeSearcherPrompt = readPrompt("code-searcher.md");
71
+ var gapAnalyzerPrompt = readPrompt("gap-analyzer.md");
72
+ var architectureAdvisorPrompt = readPrompt("architecture-advisor.md");
73
+ var docsMaintainerPrompt = readPrompt("docs-maintainer.md");
74
+ var libReaderPrompt = readPrompt("lib-reader.md");
75
+ var agentsMdWriterPrompt = readPrompt("agents-md-writer.md");
76
+ var pilotScopePrompt = readPrompt("pilot-scoper.md");
77
+ var pilotPlannerPrompt = readPrompt("pilot-planner.md");
78
+ var pilotBuilderPrompt = readPrompt("pilot-builder.md");
79
+ var pilotAssessorPrompt = readPrompt("pilot-assessor.md");
80
+ var researchPrompt = readPrompt("research.md");
81
+ var researchWebPrompt = readPrompt("research-web.md");
82
+ var researchLocalPrompt = readPrompt("research-local.md");
83
+ var researchAutoPrompt = readPrompt("research-auto.md");
84
+ var EXECUTOR_VARIANT_AGENTS = {
85
+ build: { reasoning: buildPrompt, strict: buildOpenPrompt },
86
+ "qa-reviewer": { reasoning: qaReviewerPrompt, strict: qaReviewerOpenPrompt },
87
+ "pilot-builder": { reasoning: pilotBuilderPrompt, strict: pilotBuilderPrompt }
88
+ };
89
+ function getStrictPrompt(agentName) {
90
+ const variants = EXECUTOR_VARIANT_AGENTS[agentName];
91
+ if (!variants) {
92
+ throw new Error(`getStrictPrompt: no strict variant registered for agent "${agentName}"`);
93
+ }
94
+ return variants.strict;
95
+ }
96
+ function stripFrontmatter(md) {
97
+ if (!md.startsWith("---")) return md;
98
+ const end = md.indexOf("\n---", 3);
99
+ if (end === -1) return md;
100
+ return md.slice(end + 4).trimStart();
101
+ }
102
+ function parseFrontmatter(md) {
103
+ if (!md.startsWith("---")) return {};
104
+ const end = md.indexOf("\n---", 3);
105
+ if (end === -1) return {};
106
+ const block = md.slice(4, end);
107
+ const result = {};
108
+ let currentKey = null;
109
+ let currentValue = [];
110
+ const flush = () => {
111
+ if (currentKey) {
112
+ result[currentKey] = currentValue.join(" ").trim();
113
+ }
114
+ };
115
+ for (const line of block.split("\n")) {
116
+ if (currentKey && (line.startsWith(" ") || line.startsWith(" "))) {
117
+ currentValue.push(line.trim());
118
+ continue;
119
+ }
120
+ const colon = line.indexOf(":");
121
+ if (colon === -1) {
122
+ flush();
123
+ currentKey = null;
124
+ currentValue = [];
125
+ continue;
126
+ }
127
+ flush();
128
+ currentKey = line.slice(0, colon).trim();
129
+ const value = line.slice(colon + 1).trim();
130
+ currentValue = value ? [value] : [];
131
+ }
132
+ flush();
133
+ return result;
134
+ }
135
+ function injectWorkflowMechanics(prompt) {
136
+ return prompt.replace("{WORKFLOW_MECHANICS_RULE}", WORKFLOW_MECHANICS_RULE);
137
+ }
138
+ function agentFromPrompt(raw, overrides = {}) {
139
+ const fm = parseFrontmatter(raw);
140
+ const body = stripFrontmatter(raw);
141
+ const prompt = injectWorkflowMechanics(body);
142
+ const base = {
143
+ description: fm["description"] ?? "",
144
+ mode: fm["mode"] ?? "subagent",
145
+ model: fm["model"] ?? void 0,
146
+ prompt
147
+ };
148
+ return { ...base, ...overrides };
149
+ }
150
+ var CORE_BASH_ALLOW_LIST = {
151
+ // File inspection — safe read-only commands the reviewers use heavily.
152
+ "ls *": "allow",
153
+ "cat *": "allow",
154
+ "head *": "allow",
155
+ "tail *": "allow",
156
+ "wc *": "allow",
157
+ "grep *": "allow",
158
+ "rg *": "allow",
159
+ "find *": "allow",
160
+ "file *": "allow",
161
+ "stat *": "allow",
162
+ "which *": "allow",
163
+ "whereis *": "allow",
164
+ "basename *": "allow",
165
+ "dirname *": "allow",
166
+ "realpath *": "allow",
167
+ "readlink *": "allow",
168
+ "diff *": "allow",
169
+ "sort *": "allow",
170
+ "uniq *": "allow",
171
+ "xxd *": "allow",
172
+ "tree *": "allow",
173
+ "date *": "allow",
174
+ "echo *": "allow",
175
+ // Git read-only subcommands (explicit rather than `git *` so we don't
176
+ // accidentally whitelist `git push` variants the destructive-deny
177
+ // table counteracts via longer-pattern matches — but clarity > trust).
178
+ "git status *": "allow",
179
+ "git log *": "allow",
180
+ "git diff *": "allow",
181
+ "git show *": "allow",
182
+ "git branch *": "allow",
183
+ "git merge-base *": "allow",
184
+ "git rev-parse *": "allow",
185
+ "git rev-list *": "allow",
186
+ "git blame *": "allow",
187
+ "git config --get *": "allow",
188
+ "git config --get": "allow",
189
+ "git remote *": "allow",
190
+ "git stash list *": "allow",
191
+ "git stash list": "allow",
192
+ "git ls-files *": "allow",
193
+ "git describe *": "allow",
194
+ "git tag *": "allow",
195
+ "git fetch *": "allow",
196
+ // Package/build tooling — the reviewers run lint/test/typecheck.
197
+ "pnpm lint *": "allow",
198
+ "pnpm test *": "allow",
199
+ "pnpm typecheck *": "allow",
200
+ "pnpm build *": "allow",
201
+ "pnpm run *": "allow",
202
+ "pnpm install *": "allow",
203
+ "pnpm --filter *": "allow",
204
+ "pnpm -w *": "allow",
205
+ "bun run *": "allow",
206
+ "bun test *": "allow",
207
+ "bun install *": "allow",
208
+ "bunx *": "allow",
209
+ "npm run *": "allow",
210
+ "npm test *": "allow",
211
+ "npx *": "allow",
212
+ "yarn *": "allow",
213
+ "tsc *": "allow",
214
+ "eslint *": "allow",
215
+ "prettier *": "allow",
216
+ "biome *": "allow",
217
+ // Our own CLI — the plan agent and qa-reviewer both call plan-check/plan-dir.
218
+ "bunx @glrs-dev/harness-plugin-opencode *": "allow",
219
+ "glrs-oc *": "allow",
220
+ // GitHub CLI — read-only gh calls are fine; destructive `gh pr merge`
221
+ // is gated at the PRIME level by human intent (user runs /ship).
222
+ "gh pr view *": "allow",
223
+ "gh pr list *": "allow",
224
+ "gh issue view *": "allow",
225
+ "gh issue list *": "allow",
226
+ "gh api *": "allow"
227
+ };
228
+ var CORE_DESTRUCTIVE_BASH_DENIES = {
229
+ "rm -rf /*": "deny",
230
+ "rm -rf ~*": "deny",
231
+ "chmod *": "deny",
232
+ "chown *": "deny",
233
+ "sudo *": "deny",
234
+ "git push --force*": "deny",
235
+ "git push -f *": "deny",
236
+ "git push * --force*": "deny",
237
+ "git push * -f": "deny",
238
+ "git push * main*": "deny",
239
+ "git push * master*": "deny",
240
+ // --force-with-lease is the safe variant — explicit allow rule sorts
241
+ // after the broad --force deny so the lease variant survives.
242
+ "git push --force-with-lease*": "allow",
243
+ "git push * --force-with-lease*": "allow"
244
+ };
245
+ var PRIME_PERMISSIONS = {
246
+ edit: "allow",
247
+ bash: {
248
+ "*": "allow",
249
+ ...CORE_BASH_ALLOW_LIST,
250
+ ...CORE_DESTRUCTIVE_BASH_DENIES,
251
+ // git clean & git reset --hard are allowed for prime because
252
+ // /fresh runs them after its own question-tool confirmation gate;
253
+ // a permission-layer prompt on top is redundant noise (see issue #54).
254
+ // BUILD keeps the stricter default (deny/ask).
255
+ "git clean *": "allow",
256
+ "git reset --hard*": "allow"
257
+ },
258
+ webfetch: "allow",
259
+ // Per-tool permissions (index signature on AgentConfig allows these)
260
+ ast_grep: "allow",
261
+ tsc_check: "allow",
262
+ eslint_check: "allow",
263
+ todo_scan: "allow",
264
+ comment_check: "allow",
265
+ question: "allow",
266
+ serena: "allow",
267
+ memory: "allow",
268
+ git: "allow",
269
+ playwright: "allow",
270
+ linear: "allow"
271
+ };
272
+ var PLAN_PERMISSIONS = {
273
+ edit: "allow",
274
+ // Plan agent is read-only aside from writing under the plan dir — but
275
+ // it does need to RESOLVE the plan dir via the `plan-dir` CLI
276
+ // subcommand (returns an absolute path derived from the worktree's
277
+ // repo-folder key; see src/plan-paths.ts and src/cli.ts). The object-
278
+ // form denies bash broadly and re-allows only `bunx
279
+ // @glrs-dev/harness-plugin-opencode plan-dir[...]`. No other bash invocation
280
+ // is permitted, so the read-only-aside-from-plans invariant holds.
281
+ bash: {
282
+ "*": "deny",
283
+ "bunx @glrs-dev/harness-plugin-opencode plan-dir": "allow",
284
+ "bunx @glrs-dev/harness-plugin-opencode plan-dir *": "allow",
285
+ "glrs-oc plan-dir": "allow",
286
+ "glrs-oc plan-dir *": "allow"
287
+ },
288
+ webfetch: "allow",
289
+ ast_grep: "deny",
290
+ tsc_check: "deny",
291
+ eslint_check: "deny",
292
+ todo_scan: "allow",
293
+ comment_check: "allow",
294
+ question: "allow",
295
+ serena: "allow",
296
+ memory: "allow",
297
+ git: "allow",
298
+ playwright: "deny",
299
+ linear: "allow"
300
+ };
301
+ var BUILD_PERMISSIONS = {
302
+ edit: "allow",
303
+ bash: {
304
+ "*": "allow",
305
+ ...CORE_BASH_ALLOW_LIST,
306
+ ...CORE_DESTRUCTIVE_BASH_DENIES,
307
+ // Build is stricter than prime on mutation: no `git clean`
308
+ // (build shouldn't wipe worktree mid-execution), and
309
+ // `git reset --hard` must prompt explicitly.
310
+ "git clean *": "deny",
311
+ "git reset --hard*": "ask"
312
+ },
313
+ webfetch: "allow",
314
+ ast_grep: "allow",
315
+ tsc_check: "allow",
316
+ eslint_check: "allow",
317
+ todo_scan: "allow",
318
+ comment_check: "allow",
319
+ question: "allow",
320
+ serena: "allow",
321
+ memory: "allow",
322
+ git: "allow",
323
+ playwright: "allow",
324
+ linear: "allow"
325
+ };
326
+ var QA_REVIEWER_PERMISSIONS = {
327
+ edit: "deny",
328
+ // Object-form bash: the scalar `"allow"` shape loses to OpenCode's
329
+ // upstream subagent-default `{bash, *, ask}` via last-match-wins (see
330
+ // the root-cause comment near PRIME_PERMISSIONS). Enumerated
331
+ // specific patterns in CORE_BASH_ALLOW_LIST sort AFTER the upstream
332
+ // wildcard ask and win for the commands they match. `"*": "allow"`
333
+ // is kept as a backstop but may still lose to the upstream rule for
334
+ // commands not in the enumerated list; those are the known blind spot.
335
+ bash: {
336
+ "*": "allow",
337
+ ...CORE_BASH_ALLOW_LIST,
338
+ ...CORE_DESTRUCTIVE_BASH_DENIES
339
+ },
340
+ webfetch: "deny",
341
+ ast_grep: "allow",
342
+ tsc_check: "allow",
343
+ eslint_check: "allow",
344
+ todo_scan: "allow",
345
+ comment_check: "allow",
346
+ question: "allow",
347
+ serena: "allow",
348
+ memory: "deny",
349
+ git: "allow",
350
+ playwright: "allow",
351
+ linear: "deny"
352
+ };
353
+ var QA_THOROUGH_PERMISSIONS = {
354
+ edit: "deny",
355
+ // Same object-form as QA_REVIEWER_PERMISSIONS — see the shape rationale
356
+ // there. qa-thorough re-runs the full suite unconditionally (per its
357
+ // prompt), so it touches the same command surface as qa-reviewer and
358
+ // needs the identical bash allow-list.
359
+ bash: {
360
+ "*": "allow",
361
+ ...CORE_BASH_ALLOW_LIST,
362
+ ...CORE_DESTRUCTIVE_BASH_DENIES
363
+ },
364
+ webfetch: "deny",
365
+ ast_grep: "allow",
366
+ tsc_check: "allow",
367
+ eslint_check: "allow",
368
+ todo_scan: "allow",
369
+ comment_check: "allow",
370
+ question: "allow",
371
+ serena: "allow",
372
+ memory: "deny",
373
+ git: "allow",
374
+ playwright: "allow",
375
+ linear: "deny"
376
+ };
377
+ var PLAN_REVIEWER_PERMISSIONS = {
378
+ edit: "deny",
379
+ bash: "deny",
380
+ webfetch: "deny",
381
+ ast_grep: "allow",
382
+ tsc_check: "deny",
383
+ eslint_check: "deny",
384
+ todo_scan: "allow",
385
+ comment_check: "allow",
386
+ question: "allow",
387
+ serena: "allow",
388
+ memory: "deny",
389
+ git: "allow",
390
+ playwright: "deny",
391
+ linear: "deny"
392
+ };
393
+ var GAP_ANALYZER_PERMISSIONS = {
394
+ edit: "deny",
395
+ bash: "deny",
396
+ webfetch: "deny",
397
+ ast_grep: "deny",
398
+ tsc_check: "deny",
399
+ eslint_check: "deny",
400
+ todo_scan: "allow",
401
+ comment_check: "allow",
402
+ question: "allow",
403
+ serena: "allow",
404
+ memory: "allow",
405
+ git: "deny",
406
+ playwright: "deny",
407
+ linear: "allow"
408
+ };
409
+ var CODE_SEARCHER_PERMISSIONS = {
410
+ edit: "deny",
411
+ bash: "deny",
412
+ webfetch: "deny",
413
+ ast_grep: "allow",
414
+ tsc_check: "deny",
415
+ eslint_check: "deny",
416
+ todo_scan: "deny",
417
+ comment_check: "deny",
418
+ question: "allow",
419
+ serena: "allow",
420
+ memory: "deny",
421
+ git: "deny",
422
+ playwright: "deny",
423
+ linear: "deny"
424
+ };
425
+ var ARCHITECTURE_ADVISOR_PERMISSIONS = {
426
+ edit: "deny",
427
+ bash: "deny",
428
+ webfetch: "deny",
429
+ ast_grep: "allow",
430
+ tsc_check: "deny",
431
+ eslint_check: "deny",
432
+ todo_scan: "allow",
433
+ comment_check: "allow",
434
+ question: "allow",
435
+ serena: "allow",
436
+ memory: "allow",
437
+ git: "allow",
438
+ playwright: "deny",
439
+ linear: "allow"
440
+ };
441
+ var LIB_READER_PERMISSIONS = {
442
+ edit: "deny",
443
+ bash: "deny",
444
+ webfetch: "deny",
445
+ ast_grep: "deny",
446
+ tsc_check: "deny",
447
+ eslint_check: "deny",
448
+ todo_scan: "deny",
449
+ comment_check: "deny",
450
+ question: "allow",
451
+ serena: "deny",
452
+ memory: "allow",
453
+ git: "deny",
454
+ playwright: "deny",
455
+ linear: "deny"
456
+ };
457
+ var AGENTS_MD_WRITER_PERMISSIONS = {
458
+ edit: "allow",
459
+ bash: "ask",
460
+ // preserve ask-semantics from frontmatter
461
+ webfetch: "deny",
462
+ ast_grep: "allow",
463
+ tsc_check: "deny",
464
+ eslint_check: "deny",
465
+ todo_scan: "allow",
466
+ comment_check: "allow",
467
+ question: "allow",
468
+ serena: "allow",
469
+ memory: "deny",
470
+ git: "allow",
471
+ playwright: "deny",
472
+ linear: "deny"
473
+ };
474
+ var RESEARCH_PERMISSIONS = {
475
+ edit: "allow",
476
+ bash: {
477
+ "*": "allow",
478
+ ...CORE_BASH_ALLOW_LIST,
479
+ ...CORE_DESTRUCTIVE_BASH_DENIES
480
+ },
481
+ webfetch: "allow",
482
+ ast_grep: "allow",
483
+ tsc_check: "deny",
484
+ eslint_check: "deny",
485
+ todo_scan: "allow",
486
+ comment_check: "allow",
487
+ question: "allow",
488
+ serena: "allow",
489
+ memory: "allow",
490
+ git: "allow",
491
+ playwright: "deny",
492
+ linear: "allow"
493
+ };
494
+ var AGENT_TIERS = {
495
+ prime: "deep",
496
+ plan: "deep",
497
+ "qa-thorough": "deep",
498
+ "architecture-advisor": "deep",
499
+ "plan-reviewer": "deep",
500
+ "gap-analyzer": "deep",
501
+ research: "deep",
502
+ "research-web": "deep",
503
+ "research-local": "deep",
504
+ "research-auto": "deep",
505
+ // Pilot v2 agents
506
+ "pilot-scoper": "mid",
507
+ "pilot-planner": "mid",
508
+ "pilot-builder": "mid-execute",
509
+ "pilot-assessor": "mid",
510
+ build: "mid-execute",
511
+ "qa-reviewer": "mid-execute",
512
+ "docs-maintainer": "mid",
513
+ "lib-reader": "mid",
514
+ "agents-md-writer": "mid",
515
+ "code-searcher": "fast"
516
+ };
517
+ var PILOT_SCOPER_PERMISSIONS = {
518
+ edit: "deny",
519
+ bash: {
520
+ "*": "deny",
521
+ "ls *": "allow",
522
+ "cat *": "allow",
523
+ "head *": "allow",
524
+ "tail *": "allow",
525
+ "wc *": "allow",
526
+ "grep *": "allow",
527
+ "rg *": "allow",
528
+ "find *": "allow",
529
+ "git status *": "allow",
530
+ "git log *": "allow",
531
+ "git diff *": "allow",
532
+ "git show *": "allow",
533
+ "git branch *": "allow",
534
+ "git rev-parse *": "allow"
535
+ },
536
+ webfetch: "deny",
537
+ ast_grep: "allow",
538
+ tsc_check: "deny",
539
+ eslint_check: "deny",
540
+ todo_scan: "allow",
541
+ comment_check: "allow",
542
+ question: "allow",
543
+ serena: "allow",
544
+ memory: "deny",
545
+ git: "allow",
546
+ playwright: "deny",
547
+ linear: "deny"
548
+ };
549
+ var PILOT_PLANNER_PERMISSIONS = {
550
+ edit: "allow",
551
+ bash: {
552
+ "*": "deny",
553
+ "ls *": "allow",
554
+ "cat *": "allow",
555
+ "head *": "allow",
556
+ "tail *": "allow",
557
+ "wc *": "allow",
558
+ "grep *": "allow",
559
+ "rg *": "allow",
560
+ "find *": "allow",
561
+ "git status *": "allow",
562
+ "git log *": "allow",
563
+ "git diff *": "allow",
564
+ "git show *": "allow",
565
+ "git branch *": "allow",
566
+ "git rev-parse *": "allow"
567
+ },
568
+ webfetch: "deny",
569
+ ast_grep: "allow",
570
+ tsc_check: "deny",
571
+ eslint_check: "deny",
572
+ todo_scan: "allow",
573
+ comment_check: "allow",
574
+ question: "deny",
575
+ serena: "allow",
576
+ memory: "deny",
577
+ git: "allow",
578
+ playwright: "deny",
579
+ linear: "deny"
580
+ };
581
+ var PILOT_BUILDER_PERMISSIONS = {
582
+ edit: "allow",
583
+ bash: {
584
+ "*": "allow",
585
+ ...CORE_BASH_ALLOW_LIST,
586
+ ...CORE_DESTRUCTIVE_BASH_DENIES,
587
+ "git commit*": "deny",
588
+ "git push*": "deny",
589
+ "git tag*": "deny",
590
+ "git checkout *": "deny",
591
+ "git switch *": "deny",
592
+ "git branch *": "deny",
593
+ "git restore --source*": "deny",
594
+ "git reset *": "deny",
595
+ "gh pr *": "deny",
596
+ "gh release *": "deny"
597
+ },
598
+ webfetch: "allow",
599
+ ast_grep: "allow",
600
+ tsc_check: "allow",
601
+ eslint_check: "allow",
602
+ todo_scan: "allow",
603
+ comment_check: "allow",
604
+ question: "deny",
605
+ serena: "allow",
606
+ memory: "deny",
607
+ git: "allow",
608
+ playwright: "deny",
609
+ linear: "deny"
610
+ };
611
+ var PILOT_ASSESSOR_PERMISSIONS = {
612
+ edit: "allow",
613
+ bash: {
614
+ "*": "allow",
615
+ ...CORE_BASH_ALLOW_LIST,
616
+ ...CORE_DESTRUCTIVE_BASH_DENIES,
617
+ "git commit*": "deny",
618
+ "git push*": "deny",
619
+ "git checkout *": "deny",
620
+ "git switch *": "deny",
621
+ "git reset *": "deny",
622
+ "gh pr *": "deny"
623
+ },
624
+ webfetch: "deny",
625
+ ast_grep: "allow",
626
+ tsc_check: "allow",
627
+ eslint_check: "allow",
628
+ todo_scan: "allow",
629
+ comment_check: "allow",
630
+ question: "deny",
631
+ serena: "allow",
632
+ memory: "deny",
633
+ git: "allow",
634
+ playwright: "allow",
635
+ linear: "deny"
636
+ };
637
+ function createAgents() {
638
+ return {
639
+ // Primary agents
640
+ prime: agentFromPrompt(primePrompt, {
641
+ description: "End-to-end PRIME (Primary Routing and Intelligence Management Entity). Takes a request from intent to ready-to-ship in one session. Default primary agent.",
642
+ mode: "primary",
643
+ model: "anthropic/claude-opus-4-7",
644
+ temperature: 0.2,
645
+ permission: PRIME_PERMISSIONS
646
+ }),
647
+ plan: agentFromPrompt(planPrompt, {
648
+ description: "Interactive planner. Orchestrates gap analysis and adversarial review. Produces a written plan in the repo-shared plan directory (resolve via `bunx @glrs-dev/harness-plugin-opencode plan-dir`).",
649
+ mode: "all",
650
+ model: "anthropic/claude-opus-4-7",
651
+ temperature: 0.3,
652
+ permission: PLAN_PERMISSIONS
653
+ }),
654
+ build: agentFromPrompt(buildPrompt, {
655
+ description: "Executes a written plan. Runs tests inline, gates completion on QA review.",
656
+ mode: "all",
657
+ model: "anthropic/claude-sonnet-4-6",
658
+ temperature: 0.1,
659
+ permission: BUILD_PERMISSIONS
660
+ }),
661
+ // Subagents — model/mode/description from frontmatter, permissions
662
+ // via overrides (see permission blocks above). docs-maintainer has no
663
+ // frontmatter permission declaration and keeps that behavior.
664
+ "qa-reviewer": agentFromPrompt(qaReviewerPrompt, {
665
+ permission: QA_REVIEWER_PERMISSIONS
666
+ }),
667
+ "qa-thorough": agentFromPrompt(qaThoroughPrompt, {
668
+ permission: QA_THOROUGH_PERMISSIONS
669
+ }),
670
+ "plan-reviewer": agentFromPrompt(planReviewerPrompt, {
671
+ permission: PLAN_REVIEWER_PERMISSIONS
672
+ }),
673
+ "code-searcher": agentFromPrompt(codeSearcherPrompt, {
674
+ permission: CODE_SEARCHER_PERMISSIONS
675
+ }),
676
+ "gap-analyzer": agentFromPrompt(gapAnalyzerPrompt, {
677
+ permission: GAP_ANALYZER_PERMISSIONS
678
+ }),
679
+ "architecture-advisor": agentFromPrompt(architectureAdvisorPrompt, {
680
+ permission: ARCHITECTURE_ADVISOR_PERMISSIONS
681
+ }),
682
+ "docs-maintainer": agentFromPrompt(docsMaintainerPrompt),
683
+ "lib-reader": agentFromPrompt(libReaderPrompt, {
684
+ permission: LIB_READER_PERMISSIONS
685
+ }),
686
+ "agents-md-writer": agentFromPrompt(agentsMdWriterPrompt, {
687
+ permission: AGENTS_MD_WRITER_PERMISSIONS
688
+ }),
689
+ // Research agent — mode:all for both primary invocation and task-tool dispatch
690
+ research: agentFromPrompt(researchPrompt, {
691
+ description: "Research orchestrator \u2014 decomposes a research query into parallel workstreams, dispatches research skills (research / research-web / research-local / research-auto) as subagents, reviews findings for gaps, iterates, and synthesizes. Use when the user asks to investigate, explore, deep-dive, or understand a complex topic that needs multiple workstreams.",
692
+ mode: "all",
693
+ model: "anthropic/claude-opus-4-7",
694
+ temperature: 0.3,
695
+ permission: RESEARCH_PERMISSIONS
696
+ }),
697
+ // Research subagents — thin shims that load the bundled skills
698
+ "research-web": agentFromPrompt(researchWebPrompt, {
699
+ description: "Research orchestrator subagent \u2014 Multi-agent web research orchestrator. Decomposes a research question into parallel agent workstreams, launches them, monitors progress, and synthesizes results. Use when user says 'research this topic', 'I need to understand', 'deep dive into', 'investigate the market for', 'what do we know about'. Provide the research topic and context.",
700
+ mode: "all",
701
+ model: "anthropic/claude-opus-4-7",
702
+ temperature: 0.3,
703
+ permission: RESEARCH_PERMISSIONS
704
+ }),
705
+ "research-local": agentFromPrompt(researchLocalPrompt, {
706
+ description: "Research orchestrator subagent \u2014 Deep codebase research using parallel Explore subagents. Decomposes a question about the local codebase into research tasks, launches parallel explorations, reviews for gaps, iterates, and synthesizes findings with specific file paths and line numbers. Use when user says 'how does X work in this codebase', 'where is Y implemented', 'trace the data flow for Z', 'what patterns does this repo use', 'explain the architecture of'. Provide the research topic as arguments.",
707
+ mode: "all",
708
+ model: "anthropic/claude-opus-4-7",
709
+ temperature: 0.3,
710
+ permission: RESEARCH_PERMISSIONS
711
+ }),
712
+ "research-auto": agentFromPrompt(researchAutoPrompt, {
713
+ description: "Research orchestrator subagent \u2014 Autonomous experimentation skill. Agent interviews the user, sets up a lab, then explores freely (think, test, reflect) until stopped or a target is hit. Works for any domain where you can measure or evaluate a result. Use when user says 'optimize this', 'experiment with', 'find the best approach', 'iterate on', 'research mode'. Do NOT use for binary validation tests (use /spec-lab instead). Based on ResearcherSkill v1.4.4 by krzysztofdudek.",
714
+ mode: "all",
715
+ model: "anthropic/claude-opus-4-7",
716
+ temperature: 0.3,
717
+ permission: RESEARCH_PERMISSIONS
718
+ }),
719
+ // Pilot v2 agents (SPEAR-based autonomous execution)
720
+ "pilot-scoper": agentFromPrompt(pilotScopePrompt, {
721
+ 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.",
722
+ mode: "subagent",
723
+ model: "anthropic/claude-sonnet-4-6",
724
+ temperature: 0.3,
725
+ permission: PILOT_SCOPER_PERMISSIONS
726
+ }),
727
+ "pilot-planner": agentFromPrompt(pilotPlannerPrompt, {
728
+ description: "Pilot v2 planning agent. Reads scope.json, surveys the codebase, and produces a plan.json with an ordered task list.",
729
+ mode: "subagent",
730
+ model: "anthropic/claude-sonnet-4-6",
731
+ temperature: 0.2,
732
+ permission: PILOT_PLANNER_PERMISSIONS
733
+ }),
734
+ "pilot-builder": agentFromPrompt(pilotBuilderPrompt, {
735
+ description: "Pilot v2 builder agent. Executes a single task from the plan. Makes code changes, runs verify commands, and signals completion.",
736
+ mode: "subagent",
737
+ model: "anthropic/claude-sonnet-4-6",
738
+ temperature: 0.1,
739
+ permission: PILOT_BUILDER_PERMISSIONS
740
+ }),
741
+ "pilot-assessor": agentFromPrompt(pilotAssessorPrompt, {
742
+ description: "Pilot v2 assessor agent. Evaluates completed work against acceptance criteria, runs deployment-risk reflection, and produces an assessment report.",
743
+ mode: "subagent",
744
+ model: "anthropic/claude-sonnet-4-6",
745
+ temperature: 0.2,
746
+ permission: PILOT_ASSESSOR_PERMISSIONS
747
+ })
748
+ };
749
+ }
750
+
751
+ // src/commands/index.ts
752
+ import { readFileSync as readFileSync3 } from "fs";
753
+ import { fileURLToPath as fileURLToPath3 } from "url";
754
+ import { dirname as dirname3, join as join3 } from "path";
755
+ var HERE3 = dirname3(fileURLToPath3(import.meta.url));
756
+ function readPrompt2(name) {
757
+ const candidates = [
758
+ join3(HERE3, "prompts", name),
27
759
  // dev: src/commands/prompts/
28
- join(HERE, "commands", "prompts", name),
760
+ join3(HERE3, "commands", "prompts", name),
29
761
  // dist: dist/ → dist/commands/prompts/
30
- join(HERE, "..", "..", "src", "commands", "prompts", name)
762
+ join3(HERE3, "..", "..", "src", "commands", "prompts", name)
31
763
  // fallback dev
32
764
  ];
33
765
  for (const p of candidates) {
34
766
  try {
35
- return readFileSync(p, "utf8");
767
+ return readFileSync3(p, "utf8");
36
768
  } catch {
37
769
  }
38
770
  }
39
771
  throw new Error(`Could not find command prompt: ${name}`);
40
772
  }
41
- var autopilotPrompt = readPrompt("autopilot.md");
42
- var shipPrompt = readPrompt("ship.md");
43
- var reviewPrompt = readPrompt("review.md");
44
- var initDeepPrompt = readPrompt("init-deep.md");
45
- var researchPrompt = readPrompt("research.md");
46
- var freshPrompt = readPrompt("fresh.md");
47
- var costsPrompt = readPrompt("costs.md");
773
+ var autopilotPrompt = readPrompt2("autopilot.md");
774
+ var shipPrompt = readPrompt2("ship.md");
775
+ var reviewPrompt = readPrompt2("review.md");
776
+ var initDeepPrompt = readPrompt2("init-deep.md");
777
+ var researchPrompt2 = readPrompt2("research.md");
778
+ var freshPrompt = readPrompt2("fresh.md");
779
+ var costsPrompt = readPrompt2("costs.md");
48
780
  function createCommands() {
49
781
  return {
50
782
  autopilot: {
@@ -64,7 +796,7 @@ function createCommands() {
64
796
  description: "Generate hierarchical AGENTS.md files for the current repo."
65
797
  },
66
798
  research: {
67
- template: researchPrompt,
799
+ template: researchPrompt2,
68
800
  description: "Deep codebase exploration via parallel subagents."
69
801
  },
70
802
  fresh: {
@@ -127,11 +859,11 @@ function createMcpConfig() {
127
859
  }
128
860
 
129
861
  // src/skills/paths.ts
130
- import { fileURLToPath as fileURLToPath2 } from "url";
131
- import { dirname as dirname2, join as join2 } from "path";
862
+ import { fileURLToPath as fileURLToPath4 } from "url";
863
+ import { dirname as dirname4, join as join4 } from "path";
132
864
  function getSkillsRoot() {
133
- const here = dirname2(fileURLToPath2(import.meta.url));
134
- return join2(here, "skills");
865
+ const here = dirname4(fileURLToPath4(import.meta.url));
866
+ return join4(here, "skills");
135
867
  }
136
868
 
137
869
  // src/config-hook.ts
@@ -1411,141 +2143,74 @@ var plugin3 = async () => {
1411
2143
  var cost_tracker_default = plugin3;
1412
2144
 
1413
2145
  // src/plugins/pilot-plugin.ts
1414
- import * as path5 from "path";
1415
- var PILOT_SESSION_TITLE_PREFIX = "pilot/";
1416
- var FORBIDDEN_BUILDER_BASH_PREFIXES = [
1417
- "git commit",
1418
- "git push",
1419
- "git tag",
1420
- "git checkout ",
1421
- "git switch ",
1422
- "git branch",
1423
- "git restore --source",
1424
- "git reset",
1425
- "gh pr ",
1426
- "gh release "
2146
+ var PILOT_TITLE_PREFIX = "pilot/";
2147
+ var BUILDER_DENIED_PATTERNS = [
2148
+ /^git\s+commit/,
2149
+ /^git\s+push/,
2150
+ /^git\s+tag/,
2151
+ /^git\s+checkout\s/,
2152
+ /^git\s+switch\s/,
2153
+ /^git\s+branch\s/,
2154
+ /^git\s+restore\s+--source/,
2155
+ /^git\s+reset\s/,
2156
+ /^gh\s+pr\s/,
2157
+ /^gh\s+release\s/
1427
2158
  ];
1428
- var EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "multiedit"]);
1429
- var plugin4 = async ({ client }) => {
1430
- const sessionCache = /* @__PURE__ */ new Map();
2159
+ var sessionCache = /* @__PURE__ */ new Map();
2160
+ async function getSessionPhase(client, sessionId) {
2161
+ if (sessionCache.has(sessionId)) {
2162
+ return sessionCache.get(sessionId);
2163
+ }
2164
+ try {
2165
+ const session = await client.session.get({ sessionID: sessionId });
2166
+ const title = session.title ?? "";
2167
+ if (!title.startsWith(PILOT_TITLE_PREFIX)) return null;
2168
+ const parts = title.slice(PILOT_TITLE_PREFIX.length).split("/");
2169
+ if (parts.length < 2) return null;
2170
+ const [workflowId, phase] = parts;
2171
+ const result = { phase, workflowId };
2172
+ sessionCache.set(sessionId, result);
2173
+ return result;
2174
+ } catch {
2175
+ return null;
2176
+ }
2177
+ }
2178
+ var pilotPlugin = async (input) => {
1431
2179
  return {
1432
- "tool.execute.before": async (input, output) => {
1433
- const info = await classifySession(client, sessionCache, input.sessionID);
1434
- if (info.kind === "pilot-builder") {
1435
- if (input.tool === "bash") {
1436
- enforceBuilderBashDeny(output.args);
1437
- }
1438
- } else if (info.kind === "pilot-planner") {
1439
- if (EDIT_TOOLS.has(input.tool)) {
1440
- await enforcePlannerEditScope(output.args, info.plansDir);
2180
+ "tool.execute.before": async (toolInput, _output) => {
2181
+ const sessionId = toolInput.sessionID;
2182
+ if (!sessionId) return;
2183
+ const sessionInfo = await getSessionPhase(input.client, sessionId);
2184
+ if (!sessionInfo) return;
2185
+ const { phase } = sessionInfo;
2186
+ const toolName = toolInput.tool ?? "";
2187
+ const args = toolInput.args ?? {};
2188
+ if (phase === "execute") {
2189
+ if (toolName === "bash") {
2190
+ const cmd = String(args["command"] ?? "").trim();
2191
+ for (const pattern of BUILDER_DENIED_PATTERNS) {
2192
+ if (pattern.test(cmd)) {
2193
+ throw new Error(
2194
+ `pilot-builder: "${cmd}" is not allowed. The orchestrator handles commits and pushes after verify passes.`
2195
+ );
2196
+ }
2197
+ }
1441
2198
  }
1442
2199
  }
1443
2200
  }
1444
2201
  };
1445
2202
  };
1446
- var pilot_plugin_default = plugin4;
1447
- async function classifySession(client, cache, sessionID) {
1448
- const cached = cache.get(sessionID);
1449
- if (cached !== void 0) return cached;
1450
- let title = "";
1451
- let directory = "";
1452
- try {
1453
- const r = await client.session.get({ path: { id: sessionID } });
1454
- const data = r.data;
1455
- title = data?.title ?? "";
1456
- directory = data?.directory ?? "";
1457
- } catch {
1458
- const v2 = { kind: "non-pilot" };
1459
- cache.set(sessionID, v2);
1460
- return v2;
1461
- }
1462
- if (title.startsWith(PILOT_SESSION_TITLE_PREFIX)) {
1463
- const rest = title.slice(PILOT_SESSION_TITLE_PREFIX.length);
1464
- const slash = rest.indexOf("/");
1465
- if (slash > 0) {
1466
- const runId = rest.slice(0, slash);
1467
- const taskId = rest.slice(slash + 1);
1468
- const v2 = { kind: "pilot-builder", runId, taskId };
1469
- cache.set(sessionID, v2);
1470
- return v2;
1471
- }
1472
- }
1473
- const plansDir = inferPlannerPlansDir(directory);
1474
- if (plansDir !== null) {
1475
- const v2 = { kind: "pilot-planner", plansDir };
1476
- cache.set(sessionID, v2);
1477
- return v2;
1478
- }
1479
- const v = { kind: "non-pilot" };
1480
- cache.set(sessionID, v);
1481
- return v;
1482
- }
1483
- function inferPlannerPlansDir(directory) {
1484
- if (directory.length === 0) return null;
1485
- const norm = directory.replace(/[\\/]+$/, "");
1486
- const sepRegex = /[\\/]/;
1487
- const parts = norm.split(sepRegex);
1488
- if (parts.length < 2) return null;
1489
- const last = parts[parts.length - 1];
1490
- const prev = parts[parts.length - 2];
1491
- if (last === "plans" && prev === "pilot") return norm;
1492
- return null;
1493
- }
1494
- function enforceBuilderBashDeny(args) {
1495
- const command = extractBashCommand(args);
1496
- if (command === null) return;
1497
- const trimmed = command.trimStart();
1498
- for (const prefix of FORBIDDEN_BUILDER_BASH_PREFIXES) {
1499
- if (trimmed.startsWith(prefix) || // Exact match for `git branch` (no trailing space) — the prefix
1500
- // already has no trailing space; covered.
1501
- trimmed === prefix.trimEnd()) {
1502
- throw new Error(
1503
- `pilot-plugin: pilot-builder is not permitted to run \`${prefix.trim()}\` commands (the worker handles commits/pushes/branches). If this is the right thing to do, respond with STOP: <reason>.`
1504
- );
1505
- }
1506
- }
1507
- }
1508
- function extractBashCommand(args) {
1509
- if (typeof args !== "object" || args === null) return null;
1510
- const o = args;
1511
- if (typeof o.command === "string") return o.command;
1512
- if (typeof o.cmd === "string") return o.cmd;
1513
- if (typeof o.body === "object" && o.body !== null && typeof o.body.command === "string") {
1514
- return o.body.command;
1515
- }
1516
- return null;
1517
- }
1518
- async function enforcePlannerEditScope(args, plansDir) {
1519
- const target = extractTargetPath(args);
1520
- if (target === null) return;
1521
- const abs = path5.isAbsolute(target) ? target : path5.resolve(plansDir, target);
1522
- const normTarget = path5.normalize(abs);
1523
- const normPlans = path5.normalize(plansDir).replace(/[\\/]+$/, "");
1524
- if (normTarget === normPlans || normTarget.startsWith(normPlans + path5.sep) || normTarget.startsWith(normPlans + "/")) {
1525
- return;
1526
- }
1527
- throw new Error(
1528
- `pilot-plugin: pilot-planner is restricted to the plans directory (${plansDir}). The path ${JSON.stringify(target)} is outside scope. Save your YAML plan inside the plans dir.`
1529
- );
1530
- }
1531
- function extractTargetPath(args) {
1532
- if (typeof args !== "object" || args === null) return null;
1533
- const o = args;
1534
- if (typeof o.filePath === "string") return o.filePath;
1535
- if (typeof o.path === "string") return o.path;
1536
- if (typeof o.file === "string") return o.file;
1537
- return null;
1538
- }
2203
+ var pilot_plugin_default = pilotPlugin;
1539
2204
 
1540
2205
  // src/plugins/tool-hooks.ts
1541
2206
  import * as crypto from "crypto";
1542
2207
  import * as fs5 from "fs";
1543
- import * as path6 from "path";
2208
+ import * as path5 from "path";
1544
2209
  import * as os3 from "os";
1545
2210
  import { execFile as execFileCb2 } from "child_process";
1546
2211
  import { promisify as promisify7 } from "util";
1547
2212
  var exec6 = promisify7(execFileCb2);
1548
- var EDIT_TOOLS2 = /* @__PURE__ */ new Set(["edit", "write", "patch", "multiedit"]);
2213
+ var EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "multiedit"]);
1549
2214
  var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
1550
2215
  var DEFAULT_BACKPRESSURE_THRESHOLD = 6e3;
1551
2216
  var DEFAULT_BACKPRESSURE_HEAD = 300;
@@ -1627,8 +2292,8 @@ function resolveConfig(config, pluginOptions) {
1627
2292
  };
1628
2293
  }
1629
2294
  function getToolOutputDir() {
1630
- const stateHome = process.env["XDG_STATE_HOME"] || path6.join(os3.homedir(), ".local", "state");
1631
- return path6.join(stateHome, "harness-opencode", "tool-output");
2295
+ const stateHome = process.env["XDG_STATE_HOME"] || path5.join(os3.homedir(), ".local", "state");
2296
+ return path5.join(stateHome, "harness-opencode", "tool-output");
1632
2297
  }
1633
2298
  function hashContent(content) {
1634
2299
  return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
@@ -1661,9 +2326,9 @@ async function resolveSessionDir(client, sess, sessionID) {
1661
2326
  }
1662
2327
  function isUnderToolOutputDir(filePath) {
1663
2328
  try {
1664
- const abs = path6.resolve(filePath);
1665
- const spillDir = path6.resolve(getToolOutputDir());
1666
- return abs === spillDir || abs.startsWith(spillDir + path6.sep);
2329
+ const abs = path5.resolve(filePath);
2330
+ const spillDir = path5.resolve(getToolOutputDir());
2331
+ return abs === spillDir || abs.startsWith(spillDir + path5.sep);
1667
2332
  } catch {
1668
2333
  return false;
1669
2334
  }
@@ -1698,7 +2363,7 @@ function applyBackpressure(cfg, toolName, callID, output, args) {
1698
2363
  try {
1699
2364
  const dir = getToolOutputDir();
1700
2365
  fs5.mkdirSync(dir, { recursive: true });
1701
- diskPath = path6.join(dir, `${callID}.txt`);
2366
+ diskPath = path5.join(dir, `${callID}.txt`);
1702
2367
  fs5.writeFileSync(diskPath, text);
1703
2368
  } catch {
1704
2369
  }
@@ -1745,7 +2410,7 @@ ${tail}`;
1745
2410
  }
1746
2411
  async function runPostEditVerify(cfg, client, sess, sessionID, filePath, output) {
1747
2412
  if (!cfg.enabled) return;
1748
- const ext = path6.extname(filePath).toLowerCase();
2413
+ const ext = path5.extname(filePath).toLowerCase();
1749
2414
  if (!TS_EXTENSIONS.has(ext)) return;
1750
2415
  const now = Date.now();
1751
2416
  if (now - sess.lastVerifyTs < 2e3) return;
@@ -1778,17 +2443,17 @@ ${String(stderr)}`;
1778
2443
  }
1779
2444
  if (!raw.trim()) return;
1780
2445
  const errors = parseTscOutput(raw);
1781
- const normPath = path6.resolve(cwd, filePath);
2446
+ const normPath = path5.resolve(cwd, filePath);
1782
2447
  const fileErrors = errors.filter((e) => {
1783
- const errPath = path6.isAbsolute(e.file) ? e.file : path6.resolve(cwd, e.file);
1784
- return path6.normalize(errPath) === path6.normalize(normPath);
2448
+ const errPath = path5.isAbsolute(e.file) ? e.file : path5.resolve(cwd, e.file);
2449
+ return path5.normalize(errPath) === path5.normalize(normPath);
1785
2450
  });
1786
2451
  if (fileErrors.length === 0) return;
1787
2452
  const { rows } = dedupeAndCap(fileErrors, VERIFY_MAX_ERRORS);
1788
2453
  const lines = rows.map(formatRow);
1789
2454
  output.output += `
1790
2455
 
1791
- --- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${path6.basename(filePath)}) ---
2456
+ --- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${path5.basename(filePath)}) ---
1792
2457
  ` + lines.join("\n") + `
1793
2458
  --- Fix these before proceeding ---`;
1794
2459
  } catch {
@@ -1802,7 +2467,7 @@ function checkEditLoop(cfg, sess, filePath, output) {
1802
2467
  output.output += `
1803
2468
 
1804
2469
  --- LOOP WARNING ---
1805
- You've edited ${path6.basename(filePath)} ${count} times this session. Consider reconsidering your approach \u2014 are you stuck in a loop? Step back and think about whether a different strategy would be more effective.
2470
+ You've edited ${path5.basename(filePath)} ${count} times this session. Consider reconsidering your approach \u2014 are you stuck in a loop? Step back and think about whether a different strategy would be more effective.
1806
2471
  ---`;
1807
2472
  }
1808
2473
  }
@@ -1820,7 +2485,7 @@ function checkReadDedup(cfg, sess, filePath, output) {
1820
2485
  }
1821
2486
  var pluginConfig = null;
1822
2487
  var storedPluginOptions;
1823
- var plugin5 = async ({ client }, options) => {
2488
+ var plugin4 = async ({ client }, options) => {
1824
2489
  storedPluginOptions = options;
1825
2490
  return {
1826
2491
  config: async (config) => {
@@ -1835,7 +2500,7 @@ var plugin5 = async ({ client }, options) => {
1835
2500
  const deduped = checkReadDedup(cfg.readDedup, sess, fp, output);
1836
2501
  if (deduped) return;
1837
2502
  }
1838
- if (EDIT_TOOLS2.has(toolName)) {
2503
+ if (EDIT_TOOLS.has(toolName)) {
1839
2504
  const fp = extractFilePath(input.args);
1840
2505
  if (fp) {
1841
2506
  checkEditLoop(cfg.loopDetection, sess, fp, output);
@@ -1853,7 +2518,7 @@ var plugin5 = async ({ client }, options) => {
1853
2518
  }
1854
2519
  };
1855
2520
  };
1856
- var tool_hooks_default = plugin5;
2521
+ var tool_hooks_default = plugin4;
1857
2522
 
1858
2523
  // src/plugins/telemetry.ts
1859
2524
  import { extname as extname2 } from "path";
@@ -1861,19 +2526,19 @@ import { extname as extname2 } from "path";
1861
2526
  // src/telemetry.ts
1862
2527
  import { createHash as createHash2, randomUUID } from "crypto";
1863
2528
  import { homedir as homedir4 } from "os";
1864
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync } from "fs";
1865
- import { join as join8 } from "path";
2529
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync } from "fs";
2530
+ import { join as join10 } from "path";
1866
2531
  var APP_KEY = "A-US-3617699429";
1867
2532
  var ENDPOINT = "https://us.aptabase.com/api/v0/event";
1868
2533
  var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
1869
- var PKG_VERSION = true ? "1.2.0" : "dev";
2534
+ var PKG_VERSION = true ? "2.0.0" : "dev";
1870
2535
  var DISABLED = process.env.HARNESS_OPENCODE_TELEMETRY === "0" || process.env.HARNESS_OPENCODE_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1" || process.env.CI === "true";
1871
2536
  var SESSION_ID = randomUUID();
1872
2537
  function getInstallId() {
1873
- const dir = join8(homedir4(), ".config", "harness-opencode");
1874
- const file = join8(dir, "install-id");
2538
+ const dir = join10(homedir4(), ".config", "harness-opencode");
2539
+ const file = join10(dir, "install-id");
1875
2540
  try {
1876
- if (existsSync(file)) return readFileSync3(file, "utf8").trim();
2541
+ if (existsSync(file)) return readFileSync5(file, "utf8").trim();
1877
2542
  mkdirSync3(dir, { recursive: true });
1878
2543
  const id = createHash2("sha256").update(randomUUID()).digest("hex").slice(0, 16);
1879
2544
  writeFileSync3(file, id, { mode: 384 });
@@ -1938,7 +2603,7 @@ function track(eventName, props = {}) {
1938
2603
  }
1939
2604
 
1940
2605
  // src/plugins/telemetry.ts
1941
- var plugin6 = async () => {
2606
+ var plugin5 = async () => {
1942
2607
  if (DISABLED) {
1943
2608
  return {};
1944
2609
  }
@@ -2000,7 +2665,7 @@ var plugin6 = async () => {
2000
2665
  }
2001
2666
  };
2002
2667
  };
2003
- var telemetry_default = plugin6;
2668
+ var telemetry_default = plugin5;
2004
2669
 
2005
2670
  // src/index.ts
2006
2671
  var BUNDLED_VERSION = readOurPackageVersion(import.meta.url);
@@ -2045,7 +2710,7 @@ async function checkForUpdate(client) {
2045
2710
  } catch {
2046
2711
  }
2047
2712
  }
2048
- var plugin7 = async (input, options) => {
2713
+ var plugin6 = async (input, options) => {
2049
2714
  const pluginOptions = options ?? {};
2050
2715
  loadDotenv(input.directory);
2051
2716
  checkForUpdate(input.client).catch(() => {
@@ -2102,7 +2767,7 @@ var plugin7 = async (input, options) => {
2102
2767
  }
2103
2768
  return hooks;
2104
2769
  };
2105
- var src_default = plugin7;
2770
+ var src_default = plugin6;
2106
2771
  export {
2107
2772
  src_default as default
2108
2773
  };