@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/bin/harness-knowledge-check.js +38 -23
  3. package/bin/harness-knowledge-track.js +30 -4
  4. package/bin/harness.js +24 -1
  5. package/dist/cli/commands/index.d.ts +1 -1
  6. package/dist/cli/commands/index.d.ts.map +1 -1
  7. package/dist/cli/commands/index.js +4 -1
  8. package/dist/cli/commands/index.js.map +1 -1
  9. package/dist/cli/commands/knowledge.d.ts +21 -0
  10. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  11. package/dist/cli/commands/knowledge.js +160 -9
  12. package/dist/cli/commands/knowledge.js.map +1 -1
  13. package/dist/cli/commands/release.d.ts.map +1 -1
  14. package/dist/cli/commands/release.js +43 -6
  15. package/dist/cli/commands/release.js.map +1 -1
  16. package/dist/core/constraints/checker.js +1 -1
  17. package/dist/core/constraints/checker.js.map +1 -1
  18. package/dist/core/constraints/definitions.d.ts.map +1 -1
  19. package/dist/core/constraints/definitions.js +34 -26
  20. package/dist/core/constraints/definitions.js.map +1 -1
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/knowledge/audit.d.ts +76 -0
  26. package/dist/knowledge/audit.d.ts.map +1 -0
  27. package/dist/knowledge/audit.js +566 -0
  28. package/dist/knowledge/audit.js.map +1 -0
  29. package/dist/knowledge/doctor.d.ts +1 -1
  30. package/dist/knowledge/doctor.d.ts.map +1 -1
  31. package/dist/knowledge/import.d.ts +1 -1
  32. package/dist/knowledge/import.d.ts.map +1 -1
  33. package/dist/knowledge/import.js +3 -0
  34. package/dist/knowledge/import.js.map +1 -1
  35. package/dist/knowledge/index.d.ts +7 -2
  36. package/dist/knowledge/index.d.ts.map +1 -1
  37. package/dist/knowledge/index.js +7 -2
  38. package/dist/knowledge/index.js.map +1 -1
  39. package/dist/knowledge/ingest.d.ts +28 -1
  40. package/dist/knowledge/ingest.d.ts.map +1 -1
  41. package/dist/knowledge/ingest.js +150 -5
  42. package/dist/knowledge/ingest.js.map +1 -1
  43. package/dist/knowledge/lifecycle-hooks.d.ts +1 -1
  44. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  45. package/dist/knowledge/lifecycle-hooks.js +3 -0
  46. package/dist/knowledge/lifecycle-hooks.js.map +1 -1
  47. package/dist/knowledge/lifecycle.d.ts +60 -8
  48. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  49. package/dist/knowledge/lifecycle.js +213 -52
  50. package/dist/knowledge/lifecycle.js.map +1 -1
  51. package/dist/knowledge/lint.d.ts +1 -1
  52. package/dist/knowledge/lint.d.ts.map +1 -1
  53. package/dist/knowledge/lint.js +44 -3
  54. package/dist/knowledge/lint.js.map +1 -1
  55. package/dist/knowledge/migration.d.ts +25 -0
  56. package/dist/knowledge/migration.d.ts.map +1 -0
  57. package/dist/knowledge/migration.js +96 -0
  58. package/dist/knowledge/migration.js.map +1 -0
  59. package/dist/knowledge/query.d.ts +30 -2
  60. package/dist/knowledge/query.d.ts.map +1 -1
  61. package/dist/knowledge/query.js +58 -0
  62. package/dist/knowledge/query.js.map +1 -1
  63. package/dist/knowledge/reference-tracker.d.ts +1 -1
  64. package/dist/knowledge/reference-tracker.d.ts.map +1 -1
  65. package/dist/knowledge/store.d.ts +50 -3
  66. package/dist/knowledge/store.d.ts.map +1 -1
  67. package/dist/knowledge/store.js +111 -9
  68. package/dist/knowledge/store.js.map +1 -1
  69. package/dist/knowledge/types.d.ts +43 -1
  70. package/dist/knowledge/types.d.ts.map +1 -1
  71. package/dist/knowledge/types.js +1 -0
  72. package/dist/knowledge/types.js.map +1 -1
  73. package/dist/monitoring/knowledge-evolver.d.ts +1 -1
  74. package/dist/monitoring/knowledge-evolver.d.ts.map +1 -1
  75. package/dist/types/constraint.d.ts +1 -1
  76. package/dist/types/constraint.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/__tests__/checker.test.ts +4 -4
  79. package/src/cli/commands/__tests__/knowledge.test.ts +167 -0
  80. package/src/cli/commands/index.ts +1 -1
  81. package/src/cli/commands/knowledge.ts +176 -5
  82. package/src/cli/commands/release.ts +44 -6
  83. package/src/context/__tests__/knowledge-injector.test.ts +4 -1
  84. package/src/core/constraints/checker.ts +1 -1
  85. package/src/core/constraints/definitions.ts +35 -27
  86. package/src/dashboard/__tests__/data.test.ts +3 -0
  87. package/src/dashboard/__tests__/stats.test.ts +3 -0
  88. package/src/index.ts +3 -1
  89. package/src/knowledge/CONTEXT.md +29 -7
  90. package/src/knowledge/__tests__/audit.test.ts +598 -0
  91. package/src/knowledge/__tests__/doctor.test.ts +28 -1
  92. package/src/knowledge/__tests__/import.test.ts +1 -1
  93. package/src/knowledge/__tests__/ingest.test.ts +320 -1
  94. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +4 -1
  95. package/src/knowledge/__tests__/lifecycle.test.ts +656 -3
  96. package/src/knowledge/__tests__/lint.test.ts +52 -1
  97. package/src/knowledge/__tests__/migration.test.ts +115 -0
  98. package/src/knowledge/__tests__/query.test.ts +87 -1
  99. package/src/knowledge/__tests__/reference-tracker.test.ts +4 -1
  100. package/src/knowledge/__tests__/store.test.ts +208 -1
  101. package/src/knowledge/audit.ts +641 -0
  102. package/src/knowledge/doctor.ts +1 -1
  103. package/src/knowledge/import.ts +4 -1
  104. package/src/knowledge/index.ts +7 -2
  105. package/src/knowledge/ingest.ts +170 -6
  106. package/src/knowledge/lifecycle-hooks.ts +4 -1
  107. package/src/knowledge/lifecycle.ts +219 -50
  108. package/src/knowledge/lint.ts +45 -4
  109. package/src/knowledge/migration.ts +73 -0
  110. package/src/knowledge/query.ts +72 -1
  111. package/src/knowledge/reference-tracker.ts +1 -1
  112. package/src/knowledge/store.ts +125 -8
  113. package/src/knowledge/types.ts +50 -1
  114. package/src/monitoring/__tests__/knowledge-doctor.test.ts +3 -0
  115. package/src/monitoring/__tests__/knowledge-evolver.test.ts +3 -0
  116. package/src/monitoring/knowledge-evolver.ts +1 -1
  117. 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
