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.
Files changed (85) hide show
  1. package/config/roles/_common/wiki-instructions.md +33 -0
  2. package/config/roles/orchestrator/prompt.md +66 -4
  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/chat/chat.controller.d.ts.map +1 -1
  15. package/dist/backend/backend/src/controllers/chat/chat.controller.js +20 -0
  16. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  18. package/dist/backend/backend/src/controllers/slack/slack.controller.js +15 -0
  19. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  20. package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +134 -0
  21. package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -0
  22. package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +718 -0
  23. package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -0
  24. package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts +23 -0
  25. package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -0
  26. package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +43 -0
  27. package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -0
  28. package/dist/backend/backend/src/index.d.ts.map +1 -1
  29. package/dist/backend/backend/src/index.js +65 -0
  30. package/dist/backend/backend/src/index.js.map +1 -1
  31. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  32. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  33. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  34. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
  35. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
  36. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
  37. package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
  38. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
  39. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  40. package/dist/backend/backend/src/services/session/pty/pty-session.js +162 -4
  41. package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
  42. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts +69 -0
  43. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts.map +1 -0
  44. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js +174 -0
  45. package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js.map +1 -0
  46. package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts +57 -0
  47. package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts.map +1 -0
  48. package/dist/backend/backend/src/services/wiki/schema-loader.service.js +183 -0
  49. package/dist/backend/backend/src/services/wiki/schema-loader.service.js.map +1 -0
  50. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +86 -0
  51. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -0
  52. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +187 -0
  53. package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -0
  54. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts +116 -0
  55. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts.map +1 -0
  56. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js +299 -0
  57. package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js.map +1 -0
  58. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
  59. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
  61. package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
  62. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +100 -0
  63. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js +212 -0
  65. package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -0
  66. package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts +84 -0
  67. package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/wiki/wiki-process.service.js +138 -0
  69. package/dist/backend/backend/src/services/wiki/wiki-process.service.js.map +1 -0
  70. package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts +115 -0
  71. package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/wiki/wiki-query.service.js +291 -0
  73. package/dist/backend/backend/src/services/wiki/wiki-query.service.js.map +1 -0
  74. package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts +115 -0
  75. package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/wiki/wiki-queue.service.js +261 -0
  77. package/dist/backend/backend/src/services/wiki/wiki-queue.service.js.map +1 -0
  78. package/dist/backend/backend/src/services/wiki/wiki.types.d.ts +84 -0
  79. package/dist/backend/backend/src/services/wiki/wiki.types.d.ts.map +1 -0
  80. package/dist/backend/backend/src/services/wiki/wiki.types.js +10 -0
  81. package/dist/backend/backend/src/services/wiki/wiki.types.js.map +1 -0
  82. package/frontend/dist/assets/{index-b279da34.js → index-cc115bb4.js} +246 -246
  83. package/frontend/dist/assets/{index-c07e04c0.css → index-db3f5041.css} +1 -1
  84. package/frontend/dist/index.html +2 -2
  85. package/package.json +1 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * WikiChatSubscriberService — listens on chat-v2's `chat_message` event
