crewly 1.8.4 → 1.8.6
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/config/roles/_common/wiki-instructions.md +33 -0
- package/config/roles/orchestrator/prompt.md +66 -4
- package/config/roles/team-leader/prompt.md +38 -0
- package/config/skills/agent/core/wiki-query/SKILL.md +66 -0
- package/config/skills/agent/core/wiki-query/execute.sh +107 -0
- package/config/skills/orchestrator/wiki-bookkeep/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-bookkeep/execute.sh +72 -0
- package/config/skills/orchestrator/wiki-ingest/SKILL.md +63 -0
- package/config/skills/orchestrator/wiki-ingest/execute.sh +113 -0
- package/config/skills/orchestrator/wiki-process-queue/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-process-queue/execute.sh +93 -0
- package/config/skills/orchestrator/wiki-queue-add/SKILL.md +89 -0
- package/config/skills/orchestrator/wiki-queue-add/execute.sh +115 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +20 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +15 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +134 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +718 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts +23 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +43 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +65 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.js +162 -4
- package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts +69 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js +174 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts +57 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js +183 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +86 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +187 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts +116 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js +299 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +100 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js +212 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js +138 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js +291 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js +261 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js +10 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js.map +1 -0
- package/frontend/dist/assets/{index-b279da34.js → index-cc115bb4.js} +246 -246
- package/frontend/dist/assets/{index-c07e04c0.css → index-db3f5041.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiBookkeepTriggerService — periodic vault scan that fires a
|
|
3
|
+
* notification to ORC when bookkeeping is due.
|
|
4
|
+
*
|
|
5
|
+
* Per Steve's 2026-05-22 design point #5: "还要时不时让 agent 针对自己的
|
|
6
|
+
* vault 进行 bookkeeping (可以根据存入的 md 数量来 trigger), 譬如过去
|
|
7
|
+
* N 天超过 X 个 md, 然后总结一下."
|
|
8
|
+
*
|
|
9
|
+
* Mechanism:
|
|
10
|
+
* - every `intervalMs` (default 30 min) tick
|
|
11
|
+
* - discover known vaults (project + team + global) by walking known
|
|
12
|
+
* filesystem roots for SCHEMA.md
|
|
13
|
+
* - call `WikiBookkeepService.generate` for each
|
|
14
|
+
* - if `report.shouldFire`, invoke the caller-injected `fireFn` (in
|
|
15
|
+
* production: enqueue a `[BOOKKEEP] vault=…` message to ORC)
|
|
16
|
+
* - debounce per-vault so we don't spam — only refire after
|
|
17
|
+
* `debounceMs` (default 6 h)
|
|
18
|
+
*
|
|
19
|
+
* The service intentionally does NOT do the consolidation itself — it
|
|
20
|
+
* notifies the agent, which then runs `wiki-bookkeep` + `wiki-ingest`
|
|
21
|
+
* (per the orchestrator system prompt rule).
|
|
22
|
+
*
|
|
23
|
+
* @module services/wiki/wiki-bookkeep-trigger.service
|
|
24
|
+
*/
|
|
25
|
+
import * as path from 'path';
|
|
26
|
+
import * as os from 'os';
|
|
27
|
+
import * as fs from 'fs/promises';
|
|
28
|
+
import { existsSync } from 'fs';
|
|
29
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
30
|
+
import { WikiBookkeepService } from './wiki-bookkeep.service.js';
|
|
31
|
+
const DEFAULT_INTERVAL_MS = 30 * 60 * 1000;
|
|
32
|
+
const DEFAULT_DEBOUNCE_MS = 6 * 3600 * 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Discover absolute vault paths by walking the well-known Crewly roots:
|
|
35
|
+
* - `<env CREWLY_PROJECT_VAULT_PATH>` (single explicit override)
|
|
36
|
+
* - `<process.cwd>/.crewly/wiki` (current project vault)
|
|
37
|
+
* - `~/.crewly/teams/<uuid>/wiki` (every team vault)
|
|
38
|
+
* - `~/.crewly/global-wiki` (ORC cross-project vault, if present)
|
|
39
|
+
*
|
|
40
|
+
* A path is only included if `SCHEMA.md` exists inside it.
|
|
41
|
+
*/
|
|
42
|
+
export async function discoverWikiVaults() {
|
|
43
|
+
const found = new Set();
|
|
44
|
+
const candidates = [];
|
|
45
|
+
const fromEnv = process.env['CREWLY_PROJECT_VAULT_PATH'];
|
|
46
|
+
if (fromEnv && path.isAbsolute(fromEnv))
|
|
47
|
+
candidates.push(fromEnv);
|
|
48
|
+
candidates.push(path.join(process.cwd(), '.crewly/wiki'));
|
|
49
|
+
candidates.push(path.join(os.homedir(), '.crewly/global-wiki'));
|
|
50
|
+
const teamsRoot = path.join(os.homedir(), '.crewly/teams');
|
|
51
|
+
if (existsSync(teamsRoot)) {
|
|
52
|
+
try {
|
|
53
|
+
const entries = await fs.readdir(teamsRoot, { withFileTypes: true });
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (!entry.isDirectory())
|
|
56
|
+
continue;
|
|
57
|
+
candidates.push(path.join(teamsRoot, entry.name, 'wiki'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore — partial discovery is fine
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
if (existsSync(path.join(candidate, 'SCHEMA.md'))) {
|
|
66
|
+
found.add(candidate);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [...found].sort();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Periodic vault-bookkeep trigger. Start at boot, stop at shutdown.
|
|
73
|
+
*/
|
|
74
|
+
export class WikiBookkeepTriggerService {
|
|
75
|
+
static instance = null;
|
|
76
|
+
logger;
|
|
77
|
+
intervalMs;
|
|
78
|
+
debounceMs;
|
|
79
|
+
fireFn;
|
|
80
|
+
discoverRoots;
|
|
81
|
+
bookkeepService;
|
|
82
|
+
timer = null;
|
|
83
|
+
/** vaultPath → last fired timestamp (ms). */
|
|
84
|
+
lastFiredAt = new Map();
|
|
85
|
+
/** Per-vault locks so two overlapping ticks don't double-fire. */
|
|
86
|
+
inflight = new Set();
|
|
87
|
+
constructor(opts) {
|
|
88
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiBookkeepTrigger');
|
|
89
|
+
this.intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
90
|
+
this.debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
91
|
+
this.fireFn = opts.fireFn;
|
|
92
|
+
this.discoverRoots = opts.discoverRoots ?? discoverWikiVaults;
|
|
93
|
+
this.bookkeepService = opts.bookkeepService ?? WikiBookkeepService.getInstance();
|
|
94
|
+
}
|
|
95
|
+
static getInstance() {
|
|
96
|
+
return this.instance;
|
|
97
|
+
}
|
|
98
|
+
/** Wire the production singleton. Pass null to detach (tests / shutdown). */
|
|
99
|
+
static setInstance(next) {
|
|
100
|
+
if (this.instance && this.instance !== next)
|
|
101
|
+
this.instance.stop();
|
|
102
|
+
this.instance = next;
|
|
103
|
+
}
|
|
104
|
+
/** Begin scanning. Idempotent. */
|
|
105
|
+
start() {
|
|
106
|
+
if (this.timer)
|
|
107
|
+
return;
|
|
108
|
+
this.timer = setInterval(() => void this.tick(), this.intervalMs);
|
|
109
|
+
// Don't keep the event loop alive just for bookkeeping.
|
|
110
|
+
this.timer.unref?.();
|
|
111
|
+
this.logger.info('WikiBookkeepTrigger started', {
|
|
112
|
+
intervalMs: this.intervalMs,
|
|
113
|
+
debounceMs: this.debounceMs,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/** Stop scanning. Safe to call multiple times. */
|
|
117
|
+
stop() {
|
|
118
|
+
if (this.timer) {
|
|
119
|
+
clearInterval(this.timer);
|
|
120
|
+
this.timer = null;
|
|
121
|
+
this.logger.info('WikiBookkeepTrigger stopped');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run one scan pass. Public for test + the manual
|
|
126
|
+
* `/api/wiki/bookkeep/trigger-now` endpoint.
|
|
127
|
+
*/
|
|
128
|
+
async tick() {
|
|
129
|
+
const vaults = await this.discoverRoots();
|
|
130
|
+
const result = {
|
|
131
|
+
scanned: [...vaults],
|
|
132
|
+
fired: [],
|
|
133
|
+
skippedByDebounce: [],
|
|
134
|
+
quietVaults: [],
|
|
135
|
+
};
|
|
136
|
+
for (const v of vaults) {
|
|
137
|
+
if (this.inflight.has(v))
|
|
138
|
+
continue;
|
|
139
|
+
this.inflight.add(v);
|
|
140
|
+
try {
|
|
141
|
+
const outcome = await this.bookkeepService.generate({ vaultPath: v });
|
|
142
|
+
if (!outcome.ok) {
|
|
143
|
+
this.logger.warn('WikiBookkeepTrigger: bookkeep failed for vault', {
|
|
144
|
+
vault: v,
|
|
145
|
+
reason: outcome.reason,
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!outcome.report.shouldFire) {
|
|
150
|
+
result.quietVaults.push(v);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const last = this.lastFiredAt.get(v) ?? 0;
|
|
154
|
+
if (Date.now() - last < this.debounceMs) {
|
|
155
|
+
result.skippedByDebounce.push(v);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
this.lastFiredAt.set(v, Date.now());
|
|
159
|
+
try {
|
|
160
|
+
await this.fireFn(v, outcome.report);
|
|
161
|
+
result.fired.push(v);
|
|
162
|
+
this.logger.info('WikiBookkeepTrigger fired', {
|
|
163
|
+
vault: v,
|
|
164
|
+
recentMd: outcome.report.recentMdCount,
|
|
165
|
+
threshold: outcome.report.threshold,
|
|
166
|
+
duplicates: outcome.report.duplicateCandidates.length,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
this.logger.warn('WikiBookkeepTrigger: fireFn threw (swallowed)', {
|
|
171
|
+
vault: v,
|
|
172
|
+
error: err.message,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
this.inflight.delete(v);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/** Test affordance: clear the debounce ledger. */
|
|
183
|
+
_resetDebounceForTesting() {
|
|
184
|
+
this.lastFiredAt.clear();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=wiki-bookkeep-trigger.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-bookkeep-trigger.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-bookkeep-trigger.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiBookkeepService — vault health metrics + consolidation signals.
|
|
3
|
+
*
|
|
4
|
+
* Per Steve's 2026-05-22 design point #5: "还要时不时让 agent 针对自己的
|
|
5
|
+
* vault 进行 bookkeeping (可以根据存入的 md 数量来 trigger), 譬如过去 N
|
|
6
|
+
* 天超过 X 个 md, 然后总结一下."
|
|
7
|
+
*
|
|
8
|
+
* This service produces the report the bookkeeping agent reads:
|
|
9
|
+
* - md count per top-level llm-curated subfolder
|
|
10
|
+
* - new mds in the last N days
|
|
11
|
+
* - filename clusters (likely-duplicates by Jaccard of token sets)
|
|
12
|
+
* - queue stats (so the agent sees pending items too)
|
|
13
|
+
*
|
|
14
|
+
* The agent uses this report (+ its LLM) to decide:
|
|
15
|
+
* - which pages to consolidate / dedupe (calls wiki-ingest with a new
|
|
16
|
+
* consolidated body + deletes the originals via a separate skill —
|
|
17
|
+
* deletion is OUT of scope for Phase 1; surface as proposals).
|
|
18
|
+
* - which topics deserve a summary "rollup" page.
|
|
19
|
+
*
|
|
20
|
+
* The service does NOT delete or rewrite anything. It reports.
|
|
21
|
+
*
|
|
22
|
+
* @module services/wiki/wiki-bookkeep.service
|
|
23
|
+
*/
|
|
24
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
25
|
+
import { WikiQueueService } from './wiki-queue.service.js';
|
|
26
|
+
export interface WikiBookkeepInput {
|
|
27
|
+
/** Absolute vault path. */
|
|
28
|
+
vaultPath: string;
|
|
29
|
+
/** Window for "recent activity" stats. Default 7 days. */
|
|
30
|
+
windowDays?: number;
|
|
31
|
+
/** Md-count threshold used by `shouldFire` (default 10). */
|
|
32
|
+
threshold?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface WikiPageRef {
|
|
35
|
+
path: string;
|
|
36
|
+
bytes: number;
|
|
37
|
+
modifiedAt: string;
|
|
38
|
+
}
|
|
39
|
+
export interface WikiDuplicateCluster {
|
|
40
|
+
/** Shared base — the longest filename prefix across the cluster. */
|
|
41
|
+
basis: string;
|
|
42
|
+
/** Member pages (relative paths). */
|
|
43
|
+
pages: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface WikiBookkeepReport {
|
|
46
|
+
vault: {
|
|
47
|
+
scope: string;
|
|
48
|
+
id: string;
|
|
49
|
+
path: string;
|
|
50
|
+
};
|
|
51
|
+
generatedAt: string;
|
|
52
|
+
windowDays: number;
|
|
53
|
+
threshold: number;
|
|
54
|
+
/** Did we cross the threshold for "trigger a consolidation pass"? */
|
|
55
|
+
shouldFire: boolean;
|
|
56
|
+
/** Total md files under the vault (recursive). */
|
|
57
|
+
totalMdCount: number;
|
|
58
|
+
/** Newly created/touched mds within `windowDays`. */
|
|
59
|
+
recentMdCount: number;
|
|
60
|
+
/** Per-folder counts under llm-curated/<x>/. */
|
|
61
|
+
countsByFolder: Record<string, number>;
|
|
62
|
+
/** Likely-duplicate clusters by filename Jaccard. */
|
|
63
|
+
duplicateCandidates: WikiDuplicateCluster[];
|
|
64
|
+
/** Mds that haven't been touched in 90 days — stale candidates. */
|
|
65
|
+
staleCount: number;
|
|
66
|
+
/** Queue snapshot for this vault. */
|
|
67
|
+
queue: {
|
|
68
|
+
pending: number;
|
|
69
|
+
claimed: number;
|
|
70
|
+
processed: number;
|
|
71
|
+
skipped: number;
|
|
72
|
+
total: number;
|
|
73
|
+
};
|
|
74
|
+
/** Recommendations the agent's LLM should act on. */
|
|
75
|
+
recommendations: string[];
|
|
76
|
+
}
|
|
77
|
+
export type WikiBookkeepOutcome = {
|
|
78
|
+
ok: true;
|
|
79
|
+
report: WikiBookkeepReport;
|
|
80
|
+
} | {
|
|
81
|
+
ok: false;
|
|
82
|
+
reason: 'invalid_input' | 'schema_missing' | 'vault_missing';
|
|
83
|
+
message: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Vault health + bookkeeping report generator. Stateless; uses the
|
|
87
|
+
* schema loader + queue services it composes.
|
|
88
|
+
*/
|
|
89
|
+
export declare class WikiBookkeepService {
|
|
90
|
+
private static instance;
|
|
91
|
+
private readonly logger;
|
|
92
|
+
private readonly schemaLoader;
|
|
93
|
+
private readonly queue;
|
|
94
|
+
constructor(schemaLoader?: SchemaLoaderService, queue?: WikiQueueService);
|
|
95
|
+
static getInstance(): WikiBookkeepService;
|
|
96
|
+
/** Test-only reset. */
|
|
97
|
+
static _resetForTesting(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Build the bookkeeping report. Caller (skill / cron / agent) decides
|
|
100
|
+
* whether to act on `shouldFire`. The service never writes; it reports.
|
|
101
|
+
*/
|
|
102
|
+
generate(input: WikiBookkeepInput): Promise<WikiBookkeepOutcome>;
|
|
103
|
+
private collectPages;
|
|
104
|
+
/**
|
|
105
|
+
* Find filename clusters by Jaccard over name tokens.
|
|
106
|
+
* Two pages with ≥ 0.6 overlap on their non-trivial filename tokens
|
|
107
|
+
* are flagged as potential duplicates. Token = anything ≥ 3 chars
|
|
108
|
+
* after splitting on `[-_./]`, lowercased, minus date prefix.
|
|
109
|
+
*/
|
|
110
|
+
private findDuplicateClusters;
|
|
111
|
+
private tokenizeFilename;
|
|
112
|
+
private jaccard;
|
|
113
|
+
private longestCommonPrefix;
|
|
114
|
+
private buildRecommendations;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=wiki-bookkeep.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-bookkeep.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-bookkeep.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAMH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAM3D,MAAM,WAAW,iBAAiB;IAChC,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,UAAU,EAAE,OAAO,CAAC;IACpB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,qDAAqD;IACrD,mBAAmB,EAAE,oBAAoB,EAAE,CAAC;IAC5C,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,qDAAqD;IACrD,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,GACxC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjG;;;GAGG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;gBAGvC,YAAY,CAAC,EAAE,mBAAmB,EAClC,KAAK,CAAC,EAAE,gBAAgB;IAO1B,MAAM,CAAC,WAAW,IAAI,mBAAmB;IAKzC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAwGxD,YAAY;IA2C1B;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA6B7B,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,oBAAoB;CA8C7B"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiBookkeepService — vault health metrics + consolidation signals.
|
|
3
|
+
*
|
|
4
|
+
* Per Steve's 2026-05-22 design point #5: "还要时不时让 agent 针对自己的
|
|
5
|
+
* vault 进行 bookkeeping (可以根据存入的 md 数量来 trigger), 譬如过去 N
|
|
6
|
+
* 天超过 X 个 md, 然后总结一下."
|
|
7
|
+
*
|
|
8
|
+
* This service produces the report the bookkeeping agent reads:
|
|
9
|
+
* - md count per top-level llm-curated subfolder
|
|
10
|
+
* - new mds in the last N days
|
|
11
|
+
* - filename clusters (likely-duplicates by Jaccard of token sets)
|
|
12
|
+
* - queue stats (so the agent sees pending items too)
|
|
13
|
+
*
|
|
14
|
+
* The agent uses this report (+ its LLM) to decide:
|
|
15
|
+
* - which pages to consolidate / dedupe (calls wiki-ingest with a new
|
|
16
|
+
* consolidated body + deletes the originals via a separate skill —
|
|
17
|
+
* deletion is OUT of scope for Phase 1; surface as proposals).
|
|
18
|
+
* - which topics deserve a summary "rollup" page.
|
|
19
|
+
*
|
|
20
|
+
* The service does NOT delete or rewrite anything. It reports.
|
|
21
|
+
*
|
|
22
|
+
* @module services/wiki/wiki-bookkeep.service
|
|
23
|
+
*/
|
|
24
|
+
import * as path from 'path';
|
|
25
|
+
import * as fs from 'fs/promises';
|
|
26
|
+
import { existsSync } from 'fs';
|
|
27
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
28
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
29
|
+
import { WikiQueueService } from './wiki-queue.service.js';
|
|
30
|
+
const DEFAULT_WINDOW_DAYS = 7;
|
|
31
|
+
const DEFAULT_MD_THRESHOLD = 10;
|
|
32
|
+
const MAX_PAGES_SCANNED = 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Vault health + bookkeeping report generator. Stateless; uses the
|
|
35
|
+
* schema loader + queue services it composes.
|
|
36
|
+
*/
|
|
37
|
+
export class WikiBookkeepService {
|
|
38
|
+
static instance = null;
|
|
39
|
+
logger;
|
|
40
|
+
schemaLoader;
|
|
41
|
+
queue;
|
|
42
|
+
constructor(schemaLoader, queue) {
|
|
43
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiBookkeep');
|
|
44
|
+
this.schemaLoader = schemaLoader ?? new SchemaLoaderService();
|
|
45
|
+
this.queue = queue ?? WikiQueueService.getInstance();
|
|
46
|
+
}
|
|
47
|
+
static getInstance() {
|
|
48
|
+
if (!this.instance)
|
|
49
|
+
this.instance = new WikiBookkeepService();
|
|
50
|
+
return this.instance;
|
|
51
|
+
}
|
|
52
|
+
/** Test-only reset. */
|
|
53
|
+
static _resetForTesting() {
|
|
54
|
+
this.instance = null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the bookkeeping report. Caller (skill / cron / agent) decides
|
|
58
|
+
* whether to act on `shouldFire`. The service never writes; it reports.
|
|
59
|
+
*/
|
|
60
|
+
async generate(input) {
|
|
61
|
+
if (!input.vaultPath || !path.isAbsolute(input.vaultPath)) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
reason: 'invalid_input',
|
|
65
|
+
message: 'vaultPath must be absolute',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!existsSync(input.vaultPath)) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
reason: 'vault_missing',
|
|
72
|
+
message: `vault directory does not exist: ${input.vaultPath}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
let schema;
|
|
76
|
+
try {
|
|
77
|
+
schema = await this.schemaLoader.load(input.vaultPath);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: 'schema_missing',
|
|
83
|
+
message: err.message,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
87
|
+
const threshold = input.threshold ?? DEFAULT_MD_THRESHOLD;
|
|
88
|
+
const windowMs = windowDays * 24 * 3600 * 1000;
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const staleMs = 90 * 24 * 3600 * 1000;
|
|
91
|
+
const pages = await this.collectPages(input.vaultPath, schema.llm_curated.map((l) => l.path));
|
|
92
|
+
const totalMdCount = pages.length;
|
|
93
|
+
let recentMdCount = 0;
|
|
94
|
+
let staleCount = 0;
|
|
95
|
+
const countsByFolder = {};
|
|
96
|
+
for (const p of pages) {
|
|
97
|
+
const mtime = Date.parse(p.modifiedAt);
|
|
98
|
+
if (Number.isFinite(mtime)) {
|
|
99
|
+
if (now - mtime <= windowMs)
|
|
100
|
+
recentMdCount++;
|
|
101
|
+
if (now - mtime > staleMs)
|
|
102
|
+
staleCount++;
|
|
103
|
+
}
|
|
104
|
+
// Folder bucket: first directory segment under llm-curated/.
|
|
105
|
+
const rel = p.path;
|
|
106
|
+
const parts = rel.split('/').filter(Boolean);
|
|
107
|
+
// rel looks like "llm-curated/<folder>/<page>.md" → bucket = "llm-curated/<folder>".
|
|
108
|
+
if (parts.length >= 2) {
|
|
109
|
+
const bucket = `${parts[0]}/${parts[1]}`;
|
|
110
|
+
countsByFolder[bucket] = (countsByFolder[bucket] ?? 0) + 1;
|
|
111
|
+
}
|
|
112
|
+
else if (parts.length === 1) {
|
|
113
|
+
const bucket = parts[0];
|
|
114
|
+
countsByFolder[bucket] = (countsByFolder[bucket] ?? 0) + 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const duplicateCandidates = this.findDuplicateClusters(pages);
|
|
118
|
+
const queueStats = await this.queue.getStats(input.vaultPath);
|
|
119
|
+
const shouldFire = recentMdCount >= threshold || duplicateCandidates.length > 0;
|
|
120
|
+
const recommendations = this.buildRecommendations({
|
|
121
|
+
shouldFire,
|
|
122
|
+
recentMdCount,
|
|
123
|
+
threshold,
|
|
124
|
+
duplicates: duplicateCandidates,
|
|
125
|
+
countsByFolder,
|
|
126
|
+
staleCount,
|
|
127
|
+
queueStats,
|
|
128
|
+
});
|
|
129
|
+
const report = {
|
|
130
|
+
vault: {
|
|
131
|
+
scope: schema.vault_scope,
|
|
132
|
+
id: schema.vault_id,
|
|
133
|
+
path: input.vaultPath,
|
|
134
|
+
},
|
|
135
|
+
generatedAt: new Date().toISOString(),
|
|
136
|
+
windowDays,
|
|
137
|
+
threshold,
|
|
138
|
+
shouldFire,
|
|
139
|
+
totalMdCount,
|
|
140
|
+
recentMdCount,
|
|
141
|
+
countsByFolder,
|
|
142
|
+
duplicateCandidates,
|
|
143
|
+
staleCount,
|
|
144
|
+
queue: queueStats,
|
|
145
|
+
recommendations,
|
|
146
|
+
};
|
|
147
|
+
this.logger.info('WikiBookkeep generated', {
|
|
148
|
+
vault: input.vaultPath,
|
|
149
|
+
totalMd: totalMdCount,
|
|
150
|
+
recentMd: recentMdCount,
|
|
151
|
+
duplicates: duplicateCandidates.length,
|
|
152
|
+
shouldFire,
|
|
153
|
+
});
|
|
154
|
+
return { ok: true, report };
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
async collectPages(vaultPath, llmCuratedFolders) {
|
|
158
|
+
const pages = [];
|
|
159
|
+
let scanned = 0;
|
|
160
|
+
for (const folder of llmCuratedFolders) {
|
|
161
|
+
const root = path.join(vaultPath, folder.replace(/[/\\]+$/, ''));
|
|
162
|
+
if (!existsSync(root))
|
|
163
|
+
continue;
|
|
164
|
+
const stack = [root];
|
|
165
|
+
while (stack.length > 0 && scanned < MAX_PAGES_SCANNED) {
|
|
166
|
+
const current = stack.pop();
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (entry.name.startsWith('.'))
|
|
176
|
+
continue;
|
|
177
|
+
const full = path.join(current, entry.name);
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
stack.push(full);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
183
|
+
continue;
|
|
184
|
+
scanned++;
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs.stat(full);
|
|
187
|
+
pages.push({
|
|
188
|
+
path: path.relative(vaultPath, full).replace(/\\/g, '/'),
|
|
189
|
+
bytes: stat.size,
|
|
190
|
+
modifiedAt: new Date(stat.mtimeMs).toISOString(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// ignore
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return pages;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Find filename clusters by Jaccard over name tokens.
|
|
203
|
+
* Two pages with ≥ 0.6 overlap on their non-trivial filename tokens
|
|
204
|
+
* are flagged as potential duplicates. Token = anything ≥ 3 chars
|
|
205
|
+
* after splitting on `[-_./]`, lowercased, minus date prefix.
|
|
206
|
+
*/
|
|
207
|
+
findDuplicateClusters(pages) {
|
|
208
|
+
if (pages.length < 2)
|
|
209
|
+
return [];
|
|
210
|
+
const tokens = pages.map((p) => ({
|
|
211
|
+
path: p.path,
|
|
212
|
+
tokens: this.tokenizeFilename(p.path),
|
|
213
|
+
}));
|
|
214
|
+
const used = new Set();
|
|
215
|
+
const clusters = [];
|
|
216
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
217
|
+
if (used.has(i))
|
|
218
|
+
continue;
|
|
219
|
+
const peers = [i];
|
|
220
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
221
|
+
if (used.has(j))
|
|
222
|
+
continue;
|
|
223
|
+
if (this.jaccard(tokens[i].tokens, tokens[j].tokens) >= 0.6) {
|
|
224
|
+
peers.push(j);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (peers.length >= 2) {
|
|
228
|
+
peers.forEach((p) => used.add(p));
|
|
229
|
+
const memberPaths = peers.map((p) => tokens[p].path);
|
|
230
|
+
clusters.push({
|
|
231
|
+
basis: this.longestCommonPrefix(memberPaths),
|
|
232
|
+
pages: memberPaths,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return clusters;
|
|
237
|
+
}
|
|
238
|
+
tokenizeFilename(relPath) {
|
|
239
|
+
const basename = relPath.split('/').pop()?.replace(/\.md$/, '') ?? '';
|
|
240
|
+
// Strip ISO-date prefix (YYYY-MM-DD-)
|
|
241
|
+
const withoutDate = basename.replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
242
|
+
const tokens = withoutDate
|
|
243
|
+
.toLowerCase()
|
|
244
|
+
.split(/[-_./\s]+/)
|
|
245
|
+
.filter((t) => t.length >= 3);
|
|
246
|
+
return new Set(tokens);
|
|
247
|
+
}
|
|
248
|
+
jaccard(a, b) {
|
|
249
|
+
if (a.size === 0 && b.size === 0)
|
|
250
|
+
return 0;
|
|
251
|
+
let inter = 0;
|
|
252
|
+
for (const x of a)
|
|
253
|
+
if (b.has(x))
|
|
254
|
+
inter++;
|
|
255
|
+
const union = a.size + b.size - inter;
|
|
256
|
+
return union === 0 ? 0 : inter / union;
|
|
257
|
+
}
|
|
258
|
+
longestCommonPrefix(paths) {
|
|
259
|
+
if (paths.length === 0)
|
|
260
|
+
return '';
|
|
261
|
+
let prefix = paths[0];
|
|
262
|
+
for (const p of paths.slice(1)) {
|
|
263
|
+
while (p.indexOf(prefix) !== 0) {
|
|
264
|
+
prefix = prefix.slice(0, -1);
|
|
265
|
+
if (!prefix)
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return prefix;
|
|
270
|
+
}
|
|
271
|
+
buildRecommendations(args) {
|
|
272
|
+
const recs = [];
|
|
273
|
+
if (!args.shouldFire) {
|
|
274
|
+
recs.push(`Quiet vault — only ${args.recentMdCount} new md(s) in the window (threshold ${args.threshold}). No action required.`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
recs.push(`Window has ${args.recentMdCount} new md(s) — at or above threshold (${args.threshold}). Consider a consolidation pass.`);
|
|
278
|
+
}
|
|
279
|
+
if (args.duplicates.length > 0) {
|
|
280
|
+
recs.push(`${args.duplicates.length} likely-duplicate cluster(s) detected. Review each with your LLM and call wiki-ingest with a consolidated body when you can merge.`);
|
|
281
|
+
args.duplicates.slice(0, 3).forEach((c) => {
|
|
282
|
+
recs.push(` • cluster "${c.basis}": ${c.pages.join(', ')}`);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (args.queueStats.pending > 5) {
|
|
286
|
+
recs.push(`${args.queueStats.pending} pending queue items. Drain via wiki-process-queue before doing other bookkeeping.`);
|
|
287
|
+
}
|
|
288
|
+
const folderEntries = Object.entries(args.countsByFolder).sort((a, b) => b[1] - a[1]);
|
|
289
|
+
if (folderEntries.length > 0) {
|
|
290
|
+
const top = folderEntries[0];
|
|
291
|
+
recs.push(`Hottest folder: \`${top[0]}\` (${top[1]} pages). If it's grown >20 pages without a rollup, write a summary index page.`);
|
|
292
|
+
}
|
|
293
|
+
if (args.staleCount > 0) {
|
|
294
|
+
recs.push(`${args.staleCount} page(s) untouched in 90+ days. Consider whether they're still authoritative — flag stale or archive.`);
|
|
295
|
+
}
|
|
296
|
+
return recs;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=wiki-bookkeep.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-bookkeep.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-bookkeep.service.ts"],"names":[],"mappings":"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"}
|