project-knowledge 0.1.0 → 1.0.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 +41 -0
- package/README.md +184 -58
- package/_site/_test/ai-profile-test.js +59 -1
- package/_site/_test/baseline-schema-test.js +4 -3
- package/_site/_test/claude-workbench-test.js +72 -0
- package/_site/_test/draft-apply-test.js +12 -6
- package/_site/_test/kb-v2-templates-test.js +31 -43
- package/_site/_test/knowledge-store-logs-supervision-test.js +143 -0
- package/_site/_test/project-control-panel-task14-test.js +151 -0
- package/_site/_test/task15-20-integration-test.js +194 -0
- package/_site/_test/task15-20-ui-flow-test.js +144 -0
- package/_site/_test/ui-smoke-test.js +2 -2
- package/_site/index.html +1640 -90
- package/_site/lib/ai-adapter.js +3 -3
- package/_site/lib/ai-workspace.js +120 -0
- package/_site/lib/analysis-orchestrator.js +117 -32
- package/_site/lib/claude-cli-runner.js +862 -0
- package/_site/lib/context-pack-builder.js +19 -11
- package/_site/lib/draft-apply.js +80 -31
- package/_site/lib/index-builder.js +100 -0
- package/_site/lib/job-orchestrator.js +12 -9
- package/_site/lib/kb-v3.js +188 -0
- package/_site/lib/kb-validator.js +84 -0
- package/_site/lib/knowledge-store.js +141 -0
- package/_site/lib/llm-client.js +103 -56
- package/_site/lib/prompt-registry.js +102 -0
- package/_site/lib/structured-logger.js +120 -0
- package/_site/lib/supervision.js +103 -0
- package/_site/server.js +835 -19
- package/_site/vendor/tailwind-browser.js +947 -0
- package/_site/vendor/vue.global.prod.js +9 -0
- package/ai-profiles.json +12 -10
- package/docs/development-progress.md +141 -0
- package/package.json +7 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const SCHEMA = 'logging/v1';
|
|
5
|
+
const LEVELS = new Set(['info', 'warn', 'error']);
|
|
6
|
+
|
|
7
|
+
function defaultConfig(appRoot) {
|
|
8
|
+
return {
|
|
9
|
+
schema: SCHEMA,
|
|
10
|
+
rootPath: path.join(appRoot, 'logs'),
|
|
11
|
+
retentionDays: 30,
|
|
12
|
+
levels: ['info', 'warn', 'error'],
|
|
13
|
+
configured: false,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeConfig(input, appRoot) {
|
|
18
|
+
const source = input && typeof input === 'object' ? input : {};
|
|
19
|
+
const levels = Array.isArray(source.levels)
|
|
20
|
+
? source.levels.filter(level => LEVELS.has(level))
|
|
21
|
+
: ['info', 'warn', 'error'];
|
|
22
|
+
return {
|
|
23
|
+
schema: SCHEMA,
|
|
24
|
+
rootPath: path.resolve(source.rootPath || path.join(appRoot, 'logs')),
|
|
25
|
+
retentionDays: Number.isInteger(source.retentionDays) && source.retentionDays > 0 ? source.retentionDays : 30,
|
|
26
|
+
levels: levels.length ? levels : ['info', 'warn', 'error'],
|
|
27
|
+
configured: source.configured === true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readConfig(configPath, appRoot) {
|
|
32
|
+
if (!fs.existsSync(configPath)) return defaultConfig(appRoot);
|
|
33
|
+
try {
|
|
34
|
+
return normalizeConfig(JSON.parse(fs.readFileSync(configPath, 'utf-8')), appRoot);
|
|
35
|
+
} catch {
|
|
36
|
+
return defaultConfig(appRoot);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeConfig(configPath, appRoot, config) {
|
|
41
|
+
const normalized = normalizeConfig({ ...config, configured: true }, appRoot);
|
|
42
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
43
|
+
fs.mkdirSync(normalized.rootPath, { recursive: true });
|
|
44
|
+
fs.writeFileSync(configPath, JSON.stringify(normalized, null, 2) + '\n', 'utf-8');
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function dayKey(date = new Date()) {
|
|
49
|
+
return date.toISOString().slice(0, 10);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function appendLog(configPath, appRoot, entry) {
|
|
53
|
+
const cfg = readConfig(configPath, appRoot);
|
|
54
|
+
const level = LEVELS.has(entry.level) ? entry.level : 'info';
|
|
55
|
+
if (!cfg.levels.includes(level)) return null;
|
|
56
|
+
fs.mkdirSync(cfg.rootPath, { recursive: true });
|
|
57
|
+
const record = {
|
|
58
|
+
ts: entry.ts || new Date().toISOString(),
|
|
59
|
+
level,
|
|
60
|
+
projectSlug: entry.projectSlug || '',
|
|
61
|
+
source: entry.source || 'app',
|
|
62
|
+
event: entry.event || 'message',
|
|
63
|
+
message: entry.message || '',
|
|
64
|
+
jobId: entry.jobId || '',
|
|
65
|
+
runId: entry.runId || '',
|
|
66
|
+
meta: entry.meta && typeof entry.meta === 'object' ? entry.meta : {},
|
|
67
|
+
};
|
|
68
|
+
const file = path.join(cfg.rootPath, `${dayKey(new Date(record.ts))}.log`);
|
|
69
|
+
fs.appendFileSync(file, JSON.stringify(record) + '\n', 'utf-8');
|
|
70
|
+
return { file, record };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseLogLine(line) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(line);
|
|
76
|
+
if (parsed && parsed.ts && parsed.level && parsed.message != null) return parsed;
|
|
77
|
+
} catch {}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readLogs(configPath, appRoot, filters = {}) {
|
|
82
|
+
const cfg = readConfig(configPath, appRoot);
|
|
83
|
+
let files = [];
|
|
84
|
+
try { files = fs.readdirSync(cfg.rootPath).filter(file => file.endsWith('.log')).sort(); } catch { return []; }
|
|
85
|
+
const dateFrom = filters.dateFrom || '';
|
|
86
|
+
const dateTo = filters.dateTo || '';
|
|
87
|
+
const q = String(filters.q || '').toLowerCase();
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
const date = file.replace(/\.log$/, '');
|
|
91
|
+
if (dateFrom && date < dateFrom) continue;
|
|
92
|
+
if (dateTo && date > dateTo) continue;
|
|
93
|
+
const abs = path.join(cfg.rootPath, file);
|
|
94
|
+
const lines = fs.readFileSync(abs, 'utf-8').split(/\r?\n/).filter(Boolean);
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
const record = parseLogLine(line);
|
|
97
|
+
if (!record) continue;
|
|
98
|
+
if (filters.level && filters.level !== 'all' && record.level !== filters.level) continue;
|
|
99
|
+
if (filters.projectSlug && filters.projectSlug !== 'all' && record.projectSlug !== filters.projectSlug) continue;
|
|
100
|
+
if (filters.source && filters.source !== 'all' && record.source !== filters.source) continue;
|
|
101
|
+
if (q) {
|
|
102
|
+
const haystack = `${record.message} ${record.event} ${record.projectSlug} ${record.source} ${JSON.stringify(record.meta || {})}`.toLowerCase();
|
|
103
|
+
if (!haystack.includes(q)) continue;
|
|
104
|
+
}
|
|
105
|
+
out.push({ ...record, file });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out.sort((a, b) => String(b.ts).localeCompare(String(a.ts))).slice(0, filters.limit || 500);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
SCHEMA,
|
|
113
|
+
LEVELS,
|
|
114
|
+
defaultConfig,
|
|
115
|
+
normalizeConfig,
|
|
116
|
+
readConfig,
|
|
117
|
+
writeConfig,
|
|
118
|
+
appendLog,
|
|
119
|
+
readLogs,
|
|
120
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { scanProject } = require('./scanner');
|
|
4
|
+
const { validateKb } = require('./kb-validator');
|
|
5
|
+
|
|
6
|
+
function issue(id, projectSlug, level, source, message, meta = {}) {
|
|
7
|
+
return {
|
|
8
|
+
issueId: id,
|
|
9
|
+
projectSlug: projectSlug || '',
|
|
10
|
+
level,
|
|
11
|
+
source,
|
|
12
|
+
message,
|
|
13
|
+
meta,
|
|
14
|
+
detectedAt: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function projectIssues(slug, cfg, kbPath) {
|
|
19
|
+
const out = [];
|
|
20
|
+
if (!cfg) return out;
|
|
21
|
+
if (cfg.repoStatus && !['ok', 'empty', 'unknown'].includes(cfg.repoStatus)) {
|
|
22
|
+
out.push(issue(`git-${slug}-${cfg.repoStatus}`, slug, 'error', 'git', `Git status is ${cfg.repoStatus}`, { error: cfg.lastScanError || '' }));
|
|
23
|
+
}
|
|
24
|
+
if (cfg.repoStatus === 'empty') {
|
|
25
|
+
out.push(issue(`git-${slug}-empty`, slug, 'warn', 'git', 'Repository has no commits yet.'));
|
|
26
|
+
}
|
|
27
|
+
if (!fs.existsSync(kbPath)) {
|
|
28
|
+
out.push(issue(`kb-${slug}-missing`, slug, 'error', 'kb', 'Knowledge base directory is missing.', { kbPath }));
|
|
29
|
+
} else {
|
|
30
|
+
const validation = validateKb(kbPath);
|
|
31
|
+
if (!validation.ok) {
|
|
32
|
+
out.push(issue(`kb-${slug}-invalid`, slug, 'error', 'kb', 'Knowledge base validation failed.', {
|
|
33
|
+
errors: validation.errors || [],
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!cfg.lastAnalyzedCommit) {
|
|
38
|
+
out.push(issue(`analysis-${slug}-not-started`, slug, 'warn', 'analysis', 'Project has not been analyzed yet.'));
|
|
39
|
+
}
|
|
40
|
+
if (cfg.goalStatus !== 'accepted') {
|
|
41
|
+
out.push(issue(`goal-${slug}-not-accepted`, slug, 'warn', 'kb', 'Project goal is not accepted yet.'));
|
|
42
|
+
}
|
|
43
|
+
if ((cfg.lastScanPendingCount || 0) > 0) {
|
|
44
|
+
out.push(issue(`analysis-${slug}-pending`, slug, 'warn', 'analysis', `${cfg.lastScanPendingCount} pending commits need analysis.`, {
|
|
45
|
+
pendingCount: cfg.lastScanPendingCount,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function jobIssues(history = []) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const job of history || []) {
|
|
54
|
+
if (!['failed', 'partial'].includes(job.status)) continue;
|
|
55
|
+
out.push(issue(`job-${job.jobId}`, job.slug, job.status === 'failed' ? 'error' : 'warn', 'automation', `${job.mode} job ${job.status}.`, {
|
|
56
|
+
jobId: job.jobId,
|
|
57
|
+
mode: job.mode,
|
|
58
|
+
summary: job.summary || null,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function pendingCommits(projects, resolveKbPath) {
|
|
65
|
+
const items = [];
|
|
66
|
+
for (const [slug, cfg] of Object.entries(projects || {})) {
|
|
67
|
+
if (cfg.enabled === false) continue;
|
|
68
|
+
const scan = await scanProject({ slug, ...cfg }, { maxCommits: 200 });
|
|
69
|
+
items.push({
|
|
70
|
+
slug,
|
|
71
|
+
displayName: cfg.displayName || slug,
|
|
72
|
+
sourcePath: cfg.gitPath || cfg.localPath || '',
|
|
73
|
+
kbPath: resolveKbPath(slug, cfg),
|
|
74
|
+
repoStatus: scan.repoStatus,
|
|
75
|
+
mode: scan.mode,
|
|
76
|
+
range: scan.range,
|
|
77
|
+
headCommit: scan.headCommit,
|
|
78
|
+
lastAnalyzedCommit: scan.lastAnalyzedCommit,
|
|
79
|
+
pendingCount: scan.pendingCount,
|
|
80
|
+
commits: scan.commits || [],
|
|
81
|
+
error: scan.error || null,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function summary(projects, runningJobs, issues) {
|
|
88
|
+
const list = Object.values(projects || {});
|
|
89
|
+
const pending = list.reduce((sum, cfg) => sum + Number(cfg.lastScanPendingCount || 0), 0);
|
|
90
|
+
return {
|
|
91
|
+
projects: list.length,
|
|
92
|
+
runningJobs: (runningJobs || []).length,
|
|
93
|
+
pendingCommits: pending,
|
|
94
|
+
recentIssues: (issues || []).filter(item => ['warn', 'error'].includes(item.level)).length,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
projectIssues,
|
|
100
|
+
jobIssues,
|
|
101
|
+
pendingCommits,
|
|
102
|
+
summary,
|
|
103
|
+
};
|