+ }
@@ -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
  }
@@ -8,7 +8,7 @@
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import type { ReferenceRecord } from './types';
11
- import { KnowledgeStore } from './store';
11
+ import type { KnowledgeStore } from './store';
12
12
 
13
13
  const REFERENCES_FILE = 'references.jsonl';
14
14
 
@@ -22,7 +22,30 @@ const DEFAULT_CONFIG: StoreConfig = {
22
22
  baseDir: DEFAULT_DIR,
23
23
  };
24
24
 
25
- export class KnowledgeStore {
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
- return files.find(f => path.basename(f).includes(`-${id}.md`));
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
- private readIndex(): IndexEntry[] {
349
+ readIndex(): IndexEntry[] {
233
350
  const indexPath = path.join(this.baseDir, INDEX_FILE);
234
351
  if (!fs.existsSync(indexPath)) return [];
235
352
  try {
@@ -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
 
@@ -220,7 +220,7 @@ export interface ConstraintContext {
220
220
  /** 是否有需求文档(用于 no_implementation_without_requirement) */
221
221
  hasRequirement?: boolean;
222
222
 
223
- /** 是否在 worktree 中执行(用于 must_use_worktree) */
223
+ /** 是否在 worktree 中执行(用于 prefer_worktree) */
224
224
  hasWorktree?: boolean;
225
225
 
226
226
  /** worktree 路径(用于 worktree 相关检查) */