@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.
Files changed (133) hide show
  1. package/CHANGELOG.md +24 -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/diagnose.d.ts +16 -0
  6. package/dist/cli/commands/diagnose.d.ts.map +1 -0
  7. package/dist/cli/commands/diagnose.js +195 -0
  8. package/dist/cli/commands/diagnose.js.map +1 -0
  9. package/dist/cli/commands/index.d.ts +1 -1
  10. package/dist/cli/commands/index.d.ts.map +1 -1
  11. package/dist/cli/commands/index.js +4 -1
  12. package/dist/cli/commands/index.js.map +1 -1
  13. package/dist/cli/commands/knowledge.d.ts +21 -0
  14. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  15. package/dist/cli/commands/knowledge.js +155 -4
  16. package/dist/cli/commands/knowledge.js.map +1 -1
  17. package/dist/cli/commands/propose.d.ts +18 -0
  18. package/dist/cli/commands/propose.d.ts.map +1 -0
  19. package/dist/cli/commands/propose.js +331 -0
  20. package/dist/cli/commands/propose.js.map +1 -0
  21. package/dist/cli/commands/traces.d.ts +15 -0
  22. package/dist/cli/commands/traces.d.ts.map +1 -0
  23. package/dist/cli/commands/traces.js +167 -0
  24. package/dist/cli/commands/traces.js.map +1 -0
  25. package/dist/constraints/definitions.d.ts +39 -0
  26. package/dist/constraints/definitions.d.ts.map +1 -0
  27. package/dist/constraints/definitions.js +71 -0
  28. package/dist/constraints/definitions.js.map +1 -0
  29. package/dist/constraints/quality.d.ts +11 -0
  30. package/dist/constraints/quality.d.ts.map +1 -0
  31. package/dist/constraints/quality.js +257 -0
  32. package/dist/constraints/quality.js.map +1 -0
  33. package/dist/constraints/safety.d.ts +13 -0
  34. package/dist/constraints/safety.d.ts.map +1 -0
  35. package/dist/constraints/safety.js +103 -0
  36. package/dist/constraints/safety.js.map +1 -0
  37. package/dist/core/constraints/definitions.d.ts.map +1 -1
  38. package/dist/core/constraints/definitions.js +4 -2
  39. package/dist/core/constraints/definitions.js.map +1 -1
  40. package/dist/extensions/long-running/constraints.d.ts +38 -0
  41. package/dist/extensions/long-running/constraints.d.ts.map +1 -0
  42. package/dist/extensions/long-running/constraints.js +153 -0
  43. package/dist/extensions/long-running/constraints.js.map +1 -0
  44. package/dist/extensions/long-running/index.d.ts +30 -0
  45. package/dist/extensions/long-running/index.d.ts.map +1 -0
  46. package/dist/extensions/long-running/index.js +50 -0
  47. package/dist/extensions/long-running/index.js.map +1 -0
  48. package/dist/extensions/long-running/types.d.ts +154 -0
  49. package/dist/extensions/long-running/types.d.ts.map +1 -0
  50. package/dist/extensions/long-running/types.js +9 -0
  51. package/dist/extensions/long-running/types.js.map +1 -0
  52. package/dist/index.d.ts +3 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +6 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/knowledge/audit.d.ts +76 -0
  57. package/dist/knowledge/audit.d.ts.map +1 -0
  58. package/dist/knowledge/audit.js +566 -0
  59. package/dist/knowledge/audit.js.map +1 -0
  60. package/dist/knowledge/import.d.ts.map +1 -1
  61. package/dist/knowledge/import.js +3 -0
  62. package/dist/knowledge/import.js.map +1 -1
  63. package/dist/knowledge/index.d.ts +5 -1
  64. package/dist/knowledge/index.d.ts.map +1 -1
  65. package/dist/knowledge/index.js +6 -1
  66. package/dist/knowledge/index.js.map +1 -1
  67. package/dist/knowledge/ingest.d.ts +27 -0
  68. package/dist/knowledge/ingest.d.ts.map +1 -1
  69. package/dist/knowledge/ingest.js +150 -5
  70. package/dist/knowledge/ingest.js.map +1 -1
  71. package/dist/knowledge/lifecycle-hooks.d.ts.map +1 -1
  72. package/dist/knowledge/lifecycle-hooks.js +3 -0
  73. package/dist/knowledge/lifecycle-hooks.js.map +1 -1
  74. package/dist/knowledge/lifecycle.d.ts +59 -7
  75. package/dist/knowledge/lifecycle.d.ts.map +1 -1
  76. package/dist/knowledge/lifecycle.js +213 -52
  77. package/dist/knowledge/lifecycle.js.map +1 -1
  78. package/dist/knowledge/lint.d.ts.map +1 -1
  79. package/dist/knowledge/lint.js +43 -2
  80. package/dist/knowledge/lint.js.map +1 -1
  81. package/dist/knowledge/migration.d.ts +25 -0
  82. package/dist/knowledge/migration.d.ts.map +1 -0
  83. package/dist/knowledge/migration.js +96 -0
  84. package/dist/knowledge/migration.js.map +1 -0
  85. package/dist/knowledge/query.d.ts +29 -1
  86. package/dist/knowledge/query.d.ts.map +1 -1
  87. package/dist/knowledge/query.js +58 -0
  88. package/dist/knowledge/query.js.map +1 -1
  89. package/dist/knowledge/store.d.ts +21 -1
  90. package/dist/knowledge/store.d.ts.map +1 -1
  91. package/dist/knowledge/store.js +103 -6
  92. package/dist/knowledge/store.js.map +1 -1
  93. package/dist/knowledge/types.d.ts +29 -1
  94. package/dist/knowledge/types.d.ts.map +1 -1
  95. package/dist/knowledge/types.js +1 -0
  96. package/dist/knowledge/types.js.map +1 -1
  97. package/dist/presets/long-running.d.ts +33 -0
  98. package/dist/presets/long-running.d.ts.map +1 -0
  99. package/dist/presets/long-running.js +52 -0
  100. package/dist/presets/long-running.js.map +1 -0
  101. package/package.json +1 -1
  102. package/src/cli/commands/__tests__/knowledge.test.ts +167 -0
  103. package/src/cli/commands/index.ts +1 -1
  104. package/src/cli/commands/knowledge.ts +175 -4
  105. package/src/context/__tests__/knowledge-injector.test.ts +3 -0
  106. package/src/core/constraints/definitions.ts +4 -2
  107. package/src/dashboard/__tests__/data.test.ts +3 -0
  108. package/src/dashboard/__tests__/stats.test.ts +3 -0
  109. package/src/index.ts +3 -1
  110. package/src/knowledge/CONTEXT.md +29 -7
  111. package/src/knowledge/__tests__/audit.test.ts +598 -0
  112. package/src/knowledge/__tests__/doctor.test.ts +27 -0
  113. package/src/knowledge/__tests__/ingest.test.ts +319 -0
  114. package/src/knowledge/__tests__/lifecycle-hooks.test.ts +3 -0
  115. package/src/knowledge/__tests__/lifecycle.test.ts +655 -2
  116. package/src/knowledge/__tests__/lint.test.ts +51 -0
  117. package/src/knowledge/__tests__/migration.test.ts +115 -0
  118. package/src/knowledge/__tests__/query.test.ts +86 -0
  119. package/src/knowledge/__tests__/reference-tracker.test.ts +3 -0
  120. package/src/knowledge/__tests__/store.test.ts +207 -0
  121. package/src/knowledge/audit.ts +640 -0
  122. package/src/knowledge/import.ts +3 -0
  123. package/src/knowledge/index.ts +5 -1
  124. package/src/knowledge/ingest.ts +169 -5
  125. package/src/knowledge/lifecycle-hooks.ts +3 -0
  126. package/src/knowledge/lifecycle.ts +218 -49
  127. package/src/knowledge/lint.ts +43 -2
  128. package/src/knowledge/migration.ts +73 -0
  129. package/src/knowledge/query.ts +71 -0
  130. package/src/knowledge/store.ts +100 -6
  131. package/src/knowledge/types.ts +33 -1
  132. package/src/monitoring/__tests__/knowledge-doctor.test.ts +3 -0
  133. 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
+ }
@@ -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
  }
@@ -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
- return files.find(f => path.basename(f).includes(`-${id}.md`));
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[] {
@@ -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
  }