@glrs-dev/cli 2.0.1 → 2.2.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 +8 -0
- package/dist/chunk-SB3MLROC.js +113 -0
- package/dist/cli.js +21 -0
- package/dist/lib/auto-update.d.ts +23 -0
- package/dist/lib/auto-update.js +7 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/build.md +18 -4
- package/dist/vendor/harness-opencode/dist/agents/prompts/build.open.md +18 -4
- package/dist/vendor/harness-opencode/dist/agents/prompts/{qa-thorough.md → code-reviewer-thorough.md} +34 -19
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer.md +80 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/code-reviewer.open.md +68 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/gap-analyzer.md +2 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan-reviewer.md +3 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +23 -4
- package/dist/vendor/harness-opencode/dist/agents/prompts/prime.md +146 -87
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-auto.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-local.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research-web.md +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/research.md +2 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.md +54 -0
- package/dist/vendor/harness-opencode/dist/agents/prompts/spec-reviewer.open.md +57 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/index.ts +1 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/ui-evaluation-ladder.md +50 -0
- package/dist/vendor/harness-opencode/dist/agents/shared/workflow-mechanics.md +5 -5
- package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +80 -0
- package/dist/vendor/harness-opencode/dist/{chunk-VJUETC6A.js → chunk-PDMXYZM4.js} +53 -1
- package/dist/vendor/harness-opencode/dist/cli.js +1333 -1646
- package/dist/vendor/harness-opencode/dist/commands/prompts/fresh.md +27 -24
- package/dist/vendor/harness-opencode/dist/commands/prompts/review.md +3 -3
- package/dist/vendor/harness-opencode/dist/commands/prompts/ship.md +2 -0
- package/dist/vendor/harness-opencode/dist/index.js +106 -627
- package/dist/vendor/harness-opencode/dist/skills/adversarial-review-rubric/SKILL.md +47 -0
- package/dist/vendor/harness-opencode/dist/skills/code-quality/SKILL.md +1 -1
- package/dist/vendor/harness-opencode/dist/skills/root-cause-diagnosis/SKILL.md +24 -0
- package/dist/vendor/harness-opencode/dist/skills/spear-protocol/SKILL.md +166 -0
- package/dist/vendor/harness-opencode/package.json +1 -1
- package/package.json +1 -1
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-assessor.md +0 -77
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-builder.md +0 -40
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-planner.md +0 -56
- package/dist/vendor/harness-opencode/dist/agents/prompts/pilot-scoper.md +0 -58
- package/dist/vendor/harness-opencode/dist/agents/prompts/qa-reviewer.md +0 -68
- package/dist/vendor/harness-opencode/dist/agents/prompts/qa-reviewer.open.md +0 -58
- package/dist/vendor/harness-opencode/dist/chunk-6CZPRUMJ.js +0 -869
- package/dist/vendor/harness-opencode/dist/chunk-DZG4D3OH.js +0 -54
- package/dist/vendor/harness-opencode/dist/chunk-OYRKOEXK.js +0 -88
- package/dist/vendor/harness-opencode/dist/commands/prompts/autopilot.md +0 -96
- package/dist/vendor/harness-opencode/dist/install-6775ZBDG.js +0 -13
- package/dist/vendor/harness-opencode/dist/paths-WZ23ZQOV.js +0 -18
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatModelOverrideWarning,
|
|
3
|
-
validateModelOverride
|
|
4
|
-
} from "./chunk-DZG4D3OH.js";
|
|
5
1
|
import {
|
|
6
2
|
PACKAGE_NAME,
|
|
3
|
+
formatModelOverrideWarning,
|
|
7
4
|
readOurPackageVersion,
|
|
8
|
-
refreshPluginCache
|
|
9
|
-
|
|
5
|
+
refreshPluginCache,
|
|
6
|
+
validateModelOverride
|
|
7
|
+
} from "./chunk-PDMXYZM4.js";
|
|
10
8
|
|
|
11
9
|
// src/config-hook.ts
|
|
12
10
|
import * as fs from "fs";
|
|
@@ -36,6 +34,7 @@ function readMd(name) {
|
|
|
36
34
|
throw new Error(`Could not find shared file: ${name}`);
|
|
37
35
|
}
|
|
38
36
|
var WORKFLOW_MECHANICS_RULE = readMd("workflow-mechanics.md");
|
|
37
|
+
var UI_EVALUATION_LADDER = readMd("ui-evaluation-ladder.md");
|
|
39
38
|
|
|
40
39
|
// src/agents/index.ts
|
|
41
40
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -63,9 +62,11 @@ var primePrompt = readPrompt("prime.md");
|
|
|
63
62
|
var planPrompt = readPrompt("plan.md");
|
|
64
63
|
var buildPrompt = readPrompt("build.md");
|
|
65
64
|
var buildOpenPrompt = readPrompt("build.open.md");
|
|
66
|
-
var
|
|
67
|
-
var
|
|
68
|
-
var
|
|
65
|
+
var specReviewerPrompt = readPrompt("spec-reviewer.md");
|
|
66
|
+
var specReviewerOpenPrompt = readPrompt("spec-reviewer.open.md");
|
|
67
|
+
var codeReviewerPrompt = readPrompt("code-reviewer.md");
|
|
68
|
+
var codeReviewerOpenPrompt = readPrompt("code-reviewer.open.md");
|
|
69
|
+
var codeReviewerThoroughPrompt = readPrompt("code-reviewer-thorough.md");
|
|
69
70
|
var planReviewerPrompt = readPrompt("plan-reviewer.md");
|
|
70
71
|
var codeSearcherPrompt = readPrompt("code-searcher.md");
|
|
71
72
|
var gapAnalyzerPrompt = readPrompt("gap-analyzer.md");
|
|
@@ -73,18 +74,14 @@ var architectureAdvisorPrompt = readPrompt("architecture-advisor.md");
|
|
|
73
74
|
var docsMaintainerPrompt = readPrompt("docs-maintainer.md");
|
|
74
75
|
var libReaderPrompt = readPrompt("lib-reader.md");
|
|
75
76
|
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
77
|
var researchPrompt = readPrompt("research.md");
|
|
81
78
|
var researchWebPrompt = readPrompt("research-web.md");
|
|
82
79
|
var researchLocalPrompt = readPrompt("research-local.md");
|
|
83
80
|
var researchAutoPrompt = readPrompt("research-auto.md");
|
|
84
81
|
var EXECUTOR_VARIANT_AGENTS = {
|
|
85
82
|
build: { reasoning: buildPrompt, strict: buildOpenPrompt },
|
|
86
|
-
"
|
|
87
|
-
"
|
|
83
|
+
"spec-reviewer": { reasoning: specReviewerPrompt, strict: specReviewerOpenPrompt },
|
|
84
|
+
"code-reviewer": { reasoning: codeReviewerPrompt, strict: codeReviewerOpenPrompt }
|
|
88
85
|
};
|
|
89
86
|
function getStrictPrompt(agentName) {
|
|
90
87
|
const variants = EXECUTOR_VARIANT_AGENTS[agentName];
|
|
@@ -135,10 +132,13 @@ function parseFrontmatter(md) {
|
|
|
135
132
|
function injectWorkflowMechanics(prompt) {
|
|
136
133
|
return prompt.replace("{WORKFLOW_MECHANICS_RULE}", WORKFLOW_MECHANICS_RULE);
|
|
137
134
|
}
|
|
135
|
+
function injectUIEvaluationLadder(prompt) {
|
|
136
|
+
return prompt.replace("{UI_EVALUATION_LADDER}", UI_EVALUATION_LADDER);
|
|
137
|
+
}
|
|
138
138
|
function agentFromPrompt(raw, overrides = {}) {
|
|
139
139
|
const fm = parseFrontmatter(raw);
|
|
140
140
|
const body = stripFrontmatter(raw);
|
|
141
|
-
const prompt = injectWorkflowMechanics(body);
|
|
141
|
+
const prompt = injectUIEvaluationLadder(injectWorkflowMechanics(body));
|
|
142
142
|
const base = {
|
|
143
143
|
description: fm["description"] ?? "",
|
|
144
144
|
mode: fm["mode"] ?? "subagent",
|
|
@@ -214,7 +214,7 @@ var CORE_BASH_ALLOW_LIST = {
|
|
|
214
214
|
"eslint *": "allow",
|
|
215
215
|
"prettier *": "allow",
|
|
216
216
|
"biome *": "allow",
|
|
217
|
-
// Our own CLI — the plan agent and
|
|
217
|
+
// Our own CLI — the plan agent and assessor both call plan-check/plan-dir.
|
|
218
218
|
"bunx @glrs-dev/harness-plugin-opencode *": "allow",
|
|
219
219
|
"glrs-oc *": "allow",
|
|
220
220
|
// GitHub CLI — read-only gh calls are fine; destructive `gh pr merge`
|
|
@@ -295,7 +295,7 @@ var PLAN_PERMISSIONS = {
|
|
|
295
295
|
serena: "allow",
|
|
296
296
|
memory: "allow",
|
|
297
297
|
git: "allow",
|
|
298
|
-
playwright: "
|
|
298
|
+
playwright: "allow",
|
|
299
299
|
linear: "allow"
|
|
300
300
|
};
|
|
301
301
|
var BUILD_PERMISSIONS = {
|
|
@@ -323,15 +323,28 @@ var BUILD_PERMISSIONS = {
|
|
|
323
323
|
playwright: "allow",
|
|
324
324
|
linear: "allow"
|
|
325
325
|
};
|
|
326
|
-
var
|
|
326
|
+
var SPEC_REVIEWER_PERMISSIONS = {
|
|
327
|
+
edit: "deny",
|
|
328
|
+
bash: {
|
|
329
|
+
"*": "allow",
|
|
330
|
+
...CORE_BASH_ALLOW_LIST,
|
|
331
|
+
...CORE_DESTRUCTIVE_BASH_DENIES
|
|
332
|
+
},
|
|
333
|
+
webfetch: "deny",
|
|
334
|
+
ast_grep: "allow",
|
|
335
|
+
tsc_check: "allow",
|
|
336
|
+
eslint_check: "allow",
|
|
337
|
+
todo_scan: "allow",
|
|
338
|
+
comment_check: "allow",
|
|
339
|
+
question: "allow",
|
|
340
|
+
serena: "allow",
|
|
341
|
+
memory: "deny",
|
|
342
|
+
git: "allow",
|
|
343
|
+
playwright: "allow",
|
|
344
|
+
linear: "deny"
|
|
345
|
+
};
|
|
346
|
+
var CODE_REVIEWER_PERMISSIONS = {
|
|
327
347
|
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
348
|
bash: {
|
|
336
349
|
"*": "allow",
|
|
337
350
|
...CORE_BASH_ALLOW_LIST,
|
|
@@ -350,12 +363,8 @@ var QA_REVIEWER_PERMISSIONS = {
|
|
|
350
363
|
playwright: "allow",
|
|
351
364
|
linear: "deny"
|
|
352
365
|
};
|
|
353
|
-
var
|
|
366
|
+
var CODE_REVIEWER_THOROUGH_PERMISSIONS = {
|
|
354
367
|
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
368
|
bash: {
|
|
360
369
|
"*": "allow",
|
|
361
370
|
...CORE_BASH_ALLOW_LIST,
|
|
@@ -403,7 +412,7 @@ var GAP_ANALYZER_PERMISSIONS = {
|
|
|
403
412
|
serena: "allow",
|
|
404
413
|
memory: "allow",
|
|
405
414
|
git: "deny",
|
|
406
|
-
playwright: "
|
|
415
|
+
playwright: "allow",
|
|
407
416
|
linear: "allow"
|
|
408
417
|
};
|
|
409
418
|
var CODE_SEARCHER_PERMISSIONS = {
|
|
@@ -488,13 +497,12 @@ var RESEARCH_PERMISSIONS = {
|
|
|
488
497
|
serena: "allow",
|
|
489
498
|
memory: "allow",
|
|
490
499
|
git: "allow",
|
|
491
|
-
playwright: "
|
|
500
|
+
playwright: "allow",
|
|
492
501
|
linear: "allow"
|
|
493
502
|
};
|
|
494
503
|
var AGENT_TIERS = {
|
|
495
504
|
prime: "deep",
|
|
496
505
|
plan: "deep",
|
|
497
|
-
"qa-thorough": "deep",
|
|
498
506
|
"architecture-advisor": "deep",
|
|
499
507
|
"plan-reviewer": "deep",
|
|
500
508
|
"gap-analyzer": "deep",
|
|
@@ -502,138 +510,15 @@ var AGENT_TIERS = {
|
|
|
502
510
|
"research-web": "deep",
|
|
503
511
|
"research-local": "deep",
|
|
504
512
|
"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
513
|
build: "mid-execute",
|
|
511
|
-
"
|
|
514
|
+
"spec-reviewer": "mid-execute",
|
|
515
|
+
"code-reviewer": "mid-execute",
|
|
516
|
+
"code-reviewer-thorough": "deep",
|
|
512
517
|
"docs-maintainer": "mid",
|
|
513
518
|
"lib-reader": "mid",
|
|
514
519
|
"agents-md-writer": "mid",
|
|
515
520
|
"code-searcher": "fast"
|
|
516
521
|
};
|
|
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
522
|
function createAgents() {
|
|
638
523
|
return {
|
|
639
524
|
// Primary agents
|
|
@@ -661,11 +546,14 @@ function createAgents() {
|
|
|
661
546
|
// Subagents — model/mode/description from frontmatter, permissions
|
|
662
547
|
// via overrides (see permission blocks above). docs-maintainer has no
|
|
663
548
|
// frontmatter permission declaration and keeps that behavior.
|
|
664
|
-
"
|
|
665
|
-
permission:
|
|
549
|
+
"spec-reviewer": agentFromPrompt(specReviewerPrompt, {
|
|
550
|
+
permission: SPEC_REVIEWER_PERMISSIONS
|
|
551
|
+
}),
|
|
552
|
+
"code-reviewer": agentFromPrompt(codeReviewerPrompt, {
|
|
553
|
+
permission: CODE_REVIEWER_PERMISSIONS
|
|
666
554
|
}),
|
|
667
|
-
"
|
|
668
|
-
permission:
|
|
555
|
+
"code-reviewer-thorough": agentFromPrompt(codeReviewerThoroughPrompt, {
|
|
556
|
+
permission: CODE_REVIEWER_THOROUGH_PERMISSIONS
|
|
669
557
|
}),
|
|
670
558
|
"plan-reviewer": agentFromPrompt(planReviewerPrompt, {
|
|
671
559
|
permission: PLAN_REVIEWER_PERMISSIONS
|
|
@@ -694,56 +582,30 @@ function createAgents() {
|
|
|
694
582
|
temperature: 0.3,
|
|
695
583
|
permission: RESEARCH_PERMISSIONS
|
|
696
584
|
}),
|
|
697
|
-
// Research subagents — thin shims that load the bundled skills
|
|
585
|
+
// Research subagents — thin shims that load the bundled skills.
|
|
586
|
+
// mode: "subagent" — these are internal implementation details of
|
|
587
|
+
// @research's orchestration; users should invoke @research (mode:all)
|
|
588
|
+
// as the primary entry point, not these directly.
|
|
698
589
|
"research-web": agentFromPrompt(researchWebPrompt, {
|
|
699
590
|
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: "
|
|
591
|
+
mode: "subagent",
|
|
701
592
|
model: "anthropic/claude-opus-4-7",
|
|
702
593
|
temperature: 0.3,
|
|
703
594
|
permission: RESEARCH_PERMISSIONS
|
|
704
595
|
}),
|
|
705
596
|
"research-local": agentFromPrompt(researchLocalPrompt, {
|
|
706
597
|
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: "
|
|
598
|
+
mode: "subagent",
|
|
708
599
|
model: "anthropic/claude-opus-4-7",
|
|
709
600
|
temperature: 0.3,
|
|
710
601
|
permission: RESEARCH_PERMISSIONS
|
|
711
602
|
}),
|
|
712
603
|
"research-auto": agentFromPrompt(researchAutoPrompt, {
|
|
713
604
|
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: "
|
|
605
|
+
mode: "subagent",
|
|
715
606
|
model: "anthropic/claude-opus-4-7",
|
|
716
607
|
temperature: 0.3,
|
|
717
608
|
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
609
|
})
|
|
748
610
|
};
|
|
749
611
|
}
|
|
@@ -770,7 +632,6 @@ function readPrompt2(name) {
|
|
|
770
632
|
}
|
|
771
633
|
throw new Error(`Could not find command prompt: ${name}`);
|
|
772
634
|
}
|
|
773
|
-
var autopilotPrompt = readPrompt2("autopilot.md");
|
|
774
635
|
var shipPrompt = readPrompt2("ship.md");
|
|
775
636
|
var reviewPrompt = readPrompt2("review.md");
|
|
776
637
|
var initDeepPrompt = readPrompt2("init-deep.md");
|
|
@@ -779,10 +640,6 @@ var freshPrompt = readPrompt2("fresh.md");
|
|
|
779
640
|
var costsPrompt = readPrompt2("costs.md");
|
|
780
641
|
function createCommands() {
|
|
781
642
|
return {
|
|
782
|
-
autopilot: {
|
|
783
|
-
template: autopilotPrompt,
|
|
784
|
-
description: "Self-driving run. Pass a ticket ref (any tracker), a task description, or a question."
|
|
785
|
-
},
|
|
786
643
|
ship: {
|
|
787
644
|
template: shipPrompt,
|
|
788
645
|
description: "Finalize, commit, push, and open a PR/MR. Human-gated at each step."
|
|
@@ -927,7 +784,7 @@ function resolveHarnessModels(agents, config, pluginOptions) {
|
|
|
927
784
|
}
|
|
928
785
|
}
|
|
929
786
|
if (midExecuteConfigured) {
|
|
930
|
-
const EXECUTOR_AGENTS = ["build", "
|
|
787
|
+
const EXECUTOR_AGENTS = ["build", "spec-reviewer", "code-reviewer"];
|
|
931
788
|
for (const agentName of EXECUTOR_AGENTS) {
|
|
932
789
|
const agentCfg = agents[agentName];
|
|
933
790
|
if (!agentCfg) continue;
|
|
@@ -1492,311 +1349,8 @@ function loadDotenv(directory) {
|
|
|
1492
1349
|
return { filesLoaded, varsSet };
|
|
1493
1350
|
}
|
|
1494
1351
|
|
|
1495
|
-
// src/plugins/autopilot.ts
|
|
1496
|
-
import { execFile as execFileCb } from "child_process";
|
|
1497
|
-
import * as fs3 from "fs/promises";
|
|
1498
|
-
import * as path3 from "path";
|
|
1499
|
-
import { promisify as promisify6 } from "util";
|
|
1500
|
-
var STATE_PATH = ".agent/autopilot-state.json";
|
|
1501
|
-
var KILL_SWITCH_PATH = ".agent/autopilot-disable";
|
|
1502
|
-
var MAX_ITERATIONS = 20;
|
|
1503
|
-
var TARGET_AGENTS = /* @__PURE__ */ new Set(["build", "prime"]);
|
|
1504
|
-
var MESSAGE_LIMIT = 40;
|
|
1505
|
-
var NUDGE_DEBOUNCE_MS = 3e4;
|
|
1506
|
-
var PR_CACHE_MS = 5 * 60 * 1e3;
|
|
1507
|
-
var MAX_CONSECUTIVE_STOPS = 2;
|
|
1508
|
-
var UMBRELLA_MIN_BYTES = 5e4;
|
|
1509
|
-
var UMBRELLA_MIN_LINEAR_IDS = 3;
|
|
1510
|
-
var AUTOPILOT_MARKER_RE = /(^|\s)\/autopilot(\s|$)|AUTOPILOT mode/;
|
|
1511
|
-
var OPT_OUT_RE = /<!--\s*autopilot:\s*(skip|false)\s*-->/i;
|
|
1512
|
-
var UMBRELLA_SECTION_RE = /^##\s+(Chunks|Milestones|Workstreams)\b/m;
|
|
1513
|
-
var LINEAR_ID_RE = /\b[A-Z]{2,10}-\d+\b/g;
|
|
1514
|
-
var MEASUREMENT_GATE_RE = /\b(7-day|production window|post-deploy|post-launch|SLO|success rate reaches|after deploy|bake time)\b/i;
|
|
1515
|
-
var STOP_REPORT_RE = /^STOP[:.\s—]/m;
|
|
1516
|
-
var NUDGE_TEXT = "[autopilot] Session idled with unchecked acceptance criteria. Re-read the plan, do the most important unchecked item, check its box when done, then move to the next. When all boxes are `[x]`, print the Phase 5 handoff and stop \u2014 the user runs `/ship` manually.";
|
|
1517
|
-
var MAX_ITERATIONS_TEXT = `[autopilot] Stopped: hit max iterations (${MAX_ITERATIONS}). Either the work is complete or the loop is stuck. Review and resume manually; a new \`/autopilot\` session will re-enable nudges.`;
|
|
1518
|
-
async function readState(dir) {
|
|
1519
|
-
try {
|
|
1520
|
-
const raw = await fs3.readFile(path3.join(dir, STATE_PATH), "utf8");
|
|
1521
|
-
const parsed = JSON.parse(raw);
|
|
1522
|
-
return { sessions: parsed.sessions ?? {} };
|
|
1523
|
-
} catch {
|
|
1524
|
-
return { sessions: {} };
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
async function writeState(dir, state) {
|
|
1528
|
-
const p = path3.join(dir, STATE_PATH);
|
|
1529
|
-
await fs3.mkdir(path3.dirname(p), { recursive: true });
|
|
1530
|
-
await fs3.writeFile(p, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
1531
|
-
}
|
|
1532
|
-
function userText(msg) {
|
|
1533
|
-
if (msg.info?.role !== "user") return "";
|
|
1534
|
-
const parts = msg.parts ?? [];
|
|
1535
|
-
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
|
|
1536
|
-
}
|
|
1537
|
-
function latestUserAgent(messages) {
|
|
1538
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1539
|
-
const info = messages[i].info;
|
|
1540
|
-
if (info?.role === "user" && typeof info.agent === "string") {
|
|
1541
|
-
return info.agent;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return void 0;
|
|
1545
|
-
}
|
|
1546
|
-
function latestAssistantText(messages) {
|
|
1547
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1548
|
-
const msg = messages[i];
|
|
1549
|
-
if (msg.info?.role !== "assistant") continue;
|
|
1550
|
-
const parts = msg.parts ?? [];
|
|
1551
|
-
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
|
|
1552
|
-
}
|
|
1553
|
-
return "";
|
|
1554
|
-
}
|
|
1555
|
-
function detectActivation(messages) {
|
|
1556
|
-
for (const msg of messages) {
|
|
1557
|
-
if (msg.info?.role !== "user") continue;
|
|
1558
|
-
return AUTOPILOT_MARKER_RE.test(userText(msg));
|
|
1559
|
-
}
|
|
1560
|
-
return false;
|
|
1561
|
-
}
|
|
1562
|
-
var PLAN_PATH_RE = /(?:\.agent\/plans\/[\w-]+\.md|(?:\/[^\s`"']*)?\/[\w.-]+\/plans\/[\w-]+\.md)/;
|
|
1563
|
-
function findPlanPath(messages) {
|
|
1564
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1565
|
-
const parts = messages[i].parts ?? [];
|
|
1566
|
-
for (const part of parts) {
|
|
1567
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
1568
|
-
const m = part.text.match(PLAN_PATH_RE);
|
|
1569
|
-
if (m) return m[0];
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
return null;
|
|
1574
|
-
}
|
|
1575
|
-
function countUnchecked(planContent) {
|
|
1576
|
-
const section = /## Acceptance criteria([\s\S]*?)(?=\n##|$)/.exec(planContent);
|
|
1577
|
-
if (!section) return 0;
|
|
1578
|
-
const matches = section[1].match(/^- \[ \]/gm);
|
|
1579
|
-
return matches?.length ?? 0;
|
|
1580
|
-
}
|
|
1581
|
-
function classifyPlan(content) {
|
|
1582
|
-
if (OPT_OUT_RE.test(content)) return "opted-out";
|
|
1583
|
-
if (UMBRELLA_SECTION_RE.test(content)) return "umbrella";
|
|
1584
|
-
if (content.length > UMBRELLA_MIN_BYTES) return "umbrella";
|
|
1585
|
-
const linearIds = content.match(LINEAR_ID_RE) ?? [];
|
|
1586
|
-
const unique = new Set(linearIds);
|
|
1587
|
-
if (unique.size >= UMBRELLA_MIN_LINEAR_IDS) return "umbrella";
|
|
1588
|
-
const acSection = /## Acceptance criteria([\s\S]*?)(?=\n##|$)/.exec(content);
|
|
1589
|
-
if (acSection && MEASUREMENT_GATE_RE.test(acSection[1])) {
|
|
1590
|
-
return "measurement-gated";
|
|
1591
|
-
}
|
|
1592
|
-
return "unit";
|
|
1593
|
-
}
|
|
1594
|
-
function planGoalLinearId(content) {
|
|
1595
|
-
const goal = /## Goal([\s\S]*?)(?=\n##|$)/.exec(content);
|
|
1596
|
-
if (!goal) return null;
|
|
1597
|
-
const m = goal[1].match(LINEAR_ID_RE);
|
|
1598
|
-
return m ? m[0] : null;
|
|
1599
|
-
}
|
|
1600
|
-
function detectStopReport(assistantText) {
|
|
1601
|
-
if (!assistantText) return false;
|
|
1602
|
-
return STOP_REPORT_RE.test(assistantText);
|
|
1603
|
-
}
|
|
1604
|
-
var execFile6 = promisify6(execFileCb);
|
|
1605
|
-
async function currentBranch(dir) {
|
|
1606
|
-
try {
|
|
1607
|
-
const { stdout } = await execFile6(
|
|
1608
|
-
"git",
|
|
1609
|
-
["-C", dir, "branch", "--show-current"],
|
|
1610
|
-
{ timeout: 2e3 }
|
|
1611
|
-
);
|
|
1612
|
-
const branch = stdout.trim();
|
|
1613
|
-
return branch.length > 0 ? branch : null;
|
|
1614
|
-
} catch {
|
|
1615
|
-
return null;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
async function pullRequestState(dir) {
|
|
1619
|
-
try {
|
|
1620
|
-
const { stdout } = await execFile6(
|
|
1621
|
-
"gh",
|
|
1622
|
-
["pr", "view", "--json", "state", "--jq", ".state"],
|
|
1623
|
-
{ cwd: dir, timeout: 5e3 }
|
|
1624
|
-
);
|
|
1625
|
-
const state = stdout.trim();
|
|
1626
|
-
return state.length > 0 ? state : null;
|
|
1627
|
-
} catch {
|
|
1628
|
-
return null;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
async function killSwitchEngaged(dir) {
|
|
1632
|
-
try {
|
|
1633
|
-
await fs3.access(path3.join(dir, KILL_SWITCH_PATH));
|
|
1634
|
-
return true;
|
|
1635
|
-
} catch {
|
|
1636
|
-
return false;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
async function sendNudge(client, sessionID, sessState, text, now = Date.now()) {
|
|
1640
|
-
if (sessState.lastNudgeAt !== void 0 && now - sessState.lastNudgeAt < NUDGE_DEBOUNCE_MS) {
|
|
1641
|
-
return false;
|
|
1642
|
-
}
|
|
1643
|
-
await client.session.promptAsync({
|
|
1644
|
-
path: { id: sessionID },
|
|
1645
|
-
body: { parts: [{ type: "text", text }] }
|
|
1646
|
-
});
|
|
1647
|
-
sessState.lastNudgeAt = now;
|
|
1648
|
-
return true;
|
|
1649
|
-
}
|
|
1650
|
-
var plugin = async ({ client, directory }) => {
|
|
1651
|
-
return {
|
|
1652
|
-
event: async ({ event }) => {
|
|
1653
|
-
if (event.type !== "session.idle") return;
|
|
1654
|
-
const sessionID = event.properties.sessionID;
|
|
1655
|
-
const msgsResp = await client.session.messages({
|
|
1656
|
-
path: { id: sessionID },
|
|
1657
|
-
query: { limit: MESSAGE_LIMIT }
|
|
1658
|
-
});
|
|
1659
|
-
const messages = msgsResp.data ?? [];
|
|
1660
|
-
const agent = latestUserAgent(messages);
|
|
1661
|
-
if (!agent || !TARGET_AGENTS.has(agent)) return;
|
|
1662
|
-
const state = await readState(directory);
|
|
1663
|
-
const sessState = state.sessions[sessionID] ?? {
|
|
1664
|
-
iterations: 0
|
|
1665
|
-
};
|
|
1666
|
-
if (sessState.stopped) return;
|
|
1667
|
-
if (!sessState.enabled) {
|
|
1668
|
-
if (!detectActivation(messages)) return;
|
|
1669
|
-
sessState.enabled = true;
|
|
1670
|
-
}
|
|
1671
|
-
if (await killSwitchEngaged(directory)) {
|
|
1672
|
-
state.sessions[sessionID] = {
|
|
1673
|
-
...sessState,
|
|
1674
|
-
stopped: true,
|
|
1675
|
-
stopReason: "kill-switch"
|
|
1676
|
-
};
|
|
1677
|
-
await writeState(directory, state);
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
if (sessState.iterations >= MAX_ITERATIONS) {
|
|
1681
|
-
await sendNudge(client, sessionID, sessState, MAX_ITERATIONS_TEXT);
|
|
1682
|
-
state.sessions[sessionID] = {
|
|
1683
|
-
...sessState,
|
|
1684
|
-
stopped: true,
|
|
1685
|
-
stopReason: "max-iterations"
|
|
1686
|
-
};
|
|
1687
|
-
await writeState(directory, state);
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
const planPath = findPlanPath(messages);
|
|
1691
|
-
if (!planPath) return;
|
|
1692
|
-
const resolvedPlanPath = path3.isAbsolute(planPath) ? planPath : path3.join(directory, planPath);
|
|
1693
|
-
let planContent;
|
|
1694
|
-
try {
|
|
1695
|
-
planContent = await fs3.readFile(resolvedPlanPath, "utf8");
|
|
1696
|
-
} catch {
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
const shape = classifyPlan(planContent);
|
|
1700
|
-
if (shape !== "unit") {
|
|
1701
|
-
state.sessions[sessionID] = {
|
|
1702
|
-
...sessState,
|
|
1703
|
-
stopped: true,
|
|
1704
|
-
stopReason: `plan-shape:${shape}`
|
|
1705
|
-
};
|
|
1706
|
-
await writeState(directory, state);
|
|
1707
|
-
return;
|
|
1708
|
-
}
|
|
1709
|
-
const planLinearId = planGoalLinearId(planContent);
|
|
1710
|
-
if (planLinearId) {
|
|
1711
|
-
const branch = await currentBranch(directory);
|
|
1712
|
-
if (branch && !branch.toLowerCase().includes(planLinearId.toLowerCase())) {
|
|
1713
|
-
state.sessions[sessionID] = {
|
|
1714
|
-
...sessState,
|
|
1715
|
-
stopped: true,
|
|
1716
|
-
stopReason: "branch-mismatch"
|
|
1717
|
-
};
|
|
1718
|
-
await writeState(directory, state);
|
|
1719
|
-
return;
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
const now = Date.now();
|
|
1723
|
-
let prState = sessState.prState;
|
|
1724
|
-
const prExpired = sessState.prCheckedAt === void 0 || now - sessState.prCheckedAt > PR_CACHE_MS;
|
|
1725
|
-
if (prExpired) {
|
|
1726
|
-
const fetched = await pullRequestState(directory);
|
|
1727
|
-
prState = fetched ?? "none";
|
|
1728
|
-
sessState.prState = prState;
|
|
1729
|
-
sessState.prCheckedAt = now;
|
|
1730
|
-
}
|
|
1731
|
-
if (prState === "MERGED") {
|
|
1732
|
-
state.sessions[sessionID] = {
|
|
1733
|
-
...sessState,
|
|
1734
|
-
stopped: true,
|
|
1735
|
-
stopReason: "pr-merged"
|
|
1736
|
-
};
|
|
1737
|
-
await writeState(directory, state);
|
|
1738
|
-
return;
|
|
1739
|
-
}
|
|
1740
|
-
const unchecked = countUnchecked(planContent);
|
|
1741
|
-
if (unchecked === 0) {
|
|
1742
|
-
state.sessions[sessionID] = {
|
|
1743
|
-
...sessState,
|
|
1744
|
-
consecutiveStops: 0,
|
|
1745
|
-
lastUncheckedCount: 0
|
|
1746
|
-
};
|
|
1747
|
-
await writeState(directory, state);
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
const lastUnchecked = sessState.lastUncheckedCount;
|
|
1751
|
-
const madeProgress = lastUnchecked !== void 0 && unchecked < lastUnchecked;
|
|
1752
|
-
const stopReported = detectStopReport(latestAssistantText(messages));
|
|
1753
|
-
let consecutiveStops = sessState.consecutiveStops ?? 0;
|
|
1754
|
-
if (madeProgress) {
|
|
1755
|
-
consecutiveStops = 0;
|
|
1756
|
-
} else if (stopReported) {
|
|
1757
|
-
consecutiveStops += 1;
|
|
1758
|
-
} else {
|
|
1759
|
-
}
|
|
1760
|
-
sessState.consecutiveStops = consecutiveStops;
|
|
1761
|
-
sessState.lastUncheckedCount = unchecked;
|
|
1762
|
-
if (consecutiveStops >= MAX_CONSECUTIVE_STOPS) {
|
|
1763
|
-
state.sessions[sessionID] = {
|
|
1764
|
-
...sessState,
|
|
1765
|
-
stopped: true,
|
|
1766
|
-
stopReason: "agent-stop-report"
|
|
1767
|
-
};
|
|
1768
|
-
await writeState(directory, state);
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
const sent = await sendNudge(client, sessionID, sessState, NUDGE_TEXT);
|
|
1772
|
-
if (sent) {
|
|
1773
|
-
state.sessions[sessionID] = {
|
|
1774
|
-
...sessState,
|
|
1775
|
-
iterations: sessState.iterations + 1
|
|
1776
|
-
};
|
|
1777
|
-
await writeState(directory, state);
|
|
1778
|
-
} else {
|
|
1779
|
-
state.sessions[sessionID] = { ...sessState };
|
|
1780
|
-
await writeState(directory, state);
|
|
1781
|
-
}
|
|
1782
|
-
},
|
|
1783
|
-
"chat.message": async ({ sessionID, agent }) => {
|
|
1784
|
-
if (!agent || !TARGET_AGENTS.has(agent)) return;
|
|
1785
|
-
const state = await readState(directory);
|
|
1786
|
-
const existing = state.sessions[sessionID];
|
|
1787
|
-
if (!existing?.enabled) return;
|
|
1788
|
-
state.sessions[sessionID] = {
|
|
1789
|
-
...existing,
|
|
1790
|
-
iterations: 0
|
|
1791
|
-
};
|
|
1792
|
-
await writeState(directory, state);
|
|
1793
|
-
}
|
|
1794
|
-
};
|
|
1795
|
-
};
|
|
1796
|
-
var autopilot_default = plugin;
|
|
1797
|
-
|
|
1798
1352
|
// src/plugins/notify.ts
|
|
1799
|
-
var
|
|
1353
|
+
var plugin = async ({ $, client }) => {
|
|
1800
1354
|
async function notify(title, message) {
|
|
1801
1355
|
if (process.platform === "darwin") {
|
|
1802
1356
|
const esc = (s) => s.replace(/"/g, '\\"');
|
|
@@ -1819,11 +1373,11 @@ var plugin2 = async ({ $, client }) => {
|
|
|
1819
1373
|
}
|
|
1820
1374
|
};
|
|
1821
1375
|
};
|
|
1822
|
-
var notify_default =
|
|
1376
|
+
var notify_default = plugin;
|
|
1823
1377
|
|
|
1824
1378
|
// src/plugins/cost-tracker.ts
|
|
1825
|
-
import * as
|
|
1826
|
-
import * as
|
|
1379
|
+
import * as fs3 from "fs/promises";
|
|
1380
|
+
import * as path3 from "path";
|
|
1827
1381
|
import * as os2 from "os";
|
|
1828
1382
|
var MAX_LINE_BYTES = 2048;
|
|
1829
1383
|
var ROLLUP_DEBOUNCE_MS = 5e3;
|
|
@@ -1831,11 +1385,11 @@ function resolveDataDir() {
|
|
|
1831
1385
|
const override = process.env.GLORIOUS_COST_TRACKER_DIR;
|
|
1832
1386
|
if (override) {
|
|
1833
1387
|
if (override.startsWith("~")) {
|
|
1834
|
-
return
|
|
1388
|
+
return path3.join(os2.homedir(), override.slice(1));
|
|
1835
1389
|
}
|
|
1836
1390
|
return override;
|
|
1837
1391
|
}
|
|
1838
|
-
return
|
|
1392
|
+
return path3.join(os2.homedir(), ".glorious", "opencode");
|
|
1839
1393
|
}
|
|
1840
1394
|
function zeroTokens() {
|
|
1841
1395
|
return {
|
|
@@ -1902,13 +1456,13 @@ function emptyRollup() {
|
|
|
1902
1456
|
byProvider: {}
|
|
1903
1457
|
};
|
|
1904
1458
|
}
|
|
1905
|
-
var
|
|
1459
|
+
var plugin2 = async () => {
|
|
1906
1460
|
if (process.env.GLORIOUS_COST_TRACKER === "0") {
|
|
1907
1461
|
return {};
|
|
1908
1462
|
}
|
|
1909
1463
|
const dataDir = resolveDataDir();
|
|
1910
|
-
const jsonlPath =
|
|
1911
|
-
const rollupPath =
|
|
1464
|
+
const jsonlPath = path3.join(dataDir, "costs.jsonl");
|
|
1465
|
+
const rollupPath = path3.join(dataDir, "costs.json");
|
|
1912
1466
|
const lastSeen = /* @__PURE__ */ new Map();
|
|
1913
1467
|
const messageMeta = /* @__PURE__ */ new Map();
|
|
1914
1468
|
const rollup = emptyRollup();
|
|
@@ -1924,7 +1478,7 @@ var plugin3 = async () => {
|
|
|
1924
1478
|
async function ensureDir() {
|
|
1925
1479
|
if (disabled) return false;
|
|
1926
1480
|
try {
|
|
1927
|
-
await
|
|
1481
|
+
await fs3.mkdir(dataDir, { recursive: true });
|
|
1928
1482
|
return true;
|
|
1929
1483
|
} catch (err) {
|
|
1930
1484
|
warnOnce("mkdir", err);
|
|
@@ -1973,13 +1527,13 @@ var plugin3 = async () => {
|
|
|
1973
1527
|
if (!await ensureDir()) return;
|
|
1974
1528
|
const tmp = `${rollupPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 10)}`;
|
|
1975
1529
|
try {
|
|
1976
|
-
await
|
|
1977
|
-
await
|
|
1530
|
+
await fs3.writeFile(tmp, JSON.stringify(rollup, null, 2) + "\n", "utf8");
|
|
1531
|
+
await fs3.rename(tmp, rollupPath);
|
|
1978
1532
|
lastRollupWriteAt = now;
|
|
1979
1533
|
} catch (err) {
|
|
1980
1534
|
warnOnce("rollup-write", err);
|
|
1981
1535
|
try {
|
|
1982
|
-
await
|
|
1536
|
+
await fs3.unlink(tmp);
|
|
1983
1537
|
} catch {
|
|
1984
1538
|
}
|
|
1985
1539
|
}
|
|
@@ -1999,14 +1553,14 @@ var plugin3 = async () => {
|
|
|
1999
1553
|
return;
|
|
2000
1554
|
}
|
|
2001
1555
|
try {
|
|
2002
|
-
await
|
|
1556
|
+
await fs3.appendFile(jsonlPath, text, "utf8");
|
|
2003
1557
|
} catch (err) {
|
|
2004
1558
|
warnOnce("jsonl-append", err);
|
|
2005
1559
|
}
|
|
2006
1560
|
}
|
|
2007
1561
|
async function warmUp() {
|
|
2008
1562
|
try {
|
|
2009
|
-
const raw = await
|
|
1563
|
+
const raw = await fs3.readFile(jsonlPath, "utf8");
|
|
2010
1564
|
const byMsg = /* @__PURE__ */ new Map();
|
|
2011
1565
|
for (const rawLine of raw.split("\n")) {
|
|
2012
1566
|
if (!rawLine) continue;
|
|
@@ -2140,76 +1694,16 @@ var plugin3 = async () => {
|
|
|
2140
1694
|
}
|
|
2141
1695
|
};
|
|
2142
1696
|
};
|
|
2143
|
-
var cost_tracker_default =
|
|
2144
|
-
|
|
2145
|
-
// src/plugins/pilot-plugin.ts
|
|
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/
|
|
2158
|
-
];
|
|
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) => {
|
|
2179
|
-
return {
|
|
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
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
};
|
|
2202
|
-
};
|
|
2203
|
-
var pilot_plugin_default = pilotPlugin;
|
|
1697
|
+
var cost_tracker_default = plugin2;
|
|
2204
1698
|
|
|
2205
1699
|
// src/plugins/tool-hooks.ts
|
|
2206
1700
|
import * as crypto from "crypto";
|
|
2207
|
-
import * as
|
|
2208
|
-
import * as
|
|
1701
|
+
import * as fs4 from "fs";
|
|
1702
|
+
import * as path4 from "path";
|
|
2209
1703
|
import * as os3 from "os";
|
|
2210
|
-
import { execFile as
|
|
2211
|
-
import { promisify as
|
|
2212
|
-
var exec6 =
|
|
1704
|
+
import { execFile as execFileCb } from "child_process";
|
|
1705
|
+
import { promisify as promisify6 } from "util";
|
|
1706
|
+
var exec6 = promisify6(execFileCb);
|
|
2213
1707
|
var EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "patch", "multiedit"]);
|
|
2214
1708
|
var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
2215
1709
|
var DEFAULT_BACKPRESSURE_THRESHOLD = 6e3;
|
|
@@ -2292,8 +1786,8 @@ function resolveConfig(config, pluginOptions) {
|
|
|
2292
1786
|
};
|
|
2293
1787
|
}
|
|
2294
1788
|
function getToolOutputDir() {
|
|
2295
|
-
const stateHome = process.env["XDG_STATE_HOME"] ||
|
|
2296
|
-
return
|
|
1789
|
+
const stateHome = process.env["XDG_STATE_HOME"] || path4.join(os3.homedir(), ".local", "state");
|
|
1790
|
+
return path4.join(stateHome, "harness-opencode", "tool-output");
|
|
2297
1791
|
}
|
|
2298
1792
|
function hashContent(content) {
|
|
2299
1793
|
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
@@ -2326,9 +1820,9 @@ async function resolveSessionDir(client, sess, sessionID) {
|
|
|
2326
1820
|
}
|
|
2327
1821
|
function isUnderToolOutputDir(filePath) {
|
|
2328
1822
|
try {
|
|
2329
|
-
const abs =
|
|
2330
|
-
const spillDir =
|
|
2331
|
-
return abs === spillDir || abs.startsWith(spillDir +
|
|
1823
|
+
const abs = path4.resolve(filePath);
|
|
1824
|
+
const spillDir = path4.resolve(getToolOutputDir());
|
|
1825
|
+
return abs === spillDir || abs.startsWith(spillDir + path4.sep);
|
|
2332
1826
|
} catch {
|
|
2333
1827
|
return false;
|
|
2334
1828
|
}
|
|
@@ -2362,9 +1856,9 @@ function applyBackpressure(cfg, toolName, callID, output, args) {
|
|
|
2362
1856
|
let diskPath = null;
|
|
2363
1857
|
try {
|
|
2364
1858
|
const dir = getToolOutputDir();
|
|
2365
|
-
|
|
2366
|
-
diskPath =
|
|
2367
|
-
|
|
1859
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1860
|
+
diskPath = path4.join(dir, `${callID}.txt`);
|
|
1861
|
+
fs4.writeFileSync(diskPath, text);
|
|
2368
1862
|
} catch {
|
|
2369
1863
|
}
|
|
2370
1864
|
const pathNote = diskPath ? ` Full output saved to: ${diskPath}` : "";
|
|
@@ -2410,7 +1904,7 @@ ${tail}`;
|
|
|
2410
1904
|
}
|
|
2411
1905
|
async function runPostEditVerify(cfg, client, sess, sessionID, filePath, output) {
|
|
2412
1906
|
if (!cfg.enabled) return;
|
|
2413
|
-
const ext =
|
|
1907
|
+
const ext = path4.extname(filePath).toLowerCase();
|
|
2414
1908
|
if (!TS_EXTENSIONS.has(ext)) return;
|
|
2415
1909
|
const now = Date.now();
|
|
2416
1910
|
if (now - sess.lastVerifyTs < 2e3) return;
|
|
@@ -2443,17 +1937,17 @@ ${String(stderr)}`;
|
|
|
2443
1937
|
}
|
|
2444
1938
|
if (!raw.trim()) return;
|
|
2445
1939
|
const errors = parseTscOutput(raw);
|
|
2446
|
-
const normPath =
|
|
1940
|
+
const normPath = path4.resolve(cwd, filePath);
|
|
2447
1941
|
const fileErrors = errors.filter((e) => {
|
|
2448
|
-
const errPath =
|
|
2449
|
-
return
|
|
1942
|
+
const errPath = path4.isAbsolute(e.file) ? e.file : path4.resolve(cwd, e.file);
|
|
1943
|
+
return path4.normalize(errPath) === path4.normalize(normPath);
|
|
2450
1944
|
});
|
|
2451
1945
|
if (fileErrors.length === 0) return;
|
|
2452
1946
|
const { rows } = dedupeAndCap(fileErrors, VERIFY_MAX_ERRORS);
|
|
2453
1947
|
const lines = rows.map(formatRow);
|
|
2454
1948
|
output.output += `
|
|
2455
1949
|
|
|
2456
|
-
--- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${
|
|
1950
|
+
--- POST-EDIT DIAGNOSTICS (${fileErrors.length} error${fileErrors.length !== 1 ? "s" : ""} in ${path4.basename(filePath)}) ---
|
|
2457
1951
|
` + lines.join("\n") + `
|
|
2458
1952
|
--- Fix these before proceeding ---`;
|
|
2459
1953
|
} catch {
|
|
@@ -2467,7 +1961,7 @@ function checkEditLoop(cfg, sess, filePath, output) {
|
|
|
2467
1961
|
output.output += `
|
|
2468
1962
|
|
|
2469
1963
|
--- LOOP WARNING ---
|
|
2470
|
-
You've edited ${
|
|
1964
|
+
You've edited ${path4.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.
|
|
2471
1965
|
---`;
|
|
2472
1966
|
}
|
|
2473
1967
|
}
|
|
@@ -2485,7 +1979,7 @@ function checkReadDedup(cfg, sess, filePath, output) {
|
|
|
2485
1979
|
}
|
|
2486
1980
|
var pluginConfig = null;
|
|
2487
1981
|
var storedPluginOptions;
|
|
2488
|
-
var
|
|
1982
|
+
var plugin3 = async ({ client }, options) => {
|
|
2489
1983
|
storedPluginOptions = options;
|
|
2490
1984
|
return {
|
|
2491
1985
|
config: async (config) => {
|
|
@@ -2518,7 +2012,7 @@ var plugin4 = async ({ client }, options) => {
|
|
|
2518
2012
|
}
|
|
2519
2013
|
};
|
|
2520
2014
|
};
|
|
2521
|
-
var tool_hooks_default =
|
|
2015
|
+
var tool_hooks_default = plugin3;
|
|
2522
2016
|
|
|
2523
2017
|
// src/plugins/telemetry.ts
|
|
2524
2018
|
import { extname as extname2 } from "path";
|
|
@@ -2527,16 +2021,16 @@ import { extname as extname2 } from "path";
|
|
|
2527
2021
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
2528
2022
|
import { homedir as homedir4 } from "os";
|
|
2529
2023
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync } from "fs";
|
|
2530
|
-
import { join as
|
|
2024
|
+
import { join as join9 } from "path";
|
|
2531
2025
|
var APP_KEY = "A-US-3617699429";
|
|
2532
2026
|
var ENDPOINT = "https://us.aptabase.com/api/v0/event";
|
|
2533
2027
|
var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
|
|
2534
|
-
var PKG_VERSION = true ? "2.0
|
|
2028
|
+
var PKG_VERSION = true ? "2.2.0" : "dev";
|
|
2535
2029
|
var DISABLED = process.env.HARNESS_OPENCODE_TELEMETRY === "0" || process.env.HARNESS_OPENCODE_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1" || process.env.CI === "true";
|
|
2536
2030
|
var SESSION_ID = randomUUID();
|
|
2537
2031
|
function getInstallId() {
|
|
2538
|
-
const dir =
|
|
2539
|
-
const file =
|
|
2032
|
+
const dir = join9(homedir4(), ".config", "harness-opencode");
|
|
2033
|
+
const file = join9(dir, "install-id");
|
|
2540
2034
|
try {
|
|
2541
2035
|
if (existsSync(file)) return readFileSync5(file, "utf8").trim();
|
|
2542
2036
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -2603,7 +2097,7 @@ function track(eventName, props = {}) {
|
|
|
2603
2097
|
}
|
|
2604
2098
|
|
|
2605
2099
|
// src/plugins/telemetry.ts
|
|
2606
|
-
var
|
|
2100
|
+
var plugin4 = async () => {
|
|
2607
2101
|
if (DISABLED) {
|
|
2608
2102
|
return {};
|
|
2609
2103
|
}
|
|
@@ -2665,7 +2159,7 @@ var plugin5 = async () => {
|
|
|
2665
2159
|
}
|
|
2666
2160
|
};
|
|
2667
2161
|
};
|
|
2668
|
-
var telemetry_default =
|
|
2162
|
+
var telemetry_default = plugin4;
|
|
2669
2163
|
|
|
2670
2164
|
// src/index.ts
|
|
2671
2165
|
var BUNDLED_VERSION = readOurPackageVersion(import.meta.url);
|
|
@@ -2710,22 +2204,19 @@ async function checkForUpdate(client) {
|
|
|
2710
2204
|
} catch {
|
|
2711
2205
|
}
|
|
2712
2206
|
}
|
|
2713
|
-
var
|
|
2207
|
+
var plugin5 = async (input, options) => {
|
|
2714
2208
|
const pluginOptions = options ?? {};
|
|
2715
2209
|
loadDotenv(input.directory);
|
|
2716
2210
|
checkForUpdate(input.client).catch(() => {
|
|
2717
2211
|
});
|
|
2718
|
-
const autopilotHooks = await autopilot_default(input);
|
|
2719
2212
|
const notifyHooks = await notify_default(input);
|
|
2720
2213
|
const costTrackerHooks = await cost_tracker_default(input);
|
|
2721
|
-
const pilotHooks = await pilot_plugin_default(input);
|
|
2722
2214
|
const toolHooks = await tool_hooks_default(input, pluginOptions);
|
|
2723
2215
|
const telemetryHooks = await telemetry_default(input);
|
|
2724
2216
|
const hooks = {
|
|
2725
2217
|
// Config hook: register agents, commands, MCPs, skills
|
|
2726
2218
|
config: async (config) => {
|
|
2727
2219
|
applyConfig(config, pluginOptions);
|
|
2728
|
-
if (autopilotHooks.config) await autopilotHooks.config(config);
|
|
2729
2220
|
if (notifyHooks.config) await notifyHooks.config(config);
|
|
2730
2221
|
if (costTrackerHooks.config) await costTrackerHooks.config(config);
|
|
2731
2222
|
if (toolHooks.config) await toolHooks.config(config);
|
|
@@ -2734,27 +2225,15 @@ var plugin6 = async (input, options) => {
|
|
|
2734
2225
|
tool: createTools(),
|
|
2735
2226
|
// Event handlers from sub-plugins
|
|
2736
2227
|
event: async (input2) => {
|
|
2737
|
-
if (autopilotHooks.event) await autopilotHooks.event(input2);
|
|
2738
2228
|
if (notifyHooks.event) await notifyHooks.event(input2);
|
|
2739
2229
|
if (costTrackerHooks.event) await costTrackerHooks.event(input2);
|
|
2740
2230
|
if (telemetryHooks.event) await telemetryHooks.event(input2);
|
|
2741
2231
|
}
|
|
2742
2232
|
};
|
|
2743
|
-
if (autopilotHooks["chat.params"] !== void 0) {
|
|
2744
|
-
hooks["chat.params"] = autopilotHooks["chat.params"];
|
|
2745
|
-
}
|
|
2746
|
-
if (autopilotHooks["chat.message"] !== void 0) {
|
|
2747
|
-
hooks["chat.message"] = autopilotHooks["chat.message"];
|
|
2748
|
-
}
|
|
2749
|
-
if (autopilotHooks["experimental.session.compacting"] !== void 0) {
|
|
2750
|
-
hooks["experimental.session.compacting"] = autopilotHooks["experimental.session.compacting"];
|
|
2751
|
-
}
|
|
2752
2233
|
const hasTelemetryBefore = telemetryHooks["tool.execute.before"] !== void 0;
|
|
2753
|
-
|
|
2754
|
-
if (hasTelemetryBefore || hasPilotBefore) {
|
|
2234
|
+
if (hasTelemetryBefore) {
|
|
2755
2235
|
hooks["tool.execute.before"] = async (input2, output) => {
|
|
2756
2236
|
if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
|
|
2757
|
-
if (hasPilotBefore) await pilotHooks["tool.execute.before"](input2, output);
|
|
2758
2237
|
};
|
|
2759
2238
|
}
|
|
2760
2239
|
const hasToolHooksAfter = toolHooks["tool.execute.after"] !== void 0;
|
|
@@ -2767,7 +2246,7 @@ var plugin6 = async (input, options) => {
|
|
|
2767
2246
|
}
|
|
2768
2247
|
return hooks;
|
|
2769
2248
|
};
|
|
2770
|
-
var src_default =
|
|
2249
|
+
var src_default = plugin5;
|
|
2771
2250
|
export {
|
|
2772
2251
|
src_default as default
|
|
2773
2252
|
};
|