@soleur/opencode 1.0.0 → 3.354.3

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 (92) hide show
  1. package/commands/go.md +6 -5
  2. package/commands/sync.md +2 -2
  3. package/install.js +6 -85
  4. package/lib/harness.ts +1 -1
  5. package/lib/heartbeat-manifest.ts +19 -0
  6. package/lib/pr-merge-poll.ts +2 -2
  7. package/lib/workflow-fidelity.ts +2 -2
  8. package/opencode/INSTRUCTIONS.md +19 -0
  9. package/opencode/plugin.ts +343 -18
  10. package/package.json +1 -1
  11. package/scripts/admin-merge-ready.sh +1 -1
  12. package/scripts/admin-merge-ready.test.sh +0 -0
  13. package/scripts/affected-tests.sh +0 -0
  14. package/scripts/alpha-metrics.sh +0 -0
  15. package/scripts/alpha-metrics.test.sh +0 -0
  16. package/scripts/check-red-on-main.sh +0 -0
  17. package/scripts/ci-head-verdict.sh +54 -6
  18. package/scripts/cloud-detect.sh +0 -0
  19. package/scripts/delegation-verify.sh +0 -0
  20. package/scripts/delegation-verify.test.sh +0 -0
  21. package/scripts/deploy-arm.sh +0 -0
  22. package/scripts/domain-model-drift.sh +0 -0
  23. package/scripts/emit-decision.sh +0 -0
  24. package/scripts/emit-decision.test.sh +0 -0
  25. package/scripts/ext-filter-divergence.sh +0 -0
  26. package/scripts/grok-fidelity-gate.sh +0 -0
  27. package/scripts/grok-pre-push-gate.sh +0 -0
  28. package/scripts/install-opencode-adapter.ts +118 -129
  29. package/scripts/lib/affected-tests/common.sh +0 -0
  30. package/scripts/lib/affected-tests/go.sh +0 -0
  31. package/scripts/lib/affected-tests/js.sh +0 -0
  32. package/scripts/lib/affected-tests/nx-turbo.sh +0 -0
  33. package/scripts/lib/affected-tests/python.sh +0 -0
  34. package/scripts/lib/session-state.sh +0 -0
  35. package/scripts/lib/tmp-classify.sh +0 -0
  36. package/scripts/monitor-pr-checks.sh +1 -1
  37. package/scripts/pipeline-tally.sh +0 -0
  38. package/scripts/plugin-version.sh +0 -0
  39. package/scripts/precommit-guard.sh +3 -1
  40. package/scripts/precommit-guard.test.sh +0 -0
  41. package/scripts/render-c4-model.sh +0 -0
  42. package/scripts/resolve-regenerable-conflicts.sh +0 -0
  43. package/scripts/run-in-pid-namespace.sh +0 -0
  44. package/scripts/setup-ralph-loop.sh +0 -0
  45. package/scripts/sync-npm-package.ts +74 -87
  46. package/scripts/sync-opencode-adapter.ts +19 -0
  47. package/scripts/sync-pr-behind.sh +183 -70
  48. package/scripts/taste-profile-update.sh +0 -0
  49. package/scripts/taste-profile-update.test.sh +0 -0
  50. package/scripts/write-budget-marker.sh +0 -0
  51. package/skills/deepen-plan/SKILL.md +1 -1
  52. package/skills/deploy/references/hetzner-setup.md +5 -3
  53. package/skills/drain-prs/SKILL.md +3 -2
  54. package/skills/fix-issue/SKILL.md +8 -8
  55. package/skills/fix-issue/references/agent-authored-exclusion.md +4 -2
  56. package/skills/fix-issue/references/exclude-label-jq-snippet.md +4 -3
  57. package/skills/merge-pr/SKILL.md +5 -3
  58. package/skills/model-launch-review/SKILL.md +4 -4
  59. package/skills/one-shot/SKILL.md +1 -1
  60. package/skills/plan/SKILL.md +1 -1
  61. package/skills/plan/references/plan-sharp-edges.md +7 -1
  62. package/skills/postmerge/SKILL.md +2 -2
  63. package/skills/postmerge/references/deploy-status-debugging.md +3 -0
  64. package/skills/review/SKILL.md +14 -15
  65. package/skills/review/references/review-evidence-comment.md +36 -0
  66. package/skills/review/references/risk-tier-and-fix-rounds.md +2 -0
  67. package/skills/ship/SKILL.md +14 -13
  68. package/skills/ship/references/cleanup-merged-archival.md +39 -0
  69. package/skills/ship/references/delegated-act-authoring-form.md +7 -0
  70. package/skills/ship/references/epic-close-sweep.md +62 -0
  71. package/skills/ship/references/merge-queue-dequeue.md +1 -1
  72. package/skills/ship/references/operator-holds.md +30 -0
  73. package/skills/ship/references/queue-mode.md +1 -1
  74. package/skills/ship/references/ready-run-wait.md +3 -3
  75. package/skills/test-fix-loop/SKILL.md +1 -1
  76. package/skills/verification-scaffold/SKILL.md +73 -0
  77. package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
  78. package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
  79. package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
  80. package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
  81. package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
  82. package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
  83. package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
  84. package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
  85. package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
  86. package/skills/verification-scaffold/references/guards.tsv +21 -0
  87. package/skills/verification-scaffold/references/verification-readme.template +55 -0
  88. package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
  89. package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
  90. package/skills/verification-scaffold/test/generator.test.sh +319 -0
  91. package/skills/verification-scaffold/test/manifest.test.sh +144 -0
  92. package/skills/work/SKILL.md +2 -1
