@hadooppei/hwcode 1.0.10 → 1.0.12
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/.pi/dist/lib/knowledge/extractor.js +90 -9
- package/.pi/dist/lib/knowledge/review-status.js +20 -0
- package/.pi/dist/lib/knowledge/review-worker.js +105 -5
- package/.pi/dist/lib/knowledge/sanitize.js +20 -0
- package/.pi/dist/lib/knowledge/session-scanner.js +46 -9
- package/.pi/dist/lib/knowledge/store.js +83 -10
- package/.pi/dist/lib/runtime/defaults.js +9 -5
- package/.pi/dist/lib/runtime/paths.js +1 -0
- package/.pi/extensions/knowledge.ts +37 -16
- package/.pi/lib/knowledge/extractor.ts +84 -8
- package/.pi/lib/knowledge/review-status.ts +55 -0
- package/.pi/lib/knowledge/review-worker.ts +106 -6
- package/.pi/lib/knowledge/sanitize.ts +20 -0
- package/.pi/lib/knowledge/session-scanner.ts +43 -9
- package/.pi/lib/knowledge/store.ts +77 -10
- package/.pi/lib/knowledge/types.ts +4 -0
- package/.pi/lib/runtime/defaults.ts +9 -5
- package/.pi/lib/runtime/paths.ts +2 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { basename, dirname, join, resolve, sep } from "node:path";
|
|
|
8
8
|
|
|
9
9
|
import { KNOWLEDGE_RUNTIME_DEFAULTS } from "../runtime/defaults.ts";
|
|
10
10
|
import { userRuntimePaths } from "../runtime/paths.ts";
|
|
11
|
-
import { compactKnowledgeText, sanitizeKnowledgeText } from "./sanitize.ts";
|
|
11
|
+
import { compactKnowledgeSummary, compactKnowledgeText, sanitizeKnowledgeText } from "./sanitize.ts";
|
|
12
12
|
import type {
|
|
13
13
|
KnowledgeCandidate, KnowledgeCatalog, KnowledgeCatalogEntry, KnowledgeManifest, KnowledgeReviewCursor,
|
|
14
14
|
KnowledgeReviewTask, KnowledgeScope, KnowledgeSnapshot, KnowledgeTrack, PersistKnowledgeResult,
|
|
@@ -117,6 +117,56 @@ function normalizedFingerprint(candidate: KnowledgeCandidate): string {
|
|
|
117
117
|
return createHash("sha256").update(`${candidate.scope}\0${candidate.key.trim().toLowerCase()}`).digest("hex");
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function comparisonTerms(value: string): Set<string> {
|
|
121
|
+
return new Set((value.normalize("NFKC").toLowerCase().match(/\p{Script=Han}+|[\p{L}\p{N}]+/gu) ?? [])
|
|
122
|
+
.filter((term) => term.length >= 2));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function containment(left: Set<string>, right: Set<string>): number {
|
|
126
|
+
const minimum = Math.min(left.size, right.size);
|
|
127
|
+
if (minimum === 0) return 0;
|
|
128
|
+
let shared = 0;
|
|
129
|
+
for (const term of left) if (right.has(term)) shared++;
|
|
130
|
+
return shared / minimum;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function semanticDuplicateScore(candidate: KnowledgeCandidate, entry: KnowledgeCatalogEntry): number {
|
|
134
|
+
if (candidate.scope !== entry.scope || candidate.storageHint !== entry.track) return 0;
|
|
135
|
+
const titleScore = containment(comparisonTerms(candidate.title), comparisonTerms(entry.title));
|
|
136
|
+
const keywordScore = containment(
|
|
137
|
+
comparisonTerms(candidate.keywords.join(" ")),
|
|
138
|
+
comparisonTerms(entry.keywords.join(" ")),
|
|
139
|
+
);
|
|
140
|
+
if (titleScore < 0.8 || keywordScore < 0.5) return 0;
|
|
141
|
+
return titleScore * 0.7 + keywordScore * 0.3;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function semanticDuplicateIndex(candidate: KnowledgeCandidate, catalog: KnowledgeCatalog): number {
|
|
145
|
+
let bestIndex = -1;
|
|
146
|
+
let bestScore = 0;
|
|
147
|
+
for (const [index, entry] of catalog.items.entries()) {
|
|
148
|
+
const score = semanticDuplicateScore(candidate, entry);
|
|
149
|
+
if (score > bestScore) { bestIndex = index; bestScore = score; }
|
|
150
|
+
}
|
|
151
|
+
return bestIndex;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function unsupportedOperations(value: string): string[] {
|
|
155
|
+
return [...value.matchAll(/\bOperation\s+([A-Za-z][A-Za-z0-9]+)\s+is not supported\b/gu)]
|
|
156
|
+
.map((match) => match[1]!.toLowerCase());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function contradictsVerifiedFailures(body: string, validationText: string): boolean {
|
|
160
|
+
const normalizedBody = body.toLowerCase();
|
|
161
|
+
for (const operation of unsupportedOperations(validationText)) {
|
|
162
|
+
const index = normalizedBody.search(new RegExp(`\\b${operation}\\b`, "u"));
|
|
163
|
+
if (index < 0) continue;
|
|
164
|
+
const context = normalizedBody.slice(Math.max(0, index - 80), index + operation.length + 80);
|
|
165
|
+
if (!/(?:not supported|unsupported|do not|don't|avoid|不支持|不要|避免)/u.test(context)) return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
120
170
|
function contentHash(candidate: KnowledgeCandidate): string {
|
|
121
171
|
return createHash("sha256").update(`${candidate.title}\0${candidate.summary}\0${candidate.body}`).digest("hex");
|
|
122
172
|
}
|
|
@@ -127,28 +177,33 @@ function asStringArray(value: unknown, maxItems: number): string[] {
|
|
|
127
177
|
.map((item) => compactKnowledgeText(item, 240)).filter(Boolean))].slice(0, maxItems);
|
|
128
178
|
}
|
|
129
179
|
|
|
130
|
-
function normalizeCandidate(value: unknown, projectKey: string): KnowledgeCandidate | undefined {
|
|
180
|
+
function normalizeCandidate(value: unknown, projectKey: string, validationText: string): KnowledgeCandidate | undefined {
|
|
131
181
|
if (!value || typeof value !== "object") return undefined;
|
|
132
182
|
const raw = value as Record<string, unknown>;
|
|
133
183
|
if (typeof raw.key !== "string" || typeof raw.title !== "string" || typeof raw.summary !== "string"
|
|
134
184
|
|| typeof raw.body !== "string" || typeof raw.confidence !== "number") return undefined;
|
|
185
|
+
if (raw.durability !== "stable") return undefined;
|
|
135
186
|
if (raw.confidence < KNOWLEDGE_RUNTIME_DEFAULTS.review.minimumConfidence) return undefined;
|
|
136
187
|
const explicitUserDirective = raw.explicitUserDirective === true;
|
|
137
188
|
const scope: KnowledgeScope = raw.scope === "global" && explicitUserDirective ? "global" : `project:${projectKey}`;
|
|
138
189
|
const body = compactKnowledgeText(raw.body, 20_000);
|
|
139
190
|
const evidence = asStringArray(raw.evidence, 8);
|
|
140
191
|
if (!body || evidence.length === 0) return undefined;
|
|
192
|
+
if (contradictsVerifiedFailures(body, validationText)) return undefined;
|
|
141
193
|
const requestedTrack: KnowledgeTrack = raw.storageHint === "rule" ? "rule" : "topic";
|
|
142
194
|
const ruleEligible = body.length <= STORAGE.maxRuleChars && body.split("\n").length <= STORAGE.maxRuleFileLines
|
|
195
|
+
&& body.split(/\n\s*\n/gu).length === 1
|
|
143
196
|
&& (explicitUserDirective || raw.confidence >= 0.9);
|
|
197
|
+
const targetId = typeof raw.targetId === "string" && SAFE_ID_RE.test(raw.targetId) ? raw.targetId : undefined;
|
|
144
198
|
return {
|
|
145
|
-
key: compactKnowledgeText(raw.key, 160),
|
|
146
|
-
|
|
199
|
+
key: compactKnowledgeText(raw.key, 160), targetId,
|
|
200
|
+
title: compactKnowledgeText(raw.title, STORAGE.maxTitleChars),
|
|
201
|
+
summary: compactKnowledgeSummary(raw.summary, STORAGE.maxSummaryChars),
|
|
147
202
|
keywords: asStringArray(raw.keywords, STORAGE.maxKeywordCount).map((keyword) => keyword.toLowerCase()),
|
|
148
203
|
scope, body, evidence, confidence: Math.min(1, raw.confidence),
|
|
149
204
|
storageHint: requestedTrack === "rule" && ruleEligible ? "rule" : "topic",
|
|
150
205
|
action: raw.action === "revise" ? "revise" : raw.action === "reinforce" ? "reinforce" : "add",
|
|
151
|
-
explicitUserDirective,
|
|
206
|
+
explicitUserDirective, durability: "stable",
|
|
152
207
|
};
|
|
153
208
|
}
|
|
154
209
|
|
|
@@ -274,13 +329,24 @@ export function commitKnowledgeReview(
|
|
|
274
329
|
const catalog: KnowledgeCatalog = structuredClone(current.catalog);
|
|
275
330
|
const result: PersistKnowledgeResult = { saved: 0, updated: 0, pending: 0, skipped: 0 };
|
|
276
331
|
for (const value of values.slice(0, KNOWLEDGE_RUNTIME_DEFAULTS.review.maxCandidates)) {
|
|
277
|
-
const candidate = normalizeCandidate(value, task.projectKey);
|
|
332
|
+
const candidate = normalizeCandidate(value, task.projectKey, `${task.context ?? ""}\n${task.delta}`);
|
|
278
333
|
if (!candidate) { result.skipped++; continue; }
|
|
279
334
|
const fingerprint = normalizedFingerprint(candidate);
|
|
280
335
|
const hash = contentHash(candidate);
|
|
281
|
-
const
|
|
336
|
+
const targetIndex = candidate.targetId
|
|
337
|
+
? catalog.items.findIndex((item) => item.id === candidate.targetId && applicable(item, task.projectKey))
|
|
338
|
+
: -1;
|
|
339
|
+
if (candidate.targetId && targetIndex < 0) { result.skipped++; continue; }
|
|
340
|
+
let existingIndex = targetIndex >= 0
|
|
341
|
+
? targetIndex
|
|
342
|
+
: catalog.items.findIndex((item) => item.fingerprint === fingerprint);
|
|
343
|
+
let semanticReinforcement = false;
|
|
344
|
+
if (existingIndex < 0) {
|
|
345
|
+
existingIndex = semanticDuplicateIndex(candidate, catalog);
|
|
346
|
+
semanticReinforcement = existingIndex >= 0;
|
|
347
|
+
}
|
|
282
348
|
const existing = existingIndex >= 0 ? catalog.items[existingIndex] : undefined;
|
|
283
|
-
if (existing
|
|
349
|
+
if (existing && (existing.contentHash === hash || candidate.action === "reinforce" || semanticReinforcement)) {
|
|
284
350
|
existing.evidenceCount += candidate.evidence.length;
|
|
285
351
|
existing.updatedAt = new Date().toISOString();
|
|
286
352
|
result.updated++;
|
|
@@ -303,8 +369,9 @@ export function commitKnowledgeReview(
|
|
|
303
369
|
const relativeFile = `${track === "rule" ? "rules" : "topics"}/${id}.md`;
|
|
304
370
|
atomicWrite(join(temporary, relativeFile), renderKnowledgeFile(candidate));
|
|
305
371
|
const entry: KnowledgeCatalogEntry = {
|
|
306
|
-
id, fingerprint
|
|
307
|
-
|
|
372
|
+
id, fingerprint: existing?.fingerprint ?? fingerprint, contentHash: hash,
|
|
373
|
+
title: candidate.title, summary: candidate.summary,
|
|
374
|
+
keywords: candidate.keywords, scope: existing?.scope ?? candidate.scope, track, file: relativeFile,
|
|
308
375
|
evidenceCount: (existing?.evidenceCount ?? 0) + candidate.evidence.length,
|
|
309
376
|
createdAt: existing?.createdAt ?? now, updatedAt: now,
|
|
310
377
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export type KnowledgeTrack = "rule" | "topic";
|
|
2
2
|
export type KnowledgeScope = "global" | `project:${string}`;
|
|
3
3
|
export type KnowledgeAction = "add" | "reinforce" | "revise";
|
|
4
|
+
export type KnowledgeDurability = "stable";
|
|
4
5
|
|
|
5
6
|
export interface KnowledgeCandidate {
|
|
6
7
|
key: string;
|
|
8
|
+
targetId?: string;
|
|
7
9
|
title: string;
|
|
8
10
|
summary: string;
|
|
9
11
|
keywords: string[];
|
|
@@ -14,6 +16,7 @@ export interface KnowledgeCandidate {
|
|
|
14
16
|
storageHint: KnowledgeTrack;
|
|
15
17
|
action: KnowledgeAction;
|
|
16
18
|
explicitUserDirective: boolean;
|
|
19
|
+
durability: KnowledgeDurability;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface KnowledgeCatalogEntry {
|
|
@@ -84,6 +87,7 @@ export interface KnowledgeReviewTask {
|
|
|
84
87
|
projectKey: string;
|
|
85
88
|
firstEntryId: string;
|
|
86
89
|
lastEntryId: string;
|
|
90
|
+
context?: string;
|
|
87
91
|
delta: string;
|
|
88
92
|
deltaDigest: string;
|
|
89
93
|
fileSize: number;
|
|
@@ -31,14 +31,18 @@ export const KNOWLEDGE_RUNTIME_DEFAULTS = Object.freeze({
|
|
|
31
31
|
intervalMs: 60_000,
|
|
32
32
|
idleMs: 60_000,
|
|
33
33
|
capabilityPollMs: 5_000,
|
|
34
|
-
modelTimeoutMs:
|
|
35
|
-
maxDeltaChars:
|
|
36
|
-
|
|
34
|
+
modelTimeoutMs: 180_000,
|
|
35
|
+
maxDeltaChars: 12_000,
|
|
36
|
+
maxPriorContextChars: 4_000,
|
|
37
|
+
maxExistingContextItems: 5,
|
|
38
|
+
maxExistingBodyItems: 2,
|
|
39
|
+
maxExistingBodyChars: 600,
|
|
40
|
+
maxCandidates: 3,
|
|
37
41
|
minimumConfidence: 0.72,
|
|
38
42
|
}),
|
|
39
43
|
storage: Object.freeze({
|
|
40
|
-
maxRuleChars:
|
|
41
|
-
maxRuleFileLines:
|
|
44
|
+
maxRuleChars: 320,
|
|
45
|
+
maxRuleFileLines: 6,
|
|
42
46
|
maxRulesPromptChars: 12_000,
|
|
43
47
|
maxMemoryChars: 25_000,
|
|
44
48
|
maxMemoryLines: 200,
|
package/.pi/lib/runtime/paths.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface UserRuntimePaths {
|
|
|
34
34
|
knowledgeRuntime: string;
|
|
35
35
|
knowledgeCoordinatorSocket: string;
|
|
36
36
|
knowledgeLeader: string;
|
|
37
|
+
knowledgeStatus: string;
|
|
37
38
|
knowledgeCommitLock: string;
|
|
38
39
|
knowledgeElectionLock: string;
|
|
39
40
|
}
|
|
@@ -68,6 +69,7 @@ export function userRuntimePaths(home = homedir()): UserRuntimePaths {
|
|
|
68
69
|
// Unix-domain socket paths are short on purpose (macOS caps them at roughly 104 bytes).
|
|
69
70
|
knowledgeCoordinatorSocket: join(root, "knowledge-v3.sock"),
|
|
70
71
|
knowledgeLeader: join(knowledge, "runtime", "leader.json"),
|
|
72
|
+
knowledgeStatus: join(knowledge, "runtime", "status.json"),
|
|
71
73
|
knowledgeCommitLock: join(knowledge, "runtime", "commit.lock"),
|
|
72
74
|
knowledgeElectionLock: join(knowledge, "runtime", "election.lock"),
|
|
73
75
|
};
|