openhermes 4.11.2 → 4.13.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/CONTEXT.md +1 -1
- package/ETHOS.md +1 -1
- package/README.md +12 -18
- package/bootstrap.ts +73 -148
- package/docs/HOW-IT-WORKS.md +162 -0
- package/docs/adr/ADR-0001-rebuild-vs-increment.md +30 -0
- package/docs/adr/ADR-0002-routing-graph-vs-linear-chain.md +36 -0
- package/docs/adr/ADR-0003-per-directory-plan-storage.md +34 -0
- package/docs/adr/ADR-0004-composer-fragment-architecture.md +42 -0
- package/docs/adr/ADR-0005-hook-system-design.md +42 -0
- package/docs/adr/README.md +9 -0
- package/harness/codex/AUTOPILOT.md +30 -23
- package/harness/codex/CHARTER.md +3 -3
- package/harness/lib/composer/compose.test.ts +11 -0
- package/harness/lib/composer/fragments/02-delegation.md +2 -1
- package/harness/lib/composer/fragments/04-task-flow.md +42 -2
- package/harness/lib/composer/fragments/08-routing.md +1 -1
- package/harness/lib/composer/fragments/09-guardrails.md +17 -4
- package/harness/lib/composer/index.ts +1 -1
- package/harness/lib/guards/guard-config.ts +72 -0
- package/harness/lib/hooks/builtins/confidence-gate-hook.ts +2 -4
- package/harness/lib/hooks/builtins/delegation-depth-hook.ts +23 -4
- package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -0
- package/harness/lib/hooks/builtins/next-route-hook.ts +24 -0
- package/harness/lib/hooks/builtins/plan-check-hook.ts +2 -2
- package/harness/lib/hooks/builtins/route-tracking-hook.ts +79 -25
- package/harness/lib/hooks/hooks.test.ts +117 -205
- package/harness/lib/hooks/index.ts +38 -30
- package/harness/lib/hooks/registry.ts +309 -416
- package/harness/lib/hooks/types.ts +116 -71
- package/harness/lib/plans/plan-location.ts +134 -0
- package/harness/lib/routing/index.ts +21 -0
- package/harness/lib/routing/route-guidance.ts +147 -0
- package/harness/lib/routing/route-resolver.ts +58 -0
- package/harness/lib/routing/routing.test.ts +195 -0
- package/harness/lib/routing/skill-frontmatter.ts +125 -0
- package/harness/lib/routing/types.ts +52 -0
- package/harness/skills/oh-ascii/SKILL.md +1 -1
- package/harness/skills/oh-fusion/DEEP.md +56 -33
- package/harness/skills/oh-fusion/SKILL.md +30 -16
- package/harness/skills/oh-init/DEEP.md +2 -2
- package/harness/skills/oh-manifest/SKILL.md +1 -0
- package/harness/skills/oh-plan-review/DEEP.md +1 -1
- package/harness/skills/oh-planner/DEEP.md +3 -3
- package/harness/skills/oh-review/DEEP.md +2 -0
- package/harness/skills/oh-review/SKILL.md +1 -0
- package/package.json +56 -55
- package/harness/lib/background/background.test.ts +0 -197
- package/harness/lib/background/index.ts +0 -7
- package/harness/lib/background/interfaces.ts +0 -31
- package/harness/lib/background/manager.ts +0 -320
- package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
- package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
- package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
- package/harness/lib/memory/index.ts +0 -18
- package/harness/lib/memory/interfaces.ts +0 -53
- package/harness/lib/memory/memory-manager.ts +0 -205
- package/harness/lib/memory/memory.test.ts +0 -491
- package/harness/lib/memory/plan-store.ts +0 -366
- package/harness/lib/recovery/handler.ts +0 -243
- package/harness/lib/recovery/index.ts +0 -14
- package/harness/lib/recovery/interfaces.ts +0 -48
- package/harness/lib/recovery/patterns.ts +0 -149
- package/harness/lib/recovery/recovery.test.ts +0 -312
- package/harness/lib/sanity/anomaly-tracker.ts +0 -127
- package/harness/lib/sanity/checker.ts +0 -178
- package/harness/lib/sanity/index.ts +0 -13
- package/harness/lib/sanity/interfaces.ts +0 -24
- package/harness/lib/sanity/sanity.test.ts +0 -472
- package/harness/lib/sync/file-watcher.ts +0 -174
- package/harness/lib/sync/index.ts +0 -11
- package/harness/lib/sync/interfaces.ts +0 -27
- package/harness/lib/sync/plan-sync.ts +0 -536
- package/harness/lib/sync/sync.test.ts +0 -832
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { consumeRouteGuidance, parseSkillFrontmatter, resolveRoute } from "./index.ts";
|
|
4
|
+
|
|
5
|
+
describe("routing frontmatter", () => {
|
|
6
|
+
it("parses scalar route values", () => {
|
|
7
|
+
const parsed = parseSkillFrontmatter(`---
|
|
8
|
+
name: oh-test
|
|
9
|
+
route:
|
|
10
|
+
pass: oh-builder
|
|
11
|
+
fail: oh-review
|
|
12
|
+
blocker: surface
|
|
13
|
+
---`);
|
|
14
|
+
|
|
15
|
+
assert.ok(parsed);
|
|
16
|
+
assert.deepEqual(parsed.route, {
|
|
17
|
+
pass: ["oh-builder"],
|
|
18
|
+
fail: ["oh-review"],
|
|
19
|
+
blocker: ["surface"],
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("parses yaml route lists", () => {
|
|
24
|
+
const parsed = parseSkillFrontmatter(`---
|
|
25
|
+
name: oh-test
|
|
26
|
+
route:
|
|
27
|
+
pass:
|
|
28
|
+
- oh-gauntlet
|
|
29
|
+
- oh-ship
|
|
30
|
+
fail:
|
|
31
|
+
- oh-builder
|
|
32
|
+
blocker: surface
|
|
33
|
+
---`);
|
|
34
|
+
|
|
35
|
+
assert.ok(parsed);
|
|
36
|
+
assert.deepEqual(parsed.route, {
|
|
37
|
+
pass: ["oh-gauntlet", "oh-ship"],
|
|
38
|
+
fail: ["oh-builder"],
|
|
39
|
+
blocker: ["surface"],
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("parses inline route arrays", () => {
|
|
44
|
+
const parsed = parseSkillFrontmatter(`---
|
|
45
|
+
name: oh-test
|
|
46
|
+
route:
|
|
47
|
+
pass: [oh-gauntlet, oh-ship]
|
|
48
|
+
fail: [surface, oh-expert]
|
|
49
|
+
blocker: surface
|
|
50
|
+
---`);
|
|
51
|
+
|
|
52
|
+
assert.ok(parsed);
|
|
53
|
+
assert.deepEqual(parsed.route, {
|
|
54
|
+
pass: ["oh-gauntlet", "oh-ship"],
|
|
55
|
+
fail: ["surface", "oh-expert"],
|
|
56
|
+
blocker: ["surface"],
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("resolveRoute", () => {
|
|
62
|
+
const route = {
|
|
63
|
+
pass: ["oh-gauntlet", "oh-ship"],
|
|
64
|
+
fail: ["oh-builder"],
|
|
65
|
+
blocker: ["surface"],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
it("defaults to the first candidate for an outcome", () => {
|
|
69
|
+
const resolved = resolveRoute(route, { outcome: "pass" });
|
|
70
|
+
assert.deepEqual(resolved, {
|
|
71
|
+
outcome: "pass",
|
|
72
|
+
candidates: ["oh-gauntlet", "oh-ship"],
|
|
73
|
+
selected: "oh-gauntlet",
|
|
74
|
+
reason: 'Selected first declared route for outcome "pass".',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("prefers an evidence target when it matches a candidate", () => {
|
|
79
|
+
const resolved = resolveRoute(route, { outcome: "pass", target: "oh-ship" });
|
|
80
|
+
assert.deepEqual(resolved, {
|
|
81
|
+
outcome: "pass",
|
|
82
|
+
candidates: ["oh-gauntlet", "oh-ship"],
|
|
83
|
+
selected: "oh-ship",
|
|
84
|
+
reason: 'Selected "oh-ship" from output evidence.',
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("prefers oh-ship for verified done ship work", () => {
|
|
89
|
+
const resolved = resolveRoute(route, {
|
|
90
|
+
outcome: "pass",
|
|
91
|
+
verification: "verified",
|
|
92
|
+
action: "done",
|
|
93
|
+
work: "ship",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
assert.deepEqual(resolved, {
|
|
97
|
+
outcome: "pass",
|
|
98
|
+
verification: "verified",
|
|
99
|
+
action: "done",
|
|
100
|
+
work: "ship",
|
|
101
|
+
candidates: ["oh-gauntlet", "oh-ship"],
|
|
102
|
+
selected: "oh-ship",
|
|
103
|
+
reason: 'Selected "oh-ship" for verified ship-ready work.',
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("prefers oh-gauntlet for unverified work", () => {
|
|
108
|
+
const resolved = resolveRoute(route, {
|
|
109
|
+
outcome: "pass",
|
|
110
|
+
verification: "unverified",
|
|
111
|
+
action: "done",
|
|
112
|
+
work: "ship",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.deepEqual(resolved, {
|
|
116
|
+
outcome: "pass",
|
|
117
|
+
verification: "unverified",
|
|
118
|
+
action: "done",
|
|
119
|
+
work: "ship",
|
|
120
|
+
candidates: ["oh-gauntlet", "oh-ship"],
|
|
121
|
+
selected: "oh-gauntlet",
|
|
122
|
+
reason: 'Selected "oh-gauntlet" because work is still unverified.',
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("prefers oh-gauntlet for verify work", () => {
|
|
127
|
+
const resolved = resolveRoute(route, {
|
|
128
|
+
outcome: "pass",
|
|
129
|
+
verification: "verified",
|
|
130
|
+
action: "done",
|
|
131
|
+
work: "verify",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
assert.deepEqual(resolved, {
|
|
135
|
+
outcome: "pass",
|
|
136
|
+
verification: "verified",
|
|
137
|
+
action: "done",
|
|
138
|
+
work: "verify",
|
|
139
|
+
candidates: ["oh-gauntlet", "oh-ship"],
|
|
140
|
+
selected: "oh-gauntlet",
|
|
141
|
+
reason: 'Selected "oh-gauntlet" for verification work.',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("prefers oh-builder for fixable implementation work", () => {
|
|
146
|
+
const resolved = resolveRoute({
|
|
147
|
+
pass: ["oh-gauntlet", "oh-ship", "oh-builder"],
|
|
148
|
+
fail: ["oh-builder"],
|
|
149
|
+
blocker: ["surface"],
|
|
150
|
+
}, {
|
|
151
|
+
outcome: "pass",
|
|
152
|
+
verification: "verified",
|
|
153
|
+
action: "fixable",
|
|
154
|
+
work: "implement",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
assert.deepEqual(resolved, {
|
|
158
|
+
outcome: "pass",
|
|
159
|
+
verification: "verified",
|
|
160
|
+
action: "fixable",
|
|
161
|
+
work: "implement",
|
|
162
|
+
candidates: ["oh-gauntlet", "oh-ship", "oh-builder"],
|
|
163
|
+
selected: "oh-builder",
|
|
164
|
+
reason: 'Selected "oh-builder" for fixable implementation work.',
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("consumeRouteGuidance", () => {
|
|
170
|
+
it("promotes a selected route into an explicit next-route instruction", () => {
|
|
171
|
+
const consumed = consumeRouteGuidance([
|
|
172
|
+
"Review complete",
|
|
173
|
+
'ROUTE_GUIDANCE: {"outcome":"pass","candidates":["oh-gauntlet","oh-ship"],"selected":"oh-ship","reason":"Selected \\\"oh-ship\\\" from output evidence."}',
|
|
174
|
+
].join("\n"));
|
|
175
|
+
|
|
176
|
+
assert.equal(consumed.selected, "oh-ship");
|
|
177
|
+
assert.match(consumed.output, /NEXT_ROUTE: oh-ship/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("leaves output unchanged when no route guidance is present", () => {
|
|
181
|
+
const output = "plain output";
|
|
182
|
+
const consumed = consumeRouteGuidance(output);
|
|
183
|
+
|
|
184
|
+
assert.equal(consumed.selected, null);
|
|
185
|
+
assert.equal(consumed.output, output);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("ignores malformed route guidance safely", () => {
|
|
189
|
+
const output = 'Review complete\nROUTE_GUIDANCE: {"selected":42}';
|
|
190
|
+
const consumed = consumeRouteGuidance(output);
|
|
191
|
+
|
|
192
|
+
assert.equal(consumed.selected, null);
|
|
193
|
+
assert.equal(consumed.output, output);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import type { RouteOutcome, SkillRouteMap, SkillRoutingFrontmatter } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const EMPTY_ROUTES: SkillRouteMap = {
|
|
5
|
+
pass: [],
|
|
6
|
+
fail: [],
|
|
7
|
+
blocker: [],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function stripQuotes(value: string): string {
|
|
11
|
+
return value.trim().replace(/^['"]|['"]$/g, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseRouteValue(value: string): string[] {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed) return [];
|
|
17
|
+
|
|
18
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
19
|
+
return trimmed
|
|
20
|
+
.slice(1, -1)
|
|
21
|
+
.split(",")
|
|
22
|
+
.map((entry) => stripQuotes(entry))
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [stripQuotes(trimmed)].filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isRouteOutcome(value: string): value is RouteOutcome {
|
|
30
|
+
return value === "pass" || value === "fail" || value === "blocker";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function extractFrontmatter(source: string): string | null {
|
|
34
|
+
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
35
|
+
return match?.[1] ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseSkillFrontmatter(source: string): SkillRoutingFrontmatter | null {
|
|
39
|
+
const rawFrontmatter = extractFrontmatter(source);
|
|
40
|
+
if (!rawFrontmatter) return null;
|
|
41
|
+
|
|
42
|
+
const route: SkillRouteMap = {
|
|
43
|
+
pass: [],
|
|
44
|
+
fail: [],
|
|
45
|
+
blocker: [],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let name: string | undefined;
|
|
49
|
+
let description: string | undefined;
|
|
50
|
+
let tier: string | undefined;
|
|
51
|
+
let inRouteBlock = false;
|
|
52
|
+
let activeRouteKey: RouteOutcome | null = null;
|
|
53
|
+
|
|
54
|
+
for (const rawLine of rawFrontmatter.split(/\r?\n/)) {
|
|
55
|
+
const line = rawLine.trimEnd();
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
|
|
58
|
+
if (!trimmed) continue;
|
|
59
|
+
|
|
60
|
+
if (/^route:\s*$/.test(trimmed)) {
|
|
61
|
+
inRouteBlock = true;
|
|
62
|
+
activeRouteKey = null;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (inRouteBlock) {
|
|
67
|
+
const routeMatch = line.match(/^\s{2,}(pass|fail|blocker):\s*(.*)$/);
|
|
68
|
+
if (routeMatch && isRouteOutcome(routeMatch[1])) {
|
|
69
|
+
activeRouteKey = routeMatch[1];
|
|
70
|
+
route[activeRouteKey].push(...parseRouteValue(routeMatch[2]));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const listMatch = line.match(/^\s{4,}-\s+(.+)$/);
|
|
75
|
+
if (listMatch && activeRouteKey) {
|
|
76
|
+
route[activeRouteKey].push(stripQuotes(listMatch[1]));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!/^\s/.test(line)) {
|
|
81
|
+
inRouteBlock = false;
|
|
82
|
+
activeRouteKey = null;
|
|
83
|
+
} else {
|
|
84
|
+
activeRouteKey = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const fieldMatch = line.match(/^(name|description|tier):\s*(.+)$/);
|
|
90
|
+
if (!fieldMatch) continue;
|
|
91
|
+
|
|
92
|
+
const value = stripQuotes(fieldMatch[2]);
|
|
93
|
+
switch (fieldMatch[1]) {
|
|
94
|
+
case "name":
|
|
95
|
+
name = value;
|
|
96
|
+
break;
|
|
97
|
+
case "description":
|
|
98
|
+
description = value;
|
|
99
|
+
break;
|
|
100
|
+
case "tier":
|
|
101
|
+
tier = value;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
name,
|
|
108
|
+
description,
|
|
109
|
+
tier,
|
|
110
|
+
route,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function readSkillFrontmatter(skillFilePath: string): SkillRoutingFrontmatter | null {
|
|
115
|
+
if (!fs.existsSync(skillFilePath)) return null;
|
|
116
|
+
return parseSkillFrontmatter(fs.readFileSync(skillFilePath, "utf8"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function emptySkillRoutes(): SkillRouteMap {
|
|
120
|
+
return {
|
|
121
|
+
pass: [...EMPTY_ROUTES.pass],
|
|
122
|
+
fail: [...EMPTY_ROUTES.fail],
|
|
123
|
+
blocker: [...EMPTY_ROUTES.blocker],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const ROUTE_OUTCOMES = ["pass", "fail", "blocker"] as const;
|
|
2
|
+
export const ROUTE_VERIFICATIONS = ["verified", "unverified"] as const;
|
|
3
|
+
export const ROUTE_ACTIONS = ["done", "fixable", "needs-context", "blocked"] as const;
|
|
4
|
+
export const ROUTE_WORK_TYPES = ["implement", "verify", "ship", "diagnose", "surface"] as const;
|
|
5
|
+
|
|
6
|
+
export type RouteOutcome = (typeof ROUTE_OUTCOMES)[number];
|
|
7
|
+
export type RouteVerification = (typeof ROUTE_VERIFICATIONS)[number];
|
|
8
|
+
export type RouteAction = (typeof ROUTE_ACTIONS)[number];
|
|
9
|
+
export type RouteWork = (typeof ROUTE_WORK_TYPES)[number];
|
|
10
|
+
|
|
11
|
+
export interface SkillRouteMap {
|
|
12
|
+
pass: string[];
|
|
13
|
+
fail: string[];
|
|
14
|
+
blocker: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SkillRoutingFrontmatter {
|
|
18
|
+
name?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
tier?: string;
|
|
21
|
+
route: SkillRouteMap;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RouteEvidence {
|
|
25
|
+
outcome: RouteOutcome;
|
|
26
|
+
verification?: RouteVerification;
|
|
27
|
+
action?: RouteAction;
|
|
28
|
+
work?: RouteWork;
|
|
29
|
+
target?: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RouteResolution {
|
|
34
|
+
outcome: RouteOutcome;
|
|
35
|
+
verification?: RouteVerification;
|
|
36
|
+
action?: RouteAction;
|
|
37
|
+
work?: RouteWork;
|
|
38
|
+
candidates: string[];
|
|
39
|
+
selected: string | null;
|
|
40
|
+
reason: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface RuntimeRouteDecision {
|
|
44
|
+
selected: string;
|
|
45
|
+
source: "next_route" | "route_guidance";
|
|
46
|
+
outcome?: RouteOutcome;
|
|
47
|
+
verification?: RouteVerification;
|
|
48
|
+
action?: RouteAction;
|
|
49
|
+
work?: RouteWork;
|
|
50
|
+
candidates?: string[];
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
@@ -19,7 +19,7 @@ Create and validate ASCII diagrams in three phases: design, generate, validate.
|
|
|
19
19
|
3. Fence all diagrams in markdown code blocks
|
|
20
20
|
4. Keep diagrams under 80 columns with max 3 nesting levels
|
|
21
21
|
5. For complex diagrams, author PlantUML and render with `-utxt`
|
|
22
|
-
6. Validate
|
|
22
|
+
6. Validate visual alignment manually: check that box-drawing characters connect cleanly and all columns align within the markdown code block
|
|
23
23
|
7. Fix reported issues and re-validate until clean
|
|
24
24
|
|
|
25
25
|
## Routing
|
|
@@ -18,42 +18,57 @@ Confirm: content loaded, frontmatter present, no access restrictions. For multip
|
|
|
18
18
|
|
|
19
19
|
## Phase 2: Analysis
|
|
20
20
|
|
|
21
|
-
###
|
|
22
|
-
| Metric | Assessment |
|
|
23
|
-
|--------|-----------|
|
|
24
|
-
| Lines | SKILL.md length |
|
|
25
|
-
| Concrete rules | "must/never/always/banned" count |
|
|
26
|
-
| Examples | Before/after or usage code blocks |
|
|
27
|
-
| Anti-patterns | Explicit "don't" sections |
|
|
28
|
-
| Workflow steps | Sequential, actionable steps |
|
|
29
|
-
| Routing table | pass/fail/blocker defined |
|
|
21
|
+
### Required Lenses
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
Analyze every source through these lenses:
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
- **OH gaps** — what OH does not currently cover, or covers weakly
|
|
26
|
+
- **OH wins** — where OH already outperforms the source and should not regress
|
|
27
|
+
- **Missed patterns** — reusable patterns the source surfaces that OH should absorb
|
|
28
|
+
- **Overlap target** — the strongest existing `oh-*` candidate for merge, if any
|
|
35
29
|
|
|
36
|
-
###
|
|
37
|
-
- Clear description for triggering? Actionable instructions? Anti-patterns? Examples? Measurable outcomes? No time-sensitive refs? No platform assumptions?
|
|
30
|
+
### Merge Rubric
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
Choose one verdict:
|
|
33
|
+
|
|
34
|
+
| Verdict | Use when |
|
|
35
|
+
|---------|----------|
|
|
36
|
+
| Merge | The source upgrades an existing OH capability more than it defines a new one |
|
|
37
|
+
| Standalone | The source adds a distinct capability with its own trigger, workflow, and routing needs |
|
|
38
|
+
| Discard | The source is redundant, weak, or anti-native |
|
|
39
|
+
|
|
40
|
+
### Strength Check
|
|
41
|
+
|
|
42
|
+
Reject or rewrite any protocol that makes OH weaker, slower, noisier, or less OpenCode-native.
|
|
43
|
+
|
|
44
|
+
### Fusion Report Template
|
|
40
45
|
```markdown
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
## OH gaps
|
|
47
|
+
- ...
|
|
48
|
+
|
|
49
|
+
## OH wins
|
|
50
|
+
- ...
|
|
51
|
+
|
|
52
|
+
## Missed patterns
|
|
53
|
+
- ...
|
|
54
|
+
|
|
55
|
+
## Merge verdict
|
|
56
|
+
- Verdict: <merge | standalone | discard>
|
|
57
|
+
- Target: <existing oh-* or new oh-*>
|
|
58
|
+
- Why: <short rationale>
|
|
59
|
+
|
|
60
|
+
## Action plan
|
|
61
|
+
1. ...
|
|
62
|
+
2. ...
|
|
63
|
+
|
|
64
|
+
## Approval gate
|
|
65
|
+
- Status: pending approval
|
|
66
|
+
- Next move after approval: <skill/action>
|
|
45
67
|
```
|
|
46
68
|
|
|
47
69
|
## Phase 3: Decision
|
|
48
70
|
|
|
49
|
-
|
|
50
|
-
|---------|--------|
|
|
51
|
-
| Keep | High signal, no overlap. Port to `oh-<name>`. |
|
|
52
|
-
| Fuse | Partial overlap with existing. Merge complementary DNA. |
|
|
53
|
-
| Discard | Low signal or complete overlap. Surface reasoning. |
|
|
54
|
-
| Ask | Ambiguous quality. Surface findings. |
|
|
55
|
-
|
|
56
|
-
When in doubt: prefer fuse over keep, keep over discard if ANY unique signal.
|
|
71
|
+
Default to `merge` when the source sharpens an existing OH workflow. Default to `standalone` only when the capability stays clean, distinct, and reusable after removing overlap.
|
|
57
72
|
|
|
58
73
|
## Phase 4: Adaptation
|
|
59
74
|
|
|
@@ -65,10 +80,10 @@ tier: <2|3|4>
|
|
|
65
80
|
```
|
|
66
81
|
|
|
67
82
|
### Body Structure
|
|
68
|
-
1. **Summary** — one
|
|
83
|
+
1. **Summary** — one short paragraph
|
|
69
84
|
2. **When to Use** — clear triggering context
|
|
70
|
-
3. **
|
|
71
|
-
4. **
|
|
85
|
+
3. **Protocol** — numbered workflow
|
|
86
|
+
4. **Rubric / guardrails** — what to merge, reject, or escalate
|
|
72
87
|
5. **Routing** — pass/fail/blocker table
|
|
73
88
|
|
|
74
89
|
### Adaptation Rules
|
|
@@ -78,11 +93,16 @@ tier: <2|3|4>
|
|
|
78
93
|
- Keep all concrete rules, examples, and anti-patterns from the original.
|
|
79
94
|
- Discard fluff, philosophy, and motivational language.
|
|
80
95
|
- Preserve the original's unique signal — that's why you're importing it.
|
|
96
|
+
- Keep the result OpenCode/OpenHermes-native only.
|
|
81
97
|
|
|
82
98
|
### Naming
|
|
83
99
|
Match `^[a-z0-9]+(-[a-z0-9]+)*$`, prefix `oh-`. Original name if good fit, adapt if not. Fusion names signal combined purpose.
|
|
84
100
|
|
|
85
|
-
## Phase 5:
|
|
101
|
+
## Phase 5: Approval Gate
|
|
102
|
+
|
|
103
|
+
Before any harness mutation, surface the fusion report and wait for approval. Approved intent already present in the conversation counts. Do not ask new questions unless a blocker cannot be resolved from the codebase or prior conversation.
|
|
104
|
+
|
|
105
|
+
## Phase 6: Fusion (opt — skip for single imports)
|
|
86
106
|
|
|
87
107
|
For each skill being fused, identify:
|
|
88
108
|
- **Unique concepts** — content only this skill has
|
|
@@ -101,7 +121,7 @@ Structure so each source contributes its strength. Do NOT concatenate — must r
|
|
|
101
121
|
### Naming
|
|
102
122
|
Name signals combined purpose, not individual sources. E.g., `oh-facade` from redesign + design-taste + high-end-visual, not `oh-redesign-plus-taste`.
|
|
103
123
|
|
|
104
|
-
## Phase
|
|
124
|
+
## Phase 7: Integration
|
|
105
125
|
|
|
106
126
|
1. **Create file** → user dir (`~/.config/opencode/skills/oh-<name>/SKILL.md`)
|
|
107
127
|
2. **Wire AUTOPILOT** → add to auto-classify matrix in `harness/codex/AUTOPILOT.md`: signal keywords → classification → "Load **oh-<name>**. Do not ask."
|
|
@@ -113,8 +133,11 @@ Name signals combined purpose, not individual sources. E.g., `oh-facade` from re
|
|
|
113
133
|
## Anti-patterns
|
|
114
134
|
|
|
115
135
|
- Importing without analysis (always run Phase 2)
|
|
116
|
-
-
|
|
136
|
+
- Asking before checking code or prior conversation
|
|
137
|
+
- Changing the harness before the approval gate clears
|
|
138
|
+
- Keeping everything — most external skill text is fluff
|
|
117
139
|
- Fusing incompatible domains (confusing to model and user)
|
|
118
140
|
- Naming after source ("oh-tailwind-v2") instead of capability ("oh-styles")
|
|
119
141
|
- Skipping route frontmatter — without it, autopilot can't route
|
|
120
142
|
- Overwriting existing routing without checking for collisions
|
|
143
|
+
- Shipping a protocol that weakens OH or makes it less native
|
|
@@ -3,34 +3,48 @@ name: oh-fusion
|
|
|
3
3
|
description: "Use when the user has an existing skill, finds a skill in their .agents/skills, or wants to bring an external capability into OH as a skill."
|
|
4
4
|
tier: 3
|
|
5
5
|
route:
|
|
6
|
-
pass:
|
|
7
|
-
|
|
8
|
-
- oh-skill-craft
|
|
9
|
-
fail: oh-skill-craft
|
|
6
|
+
pass: [oh-skill-craft, oh-skills-link]
|
|
7
|
+
fail: surface
|
|
10
8
|
blocker: surface
|
|
11
9
|
---
|
|
12
10
|
|
|
13
11
|
# oh-fusion
|
|
14
12
|
|
|
15
|
-
Skill
|
|
13
|
+
Skill fusion pipeline: Discover → Analyze → Verdict → Approval Gate → Integrate.
|
|
16
14
|
|
|
17
|
-
##
|
|
15
|
+
## Protocol
|
|
18
16
|
|
|
19
17
|
1. Load skill content — read from `.agents/skills/`, `npx skills`, URL, user path, or inline text.
|
|
20
|
-
2. Analyze
|
|
21
|
-
3.
|
|
22
|
-
4. Decide
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
2. Analyze the source against OH — report `OH gaps`, `OH wins`, and `missed patterns`.
|
|
19
|
+
3. Compare overlap — identify the best existing `oh-*` target, if any.
|
|
20
|
+
4. Decide with a rubric:
|
|
21
|
+
- **Merge** when the source mainly strengthens an existing OH capability.
|
|
22
|
+
- **Standalone** when the source adds a distinct, reusable capability.
|
|
23
|
+
- **Discard** when the signal is weak or redundant.
|
|
24
|
+
5. Produce a fusion report with these sections, in order:
|
|
25
|
+
- `OH gaps`
|
|
26
|
+
- `OH wins`
|
|
27
|
+
- `missed patterns`
|
|
28
|
+
- `merge verdict`
|
|
29
|
+
- `action plan`
|
|
30
|
+
- `approval gate`
|
|
31
|
+
6. Resolve from code and prior conversation first. Ask only if a blocker remains.
|
|
32
|
+
7. After approval, adapt to OH-native form and route directly to implementation.
|
|
33
|
+
8. Verify discovery with `oh-skills-link` when a new or renamed skill is added.
|
|
34
|
+
|
|
35
|
+
## Merge Rubric
|
|
36
|
+
|
|
37
|
+
- **Merge into existing `oh-*`** when domain, trigger, and route already exist; the source mostly adds sharper rules, better sequencing, or stronger edge-case handling.
|
|
38
|
+
- **Create standalone `oh-*`** when the source introduces a capability with distinct trigger words, workflow, and handoff points.
|
|
39
|
+
- **Discard** when the source adds fluff, duplicates OH, or weakens OpenCode/OpenHermes-native operation.
|
|
27
40
|
|
|
28
41
|
## Routing
|
|
29
42
|
|
|
30
43
|
| Outcome | Route |
|
|
31
44
|
|---------|-------|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
45
|
+
| Approved merge or standalone plan | → oh-skill-craft |
|
|
46
|
+
| New or renamed skill needs discovery check | → oh-skills-link |
|
|
34
47
|
| Analysis: discard | → surface |
|
|
35
|
-
|
|
|
48
|
+
| Approval not yet granted | → surface |
|
|
36
49
|
| Blocker | → surface |
|
|
50
|
+
|
|
@@ -26,7 +26,7 @@ OpenHermes is the primary orchestrator. All routing, planning, and delegation fl
|
|
|
26
26
|
- **Test command**: <fill or auto-detect>
|
|
27
27
|
|
|
28
28
|
## Key Directives
|
|
29
|
-
- Plan first. Write to `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md` before multi-file changes.
|
|
29
|
+
- Plan first. Write to `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md` before multi-file changes.
|
|
30
30
|
- OpenHermes delegates everything to sub-agents — never executes directly.
|
|
31
31
|
- Verify before claiming success. Read files, run commands, confirm output.
|
|
32
32
|
- Use oh-* skills on demand via the skill tool.
|
|
@@ -40,7 +40,7 @@ Ask user to fill or auto-detect from manifests.
|
|
|
40
40
|
```markdown
|
|
41
41
|
## OpenHermes Orchestrator
|
|
42
42
|
OpenHermes is the primary orchestrator.
|
|
43
|
-
- **Plan**: `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md`
|
|
43
|
+
- **Plan**: `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md`
|
|
44
44
|
- **Never execute**: delegates everything to sub-agents
|
|
45
45
|
- **Verify before claim**: read files, run commands, confirm output
|
|
46
46
|
```
|
|
@@ -80,7 +80,7 @@ One section at a time: Architecture → Code Quality → Tests → Performance.
|
|
|
80
80
|
|
|
81
81
|
## Output
|
|
82
82
|
|
|
83
|
-
Plan file (`~/.local/share/openhermes/plans/<project-name>/plan-<nnn>.md`) updated with findings and decisions.
|
|
83
|
+
Plan file (`~/.local/share/openhermes/plans/<project-name>/plan-<nnn>.md`) updated with findings and decisions.
|
|
84
84
|
|
|
85
85
|
## Anti-patterns
|
|
86
86
|
|
|
@@ -92,18 +92,18 @@ Runs sequentially: **Strategy → Architecture → Design → Engineering → DX
|
|
|
92
92
|
Every plan written by oh-planner uses this canonical format.
|
|
93
93
|
|
|
94
94
|
### Storage
|
|
95
|
-
Canonical path: `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md`
|
|
95
|
+
Canonical path: `~/.local/share/openhermes/plans/<project>/plan-<nnn>.md`
|
|
96
96
|
|
|
97
97
|
### Template
|
|
98
98
|
```markdown
|
|
99
99
|
# PLAN: <project>
|
|
100
100
|
|
|
101
|
-
Plan ID: <project>/plan-<nnn>
|
|
101
|
+
Plan ID: <project>/plan-<nnn>
|
|
102
102
|
Project: <project>
|
|
103
103
|
Status: active | in-progress | blocked | complete | abandoned
|
|
104
104
|
Created: <ts> | Updated: <ts>
|
|
105
105
|
Project Path: <absolute-path>
|
|
106
|
-
Plan Path: <canonical-path>/<project>/plan-<nnn>.md
|
|
106
|
+
Plan Path: <canonical-path>/<project>/plan-<nnn>.md
|
|
107
107
|
Objective: <short>
|
|
108
108
|
|
|
109
109
|
## Current State
|
|
@@ -23,6 +23,8 @@ AGENTS.md, CLAUDE.md, CONTRIBUTING.md, CONTEXT.md, ADRs, eslint/biome/prettier c
|
|
|
23
23
|
### 5. Aggregate
|
|
24
24
|
Present under `## Standards` / `## Spec`. Do not merge. End with total + worst issue.
|
|
25
25
|
|
|
26
|
+
Concrete, low-risk, fixable findings should be converted into implementation work and dispatched to oh-builder immediately instead of stopping as report-only notes.
|
|
27
|
+
|
|
26
28
|
### Safety Check (inline before spawning)
|
|
27
29
|
- SQL injection, LLM trust boundary violations, conditional side effects (test vs prod), hardcoded secrets
|
|
28
30
|
- Block immediately if critical — do not spawn sub-agents.
|