@dommaker/harness 0.13.2 → 0.14.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/CHANGELOG.md +24 -0
- package/bin/harness-knowledge-check.js +38 -23
- package/bin/harness-knowledge-track.js +30 -4
- package/bin/harness.js +24 -1
- package/dist/cli/commands/diagnose.d.ts +16 -0
- package/dist/cli/commands/diagnose.d.ts.map +1 -0
- package/dist/cli/commands/diagnose.js +195 -0
- package/dist/cli/commands/diagnose.js.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +4 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +21 -0
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +155 -4
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/propose.d.ts +18 -0
- package/dist/cli/commands/propose.d.ts.map +1 -0
- package/dist/cli/commands/propose.js +331 -0
- package/dist/cli/commands/propose.js.map +1 -0
- package/dist/cli/commands/traces.d.ts +15 -0
- package/dist/cli/commands/traces.d.ts.map +1 -0
- package/dist/cli/commands/traces.js +167 -0
- package/dist/cli/commands/traces.js.map +1 -0
- package/dist/constraints/definitions.d.ts +39 -0
- package/dist/constraints/definitions.d.ts.map +1 -0
- package/dist/constraints/definitions.js +71 -0
- package/dist/constraints/definitions.js.map +1 -0
- package/dist/constraints/quality.d.ts +11 -0
- package/dist/constraints/quality.d.ts.map +1 -0
- package/dist/constraints/quality.js +257 -0
- package/dist/constraints/quality.js.map +1 -0
- package/dist/constraints/safety.d.ts +13 -0
- package/dist/constraints/safety.d.ts.map +1 -0
- package/dist/constraints/safety.js +103 -0
- package/dist/constraints/safety.js.map +1 -0
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +4 -2
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/extensions/long-running/constraints.d.ts +38 -0
- package/dist/extensions/long-running/constraints.d.ts.map +1 -0
- package/dist/extensions/long-running/constraints.js +153 -0
- package/dist/extensions/long-running/constraints.js.map +1 -0
- package/dist/extensions/long-running/index.d.ts +30 -0
- package/dist/extensions/long-running/index.d.ts.map +1 -0
- package/dist/extensions/long-running/index.js +50 -0
- package/dist/extensions/long-running/index.js.map +1 -0
- package/dist/extensions/long-running/types.d.ts +154 -0
- package/dist/extensions/long-running/types.d.ts.map +1 -0
- package/dist/extensions/long-running/types.js +9 -0
- package/dist/extensions/long-running/types.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/knowledge/audit.d.ts +76 -0
- package/dist/knowledge/audit.d.ts.map +1 -0
- package/dist/knowledge/audit.js +566 -0
- package/dist/knowledge/audit.js.map +1 -0
- package/dist/knowledge/import.d.ts.map +1 -1
- package/dist/knowledge/import.js +3 -0
- package/dist/knowledge/import.js.map +1 -1
- package/dist/knowledge/index.d.ts +5 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +6 -1
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/ingest.d.ts +27 -0
- package/dist/knowledge/ingest.d.ts.map +1 -1
- package/dist/knowledge/ingest.js +150 -5
- package/dist/knowledge/ingest.js.map +1 -1
- package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
- package/dist/knowledge/lifecycle-hooks.js +3 -0
- package/dist/knowledge/lifecycle-hooks.js.map +1 -1
- package/dist/knowledge/lifecycle.d.ts +59 -7
- package/dist/knowledge/lifecycle.d.ts.map +1 -1
- package/dist/knowledge/lifecycle.js +213 -52
- package/dist/knowledge/lifecycle.js.map +1 -1
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +43 -2
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/migration.d.ts +25 -0
- package/dist/knowledge/migration.d.ts.map +1 -0
- package/dist/knowledge/migration.js +96 -0
- package/dist/knowledge/migration.js.map +1 -0
- package/dist/knowledge/query.d.ts +29 -1
- package/dist/knowledge/query.d.ts.map +1 -1
- package/dist/knowledge/query.js +58 -0
- package/dist/knowledge/query.js.map +1 -1
- package/dist/knowledge/store.d.ts +21 -1
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +103 -6
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +29 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +1 -0
- package/dist/knowledge/types.js.map +1 -1
- package/dist/presets/long-running.d.ts +33 -0
- package/dist/presets/long-running.d.ts.map +1 -0
- package/dist/presets/long-running.js +52 -0
- package/dist/presets/long-running.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/commands/__tests__/knowledge.test.ts +167 -0
- package/src/cli/commands/index.ts +1 -1
- package/src/cli/commands/knowledge.ts +175 -4
- package/src/context/__tests__/knowledge-injector.test.ts +3 -0
- package/src/core/constraints/definitions.ts +4 -2
- package/src/dashboard/__tests__/data.test.ts +3 -0
- package/src/dashboard/__tests__/stats.test.ts +3 -0
- package/src/index.ts +3 -1
- package/src/knowledge/CONTEXT.md +29 -7
- package/src/knowledge/__tests__/audit.test.ts +598 -0
- package/src/knowledge/__tests__/doctor.test.ts +27 -0
- package/src/knowledge/__tests__/ingest.test.ts +319 -0
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +3 -0
- package/src/knowledge/__tests__/lifecycle.test.ts +655 -2
- package/src/knowledge/__tests__/lint.test.ts +51 -0
- package/src/knowledge/__tests__/migration.test.ts +115 -0
- package/src/knowledge/__tests__/query.test.ts +86 -0
- package/src/knowledge/__tests__/reference-tracker.test.ts +3 -0
- package/src/knowledge/__tests__/store.test.ts +207 -0
- package/src/knowledge/audit.ts +640 -0
- package/src/knowledge/import.ts +3 -0
- package/src/knowledge/index.ts +5 -1
- package/src/knowledge/ingest.ts +169 -5
- package/src/knowledge/lifecycle-hooks.ts +3 -0
- package/src/knowledge/lifecycle.ts +218 -49
- package/src/knowledge/lint.ts +43 -2
- package/src/knowledge/migration.ts +73 -0
- package/src/knowledge/query.ts +71 -0
- package/src/knowledge/store.ts +100 -6
- package/src/knowledge/types.ts +33 -1
- package/src/monitoring/__tests__/knowledge-doctor.test.ts +3 -0
- package/src/monitoring/__tests__/knowledge-evolver.test.ts +3 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Migration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Migrates existing knowledge entries to add new fields
|
|
5
|
+
* introduced in AS-021 (consumptionMode, origin, fullContentPath, skillId).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as yaml from 'js-yaml';
|
|
11
|
+
|
|
12
|
+
interface MigrationResult {
|
|
13
|
+
total: number;
|
|
14
|
+
migrated: number;
|
|
15
|
+
skipped: number;
|
|
16
|
+
errors: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Migrate all knowledge .md files in a directory to include
|
|
21
|
+
* consumptionMode and origin fields if missing.
|
|
22
|
+
*
|
|
23
|
+
* Existing files without these fields get defaults:
|
|
24
|
+
* - consumptionMode: 'reference'
|
|
25
|
+
* - origin: 'agent'
|
|
26
|
+
*
|
|
27
|
+
* This is idempotent — running it multiple times is safe.
|
|
28
|
+
*/
|
|
29
|
+
export function migrateKnowledgeEntries(baseDir: string): MigrationResult {
|
|
30
|
+
const result: MigrationResult = { total: 0, migrated: 0, skipped: 0, errors: [] };
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(baseDir)) {
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const files = fs.readdirSync(baseDir).filter(f => f.endsWith('.md'));
|
|
37
|
+
result.total = files.length;
|
|
38
|
+
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const filePath = path.join(baseDir, file);
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
43
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
|
|
44
|
+
if (!match) {
|
|
45
|
+
result.errors.push(`${file}: no frontmatter found`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const meta = yaml.load(match[1]) as Record<string, unknown>;
|
|
50
|
+
const content = match[2];
|
|
51
|
+
|
|
52
|
+
// Check if migration needed
|
|
53
|
+
if (meta.consumptionMode && meta.origin) {
|
|
54
|
+
result.skipped++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add missing fields with defaults
|
|
59
|
+
if (!meta.consumptionMode) meta.consumptionMode = 'reference';
|
|
60
|
+
if (!meta.origin) meta.origin = 'agent';
|
|
61
|
+
|
|
62
|
+
// Write back
|
|
63
|
+
const newFrontmatter = yaml.dump(meta, { lineWidth: 120 });
|
|
64
|
+
const newContent = `---\n${newFrontmatter}---\n\n${content}`;
|
|
65
|
+
fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
66
|
+
result.migrated++;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
result.errors.push(`${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
package/src/knowledge/query.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
KnowledgeEntry,
|
|
10
10
|
KnowledgeType,
|
|
11
11
|
MaturityLevel,
|
|
12
|
+
ConsumptionMode,
|
|
12
13
|
StorageLayer,
|
|
13
14
|
QueryBudget,
|
|
14
15
|
QueryResult,
|
|
@@ -24,8 +25,10 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
24
25
|
const MATURITY_RANK: Record<MaturityLevel, number> = {
|
|
25
26
|
proven: 3,
|
|
26
27
|
verified: 2,
|
|
28
|
+
active: 2,
|
|
27
29
|
draft: 1,
|
|
28
30
|
archived: 0,
|
|
31
|
+
deprecated: 0,
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
// ── Cache ──────────────────────────────────────────────────
|
|
@@ -108,6 +111,57 @@ export class KnowledgeQuery {
|
|
|
108
111
|
this.cache.clear();
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Query entries by consumptionMode.
|
|
116
|
+
* Convenience wrapper around store.list() with mode filter.
|
|
117
|
+
*/
|
|
118
|
+
queryByMode(mode: ConsumptionMode, filter?: QueryFilter): KnowledgeEntry[] {
|
|
119
|
+
return this.store.list({
|
|
120
|
+
...filter,
|
|
121
|
+
consumptionModes: [mode],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Consume knowledge for a task context.
|
|
127
|
+
* Returns entries grouped by consumptionMode for prompt assembly.
|
|
128
|
+
*
|
|
129
|
+
* - rules: rule-mode entries (for prompt injection)
|
|
130
|
+
* - references: reference-mode entries matching task text keywords
|
|
131
|
+
* - context: context-mode entries matching applicablePhases
|
|
132
|
+
*/
|
|
133
|
+
consume(taskContext: {
|
|
134
|
+
requirementText?: string;
|
|
135
|
+
phase?: string;
|
|
136
|
+
}): { rules: KnowledgeEntry[]; references: KnowledgeEntry[]; context: KnowledgeEntry[] } {
|
|
137
|
+
const rules = this.queryByMode('rule');
|
|
138
|
+
|
|
139
|
+
const allRefs = this.queryByMode('reference');
|
|
140
|
+
const references = taskContext.requirementText
|
|
141
|
+
? this.filterByText(allRefs, taskContext.requirementText).slice(0, 3)
|
|
142
|
+
: allRefs.slice(0, 3);
|
|
143
|
+
|
|
144
|
+
const allContext = this.queryByMode('context');
|
|
145
|
+
const context = taskContext.phase
|
|
146
|
+
? allContext.filter(e => e.applicablePhases.includes(taskContext.phase!))
|
|
147
|
+
: allContext;
|
|
148
|
+
|
|
149
|
+
return { rules, references, context };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Format entry for prompt injection.
|
|
154
|
+
* Adds [External Source] warning prefix for external-origin entries.
|
|
155
|
+
*/
|
|
156
|
+
static formatForPrompt(entry: KnowledgeEntry): string {
|
|
157
|
+
const prefix = entry.origin === 'external'
|
|
158
|
+
? '[External Source — verify before acting]\n'
|
|
159
|
+
: '';
|
|
160
|
+
return `${prefix}${entry.title}: ${entry.content}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Internal ───────────────────────────────────────────────
|
|
164
|
+
|
|
111
165
|
// ── Internal ───────────────────────────────────────────────
|
|
112
166
|
|
|
113
167
|
private sortEntries(entries: KnowledgeEntry[]): KnowledgeEntry[] {
|
|
@@ -147,4 +201,21 @@ export class KnowledgeQuery {
|
|
|
147
201
|
private cacheKey(budget: QueryBudget, filter?: QueryFilter): string {
|
|
148
202
|
return JSON.stringify({ budget, filter });
|
|
149
203
|
}
|
|
204
|
+
|
|
205
|
+
/** Filter entries by keyword overlap with text. Returns sorted by relevance. */
|
|
206
|
+
private filterByText(entries: KnowledgeEntry[], text: string): KnowledgeEntry[] {
|
|
207
|
+
const keywords = text.toLowerCase().split(/\s+/).filter(w => w.length >= 3);
|
|
208
|
+
if (keywords.length === 0) return entries;
|
|
209
|
+
|
|
210
|
+
const scored = entries.map(entry => {
|
|
211
|
+
const haystack = `${entry.title} ${entry.content}`.toLowerCase();
|
|
212
|
+
const score = keywords.filter(k => haystack.includes(k)).length;
|
|
213
|
+
return { entry, score };
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return scored
|
|
217
|
+
.filter(s => s.score > 0)
|
|
218
|
+
.sort((a, b) => b.score - a.score)
|
|
219
|
+
.map(s => s.entry);
|
|
220
|
+
}
|
|
150
221
|
}
|
package/src/knowledge/store.ts
CHANGED
|
@@ -30,6 +30,10 @@ export class KnowledgeStore {
|
|
|
30
30
|
this.ensureDirectory();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
getBaseDir(): string {
|
|
34
|
+
return this.baseDir;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
// ── CRUD ─────────────────────────────────────────────────
|
|
34
38
|
|
|
35
39
|
get(id: string): KnowledgeEntry | undefined {
|
|
@@ -118,8 +122,21 @@ export class KnowledgeStore {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
private findFile(id: string): string | undefined {
|
|
125
|
+
// 1. Fast path: index has id→type mapping, construct exact filename
|
|
126
|
+
const index = this.readIndex();
|
|
127
|
+
const idx = index.find(e => e.id === id);
|
|
128
|
+
if (idx) {
|
|
129
|
+
const exact = path.join(this.baseDir, `${idx.type}-${idx.id}.md`);
|
|
130
|
+
if (fs.existsSync(exact)) return exact;
|
|
131
|
+
}
|
|
132
|
+
// 2. Fallback: scan files, verify id from frontmatter
|
|
121
133
|
const files = this.listFiles();
|
|
122
|
-
|
|
134
|
+
for (const f of files) {
|
|
135
|
+
if (!path.basename(f).includes(`-${id}.md`)) continue;
|
|
136
|
+
const entry = this.readFile(f);
|
|
137
|
+
if (entry?.id === id) return f;
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
private listFiles(): string[] {
|
|
@@ -160,7 +177,12 @@ export class KnowledgeStore {
|
|
|
160
177
|
applicablePhases: (meta.applicablePhases as string[]) || [],
|
|
161
178
|
sourceReferences: (meta.sourceReferences as KnowledgeEntry['sourceReferences']) || [],
|
|
162
179
|
referencedBy: (meta.referencedBy as string[]) || [],
|
|
180
|
+
executionResults: (meta.executionResults as import('./types').ExecutionResult[]) || [],
|
|
163
181
|
decayAt: meta.decayAt as string | undefined,
|
|
182
|
+
consumptionMode: (meta.consumptionMode as KnowledgeEntry['consumptionMode']) || 'reference',
|
|
183
|
+
origin: (meta.origin as KnowledgeEntry['origin']) || 'agent',
|
|
184
|
+
fullContentPath: meta.fullContentPath as string | undefined,
|
|
185
|
+
skillId: meta.skillId as string | undefined,
|
|
164
186
|
};
|
|
165
187
|
}
|
|
166
188
|
|
|
@@ -179,8 +201,13 @@ export class KnowledgeStore {
|
|
|
179
201
|
applicablePhases: entry.applicablePhases,
|
|
180
202
|
sourceReferences: entry.sourceReferences,
|
|
181
203
|
referencedBy: entry.referencedBy,
|
|
204
|
+
consumptionMode: entry.consumptionMode,
|
|
205
|
+
origin: entry.origin,
|
|
182
206
|
};
|
|
207
|
+
if (entry.executionResults?.length > 0) meta.executionResults = entry.executionResults;
|
|
183
208
|
if (entry.decayAt) meta.decayAt = entry.decayAt;
|
|
209
|
+
if (entry.fullContentPath) meta.fullContentPath = entry.fullContentPath;
|
|
210
|
+
if (entry.skillId) meta.skillId = entry.skillId;
|
|
184
211
|
return yaml.dump(meta, { lineWidth: 120 });
|
|
185
212
|
}
|
|
186
213
|
|
|
@@ -195,6 +222,8 @@ export class KnowledgeStore {
|
|
|
195
222
|
applicablePhases: entry.applicablePhases,
|
|
196
223
|
lastReferenced: entry.lastReferenced,
|
|
197
224
|
created: entry.created,
|
|
225
|
+
consumptionMode: entry.consumptionMode,
|
|
226
|
+
origin: entry.origin,
|
|
198
227
|
};
|
|
199
228
|
}
|
|
200
229
|
|
|
@@ -210,10 +239,13 @@ export class KnowledgeStore {
|
|
|
210
239
|
lastReferenced: idx.lastReferenced,
|
|
211
240
|
contributors: [],
|
|
212
241
|
projects: [],
|
|
213
|
-
tags: idx.tags,
|
|
214
|
-
applicablePhases: idx.applicablePhases,
|
|
242
|
+
tags: idx.tags ?? [],
|
|
243
|
+
applicablePhases: idx.applicablePhases ?? [],
|
|
215
244
|
sourceReferences: [],
|
|
216
245
|
referencedBy: [],
|
|
246
|
+
executionResults: [],
|
|
247
|
+
consumptionMode: idx.consumptionMode || 'reference',
|
|
248
|
+
origin: idx.origin || 'agent',
|
|
217
249
|
};
|
|
218
250
|
}
|
|
219
251
|
|
|
@@ -221,12 +253,74 @@ export class KnowledgeStore {
|
|
|
221
253
|
if (filter.types && filter.types.length > 0 && !filter.types.includes(entry.type)) return false;
|
|
222
254
|
if (filter.maturity && filter.maturity.length > 0 && !filter.maturity.includes(entry.maturity)) return false;
|
|
223
255
|
if (filter.layers && filter.layers.length > 0 && !filter.layers.includes(entry.layer)) return false;
|
|
224
|
-
if (filter.tags && filter.tags.length > 0 && !filter.tags.some(t => entry.tags.includes(t))) return false;
|
|
225
|
-
if (filter.applicablePhases && filter.applicablePhases.length > 0 && !filter.applicablePhases.some(p => entry.applicablePhases.includes(p))) return false;
|
|
226
|
-
if (filter.excludeArchived !== false && entry.maturity === 'archived') return false;
|
|
256
|
+
if (filter.tags && filter.tags.length > 0 && !filter.tags.some(t => (entry.tags ?? []).includes(t))) return false;
|
|
257
|
+
if (filter.applicablePhases && filter.applicablePhases.length > 0 && !filter.applicablePhases.some(p => (entry.applicablePhases ?? []).includes(p))) return false;
|
|
258
|
+
if (filter.excludeArchived !== false && (entry.maturity === 'archived' || entry.maturity === 'deprecated')) return false;
|
|
259
|
+
if (filter.consumptionModes && filter.consumptionModes.length > 0 && !filter.consumptionModes.includes(entry.consumptionMode ?? 'reference')) return false;
|
|
260
|
+
if (filter.origins && filter.origins.length > 0 && !filter.origins.includes(entry.origin ?? 'agent')) return false;
|
|
227
261
|
return true;
|
|
228
262
|
}
|
|
229
263
|
|
|
264
|
+
// ── Snapshots ────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
private static readonly SNAPSHOTS_DIR = '.snapshots';
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 保存当日 index.json 快照
|
|
270
|
+
*/
|
|
271
|
+
snapshot(): string {
|
|
272
|
+
const snapDir = path.join(this.baseDir, KnowledgeStore.SNAPSHOTS_DIR);
|
|
273
|
+
if (!fs.existsSync(snapDir)) {
|
|
274
|
+
fs.mkdirSync(snapDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
277
|
+
const snapPath = path.join(snapDir, `index-${today}.json`);
|
|
278
|
+
const indexPath = path.join(this.baseDir, INDEX_FILE);
|
|
279
|
+
if (fs.existsSync(indexPath)) {
|
|
280
|
+
fs.copyFileSync(indexPath, snapPath);
|
|
281
|
+
}
|
|
282
|
+
return snapPath;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 读取指定日期的快照
|
|
287
|
+
*/
|
|
288
|
+
getSnapshot(date: string): IndexEntry[] | undefined {
|
|
289
|
+
const snapPath = path.join(this.baseDir, KnowledgeStore.SNAPSHOTS_DIR, `index-${date}.json`);
|
|
290
|
+
if (!fs.existsSync(snapPath)) return undefined;
|
|
291
|
+
try {
|
|
292
|
+
return JSON.parse(fs.readFileSync(snapPath, 'utf-8')) as IndexEntry[];
|
|
293
|
+
} catch {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 计算 N 天前快照中条目的存活率
|
|
300
|
+
* 存活 = 当前 index 中存在且 maturity !== 'archived'
|
|
301
|
+
*/
|
|
302
|
+
getSurvivalRate(daysAgo: number): { rate: number; survived: number; total: number; snapshotDate: string } | undefined {
|
|
303
|
+
const target = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
304
|
+
const snapshot = this.getSnapshot(target);
|
|
305
|
+
if (!snapshot || snapshot.length === 0) return undefined;
|
|
306
|
+
|
|
307
|
+
const currentIndex = this.readIndex();
|
|
308
|
+
const currentMap = new Map(currentIndex.map(e => [e.id, e]));
|
|
309
|
+
|
|
310
|
+
let survived = 0;
|
|
311
|
+
for (const snapEntry of snapshot) {
|
|
312
|
+
const current = currentMap.get(snapEntry.id);
|
|
313
|
+
if (current && current.maturity !== 'archived' && current.maturity !== 'deprecated') survived++;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
rate: Math.round((survived / snapshot.length) * 100),
|
|
318
|
+
survived,
|
|
319
|
+
total: snapshot.length,
|
|
320
|
+
snapshotDate: target,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
230
324
|
// ── Index I/O ────────────────────────────────────────────
|
|
231
325
|
|
|
232
326
|
private readIndex(): IndexEntry[] {
|
package/src/knowledge/types.ts
CHANGED
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
|
|
12
12
|
export type KnowledgeType = 'model' | 'decision' | 'guideline' | 'pitfall' | 'process' | 'architecture';
|
|
13
13
|
|
|
14
|
-
export type MaturityLevel = 'draft' | 'verified' | 'proven' | 'archived';
|
|
14
|
+
export type MaturityLevel = 'draft' | 'verified' | 'proven' | 'archived' | 'active' | 'deprecated';
|
|
15
15
|
|
|
16
16
|
export type StorageLayer = 'personal' | 'team' | 'tech' | 'domain' | 'project' | 'system';
|
|
17
17
|
|
|
18
|
+
/** How agent consumes this knowledge — drives injection strategy and lifecycle */
|
|
19
|
+
export type ConsumptionMode = 'rule' | 'reference' | 'context' | 'signal';
|
|
20
|
+
|
|
21
|
+
/** Where this knowledge came from — affects initial trust and maturity */
|
|
22
|
+
export type KnowledgeOrigin = 'human' | 'agent' | 'external' | 'system';
|
|
23
|
+
|
|
18
24
|
// ── Core Entry ──────────────────────────────────────────────
|
|
19
25
|
|
|
20
26
|
export interface KnowledgeEntry {
|
|
@@ -32,7 +38,16 @@ export interface KnowledgeEntry {
|
|
|
32
38
|
applicablePhases: string[];
|
|
33
39
|
sourceReferences: SourceRef[];
|
|
34
40
|
referencedBy: string[];
|
|
41
|
+
executionResults: ExecutionResult[];
|
|
35
42
|
decayAt?: string;
|
|
43
|
+
/** How agent consumes this knowledge (default: 'reference') */
|
|
44
|
+
consumptionMode: ConsumptionMode;
|
|
45
|
+
/** Where this knowledge came from (default: 'agent') */
|
|
46
|
+
origin: KnowledgeOrigin;
|
|
47
|
+
/** Path/URL to full content for on-demand retrieval */
|
|
48
|
+
fullContentPath?: string;
|
|
49
|
+
/** Skill ID if this knowledge was refined into a Skill */
|
|
50
|
+
skillId?: string;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
export interface SourceRef {
|
|
@@ -42,6 +57,13 @@ export interface SourceRef {
|
|
|
42
57
|
timestamp: string;
|
|
43
58
|
}
|
|
44
59
|
|
|
60
|
+
export interface ExecutionResult {
|
|
61
|
+
contributor: string;
|
|
62
|
+
success: boolean;
|
|
63
|
+
timestamp: string;
|
|
64
|
+
source?: 'human' | 'auto';
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
// ── Query ───────────────────────────────────────────────────
|
|
46
68
|
|
|
47
69
|
export interface KnowledgeReference {
|
|
@@ -71,6 +93,8 @@ export interface QueryFilter {
|
|
|
71
93
|
tags?: string[];
|
|
72
94
|
applicablePhases?: string[];
|
|
73
95
|
excludeArchived?: boolean;
|
|
96
|
+
consumptionModes?: ConsumptionMode[];
|
|
97
|
+
origins?: KnowledgeOrigin[];
|
|
74
98
|
}
|
|
75
99
|
|
|
76
100
|
// ── Lint ────────────────────────────────────────────────────
|
|
@@ -93,6 +117,9 @@ export interface IngestOptions {
|
|
|
93
117
|
maturity?: MaturityLevel;
|
|
94
118
|
tags?: string[];
|
|
95
119
|
projects?: string[];
|
|
120
|
+
consumptionMode?: ConsumptionMode;
|
|
121
|
+
origin?: KnowledgeOrigin;
|
|
122
|
+
fullContentPath?: string;
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
@@ -108,12 +135,15 @@ export interface DecayConfig {
|
|
|
108
135
|
provenDecayMonths: number;
|
|
109
136
|
verifiedDecayMonths: number;
|
|
110
137
|
draftDecayMonths: number;
|
|
138
|
+
/** Sources whose entries skip draft→verified promotion (start at verified) */
|
|
139
|
+
autoPromoteSources: string[];
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
export const DEFAULT_DECAY_CONFIG: DecayConfig = {
|
|
114
143
|
provenDecayMonths: 12,
|
|
115
144
|
verifiedDecayMonths: 6,
|
|
116
145
|
draftDecayMonths: 3,
|
|
146
|
+
autoPromoteSources: [],
|
|
117
147
|
};
|
|
118
148
|
|
|
119
149
|
// ── Reference ───────────────────────────────────────────────
|
|
@@ -136,4 +166,6 @@ export interface IndexEntry {
|
|
|
136
166
|
applicablePhases: string[];
|
|
137
167
|
lastReferenced: string;
|
|
138
168
|
created: string;
|
|
169
|
+
consumptionMode: ConsumptionMode;
|
|
170
|
+
origin: KnowledgeOrigin;
|
|
139
171
|
}
|
|
@@ -17,6 +17,9 @@ function makeEntry(overrides: Partial<KnowledgeEntry> = {}): KnowledgeEntry {
|
|
|
17
17
|
applicablePhases: ['implementation'],
|
|
18
18
|
sourceReferences: [],
|
|
19
19
|
referencedBy: ['ref-1'],
|
|
20
|
+
executionResults: [],
|
|
21
|
+
consumptionMode: 'reference',
|
|
22
|
+
origin: 'agent',
|
|
20
23
|
...overrides,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
@@ -18,6 +18,9 @@ function makeEntry(overrides: Partial<KnowledgeEntry> = {}): KnowledgeEntry {
|
|
|
18
18
|
applicablePhases: ['implementation'],
|
|
19
19
|
sourceReferences: [],
|
|
20
20
|
referencedBy: ['ref-1'],
|
|
21
|
+
executionResults: [],
|
|
22
|
+
consumptionMode: 'reference',
|
|
23
|
+
origin: 'agent',
|
|
21
24
|
...overrides,
|
|
22
25
|
};
|
|
23
26
|
}
|