crewly 1.8.4 → 1.8.5

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 (75) hide show
  1. package/config/roles/_common/wiki-instructions.md +33 -0
  2. package/config/roles/orchestrator/prompt.md +35 -0
  3. package/config/roles/team-leader/prompt.md +38 -0
  4. package/config/skills/agent/core/wiki-query/SKILL.md +66 -0
  5. package/config/skills/agent/core/wiki-query/execute.sh +107 -0
  6. package/config/skills/orchestrator/wiki-bookkeep/SKILL.md +71 -0
  7. package/config/skills/orchestrator/wiki-bookkeep/execute.sh +72 -0
  8. package/config/skills/orchestrator/wiki-ingest/SKILL.md +63 -0
  9. package/config/skills/orchestrator/wiki-ingest/execute.sh +113 -0
  10. package/config/skills/orchestrator/wiki-process-queue/SKILL.md +71 -0
  11. package/config/skills/orchestrator/wiki-process-queue/execute.sh +93 -0
  12. package/config/skills/orchestrator/wiki-queue-add/SKILL.md +89 -0
  13. package/config/skills/orchestrator/wiki-queue-add/execute.sh +115 -0
  14. package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +134 -0
  15. package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -0
  16. package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +718 -0
  17. package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -0
  18. package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts +23 -0
  19. package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -0
  20. package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +43 -0
  21. package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -0
  22. package/dist/backend/backend/src/index.d.ts.map +1 -1
  23. package/dist/backend/backend/src/index.js +39 -0
  24. package/dist/backend/backend/src/index.js.map +1 -1
  25. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  26. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  27. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  28. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
  29. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/session/pty/pty-session.js +162 -4
  31. package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
  32. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts +69 -0
  33. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts.map +1 -0
  34. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js +174 -0
  35. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js.map +1 -0
  36. package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts +57 -0
  37. package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts.map +1 -0
  38. package/dist/backend/backend/src/services/wiki/schema-loader.service.js +183 -0
  39. package/dist/backend/backend/src/services/wiki/schema-loader.service.js.map +1 -0
  40. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +86 -0
  41. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +187 -0
  43. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -0
  44. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts +116 -0
  45. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js +299 -0
  47. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js.map +1 -0
  48. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
  49. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
  51. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
  52. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +100 -0
  53. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js +212 -0
  55. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -0
  56. package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts +84 -0
  57. package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts.map +1 -0
  58. package/dist/backend/backend/src/services/wiki/wiki-process.service.js +138 -0
  59. package/dist/backend/backend/src/services/wiki/wiki-process.service.js.map +1 -0
  60. package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts +115 -0
  61. package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts.map +1 -0
  62. package/dist/backend/backend/src/services/wiki/wiki-query.service.js +291 -0
  63. package/dist/backend/backend/src/services/wiki/wiki-query.service.js.map +1 -0
  64. package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts +115 -0
  65. package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts.map +1 -0
  66. package/dist/backend/backend/src/services/wiki/wiki-queue.service.js +261 -0
  67. package/dist/backend/backend/src/services/wiki/wiki-queue.service.js.map +1 -0
  68. package/dist/backend/backend/src/services/wiki/wiki.types.d.ts +84 -0
  69. package/dist/backend/backend/src/services/wiki/wiki.types.d.ts.map +1 -0
  70. package/dist/backend/backend/src/services/wiki/wiki.types.js +10 -0
  71. package/dist/backend/backend/src/services/wiki/wiki.types.js.map +1 -0
  72. package/frontend/dist/assets/{index-b279da34.js → index-cc115bb4.js} +246 -246
  73. package/frontend/dist/assets/{index-c07e04c0.css → index-db3f5041.css} +1 -1
  74. package/frontend/dist/index.html +2 -2
  75. package/package.json +1 -1
