harness-everything 0.30.9 → 0.31.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +8 -9
- package/VERIFICATION.md +1 -1
- package/benchmarks/results/live-host/claude-2026-10-08-issue-297/README.md +27 -0
- package/benchmarks/results/live-host/codex-2026-10-13/README.md +55 -0
- package/benchmarks/results/live-host/codex-2026-10-13/observation-records.json +88 -0
- package/benchmarks/results/pr-303-review-2026-10-13/README.md +81 -0
- package/benchmarks/results/pr-303-review-2026-10-13/ci/run.json +1330 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-clean-env.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/mechanism-35-inherited-flag.log +34 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-captured.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate-diagnosis.md +13 -0
- package/benchmarks/results/pr-303-review-2026-10-13/diagnostics/verify-gate.json +46 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/summary.json +57 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-collision.log +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-docs-capabilities.log +131 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-platform-compatibility.log +9 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-openai.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-plugin-submission.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-references.log +6 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-repo-contract.log +5 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-invariants.log +785 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/contracts/test-routing-skills.log +44 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/summary.json +12 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-consistency.log +624 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/final-fast/test-release.log +73 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/summary.json +79 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mechanism.log +1737 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test-mutations.log +16 -0
- package/benchmarks/results/pr-303-review-2026-10-13/gates/heavy/test.log +3297 -0
- package/benchmarks/results/pr-303-review-2026-10-13/manifest.json +319 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/binding-resolution-green.log +36 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/determinism.json +174 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-evidence.json +314 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/quality-report.json +638 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/red.log +240 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/untyped-fable-probe.json +22 -0
- package/benchmarks/results/pr-303-review-2026-10-13/regression/workflow-obligations-green.log +254 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-clean-env-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/mechanism-35-inherited-flag-fixed.log +1 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.meta.json +8 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stderr.log +90 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/nested-test.stdout.log +3186 -0
- package/benchmarks/results/pr-303-review-2026-10-13/verification/verify-gate-final.json +46 -0
- package/bin/cli.js +1 -1
- package/ci/doc-capability-consistency.test.js +11 -1
- package/ci/invariant-routing.test.js +7 -4
- package/ci/mechanism-2ag-router-worktree-contract.test.js +2 -2
- package/ci/mechanism-2ai-workspace-fingerprint-batch.test.js +2 -1
- package/ci/mechanism-2al-codex-hooks-compat.test.js +19 -0
- package/ci/mechanism-2am-observations-session-import.test.js +221 -0
- package/ci/mechanism-2an-workflow-obligations.test.js +352 -3
- package/ci/mechanism-2ao-plugin-sync.test.js +129 -4
- package/ci/mechanism-2aq-fable-stage-bindings.test.js +168 -0
- package/ci/mechanism-2ar-binding-resolution.test.js +211 -0
- package/ci/mechanism-2w-platform-compatibility.test.js +12 -0
- package/ci/mechanism-34-observations.test.js +64 -0
- package/ci/mechanism-35-verification-contract.test.js +1 -1
- package/ci/mutation-check.js +2 -0
- package/ci/mutation-loader.js +4 -0
- package/ci/openai-plugin-package.test.js +7 -5
- package/ci/prompt-contract-regressions.test.js +7 -6
- package/ci/router-workflow-plan.test.js +109 -14
- package/ci/skill-routing-check.js +44 -15
- package/ci/workflow-plan-consumers.test.js +41 -1
- package/docs/architecture.md +10 -10
- package/docs/enforcement-locks.md +1 -1
- package/docs/mechanism-first-skill-mesh.md +12 -11
- package/docs/openai-plugin.md +1 -1
- package/docs/philosophy.md +7 -6
- package/docs/platform-capabilities.md +14 -12
- package/docs/platform-compatibility.json +5 -5
- package/docs/routing.md +16 -17
- package/docs/system-one-observations.md +36 -9
- package/docs/system-one-routing.md +10 -7
- package/docs/system-one-suggestion-gates.md +5 -5
- package/docs/workflow-runtime.md +6 -2
- package/docs/workflows/harness-everything.md +14 -14
- package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
- package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
- package/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/fable-mode/SKILL.md +6 -8
- package/fable-mode/agents/fable-orchestrator.md +10 -3
- package/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/harness-everything/SKILL.md +8 -7
- package/harness-everything/references/router-workflow-plan.md +11 -6
- package/harness-everything/references/triage-and-tiers.md +21 -23
- package/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/harness-everything/scripts/kernel-router-core.js +52 -55
- package/harness-everything/scripts/kernel-router.js +48 -5
- package/harness-everything/scripts/router-contract-core.js +7 -7
- package/harness-everything/scripts/router-contract.js +1 -3
- package/harness-everything/scripts/routing-keywords.json +417 -121
- package/harness-everything/scripts/tier-router.js +64 -113
- package/hooks/scripts/contract-test.js +29 -3
- package/hooks/scripts/lib/binding-resolver.js +76 -0
- package/hooks/scripts/lib/fable-contracts.js +1 -1
- package/hooks/scripts/lib/observations.js +88 -12
- package/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/hooks/scripts/workflow-disposition.js +116 -2
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/contract-test.js +29 -3
- package/plugins/harness-everything/hooks/scripts/lib/binding-resolver.js +76 -0
- package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/observations.js +88 -12
- package/plugins/harness-everything/hooks/scripts/lib/workflow-obligations.js +292 -7
- package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +8 -1
- package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +116 -2
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/scripts/lib/advisory-text.js +2 -2
- package/plugins/harness-everything/scripts/lib/generated-skills.js +78 -0
- package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +4 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +6 -8
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +10 -3
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +101 -2
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +8 -7
- package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +11 -6
- package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +21 -23
- package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +2 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router-core.js +52 -55
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +48 -5
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract-core.js +7 -7
- package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +1 -3
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +417 -121
- package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +64 -113
- package/sbom.cdx.json +7 -7
- package/scripts/codex-hooks-compat.js +39 -14
- package/scripts/lib/advisory-text.js +2 -2
- package/scripts/lib/generated-skills.js +78 -0
- package/scripts/plugin-sync.js +100 -29
- package/scripts/system-one-observations-export.js +29 -2
- package/scripts/system-one-observations-import-codex-sessions.js +307 -0
- package/scripts/system-one-observations-review.js +11 -1
|
@@ -5,11 +5,11 @@ const {
|
|
|
5
5
|
buildRouterContract,
|
|
6
6
|
writeRouterContract,
|
|
7
7
|
} = require('./router-contract');
|
|
8
|
-
const { requireWorkspace } = require('./runtime-paths');
|
|
8
|
+
const { getHarnessRoot, requireWorkspace } = require('./runtime-paths');
|
|
9
9
|
|
|
10
10
|
const DEFAULT_ROUTING_CONFIG = {
|
|
11
11
|
tiers: { tier3: [], tier2: [] },
|
|
12
|
-
|
|
12
|
+
inputSignalGroups: [],
|
|
13
13
|
factAudit: { externalClaim: [], estimate: [] },
|
|
14
14
|
};
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ function loadRoutingConfig() {
|
|
|
21
21
|
return {
|
|
22
22
|
config: {
|
|
23
23
|
tiers: parsed.tiers || DEFAULT_ROUTING_CONFIG.tiers,
|
|
24
|
-
|
|
24
|
+
inputSignalGroups: Array.isArray(parsed.inputSignalGroups) ? parsed.inputSignalGroups : DEFAULT_ROUTING_CONFIG.inputSignalGroups,
|
|
25
25
|
factAudit: parsed.factAudit || DEFAULT_ROUTING_CONFIG.factAudit,
|
|
26
26
|
},
|
|
27
27
|
routingStatus: 'ok',
|
|
@@ -60,15 +60,25 @@ function detectFableModel(prompt) {
|
|
|
60
60
|
// retains execution routing; format field lists are recognized separately.
|
|
61
61
|
function containsOnlyLookupTerms(text, operation) {
|
|
62
62
|
const terms = {
|
|
63
|
-
explanation: /\b(?:please|help|me|explain|resolve|define|what|is|does|mean|means|meaning|the|a|an|of|to|into|in|for|behavior|behaviour|profile|profiles|selection|json|record|alias|role|runtime|model|effort|floor|fableprofile)\b/gi,
|
|
64
|
-
output: /\b(?:return|respond|output|show|provide|only|a|an|small|compact|json|record|selection|profile|result|with|fields|containing|include|including)\b/gi,
|
|
63
|
+
explanation: /\b(?:please|help|me|could|would|you|explain|resolve|define|what|is|does|mean|means|meaning|the|a|an|of|to|into|in|for|as|compact|small|only|behavior|behaviour|profile|profiles|selection|json|record|object|alias|role|runtime|model|effort|floor|fableprofile)\b/gi,
|
|
64
|
+
output: /\b(?:as|return|respond|output|show|provide|only|a|an|small|compact|short|json|record|object|selection|profile|result|with|fields|containing|include|including)\b/gi,
|
|
65
65
|
};
|
|
66
66
|
const chineseTerms = {
|
|
67
|
-
explanation: /(
|
|
67
|
+
explanation: /(?:請問|麻煩|請|幫我|解釋|說明|解析|的意思|意思|含義|意義|是什麼|設定檔|行為|角色|精簡|簡短|緊湊|JSON|紀錄|記錄)/g,
|
|
68
68
|
output: /(?:只|僅|回傳|返回|輸出|顯示|提供|包含|含|的|欄位|記錄|紀錄|結果|設定檔)/g,
|
|
69
69
|
};
|
|
70
70
|
return text.replace(PROFILE_LOOKUP_FIELDS, '').replace(terms[operation], '')
|
|
71
|
-
.replace(chineseTerms[operation], '').replace(/[\s"'`“”‘’?::?]/g, '') === '';
|
|
71
|
+
.replace(chineseTerms[operation], '').replace(/[.!?]+$/g, '').replace(/[\s"'`“”‘’?::?]/g, '') === '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function splitProfileLookupClauses(prompt) {
|
|
75
|
+
let text = String(prompt || '').trim()
|
|
76
|
+
.replace(/^(?:could\s+you(?:\s+please)?|would\s+you(?:\s+please)?|please)\s*,?\s+/i, '')
|
|
77
|
+
.replace(/^(?:麻煩請|麻煩|請問|請)\s*/u, '');
|
|
78
|
+
const outputLead = String.raw`(?:please\s+)?(?:return|respond|output|show|provide|no|without|do\s+not|don't)\b|(?:請(?:問)?\s*)?(?:只|僅|回傳|返回|輸出|顯示|提供|不要|不需要|無需|無須|不必|毋須|不用|不會|不)`;
|
|
79
|
+
text = text.replace(new RegExp(String.raw`\.(?=\s*${outputLead})|[—–](?=\s*(?:${outputLead}|as\s+(?:a\s+)?(?:compact\s+|small\s+|short\s+)?json\s+(?:record|object)\b))`, 'giu'), ';');
|
|
80
|
+
return text.split(/(?:\.{3,}|[;!??!,,;。、…]|\b(?:and\s+then|and|then|also|plus|afterwards|additionally)\b|(?:並且?|以及|接著|然後|另外))/iu)
|
|
81
|
+
.map(part => part.trim().replace(/[.!?…]+$/g, '')).filter(Boolean);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
// A negation scopes only its own noun/verb list. Vocabulary-only matching would
|
|
@@ -80,11 +90,12 @@ const NEGATED_ITEM = '(?:orchestration|delegation|stages?|staging|execution|mode
|
|
|
80
90
|
const NEGATED_ACTION = '(?:switch(?:ing)?\\s+(?:the\\s+)?models?|delegate|orchestrate|stage|execute)';
|
|
81
91
|
const ENGLISH_NEGATION = new RegExp(`^(?:(?:no|without)\\s+${negatedList(NEGATED_ITEM)}|(?:do not|don't)\\s+${negatedList(NEGATED_ACTION)})$`, 'i');
|
|
82
92
|
const ENGLISH_NEGATION_CONTINUATION = new RegExp(`^(?:or\\s+)?${negatedList(NEGATED_ITEM)}$`, 'i');
|
|
83
|
-
const
|
|
93
|
+
const CHINESE_NEGATION_PREFIX = /^(?:不要|不需要|無需|無須|不必|毋須|不用|不會|不)/;
|
|
94
|
+
const CHINESE_NEGATION_TERMS = /(?:不要|不需要|無需|無須|不必|毋須|不用|不會|不|或|也|任何|一切|啟動|開始|開啟|進入|執行|進行|運行|啟用|切換|模型|階段|流程|任務|操作|編排|委派)/g;
|
|
84
95
|
|
|
85
96
|
function isBoundedNegation(clause, continuation) {
|
|
86
|
-
const text = clause.replace(/’/g, "'").replace(/[“”"`?::?]/g, '').replace(/\s+/g, ' ').trim();
|
|
87
|
-
if (
|
|
97
|
+
const text = clause.replace(/’/g, "'").replace(/[.!?…]+$/g, '').replace(/[“”"`?::?]/g, '').replace(/\s+/g, ' ').trim();
|
|
98
|
+
if (CHINESE_NEGATION_PREFIX.test(text)) {
|
|
88
99
|
return !continuation && text.replace(CHINESE_NEGATION_TERMS, '').replace(/\s/g, '') === '';
|
|
89
100
|
}
|
|
90
101
|
return (continuation ? ENGLISH_NEGATION_CONTINUATION : ENGLISH_NEGATION).test(text);
|
|
@@ -94,8 +105,7 @@ function isFableProfileLookup(prompt, hasMacroSignal) {
|
|
|
94
105
|
if (!detectFableModel(prompt) || hasMacroSignal) return false;
|
|
95
106
|
if (/\b(?:direct[- ]single|iterative[- ]single|fable[- ](?:staged|parallel|multi[- ]agent[- ]workspace))\b/i.test(prompt)) return false;
|
|
96
107
|
|
|
97
|
-
const clauses = prompt
|
|
98
|
-
.split(/(?:[;,.,;。、]|\b(?:and|then|also|plus|afterwards|additionally)\b|(?:並且?|以及|接著|然後|另外))/i)
|
|
108
|
+
const clauses = splitProfileLookupClauses(prompt)
|
|
99
109
|
.map(part => part.trim())
|
|
100
110
|
.filter(Boolean);
|
|
101
111
|
if (clauses.length === 0) return false;
|
|
@@ -105,14 +115,15 @@ function isFableProfileLookup(prompt, hasMacroSignal) {
|
|
|
105
115
|
const normalizedLookup = lookupClause.replace(/[“”]/g, '"').replace(/[‘’]/g, "'")
|
|
106
116
|
.replace(quotedInvocation, 'fableprofile')
|
|
107
117
|
.replace(new RegExp(FABLE_PROFILE_INVOCATION.source, 'gi'), 'fableprofile');
|
|
108
|
-
const isLookupLead = /^(?:(?:please|help me)\s+|(
|
|
118
|
+
const isLookupLead = /^(?:(?:please|help me|could you(?: please)?|would you(?: please)?)\s+|(?:請問|麻煩請|麻煩|請|幫我)\s*)?(?:explain|resolve|define|what (?:is|does)|解釋|說明|解析)/i.test(lookupClause)
|
|
109
119
|
&& Boolean(detectFableModel(lookupClause));
|
|
110
|
-
|
|
120
|
+
const normalizedFormatRequest = normalizedLookup.replace(/\bas\s+(?:a\s+)?(?:compact|small|short)?\s*json\s+(?:record|object)\b/gi, '');
|
|
121
|
+
if (!isLookupLead || !containsOnlyLookupTerms(normalizedFormatRequest, 'explanation')) return false;
|
|
111
122
|
|
|
112
123
|
let continuationOperation = null;
|
|
113
124
|
for (const clause of continuations) {
|
|
114
|
-
const output = /^(?:please\s+)?(?:return|respond|output|show|provide)\b|^(?:請\s*)?(?:只|僅)?(?:回傳|返回|輸出|顯示|提供)/i.test(clause);
|
|
115
|
-
const negative = /^(?:please\s+)?(?:no|without|do not|don't)\b|^(?:請\s*)?(
|
|
125
|
+
const output = /^(?:please\s+)?(?:return|respond|output|show|provide)\b|^as\s+(?:a\s+)?(?:compact\s+|small\s+|short\s+)?json\s+(?:record|object)\b|^(?:請\s*)?(?:只|僅)?(?:回傳|返回|輸出|顯示|提供)/i.test(clause);
|
|
126
|
+
const negative = /^(?:please\s+)?(?:no|without|do not|don't)\b|^(?:請\s*)?(?:不要|不需要|無需|無須|不必|毋須|不用|不會|不)/i.test(clause);
|
|
116
127
|
if (output || negative) {
|
|
117
128
|
continuationOperation = output ? 'output' : 'negative';
|
|
118
129
|
const normalizedContinuation = clause.replace(/^(?:please\s+|請\s*)/i, '');
|
|
@@ -184,16 +195,6 @@ function addReason(reasonCodes, code) {
|
|
|
184
195
|
if (code && !reasonCodes.includes(code)) reasonCodes.push(code);
|
|
185
196
|
}
|
|
186
197
|
|
|
187
|
-
function deduplicateGuidesByPath(guides) {
|
|
188
|
-
const byPath = new Map();
|
|
189
|
-
for (const guide of guides) {
|
|
190
|
-
const match = String(guide).match(/^\s*-\s+([^\s]+)/);
|
|
191
|
-
const key = match ? match[1].replace(/\\/g, '/') : String(guide).trim();
|
|
192
|
-
if (!byPath.has(key)) byPath.set(key, guide);
|
|
193
|
-
}
|
|
194
|
-
return Array.from(byPath.values());
|
|
195
|
-
}
|
|
196
|
-
|
|
197
198
|
function countDomainSignals(promptLower) {
|
|
198
199
|
const domains = [
|
|
199
200
|
/\bsecurity\b|安全|資安/i,
|
|
@@ -238,73 +239,17 @@ function plannerInputsFromContext(context, promptLower) {
|
|
|
238
239
|
return { hostCapabilities, constraints };
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
|
|
242
|
+
// Self-evolved skills are discovered from manifest metadata only: matching ids
|
|
243
|
+
// become step inputs, and the SKILL.md path resolves when a declaring step is
|
|
244
|
+
// active. Discovery is advisory and never breaks routing.
|
|
245
|
+
function matchGeneratedSkillIds(promptLower, context) {
|
|
242
246
|
try {
|
|
243
|
-
const
|
|
247
|
+
const generated = require(path.join(getHarnessRoot(), 'scripts', 'lib', 'generated-skills.js'));
|
|
244
248
|
const { getWorkspaceRoot } = requireWorkspace();
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
path.join(workspaceRoot, '.cursor', 'harness-everything', 'manifest.json'),
|
|
250
|
-
path.join(workspaceRoot, '.github', 'harness-everything', 'manifest.json'),
|
|
251
|
-
path.join(workspaceRoot, '.codex', 'harness-everything', 'manifest.json'),
|
|
252
|
-
path.join(workspaceRoot, '.continue', 'harness-everything', 'manifest.json'),
|
|
253
|
-
path.join(userHome, '.agents', 'harness-everything', 'manifest.json'),
|
|
254
|
-
path.join(userHome, '.claude', 'harness-everything', 'manifest.json'),
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
const generatedSkills = new Map();
|
|
258
|
-
for (const manifestPath of manifestPaths) {
|
|
259
|
-
if (!fs.existsSync(manifestPath)) continue;
|
|
260
|
-
try {
|
|
261
|
-
const manifestData = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
262
|
-
if (Array.isArray(manifestData.generated)) {
|
|
263
|
-
for (const skill of manifestData.generated) generatedSkills.set(skill.id, skill);
|
|
264
|
-
}
|
|
265
|
-
} catch (err) {
|
|
266
|
-
// Ignore malformed generated-skill manifests; they are advisory only.
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const allDynamicSkills = Array.from(generatedSkills.values());
|
|
271
|
-
const matchedDynamicSkills = [];
|
|
272
|
-
|
|
273
|
-
for (const skill of allDynamicSkills) {
|
|
274
|
-
let score = 0;
|
|
275
|
-
for (const rawTrigger of skill.triggers || []) {
|
|
276
|
-
const trigger = rawTrigger.toLowerCase().trim();
|
|
277
|
-
if (!trigger || trigger.length < 2) continue;
|
|
278
|
-
if (['the', 'and', 'for', 'with', 'your', 'this', 'that', 'some', 'from', 'prevent', 'resolved'].includes(trigger)) continue;
|
|
279
|
-
|
|
280
|
-
if (/[\u4e00-\u9fa5]/.test(trigger)) {
|
|
281
|
-
if (promptLower.includes(trigger)) score += trigger.length * 2;
|
|
282
|
-
} else {
|
|
283
|
-
const escapedTrigger = trigger.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
284
|
-
const regex = new RegExp(`\\b${escapedTrigger}\\b`, 'i');
|
|
285
|
-
if (regex.test(promptLower)) score += trigger.length >= 4 ? 3 : 1.5;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (score >= 3) matchedDynamicSkills.push({ skill, score });
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (matchedDynamicSkills.length > 0) {
|
|
292
|
-
matchedDynamicSkills.sort((a, b) => b.score - a.score);
|
|
293
|
-
console.log(`\n=> 🎯 HIGH-RELEVANCE SELF-EVOLVED SKILL(S) DETECTED:`);
|
|
294
|
-
matchedDynamicSkills.forEach(({ skill }) => {
|
|
295
|
-
let displayPath = skill.dirPath;
|
|
296
|
-
if (displayPath.startsWith(workspaceRoot)) displayPath = path.relative(workspaceRoot, displayPath);
|
|
297
|
-
console.log(`- ${displayPath}/SKILL.md (${skill.description} [DYNAMIC SKILL])`);
|
|
298
|
-
});
|
|
299
|
-
} else if (recommendedGuides.length === 0 && allDynamicSkills.length > 0) {
|
|
300
|
-
console.log(`\n=> ℹ️ UNMATCHED PROMPT HINT:`);
|
|
301
|
-
console.log(`No standard or dynamic skills matched your prompt directly.`);
|
|
302
|
-
console.log(`However, you have ${allDynamicSkills.length} self-evolved dynamic skill(s) registered in your manifest.json.`);
|
|
303
|
-
console.log(`To ensure you don't miss past lessons, you should inspect the "generated" section of your manifest.json or check .claude/harness-everything/skills/generated/ to see if any apply to your current task.`);
|
|
304
|
-
console.log(`If genuinely nothing covers this - including nothing already fetched via find-skills - load find-skills/SKILL.md: it checks "npx skills list" for anything already installed, then searches skills.sh/npx skills if not, and always requires explicit approval before installing anything.`);
|
|
305
|
-
}
|
|
306
|
-
} catch (err) {
|
|
307
|
-
// Dynamic-skill discovery is advisory and must never break core routing.
|
|
249
|
+
const skills = generated.readGeneratedSkills(getWorkspaceRoot(context));
|
|
250
|
+
return { registered: skills.size, matched: generated.matchGeneratedSkills(promptLower, skills) };
|
|
251
|
+
} catch (_) {
|
|
252
|
+
return { registered: 0, matched: [] };
|
|
308
253
|
}
|
|
309
254
|
}
|
|
310
255
|
|
|
@@ -427,34 +372,26 @@ function run(userPrompt, context, options = {}) {
|
|
|
427
372
|
}
|
|
428
373
|
}
|
|
429
374
|
|
|
430
|
-
const
|
|
431
|
-
|
|
375
|
+
const knowledgeSignals = [];
|
|
376
|
+
const candidateBindings = [];
|
|
377
|
+
for (const group of profileLookup ? [] : routingConfig.inputSignalGroups) {
|
|
432
378
|
let matched = false;
|
|
433
379
|
if (typeof group.regex === 'string') {
|
|
434
380
|
matched = new RegExp(group.regex, 'i').test(promptLower);
|
|
435
381
|
} else if (Array.isArray(group.keywords)) {
|
|
436
|
-
matched = group.keywords.some(keyword => promptLower
|
|
382
|
+
matched = group.keywords.some(keyword => matchKeyword(promptLower, keyword));
|
|
383
|
+
}
|
|
384
|
+
if (matched && typeof group.id === 'string') {
|
|
385
|
+
knowledgeSignals.push(group.id);
|
|
386
|
+
if (Array.isArray(group.bindings)) candidateBindings.push(...group.bindings.filter(id => typeof id === 'string'));
|
|
437
387
|
}
|
|
438
|
-
if (matched && Array.isArray(group.guides)) allRecommendedGuides.push(...group.guides);
|
|
439
388
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
console.log(`\n=> RECOMMENDED KNOWLEDGE GUIDES (Auto-loaded based on keywords):`);
|
|
444
|
-
recommendedGuides.forEach(guide => {
|
|
445
|
-
const match = guide.match(/^- ([^\s]+)/);
|
|
446
|
-
if (match) {
|
|
447
|
-
const guidePath = path.join(__dirname, '..', '..', match[1]);
|
|
448
|
-
if (!fs.existsSync(guidePath)) {
|
|
449
|
-
console.log(`${guide} [NOT INSTALLED - Ignore this recommendation]`);
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
console.log(guide);
|
|
454
|
-
});
|
|
389
|
+
if (memoryPersistenceRequested && !knowledgeSignals.includes('memory-persistence')) {
|
|
390
|
+
knowledgeSignals.push('memory-persistence');
|
|
391
|
+
candidateBindings.push('self-evolve');
|
|
455
392
|
}
|
|
456
|
-
|
|
457
|
-
if (
|
|
393
|
+
const generatedSkills = profileLookup ? { registered: 0, matched: [] } : matchGeneratedSkillIds(promptLower, context);
|
|
394
|
+
if (generatedSkills.matched.length > 0) knowledgeSignals.push('self-evolved-skill');
|
|
458
395
|
|
|
459
396
|
const externalClaimTriggers = routingConfig.factAudit.externalClaim || [];
|
|
460
397
|
const estimateTriggers = routingConfig.factAudit.estimate || [];
|
|
@@ -495,6 +432,7 @@ function run(userPrompt, context, options = {}) {
|
|
|
495
432
|
rationale,
|
|
496
433
|
reasonCodes,
|
|
497
434
|
requestedFableModel,
|
|
435
|
+
knowledgeSignals,
|
|
498
436
|
explicitRequest: {
|
|
499
437
|
fableModel: requestedFableModel,
|
|
500
438
|
strategy: requestedStrategy,
|
|
@@ -528,6 +466,19 @@ function run(userPrompt, context, options = {}) {
|
|
|
528
466
|
},
|
|
529
467
|
});
|
|
530
468
|
|
|
469
|
+
if (knowledgeSignals.length > 0) {
|
|
470
|
+
console.log(`\n=> KNOWLEDGE SIGNALS (STEP INPUT ONLY): ${knowledgeSignals.join(', ')}`);
|
|
471
|
+
console.log(' These normalized prompt signals help compose requirement steps; they do not select or load documents.');
|
|
472
|
+
const bindingIds = [...new Set(candidateBindings)];
|
|
473
|
+
if (bindingIds.length > 0) console.log(` Candidate step binding ids: ${bindingIds.join(', ')}`);
|
|
474
|
+
}
|
|
475
|
+
if (generatedSkills.matched.length > 0) {
|
|
476
|
+
console.log(`\n=> SELF-EVOLVED SKILL SIGNALS (STEP INPUT ONLY): ${generatedSkills.matched.join(', ')}`);
|
|
477
|
+
console.log(' Declare a matching id as a step binding; its SKILL.md resolves from the manifest only when that step is active.');
|
|
478
|
+
} else if (generatedSkills.registered > 0 && knowledgeSignals.length === 0) {
|
|
479
|
+
console.log(`\n=> SELF-EVOLVED SKILLS REGISTERED: ${generatedSkills.registered} (none matched this prompt; inspect manifest "generated" metadata if a past lesson may apply).`);
|
|
480
|
+
}
|
|
481
|
+
|
|
531
482
|
console.log(`\n=> WORKFLOW STRATEGY: ${contract.workflowPlan.strategy || 'deferred'}`);
|
|
532
483
|
if (contract.workflowPlan.memory.write !== 'none') {
|
|
533
484
|
console.log(`=> MEMORY WRITE: ${contract.workflowPlan.memory.write}`);
|
|
@@ -586,4 +537,4 @@ if (process.argv[2]) {
|
|
|
586
537
|
});
|
|
587
538
|
}
|
|
588
539
|
}
|
|
589
|
-
module.exports = { run };
|
|
540
|
+
module.exports = { run, splitProfileLookupClauses, isFableProfileLookup, containsOnlyLookupTerms, detectFableModel };
|
package/sbom.cdx.json
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
"$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.7",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:f36b4c60-cf13-5d9d-a29b-1c3c673c2096",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-13T07:28:22.558Z",
|
|
9
9
|
"tools": [
|
|
10
10
|
{
|
|
11
11
|
"vendor": "Harness Everything",
|
|
12
12
|
"name": "generate-sbom.js",
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.31.0"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "application",
|
|
18
|
-
"bom-ref": "pkg:npm/harness-everything@0.
|
|
18
|
+
"bom-ref": "pkg:npm/harness-everything@0.31.0",
|
|
19
19
|
"name": "harness-everything",
|
|
20
|
-
"version": "0.
|
|
21
|
-
"purl": "pkg:npm/harness-everything@0.
|
|
20
|
+
"version": "0.31.0",
|
|
21
|
+
"purl": "pkg:npm/harness-everything@0.31.0"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"components": [
|
|
@@ -1999,7 +1999,7 @@
|
|
|
1999
1999
|
]
|
|
2000
2000
|
},
|
|
2001
2001
|
{
|
|
2002
|
-
"ref": "pkg:npm/harness-everything@0.
|
|
2002
|
+
"ref": "pkg:npm/harness-everything@0.31.0",
|
|
2003
2003
|
"dependsOn": [
|
|
2004
2004
|
"pkg:npm/%40commitlint/cli@19.8.1",
|
|
2005
2005
|
"pkg:npm/%40commitlint/config-conventional@19.8.1",
|
|
@@ -52,10 +52,23 @@ function defaultCodexHome(env = process.env) {
|
|
|
52
52
|
return path.resolve(env.CODEX_HOME || path.join(os.homedir(), '.codex'));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function
|
|
55
|
+
function sourcePluginVersion(sourcePluginRoot) {
|
|
56
|
+
for (const manifestFile of [
|
|
57
|
+
path.join(sourcePluginRoot, '.codex-plugin', 'plugin.json'),
|
|
58
|
+
path.join(sourcePluginRoot, 'plugin.json')
|
|
59
|
+
]) {
|
|
60
|
+
const manifest = readJsonObject(manifestFile, null);
|
|
61
|
+
if (manifest && typeof manifest.version === 'string' && /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$/.test(manifest.version)) {
|
|
62
|
+
return manifest.version;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw new Error('installed plugin source is missing a valid plugin version: ' + sourcePluginRoot);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pathsFor(codexHome, sourceHash = null, packageVersion = PACKAGE_VERSION) {
|
|
56
69
|
const home = path.resolve(codexHome);
|
|
57
70
|
const ownerRoot = path.join(home, 'harness-everything', 'compat-hooks');
|
|
58
|
-
const runtimeId = sourceHash ?
|
|
71
|
+
const runtimeId = sourceHash ? packageVersion + '-' + sourceHash.slice(0, 12) : null;
|
|
59
72
|
return {
|
|
60
73
|
codexHome: home,
|
|
61
74
|
hooksFile: path.join(home, 'hooks.json'),
|
|
@@ -290,11 +303,11 @@ function hookEntriesFrom(pluginRoot, runtimeDir) {
|
|
|
290
303
|
return records;
|
|
291
304
|
}
|
|
292
305
|
|
|
293
|
-
function sourceHookEntries(runtimeDir) {
|
|
294
|
-
return hookEntriesFrom(
|
|
306
|
+
function sourceHookEntries(runtimeDir, sourcePluginRoot = SOURCE_PLUGIN_ROOT) {
|
|
307
|
+
return hookEntriesFrom(sourcePluginRoot, runtimeDir);
|
|
295
308
|
}
|
|
296
309
|
|
|
297
|
-
function ensureRuntime(runtimeDir, sourceHash) {
|
|
310
|
+
function ensureRuntime(runtimeDir, sourceHash, sourcePluginRoot = SOURCE_PLUGIN_ROOT) {
|
|
298
311
|
if (fs.existsSync(runtimeDir)) {
|
|
299
312
|
if (treeHash(runtimeDir) === sourceHash) return;
|
|
300
313
|
fs.rmSync(runtimeDir, { recursive: true, force: true });
|
|
@@ -303,7 +316,7 @@ function ensureRuntime(runtimeDir, sourceHash) {
|
|
|
303
316
|
const temp = runtimeDir + '.tmp-' + process.pid;
|
|
304
317
|
try {
|
|
305
318
|
fs.rmSync(temp, { recursive: true, force: true });
|
|
306
|
-
fs.cpSync(
|
|
319
|
+
fs.cpSync(sourcePluginRoot, temp, { recursive: true });
|
|
307
320
|
if (treeHash(temp) !== sourceHash) throw new Error('copied compatibility runtime hash mismatch');
|
|
308
321
|
fs.renameSync(temp, runtimeDir);
|
|
309
322
|
} finally {
|
|
@@ -313,8 +326,10 @@ function ensureRuntime(runtimeDir, sourceHash) {
|
|
|
313
326
|
|
|
314
327
|
function install(options = {}) {
|
|
315
328
|
const codexHome = path.resolve(options.codexHome || defaultCodexHome(options.env));
|
|
316
|
-
const
|
|
317
|
-
const
|
|
329
|
+
const sourcePluginRoot = path.resolve(options.sourcePluginRoot || SOURCE_PLUGIN_ROOT);
|
|
330
|
+
const packageVersion = sourcePluginVersion(sourcePluginRoot);
|
|
331
|
+
const sourceHash = treeHash(sourcePluginRoot);
|
|
332
|
+
const p = pathsFor(codexHome, sourceHash, packageVersion);
|
|
318
333
|
assertCompatibilityPaths(p);
|
|
319
334
|
const existedBefore = fs.existsSync(p.hooksFile);
|
|
320
335
|
const hooksBefore = existedBefore ? fs.readFileSync(p.hooksFile) : null;
|
|
@@ -331,9 +346,9 @@ function install(options = {}) {
|
|
|
331
346
|
base = removal.config;
|
|
332
347
|
}
|
|
333
348
|
|
|
334
|
-
ensureRuntime(p.runtimeDir, sourceHash);
|
|
349
|
+
ensureRuntime(p.runtimeDir, sourceHash, sourcePluginRoot);
|
|
335
350
|
assertCompatibilityPaths(p);
|
|
336
|
-
const generated = sourceHookEntries(p.runtimeDir);
|
|
351
|
+
const generated = sourceHookEntries(p.runtimeDir, sourcePluginRoot);
|
|
337
352
|
const ownedEntries = [];
|
|
338
353
|
const next = { ...base, hooks: { ...base.hooks } };
|
|
339
354
|
for (const record of generated) {
|
|
@@ -349,7 +364,7 @@ function install(options = {}) {
|
|
|
349
364
|
schemaVersion: MANIFEST_SCHEMA,
|
|
350
365
|
package: 'harness-everything',
|
|
351
366
|
mode: MANIFEST_MODE,
|
|
352
|
-
packageVersion
|
|
367
|
+
packageVersion,
|
|
353
368
|
sourcePluginSha256: sourceHash,
|
|
354
369
|
runtimeDir: p.runtimeDir,
|
|
355
370
|
hooksFile: p.hooksFile,
|
|
@@ -385,6 +400,8 @@ function install(options = {}) {
|
|
|
385
400
|
codexHome,
|
|
386
401
|
hooksFile: p.hooksFile,
|
|
387
402
|
runtimeDir: p.runtimeDir,
|
|
403
|
+
packageVersion,
|
|
404
|
+
sourcePluginSha256: sourceHash,
|
|
388
405
|
entries: generated.length,
|
|
389
406
|
trustChanged: false,
|
|
390
407
|
};
|
|
@@ -443,10 +460,13 @@ function status(options = {}) {
|
|
|
443
460
|
const p = pathsFor(codexHome);
|
|
444
461
|
assertCompatibilityPaths(p);
|
|
445
462
|
const prior = readManifest(p.manifestFile, p);
|
|
446
|
-
if (!prior) return { installed: false, codexHome, hooksFile: p.hooksFile, trustChanged: false
|
|
463
|
+
if (!prior) return { installed: false, codexHome, hooksFile: p.hooksFile, trustChanged: false,
|
|
464
|
+
observationCapture: { status: 'inactive', reason: 'compatibility-hooks-not-installed', trustRequired: true } };
|
|
447
465
|
const config = readHooksFile(p.hooksFile);
|
|
448
466
|
const removal = removePriorEntries(config, prior);
|
|
449
467
|
const expected = prior.entries.length;
|
|
468
|
+
const healthy = removal.removed.length === expected && removal.drift.length === 0 &&
|
|
469
|
+
fs.existsSync(prior.runtimeDir) && treeHash(prior.runtimeDir) === prior.sourcePluginSha256;
|
|
450
470
|
return {
|
|
451
471
|
installed: true,
|
|
452
472
|
codexHome,
|
|
@@ -457,8 +477,12 @@ function status(options = {}) {
|
|
|
457
477
|
expectedEntries: expected,
|
|
458
478
|
exactEntriesPresent: removal.removed.length,
|
|
459
479
|
drift: removal.drift,
|
|
460
|
-
healthy
|
|
461
|
-
|
|
480
|
+
healthy,
|
|
481
|
+
observationCapture: {
|
|
482
|
+
status: healthy ? 'configured-unverified' : 'inactive',
|
|
483
|
+
reason: healthy ? 'codex-hook-trust-state-is-not-readable-here' : 'compatibility-hooks-unhealthy',
|
|
484
|
+
trustRequired: true,
|
|
485
|
+
},
|
|
462
486
|
trustChanged: false,
|
|
463
487
|
};
|
|
464
488
|
}
|
|
@@ -511,6 +535,7 @@ module.exports = {
|
|
|
511
535
|
main,
|
|
512
536
|
pathsFor,
|
|
513
537
|
readHooksFile,
|
|
538
|
+
sourcePluginVersion,
|
|
514
539
|
removePriorEntries,
|
|
515
540
|
sourceHookEntries,
|
|
516
541
|
stableJson,
|
|
@@ -12,7 +12,7 @@ const advisoryInstructions = [
|
|
|
12
12
|
`## 🚦 ENTRY TRIAGE & SEMANTIC CONTRACT`,
|
|
13
13
|
`For software/project work, load the \`harness-everything\` skill and establish routing context before broad mutation:`,
|
|
14
14
|
`1. MUST route before broad mutation; run \`npx github:dyphn1/Harness-everything next "<Brief summary of user's prompt>"\` when terminal execution is available and no equivalent routing state was injected.`,
|
|
15
|
-
`2. MUST
|
|
15
|
+
`2. MUST decompose Tier 2/3 work into ordered requirement steps with required/optional knowledge bindings. Load only active-step bindings; required bindings and applicable skill core contracts MUST resolve before the step passes.`,
|
|
16
16
|
`3. Selected-topology required obligations are MUSTs. Implementation tactics MAY adapt; numeric iteration/revision/replan/worker values MAY guide planning but never hard-block.`,
|
|
17
17
|
`4. MUST verify before claiming completion. Tier-3/Fable broad mutation MUST resolve isolation: linked worktree or explicit degraded fallback.`,
|
|
18
18
|
`5. Use the routing checkpoint only as internal source state for the first Harness Status; do not render a competing progress block.`,
|
|
@@ -148,7 +148,7 @@ function buildCodexGlobalContent() {
|
|
|
148
148
|
`- MUST establish relevant environment/host facts before relying on environment-sensitive behavior.`,
|
|
149
149
|
`- For software/project work, MUST establish routing before broad mutation; use \`npx github:dyphn1/Harness-everything next "<summary>"\` when terminal execution is available and routing state was not already injected.`,
|
|
150
150
|
`- For non-trivial work, MUST render one Markdown \`### 🚦 Harness Status\` with bullet-aligned bold \`Current\`, \`Read / Evidence\`, \`Next\`, and optional \`Risk / Blocked\`; repeat it at major phase/direction boundaries and before final completion.`,
|
|
151
|
-
`- MUST
|
|
151
|
+
`- MUST declare required/optional skills and references on ordered steps; load only active bindings and follow applicable skill core contracts before the step passes.`,
|
|
152
152
|
`- Selected-topology required obligations are MUSTs; implementation tactics MAY adapt and numeric planning values MAY guide but never hard-block.`,
|
|
153
153
|
`- Do not impose a universal TODO/TDD/Fable sequence; use the smallest useful topology plus applicable skills.`,
|
|
154
154
|
`- MUST verify before declaring done; a reminder-only host mechanism does not make verification optional.`,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Metadata-only lookup of self-evolved skills registered in Harness
|
|
4
|
+
// manifests. The router uses it to emit step-input signals (ids only); the
|
|
5
|
+
// workflow runtime uses it to resolve a declared binding id to its SKILL.md
|
|
6
|
+
// once the declaring step is active. Nothing here reads or injects skill text.
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
11
|
+
const STOP_TRIGGERS = new Set(['the', 'and', 'for', 'with', 'your', 'this', 'that', 'some', 'from', 'prevent', 'resolved']);
|
|
12
|
+
|
|
13
|
+
function generatedManifestPaths(workspaceRoot, homeDir = process.env.HOME || process.env.USERPROFILE || '') {
|
|
14
|
+
const paths = [];
|
|
15
|
+
if (workspaceRoot) {
|
|
16
|
+
for (const platform of ['.claude', '.cursor', '.github', '.codex', '.continue']) {
|
|
17
|
+
paths.push(path.join(workspaceRoot, platform, 'harness-everything', 'manifest.json'));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (homeDir) {
|
|
21
|
+
paths.push(path.join(homeDir, '.agents', 'harness-everything', 'manifest.json'));
|
|
22
|
+
paths.push(path.join(homeDir, '.claude', 'harness-everything', 'manifest.json'));
|
|
23
|
+
}
|
|
24
|
+
return paths;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readGeneratedSkills(workspaceRoot, homeDir) {
|
|
28
|
+
const skills = new Map();
|
|
29
|
+
for (const manifestPath of generatedManifestPaths(workspaceRoot, homeDir)) {
|
|
30
|
+
let data;
|
|
31
|
+
try { data = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch (_) { continue; }
|
|
32
|
+
if (!Array.isArray(data?.generated)) continue;
|
|
33
|
+
for (const entry of data.generated) {
|
|
34
|
+
const id = typeof entry?.id === 'string' ? entry.id.trim() : '';
|
|
35
|
+
if (!SAFE_ID.test(id) || skills.has(id) || typeof entry.dirPath !== 'string' || !entry.dirPath.trim()) continue;
|
|
36
|
+
const dirPath = path.isAbsolute(entry.dirPath)
|
|
37
|
+
? entry.dirPath
|
|
38
|
+
: path.resolve(workspaceRoot || path.dirname(manifestPath), entry.dirPath);
|
|
39
|
+
skills.set(id, {
|
|
40
|
+
id,
|
|
41
|
+
dirPath,
|
|
42
|
+
triggers: Array.isArray(entry.triggers) ? entry.triggers.filter(value => typeof value === 'string') : [],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return skills;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function triggerScore(promptLower, rawTrigger) {
|
|
50
|
+
const trigger = rawTrigger.toLowerCase().trim();
|
|
51
|
+
if (trigger.length < 2 || STOP_TRIGGERS.has(trigger)) return 0;
|
|
52
|
+
if (/[一-龥]/.test(trigger)) return promptLower.includes(trigger) ? trigger.length * 2 : 0;
|
|
53
|
+
const escaped = trigger.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
54
|
+
if (!new RegExp(`\\b${escaped}\\b`, 'i').test(promptLower)) return 0;
|
|
55
|
+
return trigger.length >= 4 ? 3 : 1.5;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Returns matching generated-skill ids, strongest match first.
|
|
59
|
+
function matchGeneratedSkills(promptLower, skills) {
|
|
60
|
+
const matched = [];
|
|
61
|
+
for (const skill of skills.values()) {
|
|
62
|
+
const score = skill.triggers.reduce((total, trigger) => total + triggerScore(promptLower, trigger), 0);
|
|
63
|
+
if (score >= 3) matched.push({ id: skill.id, score });
|
|
64
|
+
}
|
|
65
|
+
return matched.sort((a, b) => b.score - a.score).map(item => item.id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findGeneratedSkill(id, workspaceRoot, homeDir) {
|
|
69
|
+
if (!SAFE_ID.test(id || '')) return null;
|
|
70
|
+
return readGeneratedSkills(workspaceRoot, homeDir).get(id) || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
generatedManifestPaths,
|
|
75
|
+
readGeneratedSkills,
|
|
76
|
+
matchGeneratedSkills,
|
|
77
|
+
findGeneratedSkill,
|
|
78
|
+
};
|