@dommaker/harness 0.13.3 → 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,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge 命令测试 — knowledgeAudit
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { knowledgeAudit, knowledgeStats } from '../knowledge';
|
|
6
|
+
|
|
7
|
+
// Mock chalk
|
|
8
|
+
jest.mock('chalk', () => ({
|
|
9
|
+
blue: jest.fn((str: string) => str),
|
|
10
|
+
bold: jest.fn((str: string) => str),
|
|
11
|
+
green: jest.fn((str: string) => str),
|
|
12
|
+
yellow: jest.fn((str: string) => str),
|
|
13
|
+
red: jest.fn((str: string) => str),
|
|
14
|
+
gray: jest.fn((str: string) => str),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Mock KnowledgeAudit
|
|
18
|
+
const mockRun = jest.fn();
|
|
19
|
+
jest.mock('../../../knowledge/audit', () => ({
|
|
20
|
+
KnowledgeAudit: jest.fn().mockImplementation(() => ({
|
|
21
|
+
run: mockRun,
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const MOCK_REPORT = {
|
|
26
|
+
timestamp: '2026-06-02T00:00:00.000Z',
|
|
27
|
+
totalEntries: 100,
|
|
28
|
+
issues: [
|
|
29
|
+
{
|
|
30
|
+
rule: 'short-content',
|
|
31
|
+
entryId: 'GUI-001',
|
|
32
|
+
title: 'Short Entry',
|
|
33
|
+
severity: 'medium',
|
|
34
|
+
action: 'flag',
|
|
35
|
+
detail: 'content 30 chars',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
summary: {
|
|
39
|
+
'frontmatter-missing': 0,
|
|
40
|
+
'test-data-pollution': 0,
|
|
41
|
+
'daily-audit-noise': 0,
|
|
42
|
+
'zero-content-proven': 0,
|
|
43
|
+
'short-content': 1,
|
|
44
|
+
'maturity-inflation': 0,
|
|
45
|
+
'title-duplicate': 0,
|
|
46
|
+
'source-refs-bloat': 0,
|
|
47
|
+
'promotion-blocked': 0,
|
|
48
|
+
'orphan-draft': 0,
|
|
49
|
+
'stale-entry': 0,
|
|
50
|
+
},
|
|
51
|
+
dimensions: {
|
|
52
|
+
structure: { score: 100, issues: 0, details: {} },
|
|
53
|
+
content: { score: 95, issues: 1, details: {} },
|
|
54
|
+
dedup: { score: 100, issues: 0, details: {} },
|
|
55
|
+
maturity: { score: 100, issues: 0, details: {} },
|
|
56
|
+
freshness: { score: 100, issues: 0, details: {} },
|
|
57
|
+
flywheel: { score: 50, issues: 0, details: {} },
|
|
58
|
+
},
|
|
59
|
+
autoFixed: 0,
|
|
60
|
+
healthScore: { before: 95, after: 95 },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe('getKnowledgeDir', () => {
|
|
64
|
+
const os = require('os');
|
|
65
|
+
const path = require('path');
|
|
66
|
+
const storeModule = require('../../../knowledge/store');
|
|
67
|
+
let storeCtorSpy: jest.SpyInstance;
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
delete process.env.KNOWLEDGE_BASE_DIR;
|
|
71
|
+
storeCtorSpy = jest.spyOn(storeModule, 'KnowledgeStore').mockImplementation(() => ({
|
|
72
|
+
list: jest.fn().mockReturnValue([]),
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
storeCtorSpy.mockRestore();
|
|
78
|
+
delete process.env.KNOWLEDGE_BASE_DIR;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should use KNOWLEDGE_BASE_DIR env var when set', async () => {
|
|
82
|
+
process.env.KNOWLEDGE_BASE_DIR = '/custom/knowledge';
|
|
83
|
+
await knowledgeStats({ json: true });
|
|
84
|
+
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
85
|
+
baseDir: '/custom/knowledge',
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should default to ~/.studio/knowledge when no env var', async () => {
|
|
90
|
+
const expected = path.join(os.homedir(), '.studio', 'knowledge');
|
|
91
|
+
await knowledgeStats({ json: true });
|
|
92
|
+
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
93
|
+
baseDir: expected,
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should use projectPath when provided', async () => {
|
|
98
|
+
await knowledgeStats({ projectPath: '/my/project', json: true });
|
|
99
|
+
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
100
|
+
baseDir: '/my/project/.harness/knowledge',
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('knowledgeAudit CLI', () => {
|
|
106
|
+
let consoleSpy: jest.SpyInstance;
|
|
107
|
+
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
111
|
+
mockRun.mockReturnValue(MOCK_REPORT);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
consoleSpy.mockRestore();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should output JSON when --json is set', async () => {
|
|
119
|
+
await knowledgeAudit({ json: true });
|
|
120
|
+
expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(MOCK_REPORT, null, 2));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should call audit.run with autoFix=true when --fix is set', async () => {
|
|
124
|
+
await knowledgeAudit({ fix: true });
|
|
125
|
+
expect(mockRun).toHaveBeenCalledWith({ autoFix: true });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should call audit.run with autoFix falsy when --dry-run is set', async () => {
|
|
129
|
+
await knowledgeAudit({ dryRun: true });
|
|
130
|
+
expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should call audit.run with autoFix falsy by default', async () => {
|
|
134
|
+
await knowledgeAudit({});
|
|
135
|
+
expect(mockRun).toHaveBeenCalledWith({ autoFix: undefined });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should display total entries and health score', async () => {
|
|
139
|
+
await knowledgeAudit({});
|
|
140
|
+
const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
141
|
+
expect(output).toContain('总条目: 100');
|
|
142
|
+
expect(output).toContain('95/100');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should display dimension scores', async () => {
|
|
146
|
+
await knowledgeAudit({});
|
|
147
|
+
const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
148
|
+
expect(output).toContain('D1 结构完整性');
|
|
149
|
+
expect(output).toContain('D6 飞轮验证');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should display autoFixed count when > 0', async () => {
|
|
153
|
+
mockRun.mockReturnValue({ ...MOCK_REPORT, autoFixed: 5, healthScore: { before: 90, after: 95 } });
|
|
154
|
+
await knowledgeAudit({ fix: true });
|
|
155
|
+
const output = consoleSpy.mock.calls.map((c: any[]) => c[0]).join('\n');
|
|
156
|
+
expect(output).toContain('自动修复: 5');
|
|
157
|
+
expect(output).toContain('修复后: 95/100');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should pass custom dir to KnowledgeAudit', async () => {
|
|
161
|
+
const { KnowledgeAudit } = require('../../../knowledge/audit');
|
|
162
|
+
await knowledgeAudit({ dir: '/custom/path' });
|
|
163
|
+
expect(KnowledgeAudit).toHaveBeenCalledWith(expect.objectContaining({
|
|
164
|
+
baseDir: '/custom/path',
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -17,7 +17,7 @@ export { contract, validateSchema, type ContractOptions } from './contract';
|
|
|
17
17
|
export { review, reviewStatus, type ReviewOptions } from './review';
|
|
18
18
|
export { executeCommand, type CommandCheckOptions } from './command';
|
|
19
19
|
export { syncDocs, type SyncDocsOptions } from './sync-docs';
|
|
20
|
-
export { knowledgeList, knowledgeSearch, knowledgeImport, knowledgeDecay, knowledgeStats, knowledgeUpsert, knowledgeSyncStatus, knowledgeSyncRag, type KnowledgeOptions, type KnowledgeUpsertOptions } from './knowledge';
|
|
20
|
+
export { knowledgeList, knowledgeSearch, knowledgeImport, knowledgeDecay, knowledgeStats, knowledgeUpsert, knowledgeSyncStatus, knowledgeSyncRag, knowledgeAudit, knowledgeSnapshot, knowledgeMigrate, type KnowledgeOptions, type KnowledgeUpsertOptions } from './knowledge';
|
|
21
21
|
export { failureList, failureStats, failureClear, type FailureOptions } from './failure';
|
|
22
22
|
export { postevalPlan, type PostEvalPlanOptions } from './posteval-plan';
|
|
23
23
|
export { release, type ReleaseOptions } from './release';
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import * as path from 'path';
|
|
9
11
|
import { KnowledgeStore } from '../../knowledge/store';
|
|
10
12
|
import { KnowledgeQuery } from '../../knowledge/query';
|
|
11
13
|
import { KnowledgeLifecycle } from '../../knowledge/lifecycle';
|
|
12
14
|
import { ColdStartImporter } from '../../knowledge/import';
|
|
15
|
+
import { KnowledgeAudit } from '../../knowledge/audit';
|
|
16
|
+
import { migrateKnowledgeEntries } from '../../knowledge/migration';
|
|
13
17
|
import type { KnowledgeType, MaturityLevel, QueryFilter } from '../../knowledge/types';
|
|
14
18
|
|
|
15
19
|
export interface KnowledgeOptions {
|
|
@@ -201,6 +205,7 @@ export async function knowledgeDecay(options: KnowledgeOptions): Promise<void> {
|
|
|
201
205
|
export async function knowledgeStats(options: KnowledgeOptions): Promise<void> {
|
|
202
206
|
const store = new KnowledgeStore({ baseDir: getKnowledgeDir(options.projectPath) });
|
|
203
207
|
const entries = store.list({ excludeArchived: false });
|
|
208
|
+
const active = entries.filter(e => e.maturity !== 'archived');
|
|
204
209
|
|
|
205
210
|
const byType: Record<string, number> = {};
|
|
206
211
|
const byMaturity: Record<string, number> = {};
|
|
@@ -212,13 +217,31 @@ export async function knowledgeStats(options: KnowledgeOptions): Promise<void> {
|
|
|
212
217
|
byLayer[entry.layer] = (byLayer[entry.layer] || 0) + 1;
|
|
213
218
|
}
|
|
214
219
|
|
|
220
|
+
// D6 flywheel metrics
|
|
221
|
+
const withRefs = active.filter(e => e.referencedBy.length > 0).length;
|
|
222
|
+
const refCoverage = active.length > 0 ? Math.round(withRefs / active.length * 100) : 0;
|
|
223
|
+
const avgRefs = active.length > 0
|
|
224
|
+
? Math.round(active.reduce((sum, e) => sum + e.referencedBy.length, 0) / active.length * 10) / 10
|
|
225
|
+
: 0;
|
|
226
|
+
let consumptionHitRate = 0;
|
|
227
|
+
try {
|
|
228
|
+
const statsPath = path.join(store.getBaseDir(), '.consumption-stats.json');
|
|
229
|
+
if (fs.existsSync(statsPath)) {
|
|
230
|
+
const stats = JSON.parse(fs.readFileSync(statsPath, 'utf-8'));
|
|
231
|
+
const dailyEvents = stats.dailyEvents || 0;
|
|
232
|
+
consumptionHitRate = active.length > 0 ? Math.min(Math.round(dailyEvents / active.length * 100), 100) : 0;
|
|
233
|
+
}
|
|
234
|
+
} catch { /* best-effort */ }
|
|
235
|
+
|
|
236
|
+
const flywheel = { refCoverage, avgRefs, consumptionHitRate };
|
|
237
|
+
|
|
215
238
|
if (options.json) {
|
|
216
|
-
console.log(JSON.stringify({ total: entries.length, byType, byMaturity, byLayer }, null, 2));
|
|
239
|
+
console.log(JSON.stringify({ total: entries.length, byType, byMaturity, byLayer, flywheel }, null, 2));
|
|
217
240
|
return;
|
|
218
241
|
}
|
|
219
242
|
|
|
220
243
|
console.log(chalk.blue(`📊 知识库统计\n`));
|
|
221
|
-
console.log(chalk.bold(` 总计: ${entries.length}
|
|
244
|
+
console.log(chalk.bold(` 总计: ${entries.length} 条 (活跃: ${active.length})\n`));
|
|
222
245
|
|
|
223
246
|
console.log(chalk.bold(' 按类型:'));
|
|
224
247
|
for (const [type, count] of Object.entries(byType)) {
|
|
@@ -235,6 +258,11 @@ export async function knowledgeStats(options: KnowledgeOptions): Promise<void> {
|
|
|
235
258
|
for (const [layer, count] of Object.entries(byLayer)) {
|
|
236
259
|
console.log(` ${layer}: ${count}`);
|
|
237
260
|
}
|
|
261
|
+
|
|
262
|
+
console.log(chalk.bold('\n 飞轮指标:'));
|
|
263
|
+
console.log(` 引用覆盖: ${refCoverage}%`);
|
|
264
|
+
console.log(` 平均引用: ${avgRefs}`);
|
|
265
|
+
console.log(` 消费命中率: ${consumptionHitRate}%`);
|
|
238
266
|
}
|
|
239
267
|
|
|
240
268
|
export interface KnowledgeUpsertOptions {
|
|
@@ -386,6 +414,149 @@ export async function knowledgeSyncStatus(options: KnowledgeOptions): Promise<vo
|
|
|
386
414
|
}
|
|
387
415
|
|
|
388
416
|
function getKnowledgeDir(projectPath?: string): string {
|
|
389
|
-
|
|
390
|
-
return
|
|
417
|
+
if (projectPath) return `${projectPath}/.harness/knowledge`;
|
|
418
|
+
if (process.env.KNOWLEDGE_BASE_DIR) return process.env.KNOWLEDGE_BASE_DIR;
|
|
419
|
+
return path.join(os.homedir(), '.studio', 'knowledge');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 知识库质量审计
|
|
424
|
+
*/
|
|
425
|
+
export async function knowledgeAudit(options: KnowledgeOptions & {
|
|
426
|
+
fix?: boolean;
|
|
427
|
+
dryRun?: boolean;
|
|
428
|
+
threshold?: number;
|
|
429
|
+
dir?: string;
|
|
430
|
+
}): Promise<void> {
|
|
431
|
+
const audit = new KnowledgeAudit({
|
|
432
|
+
baseDir: options.dir || getKnowledgeDir(options.projectPath),
|
|
433
|
+
shortContentThreshold: options.threshold ? parseInt(options.threshold as any, 10) : undefined,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const isDryRun = options.dryRun && !options.fix;
|
|
437
|
+
|
|
438
|
+
if (!options.json && !isDryRun) {
|
|
439
|
+
console.log(chalk.blue('🔍 知识库质量审计...\n'));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const report = audit.run({ autoFix: options.fix && !isDryRun });
|
|
443
|
+
|
|
444
|
+
if (options.json) {
|
|
445
|
+
console.log(JSON.stringify(report, null, 2));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Summary
|
|
450
|
+
console.log(chalk.bold(` 总条目: ${report.totalEntries}`));
|
|
451
|
+
console.log(chalk.bold(` 健康分: ${report.healthScore.before}/100`));
|
|
452
|
+
if (report.autoFixed > 0) {
|
|
453
|
+
console.log(chalk.green(` 自动修复: ${report.autoFixed} 条`));
|
|
454
|
+
console.log(chalk.bold(` 修复后: ${report.healthScore.after}/100`));
|
|
455
|
+
}
|
|
456
|
+
console.log();
|
|
457
|
+
|
|
458
|
+
// Dimension scores
|
|
459
|
+
const dimLabels: Record<string, string> = {
|
|
460
|
+
structure: 'D1 结构完整性',
|
|
461
|
+
content: 'D2 内容质量',
|
|
462
|
+
dedup: 'D3 去重有效性',
|
|
463
|
+
maturity: 'D4 成熟度健康',
|
|
464
|
+
freshness: 'D5 新鲜度',
|
|
465
|
+
flywheel: 'D6 飞轮验证',
|
|
466
|
+
};
|
|
467
|
+
console.log(chalk.bold(' 维度评分:'));
|
|
468
|
+
for (const [key, dim] of Object.entries(report.dimensions)) {
|
|
469
|
+
const label = dimLabels[key] || key;
|
|
470
|
+
const scoreColor = dim.score >= 80 ? chalk.green : dim.score >= 60 ? chalk.yellow : chalk.red;
|
|
471
|
+
console.log(` ${label}: ${scoreColor(`${dim.score}/100`)} (${dim.issues} 问题)`);
|
|
472
|
+
}
|
|
473
|
+
console.log();
|
|
474
|
+
|
|
475
|
+
// Issues by rule
|
|
476
|
+
const ruleLabels: Record<string, string> = {
|
|
477
|
+
'frontmatter-missing': 'frontmatter 缺失',
|
|
478
|
+
'test-data-pollution': '测试数据污染',
|
|
479
|
+
'daily-audit-noise': '每日审计噪音',
|
|
480
|
+
'zero-content-proven': '零内容 proven',
|
|
481
|
+
'short-content': '短内容',
|
|
482
|
+
'maturity-inflation': '成熟度虚高',
|
|
483
|
+
'title-duplicate': '标题重复',
|
|
484
|
+
'source-refs-bloat': 'sourceReferences 膨胀',
|
|
485
|
+
'promotion-blocked': 'promotion 受阻',
|
|
486
|
+
'orphan-draft': '孤儿 draft',
|
|
487
|
+
'stale-entry': '过期条目',
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
for (const [rule, count] of Object.entries(report.summary)) {
|
|
491
|
+
if (count === 0) continue;
|
|
492
|
+
const label = ruleLabels[rule] || rule;
|
|
493
|
+
console.log(` ${chalk.red(`${label}: ${count}`)}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Detail (first 20)
|
|
497
|
+
if (report.issues.length > 0) {
|
|
498
|
+
console.log(chalk.bold(`\n 问题详情 (前 20 条):\n`));
|
|
499
|
+
for (const issue of report.issues.slice(0, 20)) {
|
|
500
|
+
const severityColor = issue.severity === 'critical' ? chalk.red
|
|
501
|
+
: issue.severity === 'high' ? chalk.yellow
|
|
502
|
+
: chalk.gray;
|
|
503
|
+
console.log(` ${severityColor(`[${issue.severity}]`)} ${issue.entryId}: ${issue.title}`);
|
|
504
|
+
console.log(` ${chalk.gray(issue.detail)} → ${issue.action}`);
|
|
505
|
+
}
|
|
506
|
+
if (report.issues.length > 20) {
|
|
507
|
+
console.log(chalk.gray(`\n ... 还有 ${report.issues.length - 20} 条`));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!options.fix && report.issues.length > 0) {
|
|
512
|
+
console.log(chalk.yellow(`\n 使用 --fix 自动修复`));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 知识库快照(KR4 存活率追踪)
|
|
518
|
+
*/
|
|
519
|
+
export function knowledgeSnapshot(options: KnowledgeOptions & { dir?: string }): void {
|
|
520
|
+
const store = new KnowledgeStore({ baseDir: options.dir || getKnowledgeDir(options.projectPath) });
|
|
521
|
+
const snapPath = store.snapshot();
|
|
522
|
+
|
|
523
|
+
if (options.json) {
|
|
524
|
+
console.log(JSON.stringify({ snapshotPath: snapPath }));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
console.log(chalk.green(`✅ 快照已保存: ${snapPath}`));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* 知识库迁移 — 为现有条目添加 AS-021 新字段
|
|
533
|
+
*/
|
|
534
|
+
export function knowledgeMigrate(options: KnowledgeOptions & { dir?: string }): void {
|
|
535
|
+
const baseDir = options.dir || getKnowledgeDir(options.projectPath);
|
|
536
|
+
|
|
537
|
+
if (options.json) {
|
|
538
|
+
const result = migrateKnowledgeEntries(baseDir);
|
|
539
|
+
console.log(JSON.stringify(result, null, 2));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
console.log(chalk.blue('🔄 迁移知识条目(添加 consumptionMode/origin 字段)\n'));
|
|
544
|
+
const result = migrateKnowledgeEntries(baseDir);
|
|
545
|
+
|
|
546
|
+
console.log(chalk.green(` 总计: ${result.total} 条`));
|
|
547
|
+
console.log(chalk.green(` 已迁移: ${result.migrated} 条`));
|
|
548
|
+
console.log(chalk.gray(` 已跳过: ${result.skipped} 条`));
|
|
549
|
+
|
|
550
|
+
if (result.errors.length > 0) {
|
|
551
|
+
console.log(chalk.red(`\n 错误: ${result.errors.length} 条`));
|
|
552
|
+
for (const err of result.errors.slice(0, 5)) {
|
|
553
|
+
console.log(chalk.red(` ${err}`));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (result.migrated === 0 && result.errors.length === 0) {
|
|
558
|
+
console.log(chalk.green('\n✅ 所有条目已是最新,无需迁移'));
|
|
559
|
+
} else if (result.migrated > 0) {
|
|
560
|
+
console.log(chalk.green(`\n✅ 迁移完成`));
|
|
561
|
+
}
|
|
391
562
|
}
|
|
@@ -813,8 +813,10 @@ export const GUIDELINES: Record<string, Constraint> = {
|
|
|
813
813
|
description: `删除任何代码(包/模块/文件)前,必须:
|
|
814
814
|
1. 查设计文档(CLAUDE.md / roadmap / specs)
|
|
815
815
|
2. 分析是否有可吸收的功能
|
|
816
|
-
3.
|
|
817
|
-
|
|
816
|
+
3. 查替代函数是否丢失了关键设计模式(零引用≠无价值)
|
|
817
|
+
4. 分类:未接线(应接线)/ 被替代但有可吸收设计 / 真正无用
|
|
818
|
+
5. 记录分析结论(有/无吸收价值,原因)`,
|
|
819
|
+
promptInjection: '删除任何代码前,先查设计意图(JSDoc/commit/spec),分析被替代的函数是否有丢失的关键模式。零引用≠无价值。分类:未接线→接线,被替代→吸收模式,真正无用→才删。',
|
|
818
820
|
},
|
|
819
821
|
|
|
820
822
|
/**
|
package/src/index.ts
CHANGED
|
@@ -76,7 +76,9 @@ export * from './verification';
|
|
|
76
76
|
// ========================================
|
|
77
77
|
// 约束分层导出
|
|
78
78
|
// ========================================
|
|
79
|
-
export * from './constraints';
|
|
79
|
+
export * from './constraints/types';
|
|
80
|
+
export { ConstraintRegistry } from './constraints/registry';
|
|
81
|
+
export { ConstraintLifecycleRunner } from './constraints/lifecycle-runner';
|
|
80
82
|
|
|
81
83
|
// ========================================
|
|
82
84
|
// Dashboard 数据导出
|
package/src/knowledge/CONTEXT.md
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
# knowledge/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
知识引擎:知识存储(CRUD)
|
|
4
|
+
知识引擎:知识存储(CRUD)、语义查询、引用追踪、质量审计(Audit)、质量检查(Linter)、生命周期管理、冷启动导入、健康评分、外部内容摄入。
|
|
5
|
+
|
|
6
|
+
## 三维分类体系 (AS-021)
|
|
7
|
+
知识由三个正交维度定义:
|
|
8
|
+
- **Topic** — 开放集,用 `tags: string[]` 描述
|
|
9
|
+
- **ConsumptionMode** — 闭集 (rule/reference/context/signal),驱动注入策略和生命周期
|
|
10
|
+
- **Origin** — 闭集 (human/agent/external/system),影响信任度和 maturity 起点
|
|
11
|
+
|
|
12
|
+
## 生命周期按模式分化
|
|
13
|
+
- `rule`: draft → active → deprecated (1 次成功激活, 失败率>=50% 降级)
|
|
14
|
+
- `reference`: draft → verified → proven → archived (现有逻辑)
|
|
15
|
+
- `context`: draft → active → archived (1 次引用激活, 3 个月未引用归档)
|
|
16
|
+
- `signal`: active → archived (消费饱和: ref>=3 + 有更新同标签条目)
|
|
17
|
+
- 所有模式支持 `decayAt` 硬过期
|
|
5
18
|
|
|
6
19
|
## 核心导出
|
|
7
20
|
- `KnowledgeStore` — 知识条目 CRUD + 结构化存储
|
|
8
|
-
- `KnowledgeQuery` — 语义搜索 + 类型/标签过滤
|
|
9
|
-
- `KnowledgeLifecycle` —
|
|
10
|
-
- `KnowledgeIngest` —
|
|
21
|
+
- `KnowledgeQuery` — 语义搜索 + 类型/标签过滤 + `queryByMode()` + `consume(taskContext)`
|
|
22
|
+
- `KnowledgeLifecycle` — per-mode 生命周期管理
|
|
23
|
+
- `KnowledgeIngest` — 知识摄取引擎(含 ingest gate 质量门)+ `ingestExternal()` 外部内容摄入
|
|
24
|
+
- `sanitizeExternalContent` — 外部内容安全清洗(过滤注入模式 + 长度限制)
|
|
25
|
+
- `migrateKnowledgeEntries` — AS-021 迁移工具(为现有条目添加 consumptionMode/origin)
|
|
26
|
+
- `KnowledgeAudit` — 6 维度质量审计引擎
|
|
11
27
|
- `ReferenceTracker` — 知识引用关系图谱
|
|
12
28
|
- `KnowledgeLinter` — 知识质量检查(完整性/一致性/时效性)
|
|
13
29
|
- `ColdStartImporter` — 冷启动知识导入
|
|
14
|
-
- `KnowledgeHealthScorer` —
|
|
30
|
+
- `KnowledgeHealthScorer` — 知识健康评分(doctor.ts)
|
|
15
31
|
- `KnowledgeLifecycleHooks` — 生命周期 hook
|
|
16
32
|
|
|
17
33
|
## 依赖关系
|
|
18
34
|
- 依赖 `src/types/` 知识相关类型
|
|
19
35
|
- 依赖 `src/monitoring/` KnowledgeDoctor / KnowledgeEvolver
|
|
20
|
-
- 被 `src/cli/commands/knowledge.ts` CLI
|
|
36
|
+
- 被 `src/cli/commands/knowledge.ts` CLI 消费(含 `migrate` 子命令)
|
|
21
37
|
|
|
22
38
|
## 约定
|
|
23
|
-
-
|
|
39
|
+
- 知识条目有明确的生命周期状态(按 consumptionMode 分化)
|
|
24
40
|
- 约束退化时自动写入 KnowledgeStore
|
|
25
41
|
- Linter 检查完整性/一致性/时效性三个维度
|
|
42
|
+
- Audit 6 维度评分:D1结构 D2内容 D3去重 D4成熟度 D5新鲜度 D6飞轮
|
|
43
|
+
- Ingest gate: ingestEntry() 先经 audit.validate() 检查,reject 不入库
|
|
44
|
+
- 外部内容三层防御:ingest sanitization + retrieval marking + prompt constraint
|
|
45
|
+
- 消费饱和度替代固定 TTL 用于 signal 过期判断
|
|
26
46
|
|
|
27
47
|
## 注意事项
|
|
28
48
|
- Phase 1+4 实现的知识引擎核心
|
|
29
49
|
- 约束"退化不删除"——降级时保留规则原文 + 退化原因 + 历史数据
|
|
50
|
+
- `MaturityLevel` 包含 6 个值: draft/verified/proven/archived/active/deprecated
|
|
51
|
+
- `excludeArchived` 同时排除 archived 和 deprecated
|