@dzhechkov/harness-core 0.8.6 → 0.8.11
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/.dz-manifest.json +361 -97
- package/README.md +48 -1
- package/dist/amendment-trace.d.ts.map +1 -1
- package/dist/amendment-trace.js +12 -1
- package/dist/amendment-trace.js.map +1 -1
- package/dist/codex-invoke.d.ts +73 -0
- package/dist/codex-invoke.d.ts.map +1 -0
- package/dist/codex-invoke.js +80 -0
- package/dist/codex-invoke.js.map +1 -0
- package/dist/compounding.d.ts +54 -0
- package/dist/compounding.d.ts.map +1 -1
- package/dist/compounding.js +221 -1
- package/dist/compounding.js.map +1 -1
- package/dist/discrimination-gate.d.ts +63 -3
- package/dist/discrimination-gate.d.ts.map +1 -1
- package/dist/discrimination-gate.js +113 -16
- package/dist/discrimination-gate.js.map +1 -1
- package/dist/eta.d.ts +92 -0
- package/dist/eta.d.ts.map +1 -0
- package/dist/eta.js +488 -0
- package/dist/eta.js.map +1 -0
- package/dist/event-chain.d.ts +30 -0
- package/dist/event-chain.d.ts.map +1 -1
- package/dist/event-chain.js +24 -0
- package/dist/event-chain.js.map +1 -1
- package/dist/feature-adr-checkpoints.js +1 -1
- package/dist/feature-adr-decision-recall.d.ts +167 -0
- package/dist/feature-adr-decision-recall.d.ts.map +1 -0
- package/dist/feature-adr-decision-recall.js +519 -0
- package/dist/feature-adr-decision-recall.js.map +1 -0
- package/dist/feature-adr-landing.d.ts +37 -0
- package/dist/feature-adr-landing.d.ts.map +1 -0
- package/dist/feature-adr-landing.js +59 -0
- package/dist/feature-adr-landing.js.map +1 -0
- package/dist/feature-adr-routing.d.ts +2 -2
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +7 -11
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard-promotion.d.ts +41 -0
- package/dist/guard-promotion.d.ts.map +1 -1
- package/dist/guard-promotion.js +218 -4
- package/dist/guard-promotion.js.map +1 -1
- package/dist/guard-volume.d.ts +108 -0
- package/dist/guard-volume.d.ts.map +1 -0
- package/dist/guard-volume.js +536 -0
- package/dist/guard-volume.js.map +1 -0
- package/dist/guard.d.ts +17 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +92 -4
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +24 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -5
- package/dist/index.js.map +1 -1
- package/dist/integration-apply.d.ts +25 -0
- package/dist/integration-apply.d.ts.map +1 -0
- package/dist/integration-apply.js +299 -0
- package/dist/integration-apply.js.map +1 -0
- package/dist/integration-evidence.d.ts +46 -0
- package/dist/integration-evidence.d.ts.map +1 -0
- package/dist/integration-evidence.js +44 -0
- package/dist/integration-evidence.js.map +1 -0
- package/dist/integration-probe-worker.d.ts +22 -0
- package/dist/integration-probe-worker.d.ts.map +1 -0
- package/dist/integration-probe-worker.js +334 -0
- package/dist/integration-probe-worker.js.map +1 -0
- package/dist/integrations-verify.d.ts +60 -0
- package/dist/integrations-verify.d.ts.map +1 -0
- package/dist/integrations-verify.js +194 -0
- package/dist/integrations-verify.js.map +1 -0
- package/dist/lesson-generalization.d.ts +29 -0
- package/dist/lesson-generalization.d.ts.map +1 -0
- package/dist/lesson-generalization.js +84 -0
- package/dist/lesson-generalization.js.map +1 -0
- package/dist/mutation-gate.d.ts +39 -36
- package/dist/mutation-gate.d.ts.map +1 -1
- package/dist/mutation-gate.js +111 -5
- package/dist/mutation-gate.js.map +1 -1
- package/dist/operations.d.ts +27 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +186 -7
- package/dist/operations.js.map +1 -1
- package/dist/patterns.d.ts +27 -1
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +211 -45
- package/dist/patterns.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +27 -5
- package/dist/plugin.js.map +1 -1
- package/dist/recommend.d.ts +4 -5
- package/dist/recommend.d.ts.map +1 -1
- package/dist/recommend.js +110 -45
- package/dist/recommend.js.map +1 -1
- package/dist/registry.d.ts +32 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +165 -9
- package/dist/registry.js.map +1 -1
- package/dist/run-records.d.ts +3 -0
- package/dist/run-records.d.ts.map +1 -1
- package/dist/run-records.js +18 -0
- package/dist/run-records.js.map +1 -1
- package/dist/score.d.ts +95 -0
- package/dist/score.d.ts.map +1 -1
- package/dist/score.js +274 -2
- package/dist/score.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +20 -17
- package/dist/setup.js.map +1 -1
- package/dist/skill-selection.d.ts +72 -0
- package/dist/skill-selection.d.ts.map +1 -0
- package/dist/skill-selection.js +76 -0
- package/dist/skill-selection.js.map +1 -0
- package/dist/stem.d.ts +12 -0
- package/dist/stem.d.ts.map +1 -0
- package/dist/stem.js +89 -0
- package/dist/stem.js.map +1 -0
- package/dist/target-integrations.d.ts +65 -0
- package/dist/target-integrations.d.ts.map +1 -0
- package/dist/target-integrations.js +152 -0
- package/dist/target-integrations.js.map +1 -0
- package/dist/telemetry-vocabulary.d.ts +7 -0
- package/dist/telemetry-vocabulary.d.ts.map +1 -1
- package/dist/telemetry-vocabulary.js +29 -0
- package/dist/telemetry-vocabulary.js.map +1 -1
- package/dist/vector-tier.d.ts +6 -1
- package/dist/vector-tier.d.ts.map +1 -1
- package/dist/vector-tier.js +32 -7
- package/dist/vector-tier.js.map +1 -1
- package/package.json +7 -6
- package/sbom.json +772 -112
- package/src/amendment-trace.ts +12 -1
- package/src/codex-invoke.ts +138 -0
- package/src/compounding.ts +300 -1
- package/src/discrimination-gate.ts +183 -19
- package/src/eta.ts +590 -0
- package/src/event-chain.ts +41 -0
- package/src/feature-adr-checkpoints.ts +1 -1
- package/src/feature-adr-decision-recall.ts +652 -0
- package/src/feature-adr-landing.ts +109 -0
- package/src/feature-adr-routing.ts +7 -11
- package/src/guard-promotion.ts +245 -4
- package/src/guard-volume.ts +752 -0
- package/src/guard.ts +110 -4
- package/src/index.ts +73 -6
- package/src/integration-apply.ts +332 -0
- package/src/integration-evidence.ts +89 -0
- package/src/integration-probe-worker.ts +310 -0
- package/src/integration-receipts/claude-code/mcp/2.1.235.json +35 -0
- package/src/integrations-verify.ts +258 -0
- package/src/lesson-generalization.ts +115 -0
- package/src/mutation-gate.ts +165 -5
- package/src/operations.ts +207 -7
- package/src/patterns.ts +252 -43
- package/src/plugin.ts +27 -5
- package/src/recommend.ts +116 -46
- package/src/registry.ts +144 -11
- package/src/run-records.ts +23 -0
- package/src/score.ts +361 -3
- package/src/setup.ts +20 -17
- package/src/skill-selection.ts +111 -0
- package/src/stem.ts +87 -0
- package/src/target-integrations.ts +225 -0
- package/src/telemetry-vocabulary.ts +36 -0
- package/src/vector-tier.ts +44 -14
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** Exhaustive target companion policy and once-per-install manifest aggregation. */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { claudeIntegrationAdapter } from '@dzhechkov/adapter-claude';
|
|
7
|
+
import {
|
|
8
|
+
INTEGRATION_MANIFEST_MAX_BYTES,
|
|
9
|
+
INTEGRATION_REGISTRATION_MAX_COUNT,
|
|
10
|
+
canonicalIntegrationJson,
|
|
11
|
+
integrationManifestDigest,
|
|
12
|
+
parseHarnessIntegrationManifestJson,
|
|
13
|
+
type HarnessIntegrationManifestV1,
|
|
14
|
+
type IntegrationComponent,
|
|
15
|
+
type IntegrationPlan,
|
|
16
|
+
type IntegrationReasonCode,
|
|
17
|
+
type IntegrationStatus,
|
|
18
|
+
type TargetIntegrationAdapter,
|
|
19
|
+
} from '@dzhechkov/core';
|
|
20
|
+
|
|
21
|
+
import { TARGET_NAMES, type TargetName } from './targets.js';
|
|
22
|
+
|
|
23
|
+
export interface RegistrationObservation {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly scope: 'project' | 'user' | 'plugin';
|
|
26
|
+
readonly registered: boolean;
|
|
27
|
+
readonly approval?: 'pending' | 'approved' | 'unknown';
|
|
28
|
+
readonly ready?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IntegrationOutcome {
|
|
32
|
+
readonly target: TargetName;
|
|
33
|
+
readonly component: IntegrationComponent;
|
|
34
|
+
readonly status: IntegrationStatus;
|
|
35
|
+
readonly registrations: readonly RegistrationObservation[];
|
|
36
|
+
readonly carrier?: { readonly scope: 'project' | 'user' | 'plugin'; readonly path: string };
|
|
37
|
+
readonly runtimeVersion?: string;
|
|
38
|
+
readonly evidenceVersion?: string;
|
|
39
|
+
readonly reasonCode?: IntegrationReasonCode;
|
|
40
|
+
readonly remediation?: string;
|
|
41
|
+
/** True when carrier bytes may precede a failed live check or ownership-journal commit. */
|
|
42
|
+
readonly applied?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type IntegrationPolicyCell =
|
|
46
|
+
| { readonly disposition: 'receipt'; readonly reasonCode?: never; readonly receiptVersion: string }
|
|
47
|
+
| { readonly disposition: 'legacy-post-write'; readonly reasonCode: 'CURRENT_LIVE_CHECK_FAILED' }
|
|
48
|
+
| { readonly disposition: 'refused'; readonly reasonCode: IntegrationReasonCode };
|
|
49
|
+
|
|
50
|
+
export interface TargetIntegrationPolicy {
|
|
51
|
+
readonly mcp: IntegrationPolicyCell;
|
|
52
|
+
readonly hooks: IntegrationPolicyCell;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const refusedAdapter = (target: TargetName): TargetIntegrationAdapter => ({
|
|
56
|
+
target,
|
|
57
|
+
plan(manifest): IntegrationPlan {
|
|
58
|
+
const refusals: IntegrationPlan['refusals'][number][] = [];
|
|
59
|
+
if (Object.keys(manifest.mcpServers ?? {}).length > 0) {
|
|
60
|
+
refusals.push({ component: 'mcp', reasonCode: TARGET_INTEGRATION_POLICY[target].mcp.reasonCode ?? 'NO_QUALIFYING_LIVE_RECEIPT', remediation: 'run dz integrations-verify for this exact target product and version' });
|
|
61
|
+
}
|
|
62
|
+
if ((manifest.hooks?.length ?? 0) > 0) {
|
|
63
|
+
refusals.push({ component: 'hooks', reasonCode: TARGET_INTEGRATION_POLICY[target].hooks.reasonCode ?? 'NO_QUALIFYING_LIVE_RECEIPT', remediation: 'run dz integrations-verify with a hook canary and negative control' });
|
|
64
|
+
}
|
|
65
|
+
return { fragments: [], refusals };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** Closed 10×2 policy. No fallback/default is permitted. */
|
|
70
|
+
export const TARGET_INTEGRATION_POLICY: Record<TargetName, TargetIntegrationPolicy> = {
|
|
71
|
+
'claude-code': {
|
|
72
|
+
mcp: { disposition: 'receipt', receiptVersion: '2.1.235' },
|
|
73
|
+
hooks: { disposition: 'refused', reasonCode: 'NO_ACTIVATION_RECEIPT' },
|
|
74
|
+
},
|
|
75
|
+
codex: {
|
|
76
|
+
mcp: { disposition: 'refused', reasonCode: 'PROJECT_CARRIER_NOT_OBSERVED' },
|
|
77
|
+
hooks: { disposition: 'legacy-post-write', reasonCode: 'CURRENT_LIVE_CHECK_FAILED' },
|
|
78
|
+
},
|
|
79
|
+
cursor: {
|
|
80
|
+
mcp: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
81
|
+
hooks: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
82
|
+
},
|
|
83
|
+
copilot: {
|
|
84
|
+
mcp: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
85
|
+
hooks: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
86
|
+
},
|
|
87
|
+
windsurf: {
|
|
88
|
+
mcp: { disposition: 'refused', reasonCode: 'PRODUCT_SURFACE_AMBIGUOUS' },
|
|
89
|
+
hooks: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
90
|
+
},
|
|
91
|
+
gemini: {
|
|
92
|
+
mcp: { disposition: 'refused', reasonCode: 'NO_QUALIFYING_LIVE_RECEIPT' },
|
|
93
|
+
hooks: { disposition: 'refused', reasonCode: 'NO_QUALIFYING_LIVE_RECEIPT' },
|
|
94
|
+
},
|
|
95
|
+
opencode: {
|
|
96
|
+
mcp: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
97
|
+
hooks: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
98
|
+
},
|
|
99
|
+
openclaude: {
|
|
100
|
+
mcp: { disposition: 'refused', reasonCode: 'PROJECT_CARRIER_NOT_DOCUMENTED' },
|
|
101
|
+
hooks: { disposition: 'refused', reasonCode: 'TARGET_BINARY_UNAVAILABLE' },
|
|
102
|
+
},
|
|
103
|
+
hermes: {
|
|
104
|
+
mcp: { disposition: 'refused', reasonCode: 'LIVE_PROBE_TIMEOUT' },
|
|
105
|
+
hooks: { disposition: 'refused', reasonCode: 'NO_ACTIVATION_RECEIPT' },
|
|
106
|
+
},
|
|
107
|
+
'agents-md': {
|
|
108
|
+
mcp: { disposition: 'refused', reasonCode: 'NO_RUNTIME_SURFACE' },
|
|
109
|
+
hooks: { disposition: 'refused', reasonCode: 'NO_RUNTIME_SURFACE' },
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const TARGET_INTEGRATIONS: Record<TargetName, TargetIntegrationAdapter> = Object.fromEntries(
|
|
114
|
+
TARGET_NAMES.map((target) => [target, target === 'claude-code' ? claudeIntegrationAdapter : refusedAdapter(target)]),
|
|
115
|
+
) as Record<TargetName, TargetIntegrationAdapter>;
|
|
116
|
+
|
|
117
|
+
export interface IntegrationManifestSource {
|
|
118
|
+
readonly skillId: string;
|
|
119
|
+
readonly skillDir: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface IntegrationManifestAggregate {
|
|
123
|
+
readonly manifest: HarnessIntegrationManifestV1 | undefined;
|
|
124
|
+
readonly digest: string | undefined;
|
|
125
|
+
readonly sourcePaths: readonly string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export class IntegrationManifestError extends Error {
|
|
129
|
+
readonly code = 'MANIFEST_INVALID';
|
|
130
|
+
constructor(readonly manifestPath: string, message: string) {
|
|
131
|
+
super(`${manifestPath}: ${message}`);
|
|
132
|
+
this.name = 'IntegrationManifestError';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Load, validate and aggregate every adjacent manifest before target effects. */
|
|
137
|
+
export function aggregateIntegrationManifests(
|
|
138
|
+
sources: readonly IntegrationManifestSource[],
|
|
139
|
+
): IntegrationManifestAggregate {
|
|
140
|
+
const manifests: { path: string; value: HarnessIntegrationManifestV1 }[] = [];
|
|
141
|
+
for (const source of sources) {
|
|
142
|
+
const path = join(source.skillDir, source.skillId, 'INTEGRATIONS.json');
|
|
143
|
+
if (!existsSync(path)) continue;
|
|
144
|
+
try {
|
|
145
|
+
manifests.push({ path, value: parseHarnessIntegrationManifestJson(readFileSync(path, 'utf8')) });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw new IntegrationManifestError(path, error instanceof Error ? error.message : String(error));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (manifests.length === 0) return { manifest: undefined, digest: undefined, sourcePaths: [] };
|
|
151
|
+
|
|
152
|
+
const mcp = Object.create(null) as Record<string, NonNullable<HarnessIntegrationManifestV1['mcpServers']>[string]>;
|
|
153
|
+
const hooks = new Map<string, NonNullable<HarnessIntegrationManifestV1['hooks']>[number]>();
|
|
154
|
+
for (const source of manifests) {
|
|
155
|
+
for (const [id, intent] of Object.entries(source.value.mcpServers ?? {})) {
|
|
156
|
+
const previous = mcp[id];
|
|
157
|
+
if (previous !== undefined && canonicalIntegrationJson(previous) !== canonicalIntegrationJson(intent)) {
|
|
158
|
+
throw new IntegrationManifestError(source.path, `conflicting MCP registration id ${JSON.stringify(id)}`);
|
|
159
|
+
}
|
|
160
|
+
mcp[id] = intent;
|
|
161
|
+
}
|
|
162
|
+
for (const hook of source.value.hooks ?? []) {
|
|
163
|
+
const previous = hooks.get(hook.id);
|
|
164
|
+
if (previous !== undefined && canonicalIntegrationJson(previous) !== canonicalIntegrationJson(hook)) {
|
|
165
|
+
throw new IntegrationManifestError(source.path, `conflicting hook registration id ${JSON.stringify(hook.id)}`);
|
|
166
|
+
}
|
|
167
|
+
hooks.set(hook.id, hook);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const aggregate = {
|
|
171
|
+
version: 1 as const,
|
|
172
|
+
...(Object.keys(mcp).length > 0 ? { mcpServers: Object.fromEntries(Object.entries(mcp).sort(([a], [b]) => a.localeCompare(b))) } : {}),
|
|
173
|
+
...(hooks.size > 0 ? { hooks: [...hooks.values()].sort((a, b) => a.id.localeCompare(b.id)) } : {}),
|
|
174
|
+
} satisfies HarnessIntegrationManifestV1;
|
|
175
|
+
const count = Object.keys(aggregate.mcpServers ?? {}).length + (aggregate.hooks?.length ?? 0);
|
|
176
|
+
if (count > INTEGRATION_REGISTRATION_MAX_COUNT) {
|
|
177
|
+
throw new IntegrationManifestError('<aggregate>', `final aggregate has ${count} registrations; maximum is ${INTEGRATION_REGISTRATION_MAX_COUNT}`);
|
|
178
|
+
}
|
|
179
|
+
const aggregateBytes = Buffer.byteLength(canonicalIntegrationJson(aggregate), 'utf8');
|
|
180
|
+
if (aggregateBytes > INTEGRATION_MANIFEST_MAX_BYTES) {
|
|
181
|
+
throw new IntegrationManifestError('<aggregate>', `final aggregate exceeds ${INTEGRATION_MANIFEST_MAX_BYTES} UTF-8 bytes`);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
manifest: aggregate,
|
|
185
|
+
digest: integrationManifestDigest([aggregate]),
|
|
186
|
+
sourcePaths: manifests.map((source) => source.path),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function notRequestedOutcomes(target: TargetName): readonly [IntegrationOutcome, IntegrationOutcome] {
|
|
191
|
+
return [
|
|
192
|
+
{ target, component: 'mcp', status: 'not-requested', registrations: [] },
|
|
193
|
+
{ target, component: 'hooks', status: 'not-requested', registrations: [] },
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function refusedOutcome(
|
|
198
|
+
target: TargetName,
|
|
199
|
+
component: IntegrationComponent,
|
|
200
|
+
reasonCode: IntegrationReasonCode,
|
|
201
|
+
remediation: string,
|
|
202
|
+
): IntegrationOutcome {
|
|
203
|
+
return { target, component, status: 'refused', registrations: [], reasonCode, remediation };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Static synchronous outcomes for Gemini/agents-md and other refusal-only seams. */
|
|
207
|
+
export function staticPolicyOutcomes(
|
|
208
|
+
target: TargetName,
|
|
209
|
+
manifest: HarnessIntegrationManifestV1 | undefined,
|
|
210
|
+
noHooks = false,
|
|
211
|
+
): readonly [IntegrationOutcome, IntegrationOutcome] {
|
|
212
|
+
if (manifest === undefined) return notRequestedOutcomes(target);
|
|
213
|
+
const mcpRequested = Object.keys(manifest.mcpServers ?? {}).length > 0;
|
|
214
|
+
const hooksRequested = (manifest.hooks?.length ?? 0) > 0 && !noHooks;
|
|
215
|
+
const mcpCell = TARGET_INTEGRATION_POLICY[target].mcp;
|
|
216
|
+
const hookCell = TARGET_INTEGRATION_POLICY[target].hooks;
|
|
217
|
+
return [
|
|
218
|
+
mcpRequested
|
|
219
|
+
? refusedOutcome(target, 'mcp', mcpCell.reasonCode ?? 'NO_QUALIFYING_LIVE_RECEIPT', 'run dz integrations-verify for an exact-version receipt')
|
|
220
|
+
: { target, component: 'mcp', status: 'not-requested', registrations: [] },
|
|
221
|
+
hooksRequested
|
|
222
|
+
? refusedOutcome(target, 'hooks', hookCell.reasonCode ?? 'NO_ACTIVATION_RECEIPT', 'run dz integrations-verify with a hook canary and negative control')
|
|
223
|
+
: { target, component: 'hooks', status: 'not-requested', registrations: [] },
|
|
224
|
+
];
|
|
225
|
+
}
|
|
@@ -58,6 +58,40 @@ export interface TelemetryField {
|
|
|
58
58
|
readonly unit: 'ms' | 'tokens' | null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export type RunOutcome =
|
|
62
|
+
| 'completed' | 'completed-unverified' | 'refused-repo-root' | 'refused-design'
|
|
63
|
+
| 'refused-plan' | 'paused-checkpoint' | 'crashed' | 'unclassified';
|
|
64
|
+
|
|
65
|
+
/** Every member of the closed set, as data for reachability checks. */
|
|
66
|
+
export const RUN_OUTCOMES = [
|
|
67
|
+
'completed', 'completed-unverified', 'refused-repo-root', 'refused-design',
|
|
68
|
+
'refused-plan', 'paused-checkpoint', 'crashed', 'unclassified',
|
|
69
|
+
] as const satisfies readonly RunOutcome[];
|
|
70
|
+
|
|
71
|
+
export function runOutcomeOf(input: {
|
|
72
|
+
phase: string | null | undefined;
|
|
73
|
+
gates: Record<string, unknown> | null | undefined;
|
|
74
|
+
}): RunOutcome {
|
|
75
|
+
if (input.phase === 'repo-root-mismatch') return 'refused-repo-root';
|
|
76
|
+
if (input.phase === 'design-incomplete') return 'refused-design';
|
|
77
|
+
if (input.phase === 'plan-gate-failed') return 'refused-plan';
|
|
78
|
+
if (input.phase === 'checkpoint-after-plan') return 'paused-checkpoint';
|
|
79
|
+
|
|
80
|
+
const gates = input.gates;
|
|
81
|
+
if ((input.phase === null || input.phase === undefined || input.phase === '')
|
|
82
|
+
&& gates !== null && typeof gates === 'object') {
|
|
83
|
+
const codeCompleted = gates.code === 'produced' || gates.code === 'landed';
|
|
84
|
+
const qe = gates.qe;
|
|
85
|
+
if (codeCompleted && typeof qe === 'string' && qe !== 'not-run' && qe !== 'ran' && qe !== '') {
|
|
86
|
+
return 'completed';
|
|
87
|
+
}
|
|
88
|
+
return 'completed-unverified';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// A crashed run cannot classify itself; an external consumer assigns that outcome later.
|
|
92
|
+
return 'unclassified';
|
|
93
|
+
}
|
|
94
|
+
|
|
61
95
|
/**
|
|
62
96
|
* The vocabulary. Keys are stable identifiers for OUR code to reference; `field` is what goes on
|
|
63
97
|
* disk, so an upstream rename touches the value and never the call sites.
|
|
@@ -75,6 +109,7 @@ export const TELEMETRY_FIELDS: Readonly<Record<string, TelemetryField>> = {
|
|
|
75
109
|
// OpenTelemetry carries elapsed time as span structure, not as an attribute, so there is no
|
|
76
110
|
// upstream name to copy for a JSONL row. Ours, and said so.
|
|
77
111
|
durationMs: { field: 'dz.duration_ms', source: 'local', means: 'elapsed wall time of a stage or run', unit: 'ms' },
|
|
112
|
+
runOutcome: { field: 'dz.run_outcome', source: 'local', means: 'terminal outcome of one feature-adr pipeline run', unit: null },
|
|
78
113
|
// Upstream splits usage into input and output and has no TOTAL. Ours are totals, and folding them
|
|
79
114
|
// into input_tokens would silently change the measured quantity (cross-family review).
|
|
80
115
|
totalTokens: { field: 'dz.total_tokens', source: 'local', means: 'input + output for a run or stage', unit: 'tokens' },
|
|
@@ -122,6 +157,7 @@ export const LOCAL_FIELD_ALIASES: Readonly<Record<string, string>> = {
|
|
|
122
157
|
grade: 'evaluationLabel',
|
|
123
158
|
runId: 'runId',
|
|
124
159
|
stage: 'stage',
|
|
160
|
+
outcome: 'runOutcome',
|
|
125
161
|
};
|
|
126
162
|
|
|
127
163
|
/**
|
package/src/vector-tier.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
updateReinforcementState,
|
|
57
57
|
type PatternRecord,
|
|
58
58
|
type RecallHit,
|
|
59
|
+
type RecallPatternsOptions,
|
|
59
60
|
} from './patterns.js';
|
|
60
61
|
import {
|
|
61
62
|
indexPatternsToAgentdb,
|
|
@@ -202,6 +203,8 @@ export interface HybridHit {
|
|
|
202
203
|
readonly score: number;
|
|
203
204
|
/** lesson-quarantine: set only for a quarantined hit — display marks ⚠q, ranking was damped. */
|
|
204
205
|
readonly quarantined?: boolean;
|
|
206
|
+
/** Form provenance from the lexical pair merge; semantic ranking never invents it. */
|
|
207
|
+
readonly matchedForm?: NonNullable<RecallHit['matchedForm']>;
|
|
205
208
|
/**
|
|
206
209
|
* Raw closeness from the semantic leg, when the engine reports a genuine cosine. ABSENT for a
|
|
207
210
|
* lexical-only hit and for an engine whose score is not a cosine — the honest display there is a
|
|
@@ -491,7 +494,7 @@ export function isVectorNoise(text: string): boolean {
|
|
|
491
494
|
* ingest gate — I-6). Score is the record's REAL reward, never a fabricated 1.0.
|
|
492
495
|
*/
|
|
493
496
|
export function patternVectorEntry(p: PatternRecord, source = 'dz-teach', opts: { quarantined?: boolean } = {}): VectorEntry | undefined {
|
|
494
|
-
if (isVectorNoise(p.pattern)) return undefined;
|
|
497
|
+
if (p.lessonForm === 'class' || isVectorNoise(p.pattern)) return undefined;
|
|
495
498
|
const dzId = patternRecordId(p);
|
|
496
499
|
return {
|
|
497
500
|
dzId,
|
|
@@ -501,7 +504,13 @@ export function patternVectorEntry(p: PatternRecord, source = 'dz-teach', opts:
|
|
|
501
504
|
tags: ['dz-teach', p.type],
|
|
502
505
|
// FR-8: quarantine rides into the mirror so the HOOK DAEMON (which reads only the mirror's
|
|
503
506
|
// sqlite metadata) can exclude unproven lessons from auto-injection.
|
|
504
|
-
metadata: {
|
|
507
|
+
metadata: {
|
|
508
|
+
dzId, source, ts: p.ts, domain: p.domain,
|
|
509
|
+
...(p.lessonForm !== undefined && p.lessonPairId !== undefined
|
|
510
|
+
? { lessonForm: p.lessonForm, lessonPairId: p.lessonPairId }
|
|
511
|
+
: {}),
|
|
512
|
+
...(opts.quarantined === true ? { qStatus: 'quarantined' } : {}),
|
|
513
|
+
},
|
|
505
514
|
};
|
|
506
515
|
}
|
|
507
516
|
|
|
@@ -525,7 +534,7 @@ export function dreamVectorEntry(d: DreamPattern): VectorEntry | undefined {
|
|
|
525
534
|
|
|
526
535
|
/** ACL: stored {@link MemoryRecord} → {@link VectorEntry} (the consolidate-backfill mapper). */
|
|
527
536
|
export function memoryRecordVectorEntry(r: MemoryRecord): VectorEntry | undefined {
|
|
528
|
-
if (isVectorNoise(r.text)) return undefined;
|
|
537
|
+
if (r.metadata?.['lessonForm'] === 'class' || isVectorNoise(r.text)) return undefined;
|
|
529
538
|
const state = readReinforcementState(r);
|
|
530
539
|
return {
|
|
531
540
|
dzId: r.id,
|
|
@@ -533,7 +542,12 @@ export function memoryRecordVectorEntry(r: MemoryRecord): VectorEntry | undefine
|
|
|
533
542
|
score: r.score,
|
|
534
543
|
taskType: r.id.startsWith('dream:') ? 'dz-learning' : 'dz-teach',
|
|
535
544
|
tags: ['dz-backfill', r.outcome],
|
|
536
|
-
metadata: {
|
|
545
|
+
metadata: {
|
|
546
|
+
dzId: r.id, source: r.metadata?.['source'] ?? 'dz-backfill', ts: r.timestamp, skillId: r.skillId,
|
|
547
|
+
...(r.metadata?.['lessonForm'] === 'specific' && typeof r.metadata?.['lessonPairId'] === 'string'
|
|
548
|
+
? { lessonForm: 'specific', lessonPairId: r.metadata['lessonPairId'] }
|
|
549
|
+
: {}),
|
|
550
|
+
},
|
|
537
551
|
uses: state.uses,
|
|
538
552
|
avgReward: state.avgReward,
|
|
539
553
|
};
|
|
@@ -939,6 +953,7 @@ export interface RankedPattern {
|
|
|
939
953
|
readonly id: string;
|
|
940
954
|
readonly pattern: PatternRecord;
|
|
941
955
|
readonly backend: RecallHit['backend'];
|
|
956
|
+
readonly matchedForm?: NonNullable<RecallHit['matchedForm']>;
|
|
942
957
|
/**
|
|
943
958
|
* The semantic leg's raw closeness, when the engine reports a real cosine. Rides ALONGSIDE the RRF
|
|
944
959
|
* score and never enters the ranking maths — four things depend on RRF magnitude (the reinforce
|
|
@@ -967,11 +982,12 @@ export function mergeHybridHits(
|
|
|
967
982
|
opts: { readonly limit: number; readonly semanticWeight?: number | undefined },
|
|
968
983
|
): HybridHit[] {
|
|
969
984
|
const weight = opts.semanticWeight ?? 1;
|
|
970
|
-
interface Acc { pattern: PatternRecord; lex?: RecallHit['backend']; sem: boolean; score: number; similarity?: number }
|
|
985
|
+
interface Acc { pattern: PatternRecord; lex?: RecallHit['backend']; sem: boolean; score: number; similarity?: number; matchedForm?: NonNullable<RecallHit['matchedForm']> }
|
|
971
986
|
const acc = new Map<string, Acc>();
|
|
972
987
|
lexical.forEach((h, rank) => {
|
|
973
988
|
const cur = acc.get(h.id) ?? { pattern: h.pattern, sem: false, score: 0 };
|
|
974
989
|
cur.lex = h.backend;
|
|
990
|
+
if (h.matchedForm !== undefined) cur.matchedForm = h.matchedForm;
|
|
975
991
|
cur.score += 1 / (RRF_K + rank + 1);
|
|
976
992
|
acc.set(h.id, cur);
|
|
977
993
|
});
|
|
@@ -997,6 +1013,7 @@ export function mergeHybridHits(
|
|
|
997
1013
|
pattern: v.pattern,
|
|
998
1014
|
backend: v.lex !== undefined && v.sem ? ('both' as const) : v.lex ?? ('vector' as const),
|
|
999
1015
|
score: v.score,
|
|
1016
|
+
...(v.matchedForm === undefined ? {} : { matchedForm: v.matchedForm }),
|
|
1000
1017
|
...(v.similarity === undefined ? {} : { similarity: v.similarity }),
|
|
1001
1018
|
});
|
|
1002
1019
|
// `slice(0, -1)` drops the LAST element instead of returning nothing, so a negative limit used to
|
|
@@ -1105,6 +1122,8 @@ export async function recallHybrid(
|
|
|
1105
1122
|
* sitting at `pulls === 0` forever. Absent ⇒ `general`; it changes nothing while disarmed.
|
|
1106
1123
|
*/
|
|
1107
1124
|
readonly domain?: string | undefined;
|
|
1125
|
+
readonly onClassDegraded?: RecallPatternsOptions['onClassDegraded'];
|
|
1126
|
+
readonly classMatcher?: RecallPatternsOptions['classMatcher'];
|
|
1108
1127
|
} = {},
|
|
1109
1128
|
): Promise<HybridRecall> {
|
|
1110
1129
|
// Config-surface note (QE P3, benign by design): recall resolves the engine directly, while teach
|
|
@@ -1116,7 +1135,10 @@ export async function recallHybrid(
|
|
|
1116
1135
|
// write-side backend flag.
|
|
1117
1136
|
const limit = opts.limit ?? 10;
|
|
1118
1137
|
const mode = opts.mode ?? 'hybrid';
|
|
1119
|
-
const lexical = recallPatterns(projectRoot, query, limit
|
|
1138
|
+
const lexical = recallPatterns(projectRoot, query, limit, {
|
|
1139
|
+
...(opts.onClassDegraded === undefined ? {} : { onClassDegraded: opts.onClassDegraded }),
|
|
1140
|
+
...(opts.classMatcher === undefined ? {} : { classMatcher: opts.classMatcher }),
|
|
1141
|
+
});
|
|
1120
1142
|
const lexicalBackend: 'sqlite' | 'json' = lexical[0]?.backend === 'sqlite' ? 'sqlite' : 'json';
|
|
1121
1143
|
const records = loadStoreRecords(projectRoot);
|
|
1122
1144
|
const idToRecord = new Map<string, MemoryRecord>();
|
|
@@ -1223,7 +1245,12 @@ export async function recallHybrid(
|
|
|
1223
1245
|
};
|
|
1224
1246
|
const lexicalOnly = (extra: Partial<Pick<HybridRecall, 'vectorEngine' | 'vectorReason' | 'vectorError'>>): HybridRecall => {
|
|
1225
1247
|
// `enhance` FIRST: it is what populates `banditReport` (the key is absent while disarmed).
|
|
1226
|
-
const hits = enhance(lexical.map((h, rank) => ({
|
|
1248
|
+
const hits = enhance(lexical.map((h, rank) => ({
|
|
1249
|
+
pattern: h.pattern,
|
|
1250
|
+
backend: h.backend,
|
|
1251
|
+
score: 1 / (RRF_K + rank + 1),
|
|
1252
|
+
...(h.matchedForm === undefined ? {} : { matchedForm: h.matchedForm }),
|
|
1253
|
+
})));
|
|
1227
1254
|
return {
|
|
1228
1255
|
hits,
|
|
1229
1256
|
lexicalBackend,
|
|
@@ -1333,6 +1360,7 @@ export async function recallHybrid(
|
|
|
1333
1360
|
id: identityToId.get(patternIdentityOf(h.pattern)) ?? patternRecordId(h.pattern),
|
|
1334
1361
|
pattern: h.pattern,
|
|
1335
1362
|
backend: h.backend,
|
|
1363
|
+
...(h.matchedForm === undefined ? {} : { matchedForm: h.matchedForm }),
|
|
1336
1364
|
}));
|
|
1337
1365
|
const hits = enhance(mergeHybridHits(lex, semantic, { limit, semanticWeight: mode === 'semantic' ? 2 : 1 }));
|
|
1338
1366
|
markRecallHits(projectRoot, learning, hits, idOf, banditEmission());
|
|
@@ -1688,13 +1716,15 @@ export async function harmonizeVectorStore(projectRoot: string, opts: HarmonizeO
|
|
|
1688
1716
|
} catch {
|
|
1689
1717
|
records = [];
|
|
1690
1718
|
}
|
|
1691
|
-
const items: HarmonizeItem[] = records
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1719
|
+
const items: HarmonizeItem[] = records
|
|
1720
|
+
.filter((r) => r.metadata?.['lessonForm'] !== 'class')
|
|
1721
|
+
.map((r) => ({
|
|
1722
|
+
dzId: r.id,
|
|
1723
|
+
text: r.text,
|
|
1724
|
+
reward: r.score,
|
|
1725
|
+
ts: r.timestamp,
|
|
1726
|
+
taskType: r.id.startsWith('dream:') ? 'dz-learning' : 'dz-teach',
|
|
1727
|
+
}));
|
|
1698
1728
|
|
|
1699
1729
|
// 2. GATE: an embedder ⇒ SEMANTIC clustering; absence/failure ⇒ EXACT-text fallback (D4).
|
|
1700
1730
|
let embed: ((text: string) => Promise<Float32Array>) | undefined;
|