opencode-swarm 7.107.2 → 7.107.4

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 (69) hide show
  1. package/.opencode/skills/consult/SKILL.md +10 -0
  2. package/.opencode/skills/council/SKILL.md +10 -0
  3. package/.opencode/skills/engineering-conventions/SKILL.md +20 -0
  4. package/README.md +2 -2
  5. package/dist/agents/agent-output-schema.d.ts +1 -1
  6. package/dist/cli/{config-doctor-cntnpyvj.js → config-doctor-kdq3vae4.js} +3 -2
  7. package/dist/cli/{curator-drift-6ad8mxxb.js → curator-drift-ed7p5mfy.js} +2 -2
  8. package/dist/cli/{curator-llm-factory-r3m5e7n8.js → curator-llm-factory-abw56kg0.js} +18 -13
  9. package/dist/cli/{curator-6zvpn7fs.js → curator-rb05fjjk.js} +18 -13
  10. package/dist/cli/{dispatch-e4ejrsz8.js → dispatch-anmxxhen.js} +56 -13
  11. package/dist/cli/{evidence-summary-service-7nwhd8s4.js → evidence-summary-service-nwdfnbz1.js} +4 -4
  12. package/dist/cli/gate-evidence-ywys9vfs.js +1 -1
  13. package/dist/cli/guardrail-explain-dayy7zw4.js +37 -0
  14. package/dist/cli/{guardrail-log-fsc85hwa.js → guardrail-log-fjypsmek.js} +4 -3
  15. package/dist/cli/{hive-promoter-nwcsha2j.js → hive-promoter-34bdmw1w.js} +18 -13
  16. package/dist/cli/{index-r87xhtmx.js → index-19vxtwa6.js} +22 -17
  17. package/dist/cli/{index-t4hbye3v.js → index-4r5z8sgw.js} +4 -4
  18. package/dist/cli/{index-fxtrk7y8.js → index-4vsgp5a8.js} +2 -2
  19. package/dist/cli/{index-mpe0yk9s.js → index-57ymmh7h.js} +5 -3
  20. package/dist/cli/{index-357p0baj.js → index-91j1sqzm.js} +14 -2
  21. package/dist/cli/{index-f12bmedv.js → index-9b7a6agd.js} +91 -34
  22. package/dist/cli/index-bfwt3abw.js +14 -0
  23. package/dist/cli/{index-9wstcavp.js → index-bpgqbwmf.js} +115 -1019
  24. package/dist/cli/index-bq8kd9r9.js +199 -0
  25. package/dist/cli/index-dfdg4ws5.js +741 -0
  26. package/dist/cli/{index-jr54w7pb.js → index-f45hbd5x.js} +2 -2
  27. package/dist/cli/{index-168p3bfr.js → index-gyzxeza3.js} +910 -608
  28. package/dist/cli/index-kxm3bdjh.js +631 -0
  29. package/dist/cli/index-n1sptn1w.js +147 -0
  30. package/dist/cli/{index-2560cctg.js → index-pchpahdf.js} +11 -0
  31. package/dist/cli/{index-esg1554h.js → index-wvxhynxq.js} +1 -1
  32. package/dist/cli/{index-8w4d325g.js → index-xg18az81.js} +61 -22
  33. package/dist/cli/{index-f7nma02k.js → index-xmarctsf.js} +137 -175
  34. package/dist/cli/{index-w8dzxnx4.js → index-zej8cy0j.js} +1 -1
  35. package/dist/cli/index.js +18 -13
  36. package/dist/cli/knowledge-escalator-3sgwrgy9.js +32 -0
  37. package/dist/cli/knowledge-events-kfc6xmkw.js +62 -0
  38. package/dist/cli/{knowledge-store-tsa8ezka.js → knowledge-store-kyacem71.js} +3 -1
  39. package/dist/cli/knowledge-validator-ez3ws8ef.js +57 -0
  40. package/dist/cli/{pending-delegations-46xf2n2e.js → pending-delegations-vfnpm8zt.js} +4 -4
  41. package/dist/cli/{pr-subscriptions-3c34rnm1.js → pr-subscriptions-bpvazmsw.js} +3 -3
  42. package/dist/cli/{schema-vw2ffhe9.js → schema-x0ekqzvs.js} +2 -1
  43. package/dist/cli/{skill-generator-y6mmq8n5.js → skill-generator-x83re96d.js} +6 -2
  44. package/dist/commands/knowledge.d.ts +5 -1
  45. package/dist/commands/registry.d.ts +2 -2
  46. package/dist/config/evidence-schema.d.ts +9 -9
  47. package/dist/config/schema.d.ts +42 -12
  48. package/dist/hooks/guardrails/file-authority.d.ts +1 -1
  49. package/dist/hooks/knowledge-curator.d.ts +27 -0
  50. package/dist/hooks/knowledge-escalator.d.ts +16 -0
  51. package/dist/hooks/knowledge-events.d.ts +11 -0
  52. package/dist/hooks/knowledge-injector.d.ts +3 -0
  53. package/dist/hooks/knowledge-store.d.ts +16 -1
  54. package/dist/hooks/knowledge-types.d.ts +85 -0
  55. package/dist/hooks/knowledge-validator.d.ts +15 -0
  56. package/dist/hooks/semantic-diff-injection.d.ts +19 -0
  57. package/dist/hooks/skill-usage-log.d.ts +3 -0
  58. package/dist/index.js +354 -353
  59. package/dist/memory/consolidation.d.ts +1 -1
  60. package/dist/memory/schema.d.ts +4 -4
  61. package/dist/sast/rules/rust.d.ts +6 -0
  62. package/dist/services/directive-predicate-runner.d.ts +1 -1
  63. package/dist/tools/knowledge-archive.d.ts +6 -2
  64. package/dist/tools/knowledge-remove.d.ts +11 -0
  65. package/dist/tools/lint.d.ts +2 -2
  66. package/dist/tools/secretscan.d.ts +16 -0
  67. package/dist/tools/test-runner.d.ts +1 -1
  68. package/package.json +1 -1
  69. package/dist/cli/guardrail-explain-zajegz3a.js +0 -32