@@ -1,30 +1,22 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Generate the `@soleur/opencode` payload from the canonical tree.
3
+ * Materialize and verify the `@soleur/opencode` npm package payload from the canonical tree.
4
4
  *
5
5
  * bun plugins/soleur/scripts/sync-npm-package.ts
6
6
  * bun plugins/soleur/scripts/sync-npm-package.ts --check
7
+ * bun plugins/soleur/scripts/sync-npm-package.ts --out-dir DIR
7
8
  *
8
- * The npm distribution is a **copy** of the plugin root, and this is the only thing that writes
9
- * that copy. Nothing under `packages/opencode-plugin/` is hand-copied: the package's hand-written
10
- * files are `package.json`, `README.md`, `install.js` and `bin/`, and everything else is a mirror
11
- * of a subtree this script owns.
12
- *
13
- * Why a copy at all, when ADR-291 has OpenCode register skills straight from the canonical tree
14
- * with no second tree to drift? Because npm has no checkout to point at. The package has to carry
15
- * the bytes it installs, so a copy is unavoidable at the boundary; what ADR-245's drift obligation
16
- * buys us instead is that the copy cannot be *wrong*: `--check` compares it against a fresh walk
17
- * and exits 1 naming the path, so a canonical skill, agent, script or hook edited without
18
- * regenerating the package reds in CI rather than shipping stale.
19
- *
20
- * `PAYLOAD_DIRS` is imported from the installer rather than restated here: the set the copy
21
- * carries and the set the installer owns inside a config dir are the same set, and two lists
22
- * would drift apart silently.
9
+ * The npm distribution carries a materialized copy of `PAYLOAD_DIRS` because `npm publish` packs
10
+ * files from the package directory. To avoid duplicating ~800 files in git, the generated payload
11
+ * subtrees under `packages/opencode-plugin/` are gitignored and materialized at build/publish time
12
+ * by this script. `PAYLOAD_DIRS` and `expectedPayloadEntries` are imported from
13
+ * `install-opencode-adapter.ts` so the package builder and the vendored installer share one
14
+ * definition of the payload and preserve executable file bits (`0755`).
23
15
  */
24
16
 
25
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
17
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
26
18
  import { dirname, join, resolve } from "path";
27
- import { PAYLOAD_DIRS } from "./install-opencode-adapter";
19
+ import { PAYLOAD_DIRS, expectedPayloadEntries, walkPayloadFiles } from "./install-opencode-adapter";
28
20
 
29
21
  /** The canonical plugin root — the copy's source. */
30
22
  const CANONICAL_ROOT = resolve(dirname(import.meta.filename), "..");
@@ -32,123 +24,105 @@ const CANONICAL_ROOT = resolve(dirname(import.meta.filename), "..");
32
24
  /** Repo-relative location of the npm package. */
33
25
  const PACKAGE_RELATIVE = "packages/opencode-plugin";
