@yamo/memory-mesh 2.3.2 → 3.0.1

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 (124) hide show
  1. package/README.md +8 -2
  2. package/bin/memory_mesh.js +1 -1
  3. package/lib/llm/client.d.ts +86 -0
  4. package/lib/llm/client.js +300 -357
  5. package/lib/llm/client.ts +334 -0
  6. package/lib/llm/index.d.ts +17 -0
  7. package/lib/llm/index.js +16 -8
  8. package/lib/llm/index.ts +18 -0
  9. package/lib/memory/adapters/client.d.ts +120 -0
  10. package/lib/memory/adapters/client.js +519 -0
  11. package/lib/memory/adapters/client.ts +519 -0
  12. package/lib/memory/adapters/config.d.ts +130 -0
  13. package/lib/memory/adapters/config.js +190 -0
  14. package/lib/memory/adapters/config.ts +190 -0
  15. package/lib/memory/adapters/errors.d.ts +84 -0
  16. package/lib/memory/adapters/errors.js +129 -0
  17. package/lib/memory/adapters/errors.ts +129 -0
  18. package/lib/memory/context-manager.d.ts +41 -0
  19. package/lib/memory/context-manager.js +345 -0
  20. package/lib/memory/context-manager.ts +345 -0
  21. package/lib/memory/embeddings/factory.d.ts +57 -0
  22. package/lib/memory/embeddings/factory.js +149 -0
  23. package/lib/memory/embeddings/factory.ts +149 -0
  24. package/lib/memory/embeddings/index.d.ts +2 -0
  25. package/lib/memory/embeddings/index.js +3 -0
  26. package/lib/memory/embeddings/index.ts +3 -0
  27. package/lib/memory/embeddings/service.d.ts +134 -0
  28. package/lib/memory/embeddings/service.js +516 -0
  29. package/lib/memory/embeddings/service.ts +516 -0
  30. package/lib/memory/index.d.ts +9 -0
  31. package/lib/memory/index.js +10 -1
  32. package/lib/memory/index.ts +10 -0
  33. package/lib/memory/memory-mesh.d.ts +332 -0
  34. package/lib/memory/memory-mesh.js +1470 -678
  35. package/lib/memory/memory-mesh.ts +1517 -0
  36. package/lib/memory/memory-translator.d.ts +14 -0
  37. package/lib/memory/memory-translator.js +126 -0
  38. package/lib/memory/memory-translator.ts +126 -0
  39. package/lib/memory/schema.d.ts +130 -0
  40. package/lib/memory/schema.js +184 -0
  41. package/lib/memory/schema.ts +184 -0
  42. package/lib/memory/scorer.d.ts +25 -0
  43. package/lib/memory/scorer.js +78 -0
  44. package/lib/memory/scorer.ts +78 -0
  45. package/lib/memory/search/index.d.ts +1 -0
  46. package/lib/memory/search/index.js +2 -0
  47. package/lib/memory/search/index.ts +2 -0
  48. package/lib/memory/search/keyword-search.d.ts +46 -0
  49. package/lib/memory/search/keyword-search.js +136 -0
  50. package/lib/memory/search/keyword-search.ts +136 -0
  51. package/lib/scrubber/config/defaults.d.ts +46 -0
  52. package/lib/scrubber/config/defaults.js +50 -57
  53. package/lib/scrubber/config/defaults.ts +55 -0
  54. package/lib/scrubber/errors/scrubber-error.d.ts +22 -0
  55. package/lib/scrubber/errors/scrubber-error.js +28 -32
  56. package/lib/scrubber/errors/scrubber-error.ts +44 -0
  57. package/lib/scrubber/index.d.ts +5 -0
  58. package/lib/scrubber/index.js +4 -23
  59. package/lib/scrubber/index.ts +6 -0
  60. package/lib/scrubber/scrubber.d.ts +44 -0
  61. package/lib/scrubber/scrubber.js +100 -121
  62. package/lib/scrubber/scrubber.ts +109 -0
  63. package/lib/scrubber/stages/chunker.d.ts +25 -0
  64. package/lib/scrubber/stages/chunker.js +74 -91
  65. package/lib/scrubber/stages/chunker.ts +104 -0
  66. package/lib/scrubber/stages/metadata-annotator.d.ts +17 -0
  67. package/lib/scrubber/stages/metadata-annotator.js +55 -65
  68. package/lib/scrubber/stages/metadata-annotator.ts +75 -0
  69. package/lib/scrubber/stages/normalizer.d.ts +16 -0
  70. package/lib/scrubber/stages/normalizer.js +42 -50
  71. package/lib/scrubber/stages/normalizer.ts +60 -0
  72. package/lib/scrubber/stages/semantic-filter.d.ts +16 -0
  73. package/lib/scrubber/stages/semantic-filter.js +42 -52
  74. package/lib/scrubber/stages/semantic-filter.ts +62 -0
  75. package/lib/scrubber/stages/structural-cleaner.d.ts +18 -0
  76. package/lib/scrubber/stages/structural-cleaner.js +66 -75
  77. package/lib/scrubber/stages/structural-cleaner.ts +83 -0
  78. package/lib/scrubber/stages/validator.d.ts +17 -0
  79. package/lib/scrubber/stages/validator.js +46 -56
  80. package/lib/scrubber/stages/validator.ts +67 -0
  81. package/lib/scrubber/telemetry.d.ts +29 -0
  82. package/lib/scrubber/telemetry.js +54 -58
  83. package/lib/scrubber/telemetry.ts +62 -0
  84. package/lib/scrubber/utils/hash.d.ts +14 -0
  85. package/lib/scrubber/utils/hash.js +30 -32
  86. package/lib/scrubber/utils/hash.ts +40 -0
  87. package/lib/scrubber/utils/html-parser.d.ts +14 -0
  88. package/lib/scrubber/utils/html-parser.js +32 -39
  89. package/lib/scrubber/utils/html-parser.ts +46 -0
  90. package/lib/scrubber/utils/pattern-matcher.d.ts +12 -0
  91. package/lib/scrubber/utils/pattern-matcher.js +48 -57
  92. package/lib/scrubber/utils/pattern-matcher.ts +64 -0
  93. package/lib/scrubber/utils/token-counter.d.ts +18 -0
  94. package/lib/scrubber/utils/token-counter.js +24 -25
  95. package/lib/scrubber/utils/token-counter.ts +32 -0
  96. package/lib/utils/logger.d.ts +19 -0
  97. package/lib/utils/logger.js +65 -0
  98. package/lib/utils/logger.ts +65 -0
  99. package/lib/utils/skill-metadata.d.ts +24 -0
  100. package/lib/utils/skill-metadata.js +133 -0
  101. package/lib/utils/skill-metadata.ts +133 -0
  102. package/lib/yamo/emitter.d.ts +46 -0
  103. package/lib/yamo/emitter.js +79 -143
  104. package/lib/yamo/emitter.ts +171 -0
  105. package/lib/yamo/index.d.ts +14 -0
  106. package/lib/yamo/index.js +6 -7
  107. package/lib/yamo/index.ts +16 -0
  108. package/lib/yamo/schema.d.ts +56 -0
  109. package/lib/yamo/schema.js +82 -108
  110. package/lib/yamo/schema.ts +133 -0
  111. package/package.json +13 -8
  112. package/index.d.ts +0 -111
  113. package/lib/embeddings/factory.js +0 -151
  114. package/lib/embeddings/index.js +0 -2
  115. package/lib/embeddings/service.js +0 -586
  116. package/lib/index.js +0 -6
  117. package/lib/lancedb/client.js +0 -633
  118. package/lib/lancedb/config.js +0 -215
  119. package/lib/lancedb/errors.js +0 -144
  120. package/lib/lancedb/index.js +0 -4
  121. package/lib/lancedb/schema.js +0 -217
  122. package/lib/search/index.js +0 -1
  123. package/lib/search/keyword-search.js +0 -144
  124. package/lib/utils/index.js +0 -1