@@ -0,0 +1,741 @@
1
+ // @bun
2
+ import {
3
+ findNearDuplicate,
4
+ inferTags,
5
+ readKnowledge,
6
+ resolveSwarmKnowledgePath,
7
+ resolveSwarmRejectedPath,
8
+ transactKnowledge
9
+ } from "./index-xmarctsf.js";
10
+ import {
11
+ resolveKnowledgeStoreDir
12
+ } from "./index-n1sptn1w.js";
13
+ import {
14
+ atomicWriteFile
15
+ } from "./index-9b7qp18e.js";
16
+ import {
17
+ init_logger,
18
+ warn
19
+ } from "./index-zgwm4ryv.js";
20
+ import {
21
+ require_proper_lockfile
22
+ } from "./index-3naa2ajc.js";
23
+ import {
24
+ __toESM
25
+ } from "./index-a76rekgs.js";
26
+
27
+ // src/hooks/knowledge-validator.ts
28
+ var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
29
+ import { appendFile, mkdir } from "fs/promises";
30
+ import * as path from "path";
31
+ init_logger();
32
+ var DANGEROUS_COMMAND_ERROR_PATTERNS = [
33
+ /\brm\s+-rf\b/,
34
+ /\bsudo\s+rm\b/,
35
+ /\bmkfs\b/,
36
+ /\bdd\s+if=/,
37
+ /:\(\)\s*\{/,
38
+ /\bchmod\s+-R\s+777\b/i,
39
+ /\bdeltree\b/,
40
+ /\brmdir\s+\/s\b/
41
+ ];
42
+ var DANGEROUS_COMMAND_WARNING_PATTERNS = [
43
+ /\bformat\b/,
44
+ /\bkill\s+-9\b/,
45
+ /\bpkill\b/,
46
+ /\bkillall\b/,
47
+ /`[^`]*`/,
48
+ /\$\([^)]*\)/
49
+ ];
50
+ var DANGEROUS_COMMAND_PATTERNS = [
51
+ ...DANGEROUS_COMMAND_ERROR_PATTERNS,
52
+ ...DANGEROUS_COMMAND_WARNING_PATTERNS
53
+ ];
54
+ var SECURITY_DEGRADING_PATTERNS = [
55
+ /disable\s+.{0,50}firewall/i,
56
+ /turn\s+off\s+.{0,50}security/i,
57
+ /skip\s+.{0,50}auth/i,
58
+ /bypass\s+.{0,50}auth/i,
59
+ /ignore\s+.{0,50}certificate/i,
60
+ /disable\s+.{0,50}tls/i,
61
+ /disable\s+.{0,50}ssl/i,
62
+ /no\s+.{0,50}validation/i,
63
+ /disable\s+.{0,50}2fa/i,
64
+ /remove\s+.{0,50}password/i
65
+ ];
66
+ var INVISIBLE_FORMAT_CHARS = /[\u00AD\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/g;
67
+ var INJECTION_PATTERNS = [
68
+ /[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\x0d]/,
69
+ /[\u00AD\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/,
70
+ /^system\s*:/i,
71
+ /<script/i,
72
+ /javascript:/i,
73
+ /\beval\(/i,
74
+ /\b__proto__\b/,
75
+ /\bconstructor\[/,
76
+ /\.prototype\[/
77
+ ];
78
+ var VALID_CATEGORIES = new Set([
79
+ "process",
80
+ "architecture",
81
+ "tooling",
82
+ "security",
83
+ "testing",
84
+ "debugging",
85
+ "performance",
86
+ "integration",
87
+ "todo",
88
+ "other"
89
+ ]);
90
+ var TECH_REFERENCE_WORDS = new Set([
91
+ "git",
92
+ "docker",
93
+ "typescript",
94
+ "bun",
95
+ "vitest",
96
+ "node",
97
+ "python",
98
+ "react",
99
+ "sql",
100
+ "api",
101
+ "hook",
102
+ "test",
103
+ "schema",
104
+ "config",
105
+ "file",
106
+ "function",
107
+ "class",
108
+ "module",
109
+ "import",
110
+ "export"
111
+ ]);
112
+ var ACTION_VERB_WORDS = new Set([
113
+ "use",
114
+ "avoid",
115
+ "prefer",
116
+ "run",
117
+ "check",
118
+ "always",
119
+ "never",
120
+ "ensure",
121
+ "call",
122
+ "write",
123
+ "add",
124
+ "remove",
125
+ "update",
126
+ "set",
127
+ "enable",
128
+ "disable"
129
+ ]);
130
+ var NEGATION_PAIRS = [
131
+ ["always", "never"],
132
+ ["must", "must not"],
133
+ ["must", "should not"],
134
+ ["enable", "disable"],
135
+ ["use", "avoid"],
136
+ ["use", "don't use"],
137
+ ["recommended", "not recommended"]
138
+ ];
139
+ function normalizeText(text) {
140
+ return text.normalize("NFKC").toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
141
+ }
142
+ function extractContextWords(text, word, contextWindow = 3) {
143
+ const words = text.split(" ");
144
+ const context = new Set;
145
+ if (!word.includes(" ")) {
146
+ let from = 0;
147
+ let idx = words.indexOf(word, from);
148
+ while (idx !== -1) {
149
+ const start = Math.max(0, idx - contextWindow);
150
+ const end = Math.min(words.length, idx + contextWindow + 1);
151
+ for (let i2 = start;i2 < end; i2++) {
152
+ if (i2 !== idx && words[i2] && words[i2].length > 0) {
153
+ context.add(words[i2]);
154
+ }
155
+ }
156
+ from = idx + 1;
157
+ idx = words.indexOf(word, from);
158
+ }
159
+ return context;
160
+ }
161
+ const termLen = word.split(" ").length;
162
+ let i = 0;
163
+ while (i <= words.length - termLen) {
164
+ const slice = words.slice(i, i + termLen).join(" ");
165
+ if (slice === word) {
166
+ const start = Math.max(0, i - contextWindow);
167
+ const end = Math.min(words.length, i + termLen + contextWindow);
168
+ for (let j = start;j < end; j++) {
169
+ if (j < i || j >= i + termLen) {
170
+ if (words[j] && words[j].length > 0) {
171
+ context.add(words[j]);
172
+ }
173
+ }
174
+ }
175
+ i += termLen;
176
+ } else {
177
+ i += 1;
178
+ }
179
+ }
180
+ return context;
181
+ }
182
+ function hasSignificantOverlap(set1, set2) {
183
+ if (set1.size === 0 || set2.size === 0)
184
+ return false;
185
+ return [...set1].some((word) => set2.has(word));
186
+ }
187
+ function detectContradiction(candidate, existingLessons) {
188
+ const candidateTags = inferTags(candidate);
189
+ if (candidateTags.length === 0)
190
+ return false;
191
+ const candidateNorm = normalizeText(candidate);
192
+ for (const existing of existingLessons) {
193
+ const existingTags = inferTags(existing);
194
+ const shared = candidateTags.some((t) => existingTags.includes(t));
195
+ if (!shared)
196
+ continue;
197
+ const existingNorm = normalizeText(existing);
198
+ for (const [wordA, wordB] of NEGATION_PAIRS) {
199
+ if (candidateNorm.includes(wordA) && existingNorm.includes(wordB)) {
200
+ const contextA = extractContextWords(candidateNorm, wordA);
201
+ const contextB = extractContextWords(existingNorm, wordB);
202
+ if (hasSignificantOverlap(contextA, contextB)) {
203
+ return true;
204
+ }
205
+ }
206
+ if (candidateNorm.includes(wordB) && existingNorm.includes(wordA)) {
207
+ const contextB = extractContextWords(candidateNorm, wordB);
208
+ const contextA = extractContextWords(existingNorm, wordA);
209
+ if (hasSignificantOverlap(contextA, contextB)) {
210
+ return true;
211
+ }
212
+ }
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+ function isVagueLesson(lesson) {
218
+ const lower = normalizeText(lesson);
219
+ const words = lower.split(/\s+/);
220
+ const hasTechRef = words.some((w) => TECH_REFERENCE_WORDS.has(w));
221
+ const hasActionVerb = words.some((w) => ACTION_VERB_WORDS.has(w));
222
+ return !hasTechRef && !hasActionVerb;
223
+ }
224
+ function validateLesson(candidate, existingLessons, meta) {
225
+ if (!candidate || typeof candidate !== "string") {
226
+ return {
227
+ valid: false,
228
+ layer: 1,
229
+ reason: "lesson too short (min 15 chars)",
230
+ severity: "error"
231
+ };
232
+ }
233
+ if (!Array.isArray(existingLessons)) {
234
+ existingLessons = [];
235
+ }
236
+ if (candidate.length < 15) {
237
+ return {
238
+ valid: false,
239
+ layer: 1,
240
+ reason: "lesson too short (min 15 chars)",
241
+ severity: "error"
242
+ };
243
+ }
244
+ if (candidate.length > 280) {
245
+ return {
246
+ valid: false,
247
+ layer: 1,
248
+ reason: "lesson too long (max 280 chars)",
249
+ severity: "error"
250
+ };
251
+ }
252
+ if (!VALID_CATEGORIES.has(meta.category)) {
253
+ return {
254
+ valid: false,
255
+ layer: 1,
256
+ reason: `invalid category: ${meta.category}`,
257
+ severity: "error"
258
+ };
259
+ }
260
+ const isGlobalScope = meta.scope === "global";
261
+ const isStackScope = /^stack:[a-zA-Z0-9_-]{1,64}$/.test(meta.scope);
262
+ if (!isGlobalScope && !isStackScope) {
263
+ return {
264
+ valid: false,
265
+ layer: 1,
266
+ reason: "invalid scope: must be 'global' or 'stack:<name>'",
267
+ severity: "error"
268
+ };
269
+ }
270
+ if (!(meta.confidence >= 0 && meta.confidence <= 1)) {
271
+ return {
272
+ valid: false,
273
+ layer: 1,
274
+ reason: "confidence out of range [0.0, 1.0]",
275
+ severity: "error"
276
+ };
277
+ }
278
+ const normalizedCandidate = candidate.normalize("NFKC").replace(INVISIBLE_FORMAT_CHARS, " ").replace(/\s+/g, " ").toLowerCase();
279
+ for (const pattern of DANGEROUS_COMMAND_ERROR_PATTERNS) {
280
+ if (pattern.test(normalizedCandidate)) {
281
+ return {
282
+ valid: false,
283
+ layer: 2,
284
+ reason: "dangerous command pattern detected",
285
+ severity: "error"
286
+ };
287
+ }
288
+ }
289
+ for (const pattern of DANGEROUS_COMMAND_WARNING_PATTERNS) {
290
+ if (pattern.test(normalizedCandidate)) {
291
+ return {
292
+ valid: true,
293
+ layer: 2,
294
+ reason: "potentially dangerous command pattern queued for review",
295
+ severity: "warning"
296
+ };
297
+ }
298
+ }
299
+ for (const pattern of SECURITY_DEGRADING_PATTERNS) {
300
+ if (pattern.test(normalizedCandidate)) {
301
+ return {
302
+ valid: false,
303
+ layer: 2,
304
+ reason: "security-degrading instruction detected",
305
+ severity: "error"
306
+ };
307
+ }
308
+ }
309
+ for (const pattern of INJECTION_PATTERNS) {
310
+ if (pattern.test(candidate)) {
311
+ return {
312
+ valid: false,
313
+ layer: 2,
314
+ reason: "injection pattern detected",
315
+ severity: "error"
316
+ };
317
+ }
318
+ }
319
+ if (detectContradiction(candidate, existingLessons)) {
320
+ return {
321
+ valid: true,
322
+ layer: 3,
323
+ reason: "possible contradiction with an existing lesson with shared tags",
324
+ severity: "warning"
325
+ };
326
+ }
327
+ if (isVagueLesson(candidate)) {
328
+ return {
329
+ valid: true,
330
+ layer: 3,
331
+ reason: "lesson may be too vague (no tech reference or action verb)",
332
+ severity: "warning"
333
+ };
334
+ }
335
+ return {
336
+ valid: true,
337
+ layer: null,
338
+ reason: null,
339
+ severity: null
340
+ };
341
+ }
342
+ var ACTIONABLE_STRING_MAX = 200;
343
+ var ACTIONABLE_LIST_MAX = 20;
344
+ var NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
345
+ var SOURCE_REF_FORBIDDEN = /(\.\.\/|\.\.\\|\0|[\x00-\x1f\x7f])/;
346
+ var ALLOWED_SKILL_PATH_PREFIXES = [
347
+ ".opencode/skills/generated/",
348
+ ".swarm/skills/proposals/",
349
+ ".swarm/skills/candidates/"
350
+ ];
351
+ var VALID_DIRECTIVE_PRIORITIES = new Set([
352
+ "low",
353
+ "medium",
354
+ "high",
355
+ "critical"
356
+ ]);
357
+ function isCleanShortString(s) {
358
+ if (typeof s !== "string")
359
+ return false;
360
+ if (s.length === 0 || s.length > ACTIONABLE_STRING_MAX)
361
+ return false;
362
+ return !/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/.test(s);
363
+ }
364
+ function validateSkillPath(p) {
365
+ if (typeof p !== "string")
366
+ return false;
367
+ if (p.length === 0 || p.length > 256)
368
+ return false;
369
+ if (p.includes("\x00"))
370
+ return false;
371
+ if (path.isAbsolute(p))
372
+ return false;
373
+ if (p.includes(".."))
374
+ return false;
375
+ const norm = p.replace(/\\/g, "/");
376
+ return ALLOWED_SKILL_PATH_PREFIXES.some((prefix) => norm.startsWith(prefix));
377
+ }
378
+ function validateSkillCandidatePath(p) {
379
+ if (typeof p !== "string")
380
+ return false;
381
+ if (p.length === 0 || p.length > 256)
382
+ return false;
383
+ if (p.includes("\x00"))
384
+ return false;
385
+ if (path.isAbsolute(p))
386
+ return false;
387
+ if (p.includes(".."))
388
+ return false;
389
+ const norm = p.replace(/\\/g, "/");
390
+ if (!norm.startsWith(".swarm/skills/candidates/"))
391
+ return false;
392
+ const filename = norm.slice(".swarm/skills/candidates/".length);
393
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.json$/i.test(filename);
394
+ }
395
+ function validateActionableFields(fields) {
396
+ const errors = [];
397
+ if (!fields)
398
+ return { valid: true, errors };
399
+ function checkStringList(name, list) {
400
+ if (list === undefined)
401
+ return;
402
+ if (!Array.isArray(list)) {
403
+ errors.push(`${name} must be an array`);
404
+ return;
405
+ }
406
+ if (list.length > ACTIONABLE_LIST_MAX) {
407
+ errors.push(`${name} exceeds ${ACTIONABLE_LIST_MAX} items`);
408
+ }
409
+ for (const item of list) {
410
+ if (!isCleanShortString(item)) {
411
+ errors.push(`${name} contains invalid string`);
412
+ return;
413
+ }
414
+ }
415
+ }
416
+ function checkNameList(name, list) {
417
+ if (list === undefined)
418
+ return;
419
+ if (!Array.isArray(list)) {
420
+ errors.push(`${name} must be an array`);
421
+ return;
422
+ }
423
+ if (list.length > ACTIONABLE_LIST_MAX) {
424
+ errors.push(`${name} exceeds ${ACTIONABLE_LIST_MAX} items`);
425
+ }
426
+ for (const item of list) {
427
+ if (typeof item !== "string" || !NAME_PATTERN.test(item)) {
428
+ errors.push(`${name} contains invalid name`);
429
+ return;
430
+ }
431
+ }
432
+ }
433
+ checkStringList("triggers", fields.triggers);
434
+ checkStringList("required_actions", fields.required_actions);
435
+ checkStringList("forbidden_actions", fields.forbidden_actions);
436
+ checkStringList("verification_checks", fields.verification_checks);
437
+ checkNameList("applies_to_agents", fields.applies_to_agents);
438
+ checkNameList("applies_to_tools", fields.applies_to_tools);
439
+ if (fields.source_refs !== undefined) {
440
+ if (!Array.isArray(fields.source_refs)) {
441
+ errors.push("source_refs must be an array");
442
+ } else if (fields.source_refs.length > ACTIONABLE_LIST_MAX) {
443
+ errors.push(`source_refs exceeds ${ACTIONABLE_LIST_MAX} items`);
444
+ } else {
445
+ for (const ref of fields.source_refs) {
446
+ if (typeof ref !== "string" || ref.length === 0 || ref.length > ACTIONABLE_STRING_MAX || SOURCE_REF_FORBIDDEN.test(ref)) {
447
+ errors.push("source_refs contains invalid value");
448
+ break;
449
+ }
450
+ }
451
+ }
452
+ }
453
+ if (fields.source_knowledge_ids !== undefined) {
454
+ if (!Array.isArray(fields.source_knowledge_ids)) {
455
+ errors.push("source_knowledge_ids must be an array");
456
+ } else {
457
+ for (const id of fields.source_knowledge_ids) {
458
+ if (typeof id !== "string" || id.length === 0 || id.length > 64) {
459
+ errors.push("source_knowledge_ids contains invalid value");
460
+ break;
461
+ }
462
+ }
463
+ }
464
+ }
465
+ if (fields.directive_priority !== undefined && !VALID_DIRECTIVE_PRIORITIES.has(String(fields.directive_priority))) {
466
+ errors.push("directive_priority must be low|medium|high|critical");
467
+ }
468
+ if (fields.generated_skill_slug !== undefined) {
469
+ if (typeof fields.generated_skill_slug !== "string" || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(fields.generated_skill_slug)) {
470
+ errors.push("generated_skill_slug must be a kebab-case slug");
471
+ }
472
+ }
473
+ if (fields.generated_skill_path !== undefined && !validateSkillPath(fields.generated_skill_path)) {
474
+ errors.push("generated_skill_path must be repo-local under allowed prefix");
475
+ }
476
+ return { valid: errors.length === 0, errors };
477
+ }
478
+ function hasNonEmptyList(v) {
479
+ return Array.isArray(v) && v.length > 0;
480
+ }
481
+ function validateActionability(entry) {
482
+ const hasPredicate = hasNonEmptyList(entry.forbidden_actions) || hasNonEmptyList(entry.required_actions) || hasNonEmptyList(entry.verification_checks) || typeof entry.verification_predicate === "string" && entry.verification_predicate.trim().length > 0;
483
+ const hasScope = hasNonEmptyList(entry.applies_to_tools) || hasNonEmptyList(entry.applies_to_agents);
484
+ if (hasPredicate && hasScope)
485
+ return { actionable: true };
486
+ const reason = !hasPredicate && !hasScope ? "missing_predicate_and_scope" : !hasPredicate ? "missing_predicate" : "missing_scope";
487
+ return { actionable: false, reason };
488
+ }
489
+ function resolveUnactionablePath(directory) {
490
+ return path.join(resolveKnowledgeStoreDir(directory), "knowledge-unactionable.jsonl");
491
+ }
492
+ async function appendUnactionable(directory, entry, reason) {
493
+ const filePath = resolveUnactionablePath(directory);
494
+ const dirPath = path.dirname(filePath);
495
+ await mkdir(dirPath, { recursive: true });
496
+ await transactKnowledge(filePath, (existing) => {
497
+ const record = {
498
+ ...entry,
499
+ status: "quarantined_unactionable",
500
+ unactionable_reason: reason,
501
+ quarantined_at: new Date().toISOString()
502
+ };
503
+ const duplicate = findNearDuplicate(record.lesson, existing, 0.6);
504
+ if (duplicate?.unactionable_reason === reason) {
505
+ duplicate.quarantined_at = record.quarantined_at;
506
+ duplicate.updated_at = record.updated_at;
507
+ return existing;
508
+ }
509
+ const next = [...existing, record];
510
+ return next.length > 200 ? next.slice(-200) : next;
511
+ });
512
+ }
513
+ function auditEntryHealth(entry) {
514
+ const utilityScore = entry.utility_score;
515
+ const ro = entry.retrieval_outcomes;
516
+ const shownCount = ro?.shown_count ?? entry.retrieval_outcomes?.applied_count ?? 0;
517
+ if (shownCount >= 5 && utilityScore !== undefined && utilityScore <= 0) {
518
+ return { healthy: false, concern: "Low-utility entry" };
519
+ }
520
+ if (entry.confidence < 0.1) {
521
+ return { healthy: false, concern: "Near-zero confidence" };
522
+ }
523
+ if (entry.auto_generated === true && entry.confirmed_by.length === 0) {
524
+ return { healthy: false, concern: "Unconfirmed auto-generated" };
525
+ }
526
+ return { healthy: true };
527
+ }
528
+ async function quarantineEntry(directory, entryId, reason, reportedBy) {
529
+ if (!directory || directory.includes("..")) {
530
+ warn("[knowledge-validator] quarantineEntry: directory traversal attempt blocked");
531
+ return;
532
+ }
533
+ if (!entryId || entryId.includes("\x00") || entryId.includes(`
534
+ `)) {
535
+ warn("[knowledge-validator] quarantineEntry: invalid entryId rejected");
536
+ return;
537
+ }
538
+ const validReportedBy = ["architect", "user", "auto"];
539
+ if (!validReportedBy.includes(reportedBy)) {
540
+ return;
541
+ }
542
+ const sanitizedReason = reason.slice(0, 500).replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\x0d]/g, "");
543
+ const knowledgePath = resolveSwarmKnowledgePath(directory);
544
+ const quarantinePath = path.join(resolveKnowledgeStoreDir(directory), "knowledge-quarantined.jsonl");
545
+ const rejectedPath = resolveSwarmRejectedPath(directory);
546
+ const swarmDir = resolveKnowledgeStoreDir(directory);
547
+ await mkdir(swarmDir, { recursive: true });
548
+ let release;
549
+ try {
550
+ release = await import_proper_lockfile.default.lock(swarmDir, {
551
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
552
+ stale: 5000
553
+ });
554
+ const entries = await readKnowledge(knowledgePath);
555
+ const entry = entries.find((e) => e.id === entryId);
556
+ if (!entry) {
557
+ return;
558
+ }
559
+ const remaining = entries.filter((e) => e.id !== entryId);
560
+ const quarantined = {
561
+ ...entry,
562
+ status: "quarantined",
563
+ original_status: entry.status,
564
+ quarantine_reason: sanitizedReason,
565
+ quarantined_at: new Date().toISOString(),
566
+ reported_by: reportedBy
567
+ };
568
+ const jsonlContent = remaining.length > 0 ? `${remaining.map((e) => JSON.stringify(e)).join(`
569
+ `)}
570
+ ` : "";
571
+ await atomicWriteFile(knowledgePath, jsonlContent);
572
+ await appendFile(quarantinePath, `${JSON.stringify(quarantined)}
573
+ `, "utf-8");
574
+ const quarantinedEntries = await readKnowledge(quarantinePath);
575
+ if (quarantinedEntries.length > 100) {
576
+ const trimmed = quarantinedEntries.slice(-100);
577
+ const capContent = trimmed.length > 0 ? `${trimmed.map((e) => JSON.stringify(e)).join(`
578
+ `)}
579
+ ` : "";
580
+ await atomicWriteFile(quarantinePath, capContent);
581
+ }
582
+ const rejectedRecord = {
583
+ id: entryId,
584
+ lesson: entry.lesson,
585
+ rejection_reason: sanitizedReason,
586
+ rejected_at: new Date().toISOString(),
587
+ rejection_layer: 3
588
+ };
589
+ await appendFile(rejectedPath, `${JSON.stringify(rejectedRecord)}
590
+ `, "utf-8");
591
+ } finally {
592
+ if (release) {
593
+ await release();
594
+ }
595
+ }
596
+ }
597
+ async function restoreEntry(directory, entryId) {
598
+ if (!directory || directory.includes("..")) {
599
+ warn("[knowledge-validator] restoreEntry: directory traversal attempt blocked");
600
+ return;
601
+ }
602
+ if (!entryId || entryId.includes("\x00") || entryId.includes(`
603
+ `)) {
604
+ warn("[knowledge-validator] restoreEntry: invalid entryId rejected");
605
+ return;
606
+ }
607
+ const storeDir = resolveKnowledgeStoreDir(directory);
608
+ const knowledgePath = resolveSwarmKnowledgePath(directory);
609
+ const quarantinePath = path.join(storeDir, "knowledge-quarantined.jsonl");
610
+ const rejectedPath = resolveSwarmRejectedPath(directory);
611
+ const swarmDir = storeDir;
612
+ await mkdir(swarmDir, { recursive: true });
613
+ let release;
614
+ try {
615
+ release = await import_proper_lockfile.default.lock(swarmDir, {
616
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
617
+ stale: 5000
618
+ });
619
+ const quarantinedEntries = await readKnowledge(quarantinePath);
620
+ const entryToRestore = quarantinedEntries.find((e) => e.id === entryId);
621
+ if (!entryToRestore) {
622
+ return;
623
+ }
624
+ const remaining = quarantinedEntries.filter((e) => e.id !== entryId);
625
+ const {
626
+ quarantine_reason,
627
+ quarantined_at,
628
+ reported_by,
629
+ original_status,
630
+ status: _quarantineStatus,
631
+ ...rest
632
+ } = entryToRestore;
633
+ const original = { ...rest, status: original_status ?? "candidate" };
634
+ const validation = validateLesson(original.lesson, [], {
635
+ category: original.category,
636
+ scope: original.scope,
637
+ confidence: original.confidence
638
+ });
639
+ if (!validation.valid) {
640
+ warn(`[knowledge-validator] restoreEntry: entry ${entryId} failed re-validation: ${validation.reason}`);
641
+ return;
642
+ }
643
+ const jsonlContent = remaining.length > 0 ? `${remaining.map((e) => JSON.stringify(e)).join(`
644
+ `)}
645
+ ` : "";
646
+ await atomicWriteFile(quarantinePath, jsonlContent);
647
+ await appendFile(knowledgePath, `${JSON.stringify(original)}
648
+ `, "utf-8");
649
+ const rejectedEntries = await readKnowledge(rejectedPath);
650
+ const filtered = rejectedEntries.filter((e) => e.id !== entryId);
651
+ const rejectedContent = filtered.length > 0 ? `${filtered.map((e) => JSON.stringify(e)).join(`
652
+ `)}
653
+ ` : "";
654
+ await atomicWriteFile(rejectedPath, rejectedContent);
655
+ } finally {
656
+ if (release) {
657
+ await release();
658
+ }
659
+ }
660
+ }
661
+ async function unarchiveEntry(directory, entryId) {
662
+ if (!directory || directory.includes("..")) {
663
+ warn("[knowledge-validator] unarchiveEntry: directory traversal attempt blocked");
664
+ return { restored: false, reason: "not_found" };
665
+ }
666
+ if (!entryId || entryId.includes("\x00") || entryId.includes(`
667
+ `)) {
668
+ warn("[knowledge-validator] unarchiveEntry: invalid entryId rejected");
669
+ return { restored: false, reason: "not_found" };
670
+ }
671
+ const storeDir = resolveKnowledgeStoreDir(directory);
672
+ const knowledgePath = resolveSwarmKnowledgePath(directory);
673
+ const swarmDir = storeDir;
674
+ await mkdir(swarmDir, { recursive: true });
675
+ let release;
676
+ try {
677
+ release = await import_proper_lockfile.default.lock(swarmDir, {
678
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
679
+ stale: 5000
680
+ });
681
+ const entries = await readKnowledge(knowledgePath);
682
+ const target = entries.find((e) => e.id === entryId);
683
+ if (!target) {
684
+ return { restored: false, reason: "not_found" };
685
+ }
686
+ if (target.status !== "archived") {
687
+ return { restored: false, reason: "not_archived" };
688
+ }
689
+ const validation = validateLesson(target.lesson, [], {
690
+ category: target.category,
691
+ scope: target.scope,
692
+ confidence: target.confidence
693
+ });
694
+ if (!validation.valid) {
695
+ warn(`[knowledge-validator] unarchiveEntry: entry ${entryId} failed re-validation: ${validation.reason}`);
696
+ return { restored: false, reason: "invalid_lesson" };
697
+ }
698
+ const restoredStatus = target.archived_from ?? "candidate";
699
+ const validRestoreTargets = new Set([
700
+ "candidate",
701
+ "established",
702
+ "promoted"
703
+ ]);
704
+ const finalStatus = validRestoreTargets.has(restoredStatus) ? restoredStatus : "candidate";
705
+ const {
706
+ archived_from: _af,
707
+ archived_at: _at,
708
+ recent_negative_phase_count: _rnpc,
709
+ last_demotion_phase: _ldp,
710
+ ...rest
711
+ } = target;
712
+ const restored = {
713
+ ...rest,
714
+ status: finalStatus,
715
+ updated_at: new Date().toISOString(),
716
+ recent_negative_phase_count: 0,
717
+ last_demotion_phase: undefined
718
+ };
719
+ const next = entries.map((e) => e.id === entryId ? restored : e);
720
+ const jsonlContent = next.length > 0 ? `${next.map((e) => JSON.stringify(e)).join(`
721
+ `)}
722
+ ` : "";
723
+ await atomicWriteFile(knowledgePath, jsonlContent);
724
+ return { restored: true, restored_to: finalStatus };
725
+ } finally {
726
+ if (release) {
727
+ await release();
728
+ }
729
+ }
730
+ }
731
+ var _internals = {
732
+ validateLesson,
733
+ auditEntryHealth,
734
+ quarantineEntry,
735
+ restoreEntry,
736
+ unarchiveEntry,
737
+ extractContextWords,
738
+ hasSignificantOverlap
739
+ };
740
+
741
+ export { DANGEROUS_COMMAND_ERROR_PATTERNS, DANGEROUS_COMMAND_WARNING_PATTERNS, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, validateLesson, ACTIONABLE_STRING_MAX, ACTIONABLE_LIST_MAX, ALLOWED_SKILL_PATH_PREFIXES, validateSkillPath, validateSkillCandidatePath, validateActionableFields, validateActionability, resolveUnactionablePath, appendUnactionable, auditEntryHealth, quarantineEntry, restoreEntry, unarchiveEntry, _internals };