3
+ * and fires `WikiIngestService.ingest` for every user-authored message.
4
+ *
5
+ * Phase A (rules-only routing):
6
+ * senderType === 'user' → ingest into the Crewly project vault.
7
+ * senderType === 'agent' | 'system' → skip.
8
+ *
9
+ * Phase B (per spec §4.5) will replace the project-only routing with an
10
+ * LLM-driven gate that picks team / project / global per source.
11
+ *
12
+ * Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
13
+ * or filesystem error MUST NOT block the live chat flow. Errors are
14
+ * logged at warn-level and swallowed.
15
+ *
16
+ * @module services/wiki/wiki-chat-subscriber.service
17
+ */
18
+ import type { ChatV2Service } from '../chat-v2/chat-v2.service.js';
19
+ import { WikiIngestService } from './wiki-ingest.service.js';
20
+ /**
21
+ * Shape we consume from chat-v2's `chat_message` payload. Kept narrow so
22
+ * we don't take a heavy structural dep on the wire DTO; matches the
23
+ * fields produced by `toMessageDTO` (chat-v2.service.ts:1537).
24
+ */
25
+ interface MinimalChatMessageDTO {
26
+ id: string;
27
+ channelId: string;
28
+ senderType: 'user' | 'agent' | 'system';
29
+ senderId: string;
30
+ content: string;
31
+ contentType: string;
32
+ createdAt: string | number;
33
+ }
34
+ export interface WikiChatSubscriberOptions {
35
+ /** chat-v2 singleton — `getChatV2Service()` at composition root. */
36
+ chatService: ChatV2Service;
37
+ /** Absolute path to the project vault. Default falls back to env / well-known location. */
38
+ projectVaultPath?: string;
39
+ /** Optional override for tests. */
40
+ ingestService?: WikiIngestService;
41
+ }
42
+ /**
43
+ * Process-singleton subscriber. Call `start()` once at server boot,
44
+ * `stop()` at shutdown.
45
+ */
46
+ export declare class WikiChatSubscriberService {
47
+ private readonly logger;
48
+ private readonly chatService;
49
+ private readonly projectVaultPath;
50
+ private readonly ingestService;
51
+ private boundHandler;
52
+ private started;
53
+ constructor(opts: WikiChatSubscriberOptions);
54
+ /**
55
+ * Subscribe to `chat_message`. Idempotent — calling start() twice
56
+ * leaves a single listener.
57
+ */
58
+ start(): void;
59
+ /** Detach. Safe to call when not started. */
60
+ stop(): void;
61
+ /**
62
+ * Handle a single chat message. Public for tests.
63
+ */
64
+ handle(dto: MinimalChatMessageDTO): Promise<void>;
65
+ /**
66
+ * Decide whether a chat-v2 message should drive a wiki write.
67
+ * Public for tests; logic is conservative — only ingest user-authored
68
+ * text messages, skip agents/system, mentions-only pings, and slash
69
+ * commands.
70
+ */
71
+ isIngestable(dto: MinimalChatMessageDTO): boolean;
72
+ }
73
+ export {};
74
+ //# sourceMappingURL=wiki-chat-subscriber.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-chat-subscriber.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAsB,MAAM,0BAA0B,CAAC;AAEjF;;;;GAIG;AACH,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,WAAW,EAAE,aAAa,CAAC;IAC3B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED;;;GAGG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,YAAY,CAAuD;IAC3E,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,yBAAyB;IAO3C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAgBb,6CAA6C;IAC7C,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDvD;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO;CAYlD"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * WikiChatSubscriberService — listens on chat-v2's `chat_message` event
3
+ * and fires `WikiIngestService.ingest` for every user-authored message.
4
+ *
5
+ * Phase A (rules-only routing):
6
+ * senderType === 'user' → ingest into the Crewly project vault.
7
+ * senderType === 'agent' | 'system' → skip.
8
+ *
9
+ * Phase B (per spec §4.5) will replace the project-only routing with an
10
+ * LLM-driven gate that picks team / project / global per source.
11
+ *
12
+ * Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
13
+ * or filesystem error MUST NOT block the live chat flow. Errors are
14
+ * logged at warn-level and swallowed.
15
+ *
16
+ * @module services/wiki/wiki-chat-subscriber.service
17
+ */
18
+ import * as path from 'path';
19
+ import { LoggerService } from '../core/logger.service.js';
20
+ import { WikiIngestService, detectMessageShape } from './wiki-ingest.service.js';
21
+ /**
22
+ * Process-singleton subscriber. Call `start()` once at server boot,
23
+ * `stop()` at shutdown.
24
+ */
25
+ export class WikiChatSubscriberService {
26
+ logger;
27
+ chatService;
28
+ projectVaultPath;
29
+ ingestService;
30
+ boundHandler = null;
31
+ started = false;
32
+ constructor(opts) {
33
+ this.logger = LoggerService.getInstance().createComponentLogger('WikiChatSubscriber');
34
+ this.chatService = opts.chatService;
35
+ this.projectVaultPath = opts.projectVaultPath ?? resolveDefaultProjectVaultPath();
36
+ this.ingestService = opts.ingestService ?? WikiIngestService.getInstance();
37
+ }
38
+ /**
39
+ * Subscribe to `chat_message`. Idempotent — calling start() twice
40
+ * leaves a single listener.
41
+ */
42
+ start() {
43
+ if (this.started) {
44
+ this.logger.debug('start() called twice — ignoring');
45
+ return;
46
+ }
47
+ this.boundHandler = (dto) => {
48
+ // Fire-and-forget; any thrown error is logged + swallowed in handle().
49
+ void this.handle(dto);
50
+ };
51
+ this.chatService.on('chat_message', this.boundHandler);
52
+ this.started = true;
53
+ this.logger.info('WikiChatSubscriber started', {
54
+ vaultPath: this.projectVaultPath,
55
+ });
56
+ }
57
+ /** Detach. Safe to call when not started. */
58
+ stop() {
59
+ if (!this.started || !this.boundHandler)
60
+ return;
61
+ this.chatService.off('chat_message', this.boundHandler);
62
+ this.boundHandler = null;
63
+ this.started = false;
64
+ this.logger.info('WikiChatSubscriber stopped');
65
+ }
66
+ /**
67
+ * Handle a single chat message. Public for tests.
68
+ */
69
+ async handle(dto) {
70
+ try {
71
+ if (!this.isIngestable(dto)) {
72
+ return;
73
+ }
74
+ const ingestInput = {
75
+ vaultPath: this.projectVaultPath,
76
+ sourceType: 'user_chat',
77
+ sourceRef: `chat:${dto.channelId}:${dto.id}`,
78
+ sourceBody: dto.content,
79
+ callerSession: `user/${dto.senderId}`,
80
+ };
81
+ // Phase A rules-based promotion: decision-shaped chats land in
82
+ // BOTH log.md (audit) and llm-curated/decisions/<date>-<slug>.md
83
+ // (canonical). Phase B will replace `detectMessageShape` with an
84
+ // LLM gate per v2.1 §4.
85
+ const shape = detectMessageShape(dto.content);
86
+ if (shape === 'decision') {
87
+ const result = await this.ingestService.ingestDecision(ingestInput);
88
+ if (!result.log.ok) {
89
+ this.logger.warn('WikiChatSubscriber decision-log ingest refused', {
90
+ reason: result.log.reason,
91
+ messageId: dto.id,
92
+ });
93
+ }
94
+ if (!result.decision.ok) {
95
+ this.logger.warn('WikiChatSubscriber decisions/ promotion refused', {
96
+ reason: result.decision.reason,
97
+ messageId: dto.id,
98
+ });
99
+ }
100
+ return;
101
+ }
102
+ const outcome = await this.ingestService.ingest(ingestInput);
103
+ if (!outcome.ok) {
104
+ this.logger.warn('WikiChatSubscriber ingest refused', {
105
+ reason: outcome.reason,
106
+ messageId: dto.id,
107
+ });
108
+ }
109
+ }
110
+ catch (err) {
111
+ // Swallow — chat flow must not block on wiki write failures.
112
+ this.logger.warn('WikiChatSubscriber.handle threw (swallowed)', {
113
+ messageId: dto.id,
114
+ error: err.message,
115
+ });
116
+ }
117
+ }
118
+ /**
119
+ * Decide whether a chat-v2 message should drive a wiki write.
120
+ * Public for tests; logic is conservative — only ingest user-authored
121
+ * text messages, skip agents/system, mentions-only pings, and slash
122
+ * commands.
123
+ */
124
+ isIngestable(dto) {
125
+ if (dto.senderType !== 'user')
126
+ return false;
127
+ if (!dto.content || dto.content.trim().length === 0)
128
+ return false;
129
+ // Skip slash commands — they're orc-controls, not knowledge.
130
+ if (dto.content.trimStart().startsWith('/'))
131
+ return false;
132
+ // chat-v2 defaults `contentType` to 'markdown' when sendMessage doesn't
133
+ // supply one (chat-v2.service.ts ~line 1240). Accept the two readable
134
+ // text-bearing types; reject binary/system flavors like 'system_note'.
135
+ const acceptable = new Set(['text', 'markdown']);
136
+ if (dto.contentType && !acceptable.has(dto.contentType))
137
+ return false;
138
+ return true;
139
+ }
140
+ }
141
+ /**
142
+ * Resolve a sensible default vault path:
143
+ * 1. `CREWLY_PROJECT_VAULT_PATH` env var (explicit override)
144
+ * 2. `<cwd>/.crewly/wiki` if it exists
145
+ * 3. The Crewly repo's own project vault (the only one with SCHEMA.md today)
146
+ */
147
+ function resolveDefaultProjectVaultPath() {
148
+ const fromEnv = process.env['CREWLY_PROJECT_VAULT_PATH'];
149
+ if (fromEnv && path.isAbsolute(fromEnv)) {
150
+ return fromEnv;
151
+ }
152
+ return path.join('/Users/yellowsunhy/Desktop/projects/crewly-projects/crewly', '.crewly/wiki');
153
+ }
154
+ //# sourceMappingURL=wiki-chat-subscriber.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-chat-subscriber.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * WikiIngestService — write canonical pages into a vault's `llm-curated/`.
3
+ *
4
+ * Phase A scope per v2.1 spec: append entries to `llm-curated/log.md` for
5
+ * every chat/spec/learning source. Decisions/pattern pages (separate files
6
+ * under `llm-curated/decisions/`) are gated behind the LLM routing layer
7
+ * which lands in Phase B.
8
+ *
9
+ * Frozen-path contract (§2): refuses ANY write whose target lives under a
10
+ * `hardcoded:` folder. SchemaLoaderService.isFrozenPath() is the gate.
11
+ *
12
+ * @module services/wiki/wiki-ingest.service
13
+ */
14
+ import { SchemaLoaderService } from './schema-loader.service.js';
15
+ /**
16
+ * Categories of sources that trigger ingest. Mirrors §4 (ingest trigger
17
+ * taxonomy) plus `user_chat` for the demo path.
18
+ */
19
+ export type WikiSourceType = 'user_chat' | 'slack_message' | 'spec_file' | 'pr_merge' | 'record_learning' | 'task_verified';
20
+ export interface WikiIngestInput {
21
+ /** Absolute path to the vault root (containing SCHEMA.md). */
22
+ vaultPath: string;
23
+ /** Where the content came from. */
24
+ sourceType: WikiSourceType;
25
+ /** Stable reference for audit (URL, file path, WI id, chat msg id). */
26
+ sourceRef: string;
27
+ /** Body content to ingest. Empty bodies are rejected. */
28
+ sourceBody: string;
29
+ /** Session/user that authored the source — appears in the log header. */
30
+ callerSession?: string;
31
+ /** Optional override of the relative target path; defaults to `llm-curated/log.md`. */
32
+ targetRelativePath?: string;
33
+ }
34
+ export interface WikiIngestResult {
35
+ ok: true;
36
+ pagesWritten: string[];
37
+ logEntry: string;
38
+ frozenPathsTouched: string[];
39
+ }
40
+ export interface WikiIngestRefusedFrozen {
41
+ ok: false;
42
+ reason: 'frozen_path';
43
+ attemptedPath: string;
44
+ frozenFolders: string[];
45
+ }
46
+ export interface WikiIngestRefusedInvalid {
47
+ ok: false;
48
+ reason: 'invalid_input' | 'schema_missing' | 'empty_body';
49
+ message: string;
50
+ }
51
+ export type WikiIngestOutcome = WikiIngestResult | WikiIngestRefusedFrozen | WikiIngestRefusedInvalid;
52
+ /**
53
+ * Writes ingest pages to a vault. Construct one per process; stateless.
54
+ */
55
+ export declare class WikiIngestService {
56
+ private static instance;
57
+ private readonly logger;
58
+ private readonly schemaLoader;
59
+ constructor(schemaLoader?: SchemaLoaderService);
60
+ static getInstance(): WikiIngestService;
61
+ /** Test-only reset. */
62
+ static _resetForTesting(): void;
63
+ /**
64
+ * Ingest a single source into the vault.
65
+ *
66
+ * Errors are returned as structured outcomes — this method never throws
67
+ * on legitimate refusals (frozen path, missing schema, empty body) so
68
+ * callers can fold the result into their normal control flow.
69
+ */
70
+ ingest(input: WikiIngestInput): Promise<WikiIngestOutcome>;
71
+ private validateInput;
72
+ /**
73
+ * Build the markdown entry. Format is chosen to be safely append-only
74
+ * (no preceding section to rewrite) and grep-friendly:
75
+ *
76
+ * ## [<ISO>] <sourceType> | <callerSession or sourceRef>
77
+ *
78
+ * ref: <sourceRef>
79
+ *
80
+ * <body>
81
+ */
82
+ private formatLogEntry;
83
+ private appendOrCreate;
84
+ /**
85
+ * Pick the right header for a freshly-created page:
86
+ * - `log.md` → audit-log preamble (append-only)
87
+ * - `decisions/*.md` → decision-page title + provenance block
88
+ * - anything else → minimal title block from the source ref
89
+ */
90
+ private buildPageHeader;
91
+ /**
92
+ * Defuse markers a future skill might mis-route on: `[CHAT]`, `[NOTIFY]`,
93
+ * `[EVENT]`, `[ESCALATION]` get a zero-width space inserted so they're
94
+ * still human-readable but don't trigger regex matchers downstream.
95
+ * (Mirrors the escalation-router sanitizer from PR #606.)
96
+ */
97
+ private sanitizeBody;
98
+ private sanitizeOneLine;
99
+ }
100
+ //# sourceMappingURL=wiki-ingest.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-ingest.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-ingest.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,UAAU,EAAE,cAAc,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,eAAe,GAAG,gBAAgB,GAAG,YAAY,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,uBAAuB,GACvB,wBAAwB,CAAC;AAc7B;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;gBAEvC,YAAY,CAAC,EAAE,mBAAmB;IAK9C,MAAM,CAAC,WAAW,IAAI,iBAAiB;IAOvC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;;;;OAMG;IACG,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAqDhE,OAAO,CAAC,aAAa;IAgCrB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;YAgBR,cAAc;IAa5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAuBvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;CAKxB"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * WikiIngestService — write canonical pages into a vault's `llm-curated/`.
3
+ *
4
+ * Phase A scope per v2.1 spec: append entries to `llm-curated/log.md` for
5
+ * every chat/spec/learning source. Decisions/pattern pages (separate files
6
+ * under `llm-curated/decisions/`) are gated behind the LLM routing layer
7
+ * which lands in Phase B.
8
+ *
9
+ * Frozen-path contract (§2): refuses ANY write whose target lives under a
10
+ * `hardcoded:` folder. SchemaLoaderService.isFrozenPath() is the gate.
11
+ *
12
+ * @module services/wiki/wiki-ingest.service
13
+ */
14
+ import * as path from 'path';
15
+ import * as fs from 'fs/promises';
16
+ import { existsSync } from 'fs';
17
+ import { LoggerService } from '../core/logger.service.js';
18
+ import { SchemaLoaderService } from './schema-loader.service.js';
19
+ const DEFAULT_LOG_RELATIVE_PATH = 'llm-curated/log.md';
20
+ const MAX_BODY_BYTES = 64 * 1024;
21
+ // Spec note (Steve, 2026-05-22): the earlier keyword-based
22
+ // `detectMessageShape` heuristic + `buildDecisionSlug` + `ingestDecision`
23
+ // dual-write were REMOVED. Routing into `llm-curated/<folder>/<page>.md`
24
+ // is now agent-driven via `wiki-process-queue` + the agent's own LLM gate
25
+ // — see WikiQueueService for the queue + the orchestrator system prompt
26
+ // for the rule that says "queue worth-saving content as you see it." This
27
+ // service stays low-level: it only writes the path the caller (skill,
28
+ // route, queue processor) specifies, after the frozen-path gate.
29
+ /**
30
+ * Writes ingest pages to a vault. Construct one per process; stateless.
31
+ */
32
+ export class WikiIngestService {
33
+ static instance = null;
34
+ logger;
35
+ schemaLoader;
36
+ constructor(schemaLoader) {
37
+ this.logger = LoggerService.getInstance().createComponentLogger('WikiIngest');
38
+ this.schemaLoader = schemaLoader ?? new SchemaLoaderService();
39
+ }
40
+ static getInstance() {
41
+ if (!this.instance) {
42
+ this.instance = new WikiIngestService();
43
+ }
44
+ return this.instance;
45
+ }
46
+ /** Test-only reset. */
47
+ static _resetForTesting() {
48
+ this.instance = null;
49
+ }
50
+ /**
51
+ * Ingest a single source into the vault.
52
+ *
53
+ * Errors are returned as structured outcomes — this method never throws
54
+ * on legitimate refusals (frozen path, missing schema, empty body) so
55
+ * callers can fold the result into their normal control flow.
56
+ */
57
+ async ingest(input) {
58
+ const validation = this.validateInput(input);
59
+ if (validation) {
60
+ return validation;
61
+ }
62
+ let schema;
63
+ try {
64
+ schema = await this.schemaLoader.load(input.vaultPath);
65
+ }
66
+ catch (err) {
67
+ return {
68
+ ok: false,
69
+ reason: 'schema_missing',
70
+ message: err.message,
71
+ };
72
+ }
73
+ const target = input.targetRelativePath ?? DEFAULT_LOG_RELATIVE_PATH;
74
+ if (this.schemaLoader.isFrozenPath(schema, target)) {
75
+ return {
76
+ ok: false,
77
+ reason: 'frozen_path',
78
+ attemptedPath: target,
79
+ frozenFolders: this.schemaLoader.getFrozenPaths(schema),
80
+ };
81
+ }
82
+ const absoluteTarget = path.join(input.vaultPath, target);
83
+ await fs.mkdir(path.dirname(absoluteTarget), { recursive: true });
84
+ const logEntry = this.formatLogEntry(input);
85
+ await this.appendOrCreate(absoluteTarget, logEntry, input);
86
+ this.logger.info('WikiIngest wrote log entry', {
87
+ vault: input.vaultPath,
88
+ target,
89
+ sourceType: input.sourceType,
90
+ sourceRef: input.sourceRef,
91
+ bodyBytes: input.sourceBody.length,
92
+ });
93
+ return {
94
+ ok: true,
95
+ pagesWritten: [target],
96
+ logEntry,
97
+ frozenPathsTouched: [],
98
+ };
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Internals
102
+ // ---------------------------------------------------------------------------
103
+ validateInput(input) {
104
+ if (!input.vaultPath || !path.isAbsolute(input.vaultPath)) {
105
+ return {
106
+ ok: false,
107
+ reason: 'invalid_input',
108
+ message: `vaultPath must be an absolute path, got "${input.vaultPath ?? ''}"`,
109
+ };
110
+ }
111
+ if (!input.sourceType) {
112
+ return { ok: false, reason: 'invalid_input', message: 'sourceType is required' };
113
+ }
114
+ if (!input.sourceRef) {
115
+ return { ok: false, reason: 'invalid_input', message: 'sourceRef is required' };
116
+ }
117
+ const body = input.sourceBody ?? '';
118
+ if (body.trim().length === 0) {
119
+ return {
120
+ ok: false,
121
+ reason: 'empty_body',
122
+ message: 'sourceBody is empty after trimming whitespace',
123
+ };
124
+ }
125
+ if (Buffer.byteLength(body, 'utf8') > MAX_BODY_BYTES) {
126
+ return {
127
+ ok: false,
128
+ reason: 'invalid_input',
129
+ message: `sourceBody exceeds ${MAX_BODY_BYTES} bytes`,
130
+ };
131
+ }
132
+ return null;
133
+ }
134
+ /**
135
+ * Build the markdown entry. Format is chosen to be safely append-only
136
+ * (no preceding section to rewrite) and grep-friendly:
137
+ *
138
+ * ## [<ISO>] <sourceType> | <callerSession or sourceRef>
139
+ *
140
+ * ref: <sourceRef>
141
+ *
142
+ * <body>
143
+ */
144
+ formatLogEntry(input) {
145
+ const ts = new Date().toISOString();
146
+ const header = this.sanitizeOneLine(input.callerSession ?? input.sourceRef, 80);
147
+ const body = this.sanitizeBody(input.sourceBody);
148
+ const ref = this.sanitizeOneLine(input.sourceRef, 200);
149
+ return [
150
+ '',
151
+ `## [${ts}] ${input.sourceType} | ${header}`,
152
+ '',
153
+ `ref: ${ref}`,
154
+ '',
155
+ body,
156
+ '',
157
+ ].join('\n');
158
+ }
159
+ async appendOrCreate(absolutePath, entry, input) {
160
+ if (!existsSync(absolutePath)) {
161
+ const header = this.buildPageHeader(absolutePath, input);
162
+ await fs.writeFile(absolutePath, header + entry, 'utf8');
163
+ return;
164
+ }
165
+ await fs.appendFile(absolutePath, entry, 'utf8');
166
+ }
167
+ /**
168
+ * Pick the right header for a freshly-created page:
169
+ * - `log.md` → audit-log preamble (append-only)
170
+ * - `decisions/*.md` → decision-page title + provenance block
171
+ * - anything else → minimal title block from the source ref
172
+ */
173
+ buildPageHeader(absolutePath, input) {
174
+ const basename = absolutePath.split(/[/\\]/).pop() ?? '';
175
+ if (basename === 'log.md') {
176
+ return '# Activity log\n\nAppend-only log of ingested sources. Each entry: `## [<ISO>] <sourceType> | <caller>`.\n';
177
+ }
178
+ if (absolutePath.includes('/decisions/')) {
179
+ const title = this.sanitizeOneLine(input.sourceBody, 80);
180
+ const caller = input.callerSession ?? input.sourceRef;
181
+ return [
182
+ `# ${title}`,
183
+ '',
184
+ `> **source:** \`${this.sanitizeOneLine(input.sourceRef, 200)}\` `,
185
+ `> **caller:** \`${this.sanitizeOneLine(caller, 80)}\` `,
186
+ `> **recorded:** ${new Date().toISOString()}`,
187
+ '',
188
+ '---',
189
+ '',
190
+ ].join('\n');
191
+ }
192
+ // Generic non-log target: minimal title block.
193
+ return `# ${this.sanitizeOneLine(input.sourceRef, 80)}\n\n`;
194
+ }
195
+ /**
196
+ * Defuse markers a future skill might mis-route on: `[CHAT]`, `[NOTIFY]`,
197
+ * `[EVENT]`, `[ESCALATION]` get a zero-width space inserted so they're
198
+ * still human-readable but don't trigger regex matchers downstream.
199
+ * (Mirrors the escalation-router sanitizer from PR #606.)
200
+ */
201
+ sanitizeBody(body) {
202
+ const trimmed = body.replace(/\r\n/g, '\n').trim();
203
+ return trimmed.replace(/\[(CHAT|NOTIFY|EVENT|ESCALATION)\]/g, (_match, tag) => `[​${tag}]`);
204
+ }
205
+ sanitizeOneLine(value, maxLen) {
206
+ const flat = value.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').trim();
207
+ if (flat.length <= maxLen)
208
+ return flat;
209
+ return flat.slice(0, maxLen - 1) + '…';
210
+ }
211
+ }
212
+ //# sourceMappingURL=wiki-ingest.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-ingest.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-ingest.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * WikiProcessService — bundles "claim a pending item + build the
3
+ * system-context the agent's LLM needs to classify it."
4
+ *
5
+ * Per Steve's 2026-05-22 redesign:
6
+ * - the skill DOES NOT pick a target folder. The agent picks.
7
+ * - there is NO preset taxonomy beyond the frozen folders (sop/,
8
+ * team-norm/, memory/, sop-overrides/). Everything under
9
+ * llm-curated/* is fair game; the agent invents folder names.
10
+ * - the agent reads the queue item's `reason` (which we forced it to
11
+ * write at queue-add time) PLUS the existing vault state (so it
12
+ * doesn't re-classify into a duplicate of an existing page).
13
+ *
14
+ * Lifecycle this service participates in:
15
+ * pending → (this service) → claimed + context payload returned
16
+ * → (agent's LLM, outside the skill) picks target path
17
+ * → (agent calls wiki-ingest) → page written
18
+ * → (agent calls queue/:id/process) → status=processed
19
+ *
20
+ * @module services/wiki/wiki-process.service
21
+ */
22
+ import { WikiQueueService, WikiQueueItem } from './wiki-queue.service.js';
23
+ import { WikiQueryService, WikiQuerySystemContext } from './wiki-query.service.js';
24
+ export interface WikiProcessClaimInput {
25
+ /** Agent session claiming the item. */
26
+ claimedBy: string;
27
+ /** Filter the queue to a specific vault (default: any). */
28
+ vaultPath?: string;
29
+ /** Skip this many leading pending items. Lets the agent re-roll after a skip. */
30
+ offset?: number;
31
+ /** For the vault context: how many candidate pages to include. */
32
+ topK?: number;
33
+ /** For the vault context: how many recent log entries. */
34
+ recentLogEntries?: number;
35
+ }
36
+ export interface WikiProcessClaimResult {
37
+ /** The claimed queue item. */
38
+ item: WikiQueueItem;
39
+ /**
40
+ * System-context the agent's LLM should classify against. Includes the
41
+ * vault SCHEMA, frozen-path list, recent log, and top-K candidate pages
42
+ * by keyword overlap with the queue item's content.
43
+ *
44
+ * `null` if the vault has no SCHEMA.md (rare — vault wasn't initialized).
45
+ */
46
+ vaultContext: WikiQuerySystemContext | null;
47
+ /** Synthesis instructions for the agent's LLM. */
48
+ classifierNotes: string[];
49
+ }
50
+ export type WikiProcessClaimOutcome = {
51
+ ok: true;
52
+ result: WikiProcessClaimResult;
53
+ } | {
54
+ ok: false;
55
+ reason: 'no_pending_items' | 'vault_query_failed';
56
+ message: string;
57
+ };
58
+ /**
59
+ * Stateless façade — both backing services (queue, query) are singletons.
60
+ */
61
+ export declare class WikiProcessService {
62
+ private readonly queue;
63
+ private readonly query;
64
+ private static instance;
65
+ private readonly logger;
66
+ constructor(queue?: WikiQueueService, query?: WikiQueryService);
67
+ static getInstance(): WikiProcessService;
68
+ /** Test-only reset. */
69
+ static _resetForTesting(): void;
70
+ /**
71
+ * Claim the next pending item + return the vault context the agent
72
+ * needs to classify it. The caller (agent runtime) then runs an LLM
73
+ * call against this context + the item's content/reason, picks a
74
+ * target page, and calls `wiki-ingest` + `/queue/:id/process` to
75
+ * commit.
76
+ */
77
+ claimNext(input: WikiProcessClaimInput): Promise<WikiProcessClaimOutcome>;
78
+ /**
79
+ * The contract the agent's LLM must honor when classifying. Kept
80
+ * short so it can be inlined into the runtime's task-instruction.
81
+ */
82
+ private classifierNotes;
83
+ }
84
+ //# sourceMappingURL=wiki-process.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki-process.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-process.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB;;;;;;OAMG;IACH,YAAY,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC5C,kDAAkD;IAClD,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAA;CAAE,GAC5C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF;;GAEG;AACH,qBAAa,kBAAkB;IAK3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAGtB,KAAK,GAAE,gBAAiD,EACxD,KAAK,GAAE,gBAAiD;IAK3E,MAAM,CAAC,WAAW,IAAI,kBAAkB;IAKxC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;;;;OAMG;IACG,SAAS,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA2E/E;;;OAGG;IACH,OAAO,CAAC,eAAe;CAUxB"}