@@ -1,63 +1,54 @@
1
+ // @ts-nocheck
1
2
  /**
2
3
  * Boilerplate Pattern Matching Utilities
3
4
  * @module smora/scrubber/utils/pattern-matcher
4
5
  */
5
-
6
6
  export class PatternMatcher {
7
- constructor() {
8
- this.boilerplatePatterns = this._loadDefaultPatterns();
9
- }
10
-
11
- _loadDefaultPatterns() {
12
- return [
13
- // Legal/Footer
14
- /©\s*\d{4}/i,
15
- /all rights reserved/i,
16
- /copyright\s+\d{4}/i,
17
-
18
- // Navigation
19
- /^home\s*\|/i,
20
- /^navigation\s*:|menu\s*:/i,
21
- /sidebar/i,
22
-
23
- // Meta
24
- /^last\s+updated?\s*:/i,
25
- /cookie\s+policy/i,
26
- /privacy\s+policy/i,
27
-
28
- // Auto-generated
29
- /^table\s+of\s+contents?$/i,
30
- /^contents\s*$/i,
31
- /jump\s+to\s+(section|navigation)/i,
32
-
33
- // Strings
34
- 'home | docs | contact',
35
- 'skip to main content',
36
- 'this site uses cookies'
37
- ];
38
- }
39
-
40
- getBoilerplatePatterns() {
41
- return this.boilerplatePatterns;
42
- }
43
-
44
- addPattern(pattern) {
45
- this.boilerplatePatterns.push(pattern);
46
- }
47
-
48
- removePattern(index) {
49
- if (index >= 0 && index < this.boilerplatePatterns.length) {
50
- this.boilerplatePatterns.splice(index, 1);
7
+ constructor() {
8
+ this.boilerplatePatterns = this._loadDefaultPatterns();
51
9
  }
52
- }
53
-
54
- isBoilerplate(text) {
55
- const lowerText = text.toLowerCase().trim();
56
- return this.boilerplatePatterns.some(pattern => {
57
- if (pattern instanceof RegExp) {
58
- return pattern.test(lowerText);
59
- }
60
- return lowerText.includes(pattern);
61
- });
62
- }
63
- }
10
+ _loadDefaultPatterns() {
11
+ return [
12
+ // Legal/Footer
13
+ /©\s*\d{4}/i,
14
+ /all rights reserved/i,
15
+ /copyright\s+\d{4}/i,
16
+ // Navigation
17
+ /^home\s*\|/i,
18
+ /^navigation\s*:|menu\s*:/i,
19
+ /sidebar/i,
20
+ // Meta
21
+ /^last\s+updated?\s*:/i,
22
+ /cookie\s+policy/i,
23
+ /privacy\s+policy/i,
24
+ // Auto-generated
25
+ /^table\s+of\s+contents?$/i,
26
+ /^contents\s*$/i,
27
+ /jump\s+to\s+(section|navigation)/i,
28
+ // Strings
29
+ 'home | docs | contact',
30
+ 'skip to main content',
31
+ 'this site uses cookies'
32
+ ];
33
+ }
34
+ getBoilerplatePatterns() {
35
+ return this.boilerplatePatterns;
36
+ }
37
+ addPattern(pattern) {
38
+ this.boilerplatePatterns.push(pattern);
39
+ }
40
+ removePattern(index) {
41
+ if (index >= 0 && index < this.boilerplatePatterns.length) {
42
+ this.boilerplatePatterns.splice(index, 1);
43
+ }
44
+ }
45
+ isBoilerplate(text) {
46
+ const lowerText = text.toLowerCase().trim();
47
+ return this.boilerplatePatterns.some(pattern => {
48
+ if (pattern instanceof RegExp) {
49
+ return pattern.test(lowerText);
50
+ }
51
+ return lowerText.includes(pattern);
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,64 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Boilerplate Pattern Matching Utilities
4
+ * @module smora/scrubber/utils/pattern-matcher
5
+ */
6
+
7
+ export class PatternMatcher {
8
+ constructor() {
9
+ this.boilerplatePatterns = this._loadDefaultPatterns();
10
+ }
11
+
12
+ _loadDefaultPatterns() {
13
+ return [
14
+ // Legal/Footer
15
+ /©\s*\d{4}/i,
16
+ /all rights reserved/i,
17
+ /copyright\s+\d{4}/i,
18
+
19
+ // Navigation
20
+ /^home\s*\|/i,
21
+ /^navigation\s*:|menu\s*:/i,
22
+ /sidebar/i,
23
+
24
+ // Meta
25
+ /^last\s+updated?\s*:/i,
26
+ /cookie\s+policy/i,
27
+ /privacy\s+policy/i,
28
+
29
+ // Auto-generated
30
+ /^table\s+of\s+contents?$/i,
31
+ /^contents\s*$/i,
32
+ /jump\s+to\s+(section|navigation)/i,
33
+
34
+ // Strings
35
+ 'home | docs | contact',
36
+ 'skip to main content',
37
+ 'this site uses cookies'
38
+ ];
39
+ }
40
+
41
+ getBoilerplatePatterns() {
42
+ return this.boilerplatePatterns;
43
+ }
44
+
45
+ addPattern(pattern) {
46
+ this.boilerplatePatterns.push(pattern);
47
+ }
48
+
49
+ removePattern(index) {
50
+ if (index >= 0 && index < this.boilerplatePatterns.length) {
51
+ this.boilerplatePatterns.splice(index, 1);
52
+ }
53
+ }
54
+
55
+ isBoilerplate(text) {
56
+ const lowerText = text.toLowerCase().trim();
57
+ return this.boilerplatePatterns.some(pattern => {
58
+ if (pattern instanceof RegExp) {
59
+ return pattern.test(lowerText);
60
+ }
61
+ return lowerText.includes(pattern);
62
+ });
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Token Counting Utilities
3
+ * @module smora/scrubber/utils/token-counter
4
+ */
5
+ export declare class TokenCounter {
6
+ /**
7
+ * Estimate token count (approximation)
8
+ * @param {string} text - Text to count
9
+ * @returns {number} - Estimated token count
10
+ */
11
+ count(text: any): number;
12
+ /**
13
+ * More accurate token count (slower)
14
+ * @param {string} text - Text to count
15
+ * @returns {number} - More accurate token count
16
+ */
17
+ countAccurate(text: any): any;
18
+ }
@@ -1,31 +1,30 @@
1
+ // @ts-nocheck
1
2
  /**
2
3
  * Token Counting Utilities
3
4
  * @module smora/scrubber/utils/token-counter
4
5
  */
5
-
6
6
  export class TokenCounter {
7
- /**
8
- * Estimate token count (approximation)
9
- * @param {string} text - Text to count
10
- * @returns {number} - Estimated token count
11
- */
12
- count(text) {
13
- // Simple approximation: ~4 characters per token
14
- return Math.ceil(text.length / 4);
15
- }
16
-
17
- /**
18
- * More accurate token count (slower)
19
- * @param {string} text - Text to count
20
- * @returns {number} - More accurate token count
21
- */
22
- countAccurate(text) {
23
- const words = text.split(/\s+/).filter(w => w.length > 0);
24
- let tokens = words.length;
25
- const punctuationMatches = text.match(/[.,!?;:]/g);
26
- if (punctuationMatches) {
27
- tokens += punctuationMatches.length;
7
+ /**
8
+ * Estimate token count (approximation)
9
+ * @param {string} text - Text to count
10
+ * @returns {number} - Estimated token count
11
+ */
12
+ count(text) {
13
+ // Simple approximation: ~4 characters per token
14
+ return Math.ceil(text.length / 4);
28
15
  }
29
- return tokens;
30
- }
31
- }
16
+ /**
17
+ * More accurate token count (slower)
18
+ * @param {string} text - Text to count
19
+ * @returns {number} - More accurate token count
20
+ */
21
+ countAccurate(text) {
22
+ const words = text.split(/\s+/).filter(w => w.length > 0);
23
+ let tokens = words.length;
24
+ const punctuationMatches = text.match(/[.,!?;:]/g);
25
+ if (punctuationMatches) {
26
+ tokens += punctuationMatches.length;
27
+ }
28
+ return tokens;
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Token Counting Utilities
4
+ * @module smora/scrubber/utils/token-counter
5
+ */
6
+
7
+ export class TokenCounter {
8
+ /**
9
+ * Estimate token count (approximation)
10
+ * @param {string} text - Text to count
11
+ * @returns {number} - Estimated token count
12
+ */
13
+ count(text) {
14
+ // Simple approximation: ~4 characters per token
15
+ return Math.ceil(text.length / 4);
16
+ }
17
+
18
+ /**
19
+ * More accurate token count (slower)
20
+ * @param {string} text - Text to count
21
+ * @returns {number} - More accurate token count
22
+ */
23
+ countAccurate(text) {
24
+ const words = text.split(/\s+/).filter(w => w.length > 0);
25
+ let tokens = words.length;
26
+ const punctuationMatches = text.match(/[.,!?;:]/g);
27
+ if (punctuationMatches) {
28
+ tokens += punctuationMatches.length;
29
+ }
30
+ return tokens;
31
+ }
32
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Structured Logger using Pino
3
+ * Provides centralized logging with metadata support, PII redaction, and environment-based formatting
4
+ */
5
+ import pino from "pino";
6
+ /**
7
+ * Main logger instance with configuration
8
+ */
9
+ export declare const logger: pino.Logger<never, boolean>;
10
+ /**
11
+ * Create a child logger with module-specific context
12
+ * @param module - Module name for logging context
13
+ * @returns Child logger instance
14
+ *
15
+ * @example
16
+ * const logger = createLogger('kernel');
17
+ * logger.info({ action: 'boot' }, 'Kernel starting');
18
+ */
19
+ export declare function createLogger(module: any): pino.Logger<never, boolean>;
@@ -0,0 +1,65 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Structured Logger using Pino
4
+ * Provides centralized logging with metadata support, PII redaction, and environment-based formatting
5
+ */
6
+ import pino from "pino";
7
+ // Determine if running in production
8
+ const isProduction = process.env.NODE_ENV === "production";
9
+ const logLevel = (process.env.LOG_LEVEL || "warn");
10
+ /**
11
+ * Main logger instance with configuration
12
+ */
13
+ export const logger = pino({
14
+ level: logLevel,
15
+ // Pretty printing in development, JSON in production
16
+ transport: !isProduction
17
+ ? {
18
+ target: "pino-pretty",
19
+ options: {
20
+ colorize: true,
21
+ translateTime: "HH:MM:ss.l",
22
+ ignore: "pid,hostname,app,version",
23
+ singleLine: false,
24
+ },
25
+ }
26
+ : undefined,
27
+ // Base fields included in all logs
28
+ base: {
29
+ app: "yamo-os",
30
+ version: process.env.npm_package_version || "1.1.0",
31
+ },
32
+ // Redact sensitive fields from logs
33
+ redact: {
34
+ paths: [
35
+ "apiKey",
36
+ "password",
37
+ "token",
38
+ "secret",
39
+ "key",
40
+ "*.apiKey",
41
+ "*.password",
42
+ "*.token",
43
+ "*.secret",
44
+ "*.key",
45
+ "OPENAI_API_KEY",
46
+ "ANTHROPIC_API_KEY",
47
+ "ZAI_API_KEY",
48
+ ],
49
+ censor: "[REDACTED]",
50
+ },
51
+ // Timestamp in ISO format
52
+ timestamp: pino.stdTimeFunctions.isoTime,
53
+ });
54
+ /**
55
+ * Create a child logger with module-specific context
56
+ * @param module - Module name for logging context
57
+ * @returns Child logger instance
58
+ *
59
+ * @example
60
+ * const logger = createLogger('kernel');
61
+ * logger.info({ action: 'boot' }, 'Kernel starting');
62
+ */
63
+ export function createLogger(module) {
64
+ return logger.child({ module });
65
+ }
@@ -0,0 +1,65 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Structured Logger using Pino
4
+ * Provides centralized logging with metadata support, PII redaction, and environment-based formatting
5
+ */
6
+ import pino from "pino";
7
+ // Determine if running in production
8
+ const isProduction = process.env.NODE_ENV === "production";
9
+ const logLevel = (process.env.LOG_LEVEL || "warn");
10
+ /**
11
+ * Main logger instance with configuration
12
+ */
13
+ export const logger = pino({
14
+ level: logLevel,
15
+ // Pretty printing in development, JSON in production
16
+ transport: !isProduction
17
+ ? {
18
+ target: "pino-pretty",
19
+ options: {
20
+ colorize: true,
21
+ translateTime: "HH:MM:ss.l",
22
+ ignore: "pid,hostname,app,version",
23
+ singleLine: false,
24
+ },
25
+ }
26
+ : undefined,
27
+ // Base fields included in all logs
28
+ base: {
29
+ app: "yamo-os",
30
+ version: process.env.npm_package_version || "1.1.0",
31
+ },
32
+ // Redact sensitive fields from logs
33
+ redact: {
34
+ paths: [
35
+ "apiKey",
36
+ "password",
37
+ "token",
38
+ "secret",
39
+ "key",
40
+ "*.apiKey",
41
+ "*.password",
42
+ "*.token",
43
+ "*.secret",
44
+ "*.key",
45
+ "OPENAI_API_KEY",
46
+ "ANTHROPIC_API_KEY",
47
+ "ZAI_API_KEY",
48
+ ],
49
+ censor: "[REDACTED]",
50
+ },
51
+ // Timestamp in ISO format
52
+ timestamp: pino.stdTimeFunctions.isoTime,
53
+ });
54
+ /**
55
+ * Create a child logger with module-specific context
56
+ * @param module - Module name for logging context
57
+ * @returns Child logger instance
58
+ *
59
+ * @example
60
+ * const logger = createLogger('kernel');
61
+ * logger.info({ action: 'boot' }, 'Kernel starting');
62
+ */
63
+ export function createLogger(module) {
64
+ return logger.child({ module });
65
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Extract identity fields (name, intent, description) from .yamo content.
3
+ *
4
+ * Priority:
5
+ * 1. YAML frontmatter (--- … --- block at file start)
6
+ * 2. Legacy v0.4 root-level compact declarations
7
+ * 3. Content-hash fallback — deterministic and idempotent
8
+ */
9
+ export declare function extractSkillIdentity(content: any): {
10
+ name: any;
11
+ intent: any;
12
+ description: any;
13
+ };
14
+ /**
15
+ * Extract tags from YAML frontmatter.
16
+ * Returns an array of tag strings, or empty array if no tags found.
17
+ *
18
+ * Tags are expected in the format:
19
+ * tags: tag1, tag2, tag3
20
+ *
21
+ * This function ONLY reads the YAML frontmatter block and does NOT parse
22
+ * the skill body, following the same safety constraints as extractSkillIdentity.
23
+ */
24
+ export declare function extractSkillTags(content: any): any;
@@ -0,0 +1,133 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Structured skill-identity extraction.
4
+ *
5
+ * Reads ONLY the YAML frontmatter block (between --- delimiters) or
6
+ * root-level identity declarations in legacy v0.4 compact format.
7
+ * The body of .yamo files is LLM-interpreted and MUST NOT be machine-parsed.
8
+ */
9
+ import crypto from "crypto";
10
+ /** Fields safe to extract for indexing and display. */
11
+ const IDENTITY_FIELDS = new Set(["name", "intent", "description"]);
12
+ /** Pre-computed regexes for legacy root-level identity declarations. */
13
+ const LEGACY_REGEXES = {
14
+ name: /^name[;:]\s*([^;\n]+);?/m,
15
+ intent: /^intent[;:]\s*([^;\n]+);?/m,
16
+ description: /^description[;:]\s*([^;\n]+);?/m,
17
+ };
18
+ /**
19
+ * Parse flat key: value or key; value lines from a text block.
20
+ * Only extracts whitelisted identity fields.
21
+ */
22
+ function parseFlatBlock(block) {
23
+ const result = {};
24
+ for (const line of block.split("\n")) {
25
+ const trimmed = line.trim();
26
+ if (!trimmed || trimmed.startsWith("#")) {
27
+ continue;
28
+ }
29
+ const colonIdx = trimmed.indexOf(":");
30
+ const semiIdx = trimmed.indexOf(";");
31
+ let sepIdx;
32
+ if (colonIdx > 0 && semiIdx > 0) {
33
+ sepIdx = Math.min(colonIdx, semiIdx);
34
+ }
35
+ else if (colonIdx > 0) {
36
+ sepIdx = colonIdx;
37
+ }
38
+ else if (semiIdx > 0) {
39
+ sepIdx = semiIdx;
40
+ }
41
+ else {
42
+ continue;
43
+ }
44
+ const key = trimmed.substring(0, sepIdx).trim();
45
+ let value = trimmed.substring(sepIdx + 1).trim();
46
+ // Strip trailing semicolon (legacy compact format)
47
+ if (value.endsWith(";")) {
48
+ value = value.slice(0, -1).trim();
49
+ }
50
+ if (IDENTITY_FIELDS.has(key) && value) {
51
+ result[key] = value;
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ /**
57
+ * Extract identity fields (name, intent, description) from .yamo content.
58
+ *
59
+ * Priority:
60
+ * 1. YAML frontmatter (--- … --- block at file start)
61
+ * 2. Legacy v0.4 root-level compact declarations
62
+ * 3. Content-hash fallback — deterministic and idempotent
63
+ */
64
+ export function extractSkillIdentity(content) {
65
+ // 1. YAML frontmatter
66
+ if (content.startsWith("---")) {
67
+ const endIdx = content.indexOf("---", 3);
68
+ if (endIdx !== -1) {
69
+ const fields = parseFlatBlock(content.substring(3, endIdx));
70
+ if (fields.name) {
71
+ return {
72
+ name: fields.name,
73
+ intent: fields.intent || "general_procedure",
74
+ description: fields.description || "",
75
+ };
76
+ }
77
+ }
78
+ }
79
+ // 2. Legacy v0.4 root-level identity declarations.
80
+ // Safe: name/intent/description do not appear as body section headers.
81
+ const legacyFields = {};
82
+ for (const field of IDENTITY_FIELDS) {
83
+ const match = content.match(LEGACY_REGEXES[field]);
84
+ if (match) {
85
+ legacyFields[field] = match[1].trim();
86
+ }
87
+ }
88
+ if (legacyFields.name) {
89
+ return {
90
+ name: legacyFields.name,
91
+ intent: legacyFields.intent || "general_procedure",
92
+ description: legacyFields.description || "",
93
+ };
94
+ }
95
+ // 3. Content-hash fallback
96
+ const shortHash = crypto
97
+ .createHash("sha256")
98
+ .update(content)
99
+ .digest("hex")
100
+ .substring(0, 12);
101
+ return {
102
+ name: `Unnamed_${shortHash}`,
103
+ intent: "general_procedure",
104
+ description: "",
105
+ };
106
+ }
107
+ /**
108
+ * Extract tags from YAML frontmatter.
109
+ * Returns an array of tag strings, or empty array if no tags found.
110
+ *
111
+ * Tags are expected in the format:
112
+ * tags: tag1, tag2, tag3
113
+ *
114
+ * This function ONLY reads the YAML frontmatter block and does NOT parse
115
+ * the skill body, following the same safety constraints as extractSkillIdentity.
116
+ */
117
+ export function extractSkillTags(content) {
118
+ // Only parse YAML frontmatter (between --- delimiters)
119
+ if (content.startsWith("---")) {
120
+ const endIdx = content.indexOf("---", 3);
121
+ if (endIdx !== -1) {
122
+ const frontmatter = content.substring(3, endIdx);
123
+ const tagsMatch = frontmatter.match(/^tags:\s*(.+)$/m);
124
+ if (tagsMatch) {
125
+ return tagsMatch[1]
126
+ .split(",")
127
+ .map((t) => t.trim())
128
+ .filter((t) => t.length > 0);
129
+ }
130
+ }
131
+ }
132
+ return [];
133
+ }