@@ -0,0 +1,183 @@
1
+ /**
2
+ * SCHEMA.md loader + validator + frozen-path gate.
3
+ *
4
+ * Reads `<vault>/SCHEMA.md` (pure YAML inside a .md container), validates
5
+ * shape against `VaultSchema`, and exposes the frozen-path predicate used
6
+ * by `wiki-lint` to refuse restructure operations on OSS-pinned folders.
7
+ *
8
+ * Per v2.1 spec §2 (Hybrid folder architecture) — the `frozen: true` block
9
+ * is a HARD contract: any lint or ingest operation that targets a frozen
10
+ * path MUST fail closed.
11
+ *
12
+ * @module services/wiki/schema-loader.service
13
+ */
14
+ import * as path from 'path';
15
+ import * as fs from 'fs/promises';
16
+ import { parse as parseYAML } from 'yaml';
17
+ /** Standard schema filename inside any vault root. */
18
+ export const SCHEMA_FILENAME = 'SCHEMA.md';
19
+ /**
20
+ * Loads + validates a vault's SCHEMA.md. Stateless — instantiate once
21
+ * per service or use the static helper.
22
+ */
23
+ export class SchemaLoaderService {
24
+ /**
25
+ * Load and validate the SCHEMA.md inside a vault directory.
26
+ *
27
+ * @param vaultPath - Absolute path to the vault root (the directory
28
+ * containing SCHEMA.md). Must be absolute.
29
+ * @returns Validated VaultSchema.
30
+ * @throws If vaultPath is not absolute, SCHEMA.md is missing, YAML
31
+ * parsing fails, or the parsed object is shape-invalid.
32
+ */
33
+ async load(vaultPath) {
34
+ if (!path.isAbsolute(vaultPath)) {
35
+ throw new Error(`SchemaLoader: vaultPath must be absolute, got "${vaultPath}"`);
36
+ }
37
+ const schemaPath = path.join(vaultPath, SCHEMA_FILENAME);
38
+ let raw;
39
+ try {
40
+ raw = await fs.readFile(schemaPath, 'utf8');
41
+ }
42
+ catch (err) {
43
+ throw new Error(`SchemaLoader: SCHEMA.md not found at ${schemaPath} (${err.message})`);
44
+ }
45
+ let parsed;
46
+ try {
47
+ parsed = parseYAML(raw);
48
+ }
49
+ catch (err) {
50
+ throw new Error(`SchemaLoader: invalid YAML in ${schemaPath}: ${err.message}`);
51
+ }
52
+ return this.validate(parsed, schemaPath);
53
+ }
54
+ /**
55
+ * Determine whether a path inside a vault is frozen (OSS-pinned).
56
+ *
57
+ * Used by `wiki-lint` and `wiki-ingest` to refuse restructure / write
58
+ * operations. Comparison is by leading path segment — e.g. `sop/foo.md`
59
+ * is frozen if `sop/` is declared frozen.
60
+ *
61
+ * @param schema - The vault's parsed schema.
62
+ * @param relativePath - Path relative to vault root, e.g. `sop/team-1.md`.
63
+ * @returns true if the path lives under a frozen folder.
64
+ */
65
+ isFrozenPath(schema, relativePath) {
66
+ const normalized = relativePath.replace(/^[/\\]+/, '');
67
+ return schema.hardcoded.some((folder) => {
68
+ // Match either exact folder or anything beneath it.
69
+ const folderName = folder.path.replace(/[/\\]+$/, '');
70
+ return normalized === folderName || normalized.startsWith(`${folderName}/`);
71
+ });
72
+ }
73
+ /**
74
+ * Return every frozen folder path declared in a schema. Convenience for
75
+ * lint reports.
76
+ */
77
+ getFrozenPaths(schema) {
78
+ return schema.hardcoded.map((f) => f.path);
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Validation
82
+ // ---------------------------------------------------------------------------
83
+ validate(parsed, source) {
84
+ if (typeof parsed !== 'object' || parsed === null) {
85
+ throw new Error(`SchemaLoader: ${source} did not parse to an object`);
86
+ }
87
+ const p = parsed;
88
+ const vault_scope = this.requireScope(p.vault_scope, source);
89
+ const vault_id = this.requireString(p.vault_id, 'vault_id', source);
90
+ const hardcoded = this.requireHardcoded(p.hardcoded, source);
91
+ const llm_curated = this.requireLlmCurated(p.llm_curated, source);
92
+ const write_policy = this.requireWritePolicy(p.write_policy, source);
93
+ return { vault_scope, vault_id, hardcoded, llm_curated, write_policy };
94
+ }
95
+ requireScope(raw, source) {
96
+ if (raw !== 'team' && raw !== 'project' && raw !== 'global') {
97
+ throw new Error(`SchemaLoader: ${source} has invalid vault_scope "${raw}" — expected "team" | "project" | "global"`);
98
+ }
99
+ return raw;
100
+ }
101
+ requireString(raw, field, source) {
102
+ if (typeof raw !== 'string' || raw.length === 0) {
103
+ throw new Error(`SchemaLoader: ${source} missing or empty "${field}"`);
104
+ }
105
+ return raw;
106
+ }
107
+ requireHardcoded(raw, source) {
108
+ if (!Array.isArray(raw)) {
109
+ throw new Error(`SchemaLoader: ${source} "hardcoded" must be an array`);
110
+ }
111
+ return raw.map((entry, idx) => {
112
+ if (typeof entry !== 'object' || entry === null) {
113
+ throw new Error(`SchemaLoader: ${source} hardcoded[${idx}] is not an object`);
114
+ }
115
+ const e = entry;
116
+ const path = this.requireString(e.path, `hardcoded[${idx}].path`, source);
117
+ if (e.frozen !== true) {
118
+ throw new Error(`SchemaLoader: ${source} hardcoded[${idx}] must have frozen: true (got ${JSON.stringify(e.frozen)})`);
119
+ }
120
+ const description = this.requireString(e.description, `hardcoded[${idx}].description`, source);
121
+ const refs = this.requireStringArray(e.referenced_by, `hardcoded[${idx}].referenced_by`, source);
122
+ // Each ref must look like `<kind>:<name>` — validated lightly here so
123
+ // a malformed registry doesn't reach the resolver.
124
+ refs.forEach((ref, refIdx) => {
125
+ if (!/^(skill|service):.+$/.test(ref)) {
126
+ throw new Error(`SchemaLoader: ${source} hardcoded[${idx}].referenced_by[${refIdx}] must match "skill:<name>" or "service:<name>" (got "${ref}")`);
127
+ }
128
+ });
129
+ return {
130
+ path,
131
+ frozen: true,
132
+ description,
133
+ referenced_by: refs,
134
+ };
135
+ });
136
+ }
137
+ requireLlmCurated(raw, source) {
138
+ if (!Array.isArray(raw)) {
139
+ throw new Error(`SchemaLoader: ${source} "llm_curated" must be an array`);
140
+ }
141
+ return raw.map((entry, idx) => {
142
+ if (typeof entry !== 'object' || entry === null) {
143
+ throw new Error(`SchemaLoader: ${source} llm_curated[${idx}] is not an object`);
144
+ }
145
+ const e = entry;
146
+ const path = this.requireString(e.path, `llm_curated[${idx}].path`, source);
147
+ if (e.frozen !== false) {
148
+ throw new Error(`SchemaLoader: ${source} llm_curated[${idx}] must have frozen: false (got ${JSON.stringify(e.frozen)})`);
149
+ }
150
+ const seed_subdirs = this.requireStringArray(e.seed_subdirs, `llm_curated[${idx}].seed_subdirs`, source);
151
+ return {
152
+ path,
153
+ frozen: false,
154
+ seed_subdirs,
155
+ llm_can_create_subdirs: Boolean(e.llm_can_create_subdirs),
156
+ lint_may_restructure: Boolean(e.lint_may_restructure),
157
+ };
158
+ });
159
+ }
160
+ requireWritePolicy(raw, source) {
161
+ if (typeof raw !== 'object' || raw === null) {
162
+ throw new Error(`SchemaLoader: ${source} "write_policy" must be an object`);
163
+ }
164
+ const w = raw;
165
+ return {
166
+ canonical: this.requireStringArray(w.canonical, 'write_policy.canonical', source),
167
+ proposed_only: this.requireStringArray(w.proposed_only, 'write_policy.proposed_only', source),
168
+ schema_writer: this.requireStringArray(w.schema_writer, 'write_policy.schema_writer', source),
169
+ };
170
+ }
171
+ requireStringArray(raw, field, source) {
172
+ if (!Array.isArray(raw)) {
173
+ throw new Error(`SchemaLoader: ${source} "${field}" must be an array`);
174
+ }
175
+ raw.forEach((item, idx) => {
176
+ if (typeof item !== 'string' || item.length === 0) {
177
+ throw new Error(`SchemaLoader: ${source} "${field}[${idx}]" must be a non-empty string`);
178
+ }
179
+ });
180
+ return raw;
181
+ }
182
+ }
183
+ //# sourceMappingURL=schema-loader.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-loader.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/schema-loader.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
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 { WikiBookkeepService, WikiBookkeepReport } from './wiki-bookkeep.service.js';
26
+ export type WikiBookkeepFireFn = (vaultPath: string, report: WikiBookkeepReport) => Promise<void> | void;
27
+ export interface WikiBookkeepTriggerOptions {
28
+ /** Interval between scans, ms. Default 30 minutes. */
29
+ intervalMs?: number;
30
+ /** Minimum gap between fires for the same vault, ms. Default 6 hours. */
31
+ debounceMs?: number;
32
+ /** Caller-injected notifier. Production wires this to enqueue a message to ORC. */
33
+ fireFn: WikiBookkeepFireFn;
34
+ /** Optional override of the discovery roots (tests). */
35
+ discoverRoots?: () => Promise<string[]>;
36
+ /** Optional override of the bookkeep service (tests). */
37
+ bookkeepService?: WikiBookkeepService;
38
+ }
39
+ /**
40
+ * Discover absolute vault paths by walking the well-known Crewly roots:
41
+ * - `<env CREWLY_PROJECT_VAULT_PATH>` (single explicit override)
42
+ * - `<process.cwd>/.crewly/wiki` (current project vault)
43
+ * - `~/.crewly/teams/<uuid>/wiki` (every team vault)
44
+ * - `~/.crewly/global-wiki` (ORC cross-project vault, if present)
45
+ *
46
+ * A path is only included if `SCHEMA.md` exists inside it.
47
+ */
48
+ export declare function discoverWikiVaults(): Promise<string[]>;
49
+ /**
50
+ * Periodic vault-bookkeep trigger. Start at boot, stop at shutdown.
51
+ */
52
+ export declare class WikiBookkeepTriggerService {
53
+ private static instance;
54
+ private readonly logger;
55
+ private readonly intervalMs;
56
+ private readonly debounceMs;
57
+ private readonly fireFn;
58
+ private readonly discoverRoots;
59
+ private readonly bookkeepService;
60
+ private timer;
61
+ /** vaultPath → last fired timestamp (ms). */
62
+ private readonly lastFiredAt;
63
+ /** Per-vault locks so two overlapping ticks don't double-fire. */
64
+ private inflight;
65
+ constructor(opts: WikiBookkeepTriggerOptions);
66
+ static getInstance(): WikiBookkeepTriggerService | null;
67
+ /** Wire the production singleton. Pass null to detach (tests / shutdown). */
68
+ static setInstance(next: WikiBookkeepTriggerService | null): void;
69
+ /** Begin scanning. Idempotent. */
70
+ start(): void;
71
+ /** Stop scanning. Safe to call multiple times. */
72
+ stop(): void;
73
+ /**
74
+ * Run one scan pass. Public for test + the manual
75
+ * `/api/wiki/bookkeep/trigger-now` endpoint.
76
+ */
77
+ tick(): Promise<{
78
+ scanned: string[];
79
+ fired: string[];
80
+ skippedByDebounce: string[];
81
+ quietVaults: string[];
82
+ }>;
83
+ /** Test affordance: clear the debounce ledger. */
84
+ _resetDebounceForTesting(): void;
85
+ }
86
+ //# sourceMappingURL=wiki-bookkeep-trigger.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-bookkeep-trigger.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-bookkeep-trigger.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAErF,MAAM,MAAM,kBAAkB,GAAG,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,kBAAkB,KACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,MAAM,WAAW,0BAA0B;IACzC,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,MAAM,EAAE,kBAAkB,CAAC;IAC3B,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,yDAAyD;IACzD,eAAe,CAAC,EAAE,mBAAmB,CAAC;CACvC;AAKD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CA4B5D;AAED;;GAEG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA2C;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;IACtD,OAAO,CAAC,KAAK,CAA+B;IAC5C,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,kEAAkE;IAClE,OAAO,CAAC,QAAQ,CAAqB;gBAEzB,IAAI,EAAE,0BAA0B;IAS5C,MAAM,CAAC,WAAW,IAAI,0BAA0B,GAAG,IAAI;IAIvD,6EAA6E;IAC7E,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,0BAA0B,GAAG,IAAI,GAAG,IAAI;IAKjE,kCAAkC;IAClC,KAAK,IAAI,IAAI;IAWb,kDAAkD;IAClD,IAAI,IAAI,IAAI;IAQZ;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;QAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IAoDF,kDAAkD;IAClD,wBAAwB,IAAI,IAAI;CAGjC"}
@@ -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"}