@dommaker/harness 0.13.3 → 0.16.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 +35 -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/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 +160 -9
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +43 -6
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/core/constraints/checker.js +1 -1
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +34 -26
- package/dist/core/constraints/definitions.js.map +1 -1
- 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/doctor.d.ts +1 -1
- package/dist/knowledge/doctor.d.ts.map +1 -1
- package/dist/knowledge/import.d.ts +1 -1
- 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 +7 -2
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +7 -2
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/ingest.d.ts +28 -1
- 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 +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 +60 -8
- 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 +1 -1
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +44 -3
- 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 +30 -2
- 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/reference-tracker.d.ts +1 -1
- package/dist/knowledge/reference-tracker.d.ts.map +1 -1
- package/dist/knowledge/store.d.ts +50 -3
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +111 -9
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +43 -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/monitoring/knowledge-evolver.d.ts +1 -1
- package/dist/monitoring/knowledge-evolver.d.ts.map +1 -1
- package/dist/types/constraint.d.ts +1 -1
- package/dist/types/constraint.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/checker.test.ts +4 -4
- package/src/cli/commands/__tests__/knowledge.test.ts +167 -0
- package/src/cli/commands/index.ts +1 -1
- package/src/cli/commands/knowledge.ts +176 -5
- package/src/cli/commands/release.ts +44 -6
- package/src/context/__tests__/knowledge-injector.test.ts +4 -1
- package/src/core/constraints/checker.ts +1 -1
- package/src/core/constraints/definitions.ts +35 -27
- 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 +28 -1
- package/src/knowledge/__tests__/import.test.ts +1 -1
- package/src/knowledge/__tests__/ingest.test.ts +320 -1
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +4 -1
- package/src/knowledge/__tests__/lifecycle.test.ts +656 -3
- package/src/knowledge/__tests__/lint.test.ts +52 -1
- package/src/knowledge/__tests__/migration.test.ts +115 -0
- package/src/knowledge/__tests__/query.test.ts +87 -1
- package/src/knowledge/__tests__/reference-tracker.test.ts +4 -1
- package/src/knowledge/__tests__/store.test.ts +208 -1
- package/src/knowledge/audit.ts +641 -0
- package/src/knowledge/doctor.ts +1 -1
- package/src/knowledge/import.ts +4 -1
- package/src/knowledge/index.ts +7 -2
- package/src/knowledge/ingest.ts +170 -6
- package/src/knowledge/lifecycle-hooks.ts +4 -1
- package/src/knowledge/lifecycle.ts +219 -50
- package/src/knowledge/lint.ts +45 -4
- package/src/knowledge/migration.ts +73 -0
- package/src/knowledge/query.ts +72 -1
- package/src/knowledge/reference-tracker.ts +1 -1
- package/src/knowledge/store.ts +125 -8
- package/src/knowledge/types.ts +50 -1
- package/src/monitoring/__tests__/knowledge-doctor.test.ts +3 -0
- package/src/monitoring/__tests__/knowledge-evolver.test.ts +3 -0
- package/src/monitoring/knowledge-evolver.ts +1 -1
- package/src/types/constraint.ts +1 -1
|
@@ -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,13 +9,14 @@ import type {
|
|
|
9
9
|
KnowledgeEntry,
|
|
10
10
|
KnowledgeType,
|
|
11
11
|
MaturityLevel,
|
|
12
|
+
ConsumptionMode,
|
|
12
13
|
StorageLayer,
|
|
13
14
|
QueryBudget,
|
|
14
15
|
QueryResult,
|
|
15
16
|
QueryFilter,
|
|
16
17
|
IndexEntry,
|
|
17
18
|
} from './types';
|
|
18
|
-
import { KnowledgeStore } from './store';
|
|
19
|
+
import type { KnowledgeStore } from './store';
|
|
19
20
|
import { KnowledgeLifecycle } from './lifecycle';
|
|
20
21
|
|
|
21
22
|
// ── Constants ──────────────────────────────────────────────
|
|
@@ -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
|
@@ -22,7 +22,30 @@ const DEFAULT_CONFIG: StoreConfig = {
|
|
|
22
22
|
baseDir: DEFAULT_DIR,
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* KnowledgeStore interface — abstract contract for knowledge storage.
|
|
27
|
+
*/
|
|
28
|
+
export interface KnowledgeStore {
|
|
29
|
+
getBaseDir(): string;
|
|
30
|
+
get(id: string): KnowledgeEntry | undefined;
|
|
31
|
+
list(filter?: QueryFilter): KnowledgeEntry[];
|
|
32
|
+
save(entry: KnowledgeEntry): void;
|
|
33
|
+
delete(id: string): boolean;
|
|
34
|
+
update(id: string, partial: Partial<KnowledgeEntry>): KnowledgeEntry | undefined;
|
|
35
|
+
rebuildIndex(): void;
|
|
36
|
+
readIndex(): IndexEntry[];
|
|
37
|
+
readEntriesFromDisk(): KnowledgeEntry[];
|
|
38
|
+
snapshot(): string;
|
|
39
|
+
getSnapshot(date: string): IndexEntry[] | undefined;
|
|
40
|
+
getSurvivalRate(daysAgo: number): { rate: number; survived: number; total: number; snapshotDate: string } | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* FileKnowledgeStore — file-based implementation of KnowledgeStore.
|
|
45
|
+
* Each entry is a markdown file with YAML frontmatter.
|
|
46
|
+
* An index.json provides fast listing without reading all files.
|
|
47
|
+
*/
|
|
48
|
+
export class FileKnowledgeStore implements KnowledgeStore {
|
|
26
49
|
private baseDir: string;
|
|
27
50
|
|
|
28
51
|
constructor(config?: Partial<StoreConfig>) {
|
|
@@ -30,6 +53,10 @@ export class KnowledgeStore {
|
|
|
30
53
|
this.ensureDirectory();
|
|
31
54
|
}
|
|
32
55
|
|
|
56
|
+
getBaseDir(): string {
|
|
57
|
+
return this.baseDir;
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
// ── CRUD ─────────────────────────────────────────────────
|
|
34
61
|
|
|
35
62
|
get(id: string): KnowledgeEntry | undefined {
|
|
@@ -118,8 +145,21 @@ export class KnowledgeStore {
|
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
private findFile(id: string): string | undefined {
|
|
148
|
+
// 1. Fast path: index has id→type mapping, construct exact filename
|
|
149
|
+
const index = this.readIndex();
|
|
150
|
+
const idx = index.find(e => e.id === id);
|
|
151
|
+
if (idx) {
|
|
152
|
+
const exact = path.join(this.baseDir, `${idx.type}-${idx.id}.md`);
|
|
153
|
+
if (fs.existsSync(exact)) return exact;
|
|
154
|
+
}
|
|
155
|
+
// 2. Fallback: scan files, verify id from frontmatter
|
|
121
156
|
const files = this.listFiles();
|
|
122
|
-
|
|
157
|
+
for (const f of files) {
|
|
158
|
+
if (!path.basename(f).includes(`-${id}.md`)) continue;
|
|
159
|
+
const entry = this.readFile(f);
|
|
160
|
+
if (entry?.id === id) return f;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
123
163
|
}
|
|
124
164
|
|
|
125
165
|
private listFiles(): string[] {
|
|
@@ -160,7 +200,12 @@ export class KnowledgeStore {
|
|
|
160
200
|
applicablePhases: (meta.applicablePhases as string[]) || [],
|
|
161
201
|
sourceReferences: (meta.sourceReferences as KnowledgeEntry['sourceReferences']) || [],
|
|
162
202
|
referencedBy: (meta.referencedBy as string[]) || [],
|
|
203
|
+
executionResults: (meta.executionResults as import('./types').ExecutionResult[]) || [],
|
|
163
204
|
decayAt: meta.decayAt as string | undefined,
|
|
205
|
+
consumptionMode: (meta.consumptionMode as KnowledgeEntry['consumptionMode']) || 'reference',
|
|
206
|
+
origin: (meta.origin as KnowledgeEntry['origin']) || 'agent',
|
|
207
|
+
fullContentPath: meta.fullContentPath as string | undefined,
|
|
208
|
+
skillId: meta.skillId as string | undefined,
|
|
164
209
|
};
|
|
165
210
|
}
|
|
166
211
|
|
|
@@ -179,8 +224,13 @@ export class KnowledgeStore {
|
|
|
179
224
|
applicablePhases: entry.applicablePhases,
|
|
180
225
|
sourceReferences: entry.sourceReferences,
|
|
181
226
|
referencedBy: entry.referencedBy,
|
|
227
|
+
consumptionMode: entry.consumptionMode,
|
|
228
|
+
origin: entry.origin,
|
|
182
229
|
};
|
|
230
|
+
if (entry.executionResults?.length > 0) meta.executionResults = entry.executionResults;
|
|
183
231
|
if (entry.decayAt) meta.decayAt = entry.decayAt;
|
|
232
|
+
if (entry.fullContentPath) meta.fullContentPath = entry.fullContentPath;
|
|
233
|
+
if (entry.skillId) meta.skillId = entry.skillId;
|
|
184
234
|
return yaml.dump(meta, { lineWidth: 120 });
|
|
185
235
|
}
|
|
186
236
|
|
|
@@ -195,6 +245,8 @@ export class KnowledgeStore {
|
|
|
195
245
|
applicablePhases: entry.applicablePhases,
|
|
196
246
|
lastReferenced: entry.lastReferenced,
|
|
197
247
|
created: entry.created,
|
|
248
|
+
consumptionMode: entry.consumptionMode,
|
|
249
|
+
origin: entry.origin,
|
|
198
250
|
};
|
|
199
251
|
}
|
|
200
252
|
|
|
@@ -210,10 +262,13 @@ export class KnowledgeStore {
|
|
|
210
262
|
lastReferenced: idx.lastReferenced,
|
|
211
263
|
contributors: [],
|
|
212
264
|
projects: [],
|
|
213
|
-
tags: idx.tags,
|
|
214
|
-
applicablePhases: idx.applicablePhases,
|
|
265
|
+
tags: idx.tags ?? [],
|
|
266
|
+
applicablePhases: idx.applicablePhases ?? [],
|
|
215
267
|
sourceReferences: [],
|
|
216
268
|
referencedBy: [],
|
|
269
|
+
executionResults: [],
|
|
270
|
+
consumptionMode: idx.consumptionMode || 'reference',
|
|
271
|
+
origin: idx.origin || 'agent',
|
|
217
272
|
};
|
|
218
273
|
}
|
|
219
274
|
|
|
@@ -221,15 +276,77 @@ export class KnowledgeStore {
|
|
|
221
276
|
if (filter.types && filter.types.length > 0 && !filter.types.includes(entry.type)) return false;
|
|
222
277
|
if (filter.maturity && filter.maturity.length > 0 && !filter.maturity.includes(entry.maturity)) return false;
|
|
223
278
|
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;
|
|
279
|
+
if (filter.tags && filter.tags.length > 0 && !filter.tags.some(t => (entry.tags ?? []).includes(t))) return false;
|
|
280
|
+
if (filter.applicablePhases && filter.applicablePhases.length > 0 && !filter.applicablePhases.some(p => (entry.applicablePhases ?? []).includes(p))) return false;
|
|
281
|
+
if (filter.excludeArchived !== false && (entry.maturity === 'archived' || entry.maturity === 'deprecated')) return false;
|
|
282
|
+
if (filter.consumptionModes && filter.consumptionModes.length > 0 && !filter.consumptionModes.includes(entry.consumptionMode ?? 'reference')) return false;
|
|
283
|
+
if (filter.origins && filter.origins.length > 0 && !filter.origins.includes(entry.origin ?? 'agent')) return false;
|
|
227
284
|
return true;
|
|
228
285
|
}
|
|
229
286
|
|
|
287
|
+
// ── Snapshots ────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
private static readonly SNAPSHOTS_DIR = '.snapshots';
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 保存当日 index.json 快照
|
|
293
|
+
*/
|
|
294
|
+
snapshot(): string {
|
|
295
|
+
const snapDir = path.join(this.baseDir, FileKnowledgeStore.SNAPSHOTS_DIR);
|
|
296
|
+
if (!fs.existsSync(snapDir)) {
|
|
297
|
+
fs.mkdirSync(snapDir, { recursive: true });
|
|
298
|
+
}
|
|
299
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
300
|
+
const snapPath = path.join(snapDir, `index-${today}.json`);
|
|
301
|
+
const indexPath = path.join(this.baseDir, INDEX_FILE);
|
|
302
|
+
if (fs.existsSync(indexPath)) {
|
|
303
|
+
fs.copyFileSync(indexPath, snapPath);
|
|
304
|
+
}
|
|
305
|
+
return snapPath;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 读取指定日期的快照
|
|
310
|
+
*/
|
|
311
|
+
getSnapshot(date: string): IndexEntry[] | undefined {
|
|
312
|
+
const snapPath = path.join(this.baseDir, FileKnowledgeStore.SNAPSHOTS_DIR, `index-${date}.json`);
|
|
313
|
+
if (!fs.existsSync(snapPath)) return undefined;
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(fs.readFileSync(snapPath, 'utf-8')) as IndexEntry[];
|
|
316
|
+
} catch {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 计算 N 天前快照中条目的存活率
|
|
323
|
+
* 存活 = 当前 index 中存在且 maturity !== 'archived'
|
|
324
|
+
*/
|
|
325
|
+
getSurvivalRate(daysAgo: number): { rate: number; survived: number; total: number; snapshotDate: string } | undefined {
|
|
326
|
+
const target = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
327
|
+
const snapshot = this.getSnapshot(target);
|
|
328
|
+
if (!snapshot || snapshot.length === 0) return undefined;
|
|
329
|
+
|
|
330
|
+
const currentIndex = this.readIndex();
|
|
331
|
+
const currentMap = new Map(currentIndex.map(e => [e.id, e]));
|
|
332
|
+
|
|
333
|
+
let survived = 0;
|
|
334
|
+
for (const snapEntry of snapshot) {
|
|
335
|
+
const current = currentMap.get(snapEntry.id);
|
|
336
|
+
if (current && current.maturity !== 'archived' && current.maturity !== 'deprecated') survived++;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
rate: Math.round((survived / snapshot.length) * 100),
|
|
341
|
+
survived,
|
|
342
|
+
total: snapshot.length,
|
|
343
|
+
snapshotDate: target,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
230
347
|
// ── Index I/O ────────────────────────────────────────────
|
|
231
348
|
|
|
232
|
-
|
|
349
|
+
readIndex(): IndexEntry[] {
|
|
233
350
|
const indexPath = path.join(this.baseDir, INDEX_FILE);
|
|
234
351
|
if (!fs.existsSync(indexPath)) return [];
|
|
235
352
|
try {
|
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,14 +135,34 @@ 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
|
|
|
149
|
+
// ── Decision ────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
export interface DecisionRecord {
|
|
152
|
+
topic: string;
|
|
153
|
+
category: 'architecture' | 'tooling' | 'process' | 'design';
|
|
154
|
+
context: string;
|
|
155
|
+
decision: string;
|
|
156
|
+
alternatives: string[];
|
|
157
|
+
rationale: string;
|
|
158
|
+
consequences: string;
|
|
159
|
+
participants: string[];
|
|
160
|
+
sourceType: string;
|
|
161
|
+
sourceId?: string;
|
|
162
|
+
revisable: boolean;
|
|
163
|
+
revisitCondition?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
119
166
|
// ── Reference ───────────────────────────────────────────────
|
|
120
167
|
|
|
121
168
|
export interface ReferenceRecord {
|
|
@@ -136,4 +183,6 @@ export interface IndexEntry {
|
|
|
136
183
|
applicablePhases: string[];
|
|
137
184
|
lastReferenced: string;
|
|
138
185
|
created: string;
|
|
186
|
+
consumptionMode: ConsumptionMode;
|
|
187
|
+
origin: KnowledgeOrigin;
|
|
139
188
|
}
|
|
@@ -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
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { KnowledgeEntry, MaturityLevel } from '../knowledge/types';
|
|
11
11
|
import type { KnowledgeDiagnosis } from './knowledge-doctor';
|
|
12
|
-
import { KnowledgeStore } from '../knowledge/store';
|
|
12
|
+
import type { KnowledgeStore } from '../knowledge/store';
|
|
13
13
|
|
|
14
14
|
// ── 进化提案 ─────────────────────────────────────────────
|
|
15
15
|
|
package/src/types/constraint.ts
CHANGED
|
@@ -220,7 +220,7 @@ export interface ConstraintContext {
|
|
|
220
220
|
/** 是否有需求文档(用于 no_implementation_without_requirement) */
|
|
221
221
|
hasRequirement?: boolean;
|
|
222
222
|
|
|
223
|
-
/** 是否在 worktree 中执行(用于
|
|
223
|
+
/** 是否在 worktree 中执行(用于 prefer_worktree) */
|
|
224
224
|
hasWorktree?: boolean;
|
|
225
225
|
|
|
226
226
|
/** worktree 路径(用于 worktree 相关检查) */
|