34
26
 
35
- /** Files at the package root that this script does NOT own. */
27
+ /** Hand-written source files at the package root that this script does NOT own. */
36
28
  export const PACKAGE_SOURCE_FILES = ["package.json", "README.md", "install.js", "bin"] as const;
37
29
 
38
- function packageRoot(repoRoot: string): string {
30
+ function defaultPackageRoot(repoRoot: string): string {
39
31
  return join(repoRoot, PACKAGE_RELATIVE);
40
32
  }
41
33
 
42
- /** Every file under `<root>/<relativeDir>`, keyed `<relativeDir>/<path>`. */
43
- function walk(root: string, relativeDir: string): Map<string, string> {
44
- const found = new Map<string, string>();
45
- const visit = (current: string): void => {
46
- let entries;
47
- try {
48
- entries = readdirSync(join(root, current), { withFileTypes: true });
49
- } catch {
50
- return;
51
- }
52
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
53
- const path = `${current}/${entry.name}`;
54
- if (entry.isDirectory()) {
55
- visit(path);
56
- continue;
57
- }
58
- found.set(path, readFileSync(join(root, path), "utf-8"));
59
- }
60
- };
61
- visit(relativeDir);
62
- return found;
63
- }
64
-
65
- /** The canonical payload, keyed package-relative — the whole truth `--check` compares against. */
34
+ /** The canonical payload, keyed package-relative — the truth `--check` compares against. */
66
35
  export function expectedPackage(repoRoot: string): Map<string, string> {
67
36
  const canonical = join(repoRoot, "plugins", "soleur");
68
37
  const expected = new Map<string, string>();
69
- for (const dir of PAYLOAD_DIRS) {
70
- for (const [path, content] of walk(canonical, dir)) expected.set(path, content);
38
+ for (const [path, entry] of expectedPayloadEntries(canonical)) {
39
+ expected.set(path, entry.content);
71
40
  }
72
41
  return expected;
73
42
  }
74
43
 
75
- /** Committed payload files under the package, package-relative. */
76
- function committedPackage(repoRoot: string): string[] {
44
+ /** Materialized payload files under `pkgRoot`, package-relative. */
45
+ function materializedPackage(pkgRoot: string): string[] {
77
46
  const found: string[] = [];
78
47
  for (const dir of PAYLOAD_DIRS) {
79
- for (const path of walk(packageRoot(repoRoot), dir).keys()) found.push(path);
48
+ for (const [path] of walkPayloadFiles(pkgRoot, dir)) found.push(path);
80
49
  }
81
50
  return found.sort();
82
51
  }
83
52
 
84
- /** Drift between the committed package and a fresh walk of the canonical tree. Empty means clean. */
85
- export function packageProblems(repoRoot: string): string[] {
86
- const expected = expectedPackage(repoRoot);
87
- const root = packageRoot(repoRoot);
53
+ /** Drift between a materialized package root and a fresh walk of the canonical tree. */
54
+ export function packageProblems(repoRoot: string, outDir?: string): string[] {
55
+ const canonical = join(repoRoot, "plugins", "soleur");
56
+ const expected = expectedPayloadEntries(canonical);
57
+ const root = outDir ?? defaultPackageRoot(repoRoot);
88
58
  const problems: string[] = [];
89
- for (const [path, content] of expected) {
59
+ for (const [path, entry] of expected) {
60
+ const full = join(root, path);
90
61
  let current: string | undefined;
62
+ let currentExec = false;
91
63
  try {
92
- current = readFileSync(join(root, path), "utf-8");
64
+ current = readFileSync(full, "utf-8");
65
+ currentExec = (statSync(full).mode & 0o111) !== 0;
93
66
  } catch {
94
67
  current = undefined;
95
68
  }
96
69
  if (current === undefined) problems.push(`MISSING: ${path}`);
97
- else if (current !== content) problems.push(`DRIFT: ${path}`);
70
+ else if (current !== entry.content || (entry.executable && !currentExec)) problems.push(`DRIFT: ${path}`);
98
71
  }
99
- for (const path of committedPackage(repoRoot)) {
72
+ for (const path of materializedPackage(root)) {
100
73
  if (!expected.has(path)) problems.push(`EXTRA: ${path}`);
101
74
  }
102
- // The hand-written files are what makes the mirror a package. Their absence is a defect a
103
- // payload-only comparison cannot see: a directory of the right bytes with no bin installs
104
- // nothing.
105
75
  for (const path of PACKAGE_SOURCE_FILES) {
106
76
  if (!existsSync(join(root, path))) problems.push(`MISSING-SOURCE: ${path}`);
107
77
  }
108
78
  return problems.sort();
109
79
  }
110
80
 
111
- /** Write the payload, then report the post-write check (which catches stale extras). */
112
- export function writePackage(repoRoot: string): string[] {
113
- const root = packageRoot(repoRoot);
114
- const expected = expectedPackage(repoRoot);
115
- for (const [path, content] of expected) {
81
+ /** Write the payload into `outDir` (default: `packages/opencode-plugin`), preserving executable bits. */
82
+ export function writePackage(repoRoot: string, outDir?: string): string[] {
83
+ const canonical = join(repoRoot, "plugins", "soleur");
84
+ const root = outDir ?? defaultPackageRoot(repoRoot);
85
+ const expected = expectedPayloadEntries(canonical);
86
+ for (const [path, entry] of expected) {
116
87
  const full = join(root, path);
117
88
  mkdirSync(dirname(full), { recursive: true });
118
- writeFileSync(full, content);
89
+ writeFileSync(full, entry.content);
90
+ if (entry.executable) chmodSync(full, 0o755);
119
91
  }
120
- // A payload file the canonical tree no longer has is not removed by writing it back; drop it
121
- // so a retired skill cannot keep installing from the package.
122
- for (const path of committedPackage(repoRoot)) {
92
+ for (const path of materializedPackage(root)) {
123
93
  if (!expected.has(path)) rmSync(join(root, path), { force: true });
124
94
  }
125
- return packageProblems(repoRoot);
95
+ return packageProblems(repoRoot, root);
96
+ }
97
+
98
+ /** Remove generated payload subtrees from `outDir` (default: `packages/opencode-plugin`). */
99
+ export function cleanPackage(repoRoot: string, outDir?: string): void {
100
+ const root = outDir ?? defaultPackageRoot(repoRoot);
101
+ for (const dir of PAYLOAD_DIRS) {
102
+ rmSync(join(root, dir), { recursive: true, force: true });
103
+ }
126
104
  }
127
105
 
128
- function repoRoot(argv: readonly string[]): string {
129
- const index = argv.indexOf("--repo-root");
130
- if (index === -1) return resolve(CANONICAL_ROOT, "..", "..");
131
- const value = argv[index + 1];
132
- if (value === undefined) throw new Error("--repo-root requires a directory");
133
- return resolve(value);
106
+ function parseArg(argv: readonly string[], flag: string): string | undefined {
107
+ const index = argv.indexOf(flag);
108
+ if (index === -1) return undefined;
109
+ return argv[index + 1];
134
110
  }
135
111
 
136
112
  const USAGE = [
137
- "Usage: bun plugins/soleur/scripts/sync-npm-package.ts [--repo-root DIR] [--check] [--help]",
113
+ "Usage: bun plugins/soleur/scripts/sync-npm-package.ts [--repo-root DIR] [--out-dir DIR] [--check] [--clean] [--help]",
138
114
  "",
139
115
  " --repo-root DIR repository root containing plugins/soleur and packages/opencode-plugin",
140
116
  " (default: this checkout)",
141
- " --check compare the committed package against a fresh walk; write nothing",
117
+ " --out-dir DIR package root to materialize/check (default: <repo-root>/packages/opencode-plugin)",
118
+ " --check compare the materialized package against a fresh walk; write nothing",
119
+ " --clean remove generated payload directories from the package root",
142
120
  " --help, -h print this text and exit 0 without writing",
143
121
  ].join("\n");
144
122
 
145
- /**
146
- * Every argument must be known. A silently ignored typo (`--chek`) takes the WRITE path while the
147
- * operator believes they asked for a check — the direction that overwrites the package.
148
- */
149
123
  function validateArgs(argv: readonly string[]): void {
150
- const withValue = new Set(["--repo-root"]);
151
- const known = new Set([...withValue, "--check", "--help", "-h"]);
124
+ const withValue = new Set(["--repo-root", "--out-dir"]);
125
+ const known = new Set([...withValue, "--check", "--clean", "--help", "-h"]);
152
126
  for (let i = 0; i < argv.length; i += 1) {
153
127
  const arg = argv[i];
154
128
  if (arg === "--help" || arg === "-h") {
@@ -159,15 +133,28 @@ function validateArgs(argv: readonly string[]): void {
159
133
  console.error(`unknown argument: ${arg}\n${USAGE}`);
160
134
  process.exit(2);
161
135
  }
162
- if (withValue.has(arg)) i += 1;
136
+ if (withValue.has(arg)) {
137
+ const next = argv[i + 1];
138
+ if (next === undefined || next.startsWith("-")) {
139
+ console.error(`${arg} requires a directory\n${USAGE}`);
140
+ process.exit(2);
141
+ }
142
+ i += 1;
143
+ }
163
144
  }
164
145
  }
165
146
 
166
147
  function main(): void {
167
148
  const argv = process.argv.slice(2);
168
149
  validateArgs(argv);
169
- const root = repoRoot(argv);
170
- const problems = argv.includes("--check") ? packageProblems(root) : writePackage(root);
150
+ const root = resolve(parseArg(argv, "--repo-root") ?? resolve(CANONICAL_ROOT, "..", ".."));
151
+ const explicitOut = parseArg(argv, "--out-dir");
152
+ const outDir = explicitOut !== undefined ? resolve(explicitOut) : undefined;
153
+ if (argv.includes("--clean")) {
154
+ cleanPackage(root, outDir);
155
+ return;
156
+ }
157
+ const problems = argv.includes("--check") ? packageProblems(root, outDir) : writePackage(root, outDir);
171
158
  if (problems.length > 0) {
172
159
  for (const problem of problems) console.error(problem);
173
160
  process.exit(1);
@@ -302,6 +302,25 @@ function instructions(): string {
302
302
  "JavaScript rather than a command and are not decided by the Bash guards — a limitation this",
303
303
  "harness shares with the guard scripts' own input contract.",
304
304
  "",
305
+ "**One canonical gate is absent here, and its consequence is a refusal you cannot talk around.**",
306
+ "`hooks/operator-stage-approval.sh` is what mints the digest-bound approval receipt a staged",
307
+ "`--apply` consumes, and no OpenCode surface can both rewrite the command and ask in the same",
308
+ "decision (ADR-264). So `plugins/soleur/scripts/lib/operator-script.sh`'s `soleur_op_stage_gate`",
309
+ "finds no receipt and exits **75** (`SOLEUR_BOOTSTRAP_APPROVAL_REQUIRED`). The typed-`yes`",
310
+ "fallback is gated on `-t 0`/`-t 1` **and** `soleur_op_in_agent_harness`, which recognises only",
311
+ "`CLAUDECODE`/`CLAUDE_CODE_ENTRYPOINT` — neither is set under OpenCode — so that arm is open",
312
+ "here whenever the spawned shell has a tty, and a terminal confirmation from an agent is not",
313
+ "evidence of a person. **No receipt can be minted on this harness**, so a staged `--apply`",
314
+ "refuses rather than approving. Nothing is approved silently.",
315
+ "",
316
+ "A degraded hook decision — a shell call whose `workdir` could not be correlated, or a runtime",
317
+ "with no `tool.execute.before` surface — is announced on stderr **and** written to the event log",
318
+ "when `SOLEUR_OPENCODE_EVENT_LOG` names a file (its parent directory must exist; unset means no",
319
+ "log). Whether your terminal surfaces plugin stderr is not something this adapter measured, so",
320
+ "arm the log when you need certainty. A `shell` call whose `workdir` cannot be used is",
321
+ "**prompted** rather than run unchecked — the guards still run, against the session directory,",
322
+ "and only their declining becomes the prompt.",
323
+ "",
305
324
  "The post-compaction re-read is **armed, not guaranteed**, unlike Codex and Devin:",
306
325
  "`ctx.session.hook(\"compaction\")` sets state and the next `ctx.session.hook(\"context\")`",
307
326
  "re-injects the rule corpus plus the re-read directive. §12.3 captured that `context` fire",