opencode-swarm 7.136.1 → 7.136.2

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 (67) hide show
  1. package/.opencode/skills/swarm-pr-feedback/SKILL.md +41 -1
  2. package/.opencode/skills/swarm-pr-review/SKILL.md +72 -24
  3. package/README.md +18 -0
  4. package/dist/background/workspace-snapshot.d.ts +54 -0
  5. package/dist/cli/{config-doctor-6fphg5xm.js → config-doctor-b67nb84b.js} +2 -2
  6. package/dist/cli/{curation-policy-m24jbyag.js → curation-policy-kvzhfaj1.js} +2 -2
  7. package/dist/cli/{curator-llm-factory-w5arq7tr.js → curator-llm-factory-x7kvry9x.js} +17 -14
  8. package/dist/cli/{curator-zrt5sqj2.js → curator-nrmj8bds.js} +17 -14
  9. package/dist/cli/{evidence-summary-service-w006jnpg.js → evidence-summary-service-cf8sz2bq.js} +3 -1
  10. package/dist/cli/{guardrail-explain-462jwhbh.js → guardrail-explain-5kd44rcj.js} +18 -15
  11. package/dist/cli/{guardrail-log-ax21jd9e.js → guardrail-log-aksrzqdx.js} +3 -3
  12. package/dist/cli/hashing-mjwn6j4y.js +34 -0
  13. package/dist/cli/{hive-promoter-2vacnbws.js → hive-promoter-cy21rhnd.js} +17 -14
  14. package/dist/cli/{index-hzkvbycn.js → index-0755132s.js} +4 -4
  15. package/dist/cli/{index-9gxp450h.js → index-1kn24ja0.js} +1 -1
  16. package/dist/cli/{index-h3bweb88.js → index-21115szq.js} +5 -5
  17. package/dist/cli/{index-g7g4hqh3.js → index-37v2wxqe.js} +1 -1
  18. package/dist/cli/{index-gf7hqbmz.js → index-7ayaq27q.js} +5 -1
  19. package/dist/cli/{index-kzc2ygea.js → index-84nz7bhe.js} +1 -1
  20. package/dist/cli/{index-1sq47n6t.js → index-ad6j0m7k.js} +12 -6
  21. package/dist/cli/{index-zkddc5ye.js → index-azghvnja.js} +2 -2
  22. package/dist/cli/{index-rhgctfn3.js → index-ckybsn6p.js} +19 -16
  23. package/dist/cli/{index-cggqh2dz.js → index-kwwxevne.js} +53 -996
  24. package/dist/cli/{index-tfa40hwb.js → index-m5fw4mer.js} +19 -2
  25. package/dist/cli/{index-jwrydqjp.js → index-rw3f73aq.js} +1 -1
  26. package/dist/cli/{index-dyy3hvk3.js → index-s0rbdp61.js} +2 -2
  27. package/dist/cli/{index-p1pqqwgp.js → index-s0vahtdm.js} +1 -1
  28. package/dist/cli/{index-prnjt2jr.js → index-sr2cynyw.js} +4509 -2306
  29. package/dist/cli/index-wjm896ey.js +1100 -0
  30. package/dist/cli/{index-f4cmtd89.js → index-wnz282j3.js} +2 -2
  31. package/dist/cli/{index-y6a7gjtj.js → index-wsdnttf4.js} +228 -81
  32. package/dist/cli/{index-r9v98d0y.js → index-y0xaq4f3.js} +39 -4
  33. package/dist/cli/index-y8552snf.js +264 -0
  34. package/dist/cli/index-z1tm47nj.js +783 -0
  35. package/dist/cli/index.js +17 -14
  36. package/dist/cli/{knowledge-escalator-1zt8qy1c.js → knowledge-escalator-ta3xjrdj.js} +3 -3
  37. package/dist/cli/{knowledge-events-ej3s9tsm.js → knowledge-events-dk6banmz.js} +1 -1
  38. package/dist/cli/{knowledge-store-ey4cbkp4.js → knowledge-store-h3jz10bv.js} +1 -1
  39. package/dist/cli/{knowledge-validator-zwmq7s2c.js → knowledge-validator-jsz1dxkr.js} +4 -4
  40. package/dist/cli/runner-2v413r74.js +21 -0
  41. package/dist/cli/{scan-cursor-809hf2n1.js → scan-cursor-48gwzh9c.js} +2 -2
  42. package/dist/cli/{schema-mhd7xqwr.js → schema-f937b9cs.js} +5 -1
  43. package/dist/cli/{skill-generator-yyc8zd9j.js → skill-generator-4p10ktw1.js} +5 -5
  44. package/dist/cli/{workspace-snapshot-jmyamqnv.js → workspace-snapshot-h5rzw37b.js} +5 -1
  45. package/dist/commands/registry.d.ts +72 -0
  46. package/dist/commands/skill-opt.d.ts +41 -0
  47. package/dist/config/schema.d.ts +47 -0
  48. package/dist/hooks/guardrails/file-authority.d.ts +11 -2
  49. package/dist/hooks/pr-workflow-gate.d.ts +261 -4
  50. package/dist/hooks/pr-workflow-response-gate.d.ts +27 -9
  51. package/dist/hooks/write-target-resolver.d.ts +19 -0
  52. package/dist/index.js +353 -318
  53. package/dist/services/skill-evaluator.d.ts +17 -0
  54. package/dist/services/skill-optimizer/activation.d.ts +58 -0
  55. package/dist/services/skill-optimizer/candidates.d.ts +88 -0
  56. package/dist/services/skill-optimizer/controller.d.ts +146 -0
  57. package/dist/services/skill-optimizer/deterministic-seed.d.ts +29 -0
  58. package/dist/services/skill-optimizer/lifecycle.d.ts +70 -0
  59. package/dist/services/skill-optimizer/promoted-external-staleness.d.ts +98 -0
  60. package/dist/services/skill-optimizer/retirement.d.ts +54 -0
  61. package/dist/services/skill-optimizer/skill-eval-tasks.d.ts +47 -0
  62. package/dist/services/skill-optimizer/smoke.d.ts +46 -0
  63. package/dist/services/skill-optimizer/store.d.ts +118 -0
  64. package/dist/tools/write-pr-review-trigger-eval.d.ts +2 -1
  65. package/dist/utils/stable-stringify.d.ts +46 -0
  66. package/evaluation-fixtures/skill-eval/scoring/score-skill-eval.cjs +97 -0
  67. package/package.json +1 -1
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Append-only lifecycle store for the governed skill optimizer (issue #1822).
3
+ *
4
+ * Storage layout (all under `.swarm/`, AGENTS.md invariant #4):
5
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/lifecycle.jsonl (authoritative)
6
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/state.json (derived projection)
7
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/baseline.md (frozen baseline snapshot)
8
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/candidate.md (drafted candidate)
9
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/diff.patch (computed diff)
10
+ * .swarm/evolution/skills/<skillSlug>/<candidateId>/rollback.md (pre-activation snapshot)
11
+ * .swarm/evolution/skills/lifecycle-quarantine.<ts>.<hash> (corrupt-tail salvage)
12
+ *
13
+ * Integrity model mirrors `src/plan/ledger.ts`:
14
+ * - fsync+rename atomic append, gated by an evidence lock;
15
+ * - replay stops at the first unparseable line, sets `truncated`, captures
16
+ * the bad suffix, and quarantines it WITHOUT rewriting the canonical ledger;
17
+ * - hash-before/hash-after chain (reuses the plan-ledger field semantics —
18
+ * no parallel "previousStateHash");
19
+ * - partial/corrupt writes never count as acceptance (replay-after-write
20
+ * verification in `recordTransition`, lifecycle.ts).
21
+ *
22
+ * IDs are collision-resistant and filesystem-safe (`crypto.randomUUID()`).
23
+ */
24
+ import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
25
+ import { withEvidenceLock } from '../../evidence/lock.js';
26
+ /** Lifecycle states (issue #1822 durable lifecycle). */
27
+ export type SkillOptState = 'discovered' | 'drafted' | 'smoke_validated' | 'validation_running' | 'accepted_pending_approval' | 'rejected' | 'inconclusive' | 'activated' | 'expired' | 'rolled_back';
28
+ /** A single append-only lifecycle event. Hash chain reuses plan-ledger semantics. */
29
+ export interface SkillOptEvent {
30
+ seq: number;
31
+ timestamp: string;
32
+ candidateId: string;
33
+ skillSlug: string;
34
+ eventType: string;
35
+ fromState: SkillOptState | null;
36
+ toState: SkillOptState;
37
+ actor: string;
38
+ origin: string;
39
+ contentHashBefore: string | null;
40
+ contentHashAfter: string | null;
41
+ hashBefore: string;
42
+ hashAfter: string;
43
+ reason: string;
44
+ evidenceRefs: string[];
45
+ payload?: Record<string, unknown>;
46
+ }
47
+ export interface ReplayResult {
48
+ events: SkillOptEvent[];
49
+ state: SkillOptState | null;
50
+ truncated: boolean;
51
+ badSuffix: string | null;
52
+ /** Sequence number of the last complete (hash-verified) event. */
53
+ lastCompleteSeq: number;
54
+ }
55
+ /** SHA-256 over canonical JSON of an arbitrary value. */
56
+ export declare function computeStateHash(value: unknown): string;
57
+ /** SHA-256 of raw text content (a SKILL.md body). */
58
+ export declare function computeContentHash(content: string): string;
59
+ export declare function isValidSkillSlug(slug: string): boolean;
60
+ /** Validate a candidate ID is filesystem-safe (uuid or equivalent). */
61
+ export declare function isValidCandidateId(id: string): boolean;
62
+ /** Mint a fresh collision-resistant candidate ID. */
63
+ export declare function mintCandidateId(): string;
64
+ /**
65
+ * Fsync-then-rename atomic write (mirrors `writeFileFsyncedThenRename` in
66
+ * `src/plan/ledger.ts:169`). fsync guarantees the bytes hit durable storage
67
+ * before the rename makes the file visible.
68
+ */
69
+ declare function writeFileFsyncedThenRename(tempPath: string, targetPath: string, data: string): void;
70
+ /** DI seam for test injection (AGENTS.md invariant #7 — preferred over mock.module). */
71
+ export declare const _internals: {
72
+ withEvidenceLock: typeof withEvidenceLock;
73
+ writeFileFsyncedThenRename: typeof writeFileFsyncedThenRename;
74
+ writeFileSync: typeof writeFileSync;
75
+ readFileSync: typeof readFileSync;
76
+ renameSync: typeof renameSync;
77
+ unlinkSync: typeof unlinkSync;
78
+ existsSync: typeof existsSync;
79
+ statSync: import("node:fs").StatSyncFn;
80
+ };
81
+ /**
82
+ * Append a lifecycle event atomically. Computes the hash chain from the
83
+ * current replayed state, fsync+rename under an evidence lock, then verifies
84
+ * the append by re-reading. Returns the persisted event.
85
+ *
86
+ * Throws if the post-write replay does not contain the appended event at the
87
+ * expected seq — a partial/corrupt write never counts as a successful
88
+ * transition.
89
+ */
90
+ export declare function appendEvent(directory: string, eventInput: Omit<SkillOptEvent, 'seq' | 'timestamp' | 'hashBefore' | 'hashAfter'> & {
91
+ timestamp?: string;
92
+ }): Promise<SkillOptEvent>;
93
+ /**
94
+ * Replay a candidate's lifecycle ledger. Mirrors `readLedgerEventsWithIntegrity`
95
+ * in `src/plan/ledger.ts:1105`: stops at the first unparseable line, marks the
96
+ * replay `truncated`, and surfaces the bad suffix for quarantine. The canonical
97
+ * ledger is NEVER rewritten or truncated by this read.
98
+ */
99
+ export declare function replayCandidate(directory: string, skillSlug: string, candidateId: string): ReplayResult;
100
+ /**
101
+ * Quarantine a corrupt ledger suffix. Writes the bad suffix to a unique side
102
+ * file under `.swarm/evolution/skills/`. NEVER rewrites or truncates the
103
+ * canonical `lifecycle.jsonl`. Mirrors `quarantineLedgerSuffix` in
104
+ * `src/plan/ledger.ts:1188`.
105
+ */
106
+ export declare function quarantineSuffix(directory: string, skillSlug: string, badSuffix: string): string;
107
+ /**
108
+ * Derive the projection `state.json` from the ledger replay. Derived, not
109
+ * authoritative — callers must always re-derive from the ledger rather than
110
+ * trust a stale projection. Writes atomically (temp+rename).
111
+ */
112
+ export declare function writeStateProjection(directory: string, skillSlug: string, candidateId: string, replay: ReplayResult): void;
113
+ /** Snapshot a text file (baseline/candidate/rollback) atomically. */
114
+ export declare function writeArtifact(directory: string, skillSlug: string, candidateId: string, fileName: string, content: string): string;
115
+ /** Read a snapshot artifact, returning null if absent. */
116
+ export declare function readArtifact(directory: string, skillSlug: string, candidateId: string, fileName: string): string | null;
117
+ export declare const SKILL_OPT_STORE_SCHEMA_VERSION = "1";
118
+ export {};
@@ -1,9 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { resolveExactMergeBase, resolvePrWorkflowRevisionDigest } from '../background/workspace-snapshot.js';
2
+ import { resolveExactMergeBase, resolvePrWorkflowRevisionDigest, resolvePrWorkflowRevisionDigestAsync } from '../background/workspace-snapshot.js';
3
3
  import { createSwarmTool } from './create-tool';
4
4
  export { PR_REVIEW_TRIGGER_DEFINITIONS } from '../background/pr-review-trigger-contract.js';
5
5
  export declare const _internals: {
6
6
  resolvePrWorkflowRevisionDigest: typeof resolvePrWorkflowRevisionDigest;
7
+ resolvePrWorkflowRevisionDigestAsync: typeof resolvePrWorkflowRevisionDigestAsync;
7
8
  resolveMergeBase: typeof resolveExactMergeBase;
8
9
  };
9
10
  declare const WritePrReviewTriggerEvalArgsSchema: z.ZodObject<{
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Stable JSON serialization helpers.
3
+ *
4
+ * # Why this exists
5
+ *
6
+ * Several subsystems hash tool-call arguments for repetition / spiral
7
+ * detection. Two requirements make naive `JSON.stringify(value)` incorrect:
8
+ *
9
+ * 1. **Key-order independence.** Two semantically identical objects whose
10
+ * keys were inserted in different order (`{a:1,b:2}` vs `{b:2,a:1}`) must
11
+ * hash equally, otherwise a genuine repetition loop whose args happen to
12
+ * be built with reordered keys is missed.
13
+ *
14
+ * 2. **No nested-key loss.** A `JSON.stringify(value, sortedKeysArray)`
15
+ * property-list replacer looks like it sorts keys — and it does, but only
16
+ * for the top level. At every deeper object it acts as a *filter*,
17
+ * dropping any key not present in the (top-level-derived) list. For nested
18
+ * args like `{todos:[{content,status}]}` every todo collapses to `{}`,
19
+ * re-introducing the exact false-collision class this is meant to prevent.
20
+ *
21
+ * `stableCanonicalStringify` rebuilds each object with sorted keys at every
22
+ * depth (no filtering) and serializes arrays element-wise, producing a stable
23
+ * canonical string suitable for hashing.
24
+ *
25
+ * Originally introduced for the adversarial-detector spiral hash
26
+ * (issue #2060) and shared with `file-authority.hashArgs` so both code paths
27
+ * use one correct implementation.
28
+ */
29
+ /**
30
+ * Recursively produces a canonical JSON string with object keys sorted at
31
+ * EVERY depth (not just the top level). Arrays are serialized element-wise in
32
+ * index order.
33
+ *
34
+ * Why not `JSON.stringify(value, sortedKeysArray)`: a property-list replacer
35
+ * array acts as a KEY FILTER at every object depth, not just the top level, so
36
+ * any key not in the (top-level-derived) list is silently dropped from nested
37
+ * objects. For tool args like `{todos:[{content,status}]}`, that collapses
38
+ * every todo to `{}`, re-introducing the exact false-collision class this
39
+ * function exists to eliminate. Sorting must be done by rebuilding each object
40
+ * with sorted keys before serialization.
41
+ *
42
+ * Throws on cyclic structures (infinite recursion) and on values that
43
+ * `JSON.stringify` cannot represent (BigInt); callers should wrap in try/catch
44
+ * and fall back to a stable coarse hash.
45
+ */
46
+ export declare function stableCanonicalStringify(value: unknown): string;
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Skill-eval project scorer wrapper (issue #1822 — D1, critic C1).
4
+ *
5
+ * The evaluation substrate invokes `kind:'project'` scorers as isolated
6
+ * subprocesses (runner.ts:175-228), passing:
7
+ * - SWARM_EVAL_TASK_ID, SWARM_EVAL_CANDIDATE_ID, SWARM_EVAL_SEED
8
+ * - SWARM_EVAL_ARTIFACT_DIR (contains model-output.json with the candidate's
9
+ * generated text under { v: 1, text: "..." })
10
+ * - argv: [<this-script>, <phrase-spec-path>]
11
+ *
12
+ * The scorer reads the candidate text from model-output.json, reads the phrase
13
+ * spec (required_phrases / forbidden_phrases), and emits a ScorerOutputV1 line:
14
+ * { "v": 1, "score": <0..1>, "cost": { "source": "unavailable" } }
15
+ *
16
+ * The scoring arithmetic is the SAME as `scoreSkillPhrases` in
17
+ * src/services/skill-evaluator.ts (the source of truth). A parity test
18
+ * (tests/unit/services/skill-evaluator-refactor.test.ts) proves the two agree,
19
+ * so there is no duplicate scorer — one authoritative function, one thin
20
+ * subprocess mirror that must match.
21
+ *
22
+ * Score = requiredHits / max(1, required.length), minus a 1-point penalty if
23
+ * any forbidden phrase is present, clamped to >= 0. Phrase matching is
24
+ * case-insensitive substring.
25
+ */
26
+
27
+ 'use strict';
28
+
29
+ const fs = require('node:fs');
30
+ const path = require('node:path');
31
+
32
+ function readArtifact() {
33
+ const dir = process.env.SWARM_EVAL_ARTIFACT_DIR;
34
+ if (!dir) throw new Error('missing SWARM_EVAL_ARTIFACT_DIR');
35
+ const file = path.join(dir, 'model-output.json');
36
+ const raw = fs.readFileSync(file, 'utf8');
37
+ const parsed = JSON.parse(raw);
38
+ if (!parsed || typeof parsed.text !== 'string') {
39
+ throw new Error('model-output.json missing text field');
40
+ }
41
+ return parsed.text;
42
+ }
43
+
44
+ function readPhraseSpec(specPath) {
45
+ if (!specPath || !fs.existsSync(specPath)) {
46
+ return { required: [], forbidden: [] };
47
+ }
48
+ const parsed = JSON.parse(fs.readFileSync(specPath, 'utf8'));
49
+ return {
50
+ required: Array.isArray(parsed.required_phrases) ? parsed.required_phrases : [],
51
+ forbidden: Array.isArray(parsed.forbidden_phrases) ? parsed.forbidden_phrases : [],
52
+ };
53
+ }
54
+
55
+ function includesPhrase(content, phrase) {
56
+ return content.toLowerCase().includes(String(phrase).toLowerCase());
57
+ }
58
+
59
+ function scoreSkillPhrases(content, spec) {
60
+ const required = spec.required;
61
+ const forbidden = spec.forbidden;
62
+ let requiredHits = 0;
63
+ for (const phrase of required) {
64
+ if (includesPhrase(content, phrase)) requiredHits++;
65
+ }
66
+ const requiredScore = required.length === 0 ? 1 : requiredHits / Math.max(1, required.length);
67
+ const forbiddenPenalty = forbidden.some((p) => includesPhrase(content, p)) ? 1 : 0;
68
+ return Math.max(0, requiredScore - forbiddenPenalty);
69
+ }
70
+
71
+ function main() {
72
+ const specPath = process.argv[2];
73
+ const content = readArtifact();
74
+ const spec = readPhraseSpec(specPath);
75
+ const score = scoreSkillPhrases(content, spec);
76
+ process.stdout.write(
77
+ JSON.stringify({ v: 1, score, cost: { source: 'unavailable' } }) + '\n',
78
+ );
79
+ }
80
+
81
+ try {
82
+ main();
83
+ } catch (err) {
84
+ // ScorerFailure 'malformed' — the runner treats non-zero exit / bad JSON as a
85
+ // task failure rather than a candidate score. Emit a zero-score with a
86
+ // metadata reason so the run records the failure deterministically.
87
+ process.stderr.write(`score-skill-eval failed: ${err && err.message ? err.message : String(err)}\n`);
88
+ process.stdout.write(
89
+ JSON.stringify({
90
+ v: 1,
91
+ score: 0,
92
+ cost: { source: 'unavailable' },
93
+ metadata: { failure: 'scorer-error', reason: String(err && err.message ? err.message : err).slice(0, 200) },
94
+ }) + '\n',
95
+ );
96
+ process.exitCode = 0;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.136.1",
3
+ "version": "7.136.2",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",