@shrkcrft/importer 0.1.0-alpha.2

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/api/import-agents-md.d.ts +15 -0
  4. package/dist/api/import-agents-md.d.ts.map +1 -0
  5. package/dist/api/import-agents-md.js +35 -0
  6. package/dist/api/import-claude-md.d.ts +11 -0
  7. package/dist/api/import-claude-md.d.ts.map +1 -0
  8. package/dist/api/import-claude-md.js +34 -0
  9. package/dist/api/import-cursor-rules.d.ts +14 -0
  10. package/dist/api/import-cursor-rules.d.ts.map +1 -0
  11. package/dist/api/import-cursor-rules.js +88 -0
  12. package/dist/emit/emit-knowledge-ts.d.ts +17 -0
  13. package/dist/emit/emit-knowledge-ts.d.ts.map +1 -0
  14. package/dist/emit/emit-knowledge-ts.js +66 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +10 -0
  18. package/dist/model/import-format.d.ts +7 -0
  19. package/dist/model/import-format.d.ts.map +1 -0
  20. package/dist/model/import-format.js +21 -0
  21. package/dist/model/import-result.d.ts +16 -0
  22. package/dist/model/import-result.d.ts.map +1 -0
  23. package/dist/model/import-result.js +1 -0
  24. package/dist/model/imported-entry.d.ts +27 -0
  25. package/dist/model/imported-entry.d.ts.map +1 -0
  26. package/dist/model/imported-entry.js +1 -0
  27. package/dist/parse/parse-cursor-rule.d.ts +15 -0
  28. package/dist/parse/parse-cursor-rule.d.ts.map +1 -0
  29. package/dist/parse/parse-cursor-rule.js +69 -0
  30. package/dist/parse/parse-markdown-rules.d.ts +18 -0
  31. package/dist/parse/parse-markdown-rules.d.ts.map +1 -0
  32. package/dist/parse/parse-markdown-rules.js +133 -0
  33. package/dist/parse/slugify.d.ts +4 -0
  34. package/dist/parse/slugify.d.ts.map +1 -0
  35. package/dist/parse/slugify.js +54 -0
  36. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SharkCraft contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @shrkcrft/importer
2
+
3
+ SharkCraft importer: parse AGENTS.md / CLAUDE.md / .cursor/rules into structured knowledge entries.
4
+
5
+ Part of [SharkCraft](https://github.com/shrkcrft/sharkcraft) — a deterministic, local-first toolkit that gives AI coding agents durable project context. See the main repo for documentation, examples, and the `shrk` CLI.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ bun add @shrkcrft/importer
11
+ ```
12
+
13
+ ## License
14
+
15
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,15 @@
1
+ import type { IImportResult } from '../model/import-result.js';
2
+ export interface IImportAgentsMdOptions {
3
+ /** Absolute or relative path to AGENTS.md. */
4
+ filePath: string;
5
+ /** Project root (used to relativize origin paths in the result). */
6
+ projectRoot?: string;
7
+ /** Override the entry id prefix. Defaults to "agents". */
8
+ idPrefix?: string;
9
+ /** Extra tags appended to every produced entry. */
10
+ extraTags?: readonly string[];
11
+ /** Scope tokens appended to every produced entry. */
12
+ scope?: readonly string[];
13
+ }
14
+ export declare function importAgentsMd(options: IImportAgentsMdOptions): IImportResult;
15
+ //# sourceMappingURL=import-agents-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-agents-md.d.ts","sourceRoot":"","sources":["../../src/api/import-agents-md.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,qDAAqD;IACrD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa,CA+B7E"}
@@ -0,0 +1,35 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { parseMarkdownRules } from "../parse/parse-markdown-rules.js";
4
+ export function importAgentsMd(options) {
5
+ const root = options.projectRoot ?? process.cwd();
6
+ const file = nodePath.isAbsolute(options.filePath)
7
+ ? options.filePath
8
+ : nodePath.resolve(root, options.filePath);
9
+ if (!existsSync(file)) {
10
+ return {
11
+ format: 'agents-md',
12
+ sourceFiles: [],
13
+ entries: [],
14
+ warnings: [{ origin: options.filePath, message: 'File does not exist.' }],
15
+ };
16
+ }
17
+ const raw = readFileSync(file, 'utf8');
18
+ const origin = nodePath.relative(root, file) || nodePath.basename(file);
19
+ const entries = parseMarkdownRules(raw, {
20
+ origin,
21
+ idPrefix: options.idPrefix ?? 'agents',
22
+ }).map((e) => ({
23
+ ...e,
24
+ tags: [...new Set([...e.tags, ...(options.extraTags ?? []), ...(options.scope ?? [])])],
25
+ }));
26
+ const warnings = entries.length === 0
27
+ ? [{ origin, message: 'No bullet rules or headed paragraphs found.' }]
28
+ : [];
29
+ return {
30
+ format: 'agents-md',
31
+ sourceFiles: [origin],
32
+ entries,
33
+ warnings,
34
+ };
35
+ }
@@ -0,0 +1,11 @@
1
+ import type { IImportResult } from '../model/import-result.js';
2
+ export interface IImportClaudeMdOptions {
3
+ /** Path to CLAUDE.md (absolute or relative to projectRoot). */
4
+ filePath: string;
5
+ projectRoot?: string;
6
+ idPrefix?: string;
7
+ extraTags?: readonly string[];
8
+ scope?: readonly string[];
9
+ }
10
+ export declare function importClaudeMd(options: IImportClaudeMdOptions): IImportResult;
11
+ //# sourceMappingURL=import-claude-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-claude-md.d.ts","sourceRoot":"","sources":["../../src/api/import-claude-md.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa,CA8B7E"}
@@ -0,0 +1,34 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { parseMarkdownRules } from "../parse/parse-markdown-rules.js";
4
+ export function importClaudeMd(options) {
5
+ const root = options.projectRoot ?? process.cwd();
6
+ const file = nodePath.isAbsolute(options.filePath)
7
+ ? options.filePath
8
+ : nodePath.resolve(root, options.filePath);
9
+ if (!existsSync(file)) {
10
+ return {
11
+ format: 'claude-md',
12
+ sourceFiles: [],
13
+ entries: [],
14
+ warnings: [{ origin: options.filePath, message: 'File does not exist.' }],
15
+ };
16
+ }
17
+ const raw = readFileSync(file, 'utf8');
18
+ const origin = nodePath.relative(root, file) || nodePath.basename(file);
19
+ const entries = parseMarkdownRules(raw, {
20
+ origin,
21
+ idPrefix: options.idPrefix ?? 'claude',
22
+ }).map((e) => ({
23
+ ...e,
24
+ tags: [...new Set([...e.tags, ...(options.extraTags ?? []), ...(options.scope ?? [])])],
25
+ }));
26
+ return {
27
+ format: 'claude-md',
28
+ sourceFiles: [origin],
29
+ entries,
30
+ warnings: entries.length === 0
31
+ ? [{ origin, message: 'No bullet rules or headed paragraphs found.' }]
32
+ : [],
33
+ };
34
+ }
@@ -0,0 +1,14 @@
1
+ import type { IImportResult } from '../model/import-result.js';
2
+ export interface IImportCursorRulesOptions {
3
+ /** Path to .cursor/rules directory OR a single .mdc file. */
4
+ filePath: string;
5
+ projectRoot?: string;
6
+ /** Override id prefix (per-file slug still appended). Defaults to "cursor". */
7
+ idPrefix?: string;
8
+ /** Extra tags appended to every produced entry. */
9
+ extraTags?: readonly string[];
10
+ /** Scope tokens appended to every produced entry. */
11
+ scope?: readonly string[];
12
+ }
13
+ export declare function importCursorRules(options: IImportCursorRulesOptions): IImportResult;
14
+ //# sourceMappingURL=import-cursor-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-cursor-rules.d.ts","sourceRoot":"","sources":["../../src/api/import-cursor-rules.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,2BAA2B,CAAC;AAI/E,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,qDAAqD;IACrD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA6BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,yBAAyB,GAAG,aAAa,CAoDnF"}
@@ -0,0 +1,88 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { parseCursorRuleFile } from "../parse/parse-cursor-rule.js";
4
+ import { slugify } from "../parse/slugify.js";
5
+ function walkMdcFiles(start) {
6
+ const out = [];
7
+ const stack = [start];
8
+ while (stack.length) {
9
+ const cur = stack.pop();
10
+ let stat;
11
+ try {
12
+ stat = statSync(cur);
13
+ }
14
+ catch {
15
+ continue;
16
+ }
17
+ if (stat.isFile()) {
18
+ if (cur.endsWith('.mdc') || cur.endsWith('.md'))
19
+ out.push(cur);
20
+ continue;
21
+ }
22
+ if (!stat.isDirectory())
23
+ continue;
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(cur);
27
+ }
28
+ catch {
29
+ continue;
30
+ }
31
+ for (const name of entries)
32
+ stack.push(nodePath.join(cur, name));
33
+ }
34
+ return out.sort();
35
+ }
36
+ export function importCursorRules(options) {
37
+ const root = options.projectRoot ?? process.cwd();
38
+ const target = nodePath.isAbsolute(options.filePath)
39
+ ? options.filePath
40
+ : nodePath.resolve(root, options.filePath);
41
+ if (!existsSync(target)) {
42
+ return {
43
+ format: 'cursor-rules',
44
+ sourceFiles: [],
45
+ entries: [],
46
+ warnings: [{ origin: options.filePath, message: 'File or directory does not exist.' }],
47
+ };
48
+ }
49
+ const files = walkMdcFiles(target);
50
+ const entries = [];
51
+ const warnings = [];
52
+ const seenIds = new Set();
53
+ const basePrefix = options.idPrefix ?? 'cursor';
54
+ for (const file of files) {
55
+ const raw = readFileSync(file, 'utf8');
56
+ const origin = nodePath.relative(root, file) || nodePath.basename(file);
57
+ const base = nodePath.basename(file).replace(/\.(mdc|md)$/i, '');
58
+ const idPrefix = `${basePrefix}.${slugify(base) || 'rule'}`;
59
+ const entry = parseCursorRuleFile(raw, { origin, idPrefix });
60
+ let id = entry.id;
61
+ let counter = 2;
62
+ while (seenIds.has(id)) {
63
+ id = `${entry.id}-${counter}`;
64
+ counter += 1;
65
+ }
66
+ seenIds.add(id);
67
+ const tags = [
68
+ ...new Set([
69
+ ...entry.tags,
70
+ ...(options.extraTags ?? []),
71
+ ...(options.scope ?? []),
72
+ ]),
73
+ ];
74
+ entries.push({ ...entry, id, tags });
75
+ }
76
+ if (files.length === 0) {
77
+ warnings.push({
78
+ origin: nodePath.relative(root, target) || target,
79
+ message: 'No .mdc / .md files found under target.',
80
+ });
81
+ }
82
+ return {
83
+ format: 'cursor-rules',
84
+ sourceFiles: files.map((f) => nodePath.relative(root, f) || f),
85
+ entries,
86
+ warnings,
87
+ };
88
+ }
@@ -0,0 +1,17 @@
1
+ import type { IImportedEntry } from '../model/imported-entry.js';
2
+ /**
3
+ * Render imported entries as a TypeScript module that defines a knowledge
4
+ * base. The output is a self-contained, deterministic source file callers can
5
+ * write into their `sharkcraft/` folder.
6
+ *
7
+ * The emitter avoids exotic escaping: it serializes each entry through
8
+ * JSON.stringify with re-indentation and a final `as const` cast.
9
+ */
10
+ export interface IEmitOptions {
11
+ /** Header comment explaining where the file came from. */
12
+ sourceLabel: string;
13
+ /** Variable name to export (default: importedKnowledge). */
14
+ exportName?: string;
15
+ }
16
+ export declare function emitKnowledgeTs(entries: readonly IImportedEntry[], opts: IEmitOptions): string;
17
+ //# sourceMappingURL=emit-knowledge-ts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit-knowledge-ts.d.ts","sourceRoot":"","sources":["../../src/emit/emit-knowledge-ts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAc9F"}
@@ -0,0 +1,66 @@
1
+ export function emitKnowledgeTs(entries, opts) {
2
+ const exportName = opts.exportName ?? 'importedKnowledge';
3
+ const items = entries.map(serializeEntry).join(',\n');
4
+ return [
5
+ '// Auto-generated by `shrk import`.',
6
+ `// Source: ${opts.sourceLabel}`,
7
+ '// Review every entry — the importer is best-effort and types/priorities are inferred.',
8
+ "import { defineKnowledgeEntry, KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';",
9
+ '',
10
+ `export const ${exportName} = [`,
11
+ items,
12
+ '];',
13
+ '',
14
+ ].join('\n');
15
+ }
16
+ function escapeBackticks(s) {
17
+ return s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
18
+ }
19
+ function serializeEntry(entry) {
20
+ const lines = [];
21
+ lines.push(' defineKnowledgeEntry({');
22
+ lines.push(` id: ${JSON.stringify(entry.id)},`);
23
+ lines.push(` title: ${JSON.stringify(entry.title)},`);
24
+ lines.push(` type: KnowledgeType.${capitalize(entry.type)},`);
25
+ lines.push(` priority: KnowledgePriority.${capitalize(entry.priority)},`);
26
+ if (entry.tags.length > 0) {
27
+ lines.push(` tags: ${JSON.stringify(entry.tags)},`);
28
+ }
29
+ if (entry.section) {
30
+ lines.push(` // section: ${JSON.stringify(entry.section)}`);
31
+ }
32
+ lines.push(' content: `' + escapeBackticks(entry.content) + '`,');
33
+ lines.push(' })');
34
+ return lines.join('\n');
35
+ }
36
+ function capitalize(s) {
37
+ // KnowledgeType / KnowledgePriority members are PascalCase enum members but
38
+ // string values are kebab-or-lower; map known values explicitly.
39
+ const map = {
40
+ rule: 'Rule',
41
+ path: 'Path',
42
+ template: 'Template',
43
+ architecture: 'Architecture',
44
+ technical: 'Technical',
45
+ business: 'Business',
46
+ command: 'Command',
47
+ environment: 'Environment',
48
+ dependency: 'Dependency',
49
+ feature: 'Feature',
50
+ task: 'Task',
51
+ warning: 'Warning',
52
+ decision: 'Decision',
53
+ convention: 'Convention',
54
+ workflow: 'Workflow',
55
+ testing: 'Testing',
56
+ security: 'Security',
57
+ deployment: 'Deployment',
58
+ integration: 'Integration',
59
+ custom: 'Custom',
60
+ critical: 'Critical',
61
+ high: 'High',
62
+ medium: 'Medium',
63
+ low: 'Low',
64
+ };
65
+ return map[s] ?? s.charAt(0).toUpperCase() + s.slice(1);
66
+ }
@@ -0,0 +1,11 @@
1
+ export * from './model/import-format.js';
2
+ export * from './model/import-result.js';
3
+ export * from './model/imported-entry.js';
4
+ export * from './parse/parse-markdown-rules.js';
5
+ export * from './parse/parse-cursor-rule.js';
6
+ export * from './parse/slugify.js';
7
+ export * from './api/import-agents-md.js';
8
+ export * from './api/import-claude-md.js';
9
+ export * from './api/import-cursor-rules.js';
10
+ export * from './emit/emit-knowledge-ts.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export * from "./model/import-format.js";
2
+ export * from "./model/import-result.js";
3
+ export * from "./model/imported-entry.js";
4
+ export * from "./parse/parse-markdown-rules.js";
5
+ export * from "./parse/parse-cursor-rule.js";
6
+ export * from "./parse/slugify.js";
7
+ export * from "./api/import-agents-md.js";
8
+ export * from "./api/import-claude-md.js";
9
+ export * from "./api/import-cursor-rules.js";
10
+ export * from "./emit/emit-knowledge-ts.js";
@@ -0,0 +1,7 @@
1
+ export declare enum ImportFormat {
2
+ AgentsMd = "agents-md",
3
+ ClaudeMd = "claude-md",
4
+ CursorRules = "cursor-rules"
5
+ }
6
+ export declare function parseImportFormat(input: string): ImportFormat | undefined;
7
+ //# sourceMappingURL=import-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-format.d.ts","sourceRoot":"","sources":["../../src/model/import-format.ts"],"names":[],"mappings":"AAAA,oBAAY,YAAY;IACtB,QAAQ,cAAc;IACtB,QAAQ,cAAc;IACtB,WAAW,iBAAiB;CAC7B;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAczE"}
@@ -0,0 +1,21 @@
1
+ export var ImportFormat;
2
+ (function (ImportFormat) {
3
+ ImportFormat["AgentsMd"] = "agents-md";
4
+ ImportFormat["ClaudeMd"] = "claude-md";
5
+ ImportFormat["CursorRules"] = "cursor-rules";
6
+ })(ImportFormat || (ImportFormat = {}));
7
+ export function parseImportFormat(input) {
8
+ switch (input) {
9
+ case 'agents-md':
10
+ case 'AGENTS.md':
11
+ return ImportFormat.AgentsMd;
12
+ case 'claude-md':
13
+ case 'CLAUDE.md':
14
+ return ImportFormat.ClaudeMd;
15
+ case 'cursor-rules':
16
+ case 'cursor':
17
+ return ImportFormat.CursorRules;
18
+ default:
19
+ return undefined;
20
+ }
21
+ }
@@ -0,0 +1,16 @@
1
+ import type { IImportedEntry } from './imported-entry.js';
2
+ export interface IImportWarning {
3
+ origin: string;
4
+ message: string;
5
+ }
6
+ export interface IImportResult {
7
+ /** Source format that produced the entries. */
8
+ format: string;
9
+ /** Files that were read. */
10
+ sourceFiles: string[];
11
+ /** Parsed entries (deduped by id). */
12
+ entries: IImportedEntry[];
13
+ /** Non-fatal issues (missing headings, ambiguous priority, …). */
14
+ warnings: IImportWarning[];
15
+ }
16
+ //# sourceMappingURL=import-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-result.d.ts","sourceRoot":"","sources":["../../src/model/import-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sCAAsC;IACtC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';
2
+ /**
3
+ * A single rule / fact parsed from an external agent file. Looser than
4
+ * {@link IKnowledgeEntry} — fields are best-effort and the emitter fills the
5
+ * gaps with conservative defaults.
6
+ */
7
+ export interface IImportedEntry {
8
+ /** Stable id derived from the source heading + slug. */
9
+ id: string;
10
+ /** First sentence / first line of the rule body — used as the entry title. */
11
+ title: string;
12
+ /** Best-guess knowledge type from the source. Defaults to rule. */
13
+ type: KnowledgeType;
14
+ /** Best-guess priority. Defaults to medium. */
15
+ priority: KnowledgePriority;
16
+ /** Heading the entry was found under (e.g. "Coding standards"). */
17
+ section?: string;
18
+ /** Tags inferred from heading or the entry body. */
19
+ tags: string[];
20
+ /** Full body content. Markdown is preserved as-is. */
21
+ content: string;
22
+ /** Source file (relative to the import root). */
23
+ origin: string;
24
+ /** Free-form notes the importer wants to surface (e.g. "rule had no header"). */
25
+ importerNotes?: string[];
26
+ }
27
+ //# sourceMappingURL=imported-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imported-entry.d.ts","sourceRoot":"","sources":["../../src/model/imported-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,IAAI,EAAE,aAAa,CAAC;IACpB,+CAA+C;IAC/C,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { IImportedEntry } from '../model/imported-entry.js';
2
+ /**
3
+ * Parse a single .cursor/rules/*.mdc file. MDC format = YAML frontmatter
4
+ * (between `---` markers) + markdown body. We extract:
5
+ * - `description` → entry title
6
+ * - `globs` / `tags` → tags
7
+ * - `priority` (if present) → KnowledgePriority
8
+ * - the body → content
9
+ */
10
+ export interface ICursorRuleOptions {
11
+ origin: string;
12
+ idPrefix: string;
13
+ }
14
+ export declare function parseCursorRuleFile(raw: string, options: ICursorRuleOptions): IImportedEntry;
15
+ //# sourceMappingURL=parse-cursor-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-cursor-rule.d.ts","sourceRoot":"","sources":["../../src/parse/parse-cursor-rule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoDD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,kBAAkB,GAC1B,cAAc,CAqBhB"}
@@ -0,0 +1,69 @@
1
+ import { KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';
2
+ import { keywordTags, slugify } from "./slugify.js";
3
+ function parseFrontmatter(raw) {
4
+ if (!raw.startsWith('---'))
5
+ return { fm: {}, body: raw };
6
+ const end = raw.indexOf('\n---', 3);
7
+ if (end < 0)
8
+ return { fm: {}, body: raw };
9
+ const block = raw.slice(3, end).trim();
10
+ const body = raw.slice(end + 4).replace(/^\n+/, '');
11
+ const fm = {};
12
+ for (const line of block.split('\n')) {
13
+ const m = /^([A-Za-z][\w-]*)\s*:\s*(.*)$/.exec(line);
14
+ if (!m)
15
+ continue;
16
+ const key = m[1];
17
+ let value = m[2].trim();
18
+ // Strip wrapping quotes / brackets.
19
+ if (value.startsWith('"') && value.endsWith('"'))
20
+ value = value.slice(1, -1);
21
+ if (value.startsWith("'") && value.endsWith("'"))
22
+ value = value.slice(1, -1);
23
+ if (key === 'tags' || key === 'globs') {
24
+ const list = value.replace(/^\[|\]$/g, '').split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
25
+ fm[key] = list.filter(Boolean);
26
+ }
27
+ else if (key === 'alwaysApply') {
28
+ fm.alwaysApply = value.toLowerCase() === 'true';
29
+ }
30
+ else {
31
+ fm[key] = value;
32
+ }
33
+ }
34
+ return { fm, body };
35
+ }
36
+ function priorityFromString(input) {
37
+ switch ((input ?? '').toLowerCase()) {
38
+ case 'critical':
39
+ return KnowledgePriority.Critical;
40
+ case 'high':
41
+ return KnowledgePriority.High;
42
+ case 'low':
43
+ return KnowledgePriority.Low;
44
+ default:
45
+ return KnowledgePriority.Medium;
46
+ }
47
+ }
48
+ export function parseCursorRuleFile(raw, options) {
49
+ const { fm, body } = parseFrontmatter(raw);
50
+ const title = fm.description ?? body.split('\n').find((l) => l.trim().length > 0) ?? options.idPrefix;
51
+ const baseSlug = slugify(title) || slugify(options.idPrefix);
52
+ const tags = [
53
+ ...new Set([
54
+ ...(fm.tags ?? []),
55
+ ...(fm.globs ?? []).map((g) => g.replace(/[*?]/g, '')).map((g) => slugify(g)).filter(Boolean),
56
+ ...keywordTags(title),
57
+ ]),
58
+ ];
59
+ return {
60
+ id: `${options.idPrefix}.${baseSlug}`,
61
+ title: title.slice(0, 100),
62
+ type: KnowledgeType.Rule,
63
+ priority: priorityFromString(fm.priority),
64
+ section: 'cursor-rules',
65
+ tags,
66
+ content: body.trim(),
67
+ origin: options.origin,
68
+ };
69
+ }
@@ -0,0 +1,18 @@
1
+ import type { IImportedEntry } from '../model/imported-entry.js';
2
+ /**
3
+ * Generic markdown-rules parser. Walks H1/H2/H3 sections; treats:
4
+ * - top-level bullets under a heading as separate rules
5
+ * - non-bullet paragraphs as a single rule with the heading as title
6
+ *
7
+ * Conservatively maps headings to knowledge types: "path"/"location" →
8
+ * PathConvention, "architecture"/"layer" → Architecture, "task"/"todo" → Task,
9
+ * everything else → Rule.
10
+ */
11
+ export interface IMarkdownParseOptions {
12
+ /** Origin tag stamped onto every produced entry. */
13
+ origin: string;
14
+ /** Prefix for derived ids (e.g. "claude" → "claude.rule.<slug>"). */
15
+ idPrefix: string;
16
+ }
17
+ export declare function parseMarkdownRules(raw: string, options: IMarkdownParseOptions): IImportedEntry[];
18
+ //# sourceMappingURL=parse-markdown-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-markdown-rules.d.ts","sourceRoot":"","sources":["../../src/parse/parse-markdown-rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAkED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,qBAAqB,GAC7B,cAAc,EAAE,CAiElB"}
@@ -0,0 +1,133 @@
1
+ import { KnowledgePriority, KnowledgeType } from '@shrkcrft/knowledge';
2
+ import { keywordTags, slugify } from "./slugify.js";
3
+ const PRIORITY_PATTERNS = [
4
+ { pattern: /\b(critical|never|must not|forbidden|do not)\b/i, value: KnowledgePriority.Critical },
5
+ { pattern: /\b(must|required|always|do)\b/i, value: KnowledgePriority.High },
6
+ { pattern: /\b(should|prefer)\b/i, value: KnowledgePriority.Medium },
7
+ { pattern: /\b(may|optional|nice to have)\b/i, value: KnowledgePriority.Low },
8
+ ];
9
+ function inferPriority(text) {
10
+ for (const { pattern, value } of PRIORITY_PATTERNS) {
11
+ if (pattern.test(text))
12
+ return value;
13
+ }
14
+ return KnowledgePriority.Medium;
15
+ }
16
+ function inferType(heading) {
17
+ const h = heading.toLowerCase();
18
+ if (h.includes('path') || h.includes('layout') || h.includes('location')) {
19
+ return KnowledgeType.Path;
20
+ }
21
+ if (h.includes('architecture') || h.includes('layer'))
22
+ return KnowledgeType.Architecture;
23
+ if (h.includes('task') || h.includes('todo') || h.includes('roadmap'))
24
+ return KnowledgeType.Task;
25
+ if (h.includes('decision') || h.includes('adr'))
26
+ return KnowledgeType.Decision;
27
+ if (h.includes('command'))
28
+ return KnowledgeType.Command;
29
+ if (h.includes('test'))
30
+ return KnowledgeType.Testing;
31
+ if (h.includes('security'))
32
+ return KnowledgeType.Security;
33
+ if (h.includes('deploy'))
34
+ return KnowledgeType.Deployment;
35
+ if (h.includes('convention'))
36
+ return KnowledgeType.Convention;
37
+ if (h.includes('workflow'))
38
+ return KnowledgeType.Workflow;
39
+ return KnowledgeType.Rule;
40
+ }
41
+ function extractTitle(text) {
42
+ const stripped = text.replace(/\*\*|__|`/g, '').trim();
43
+ const firstLine = stripped.split('\n')[0] ?? '';
44
+ // First sentence up to 80 chars.
45
+ const sentenceEnd = firstLine.search(/[.!?](\s|$)/);
46
+ if (sentenceEnd > 0 && sentenceEnd < 100) {
47
+ return firstLine.slice(0, sentenceEnd).trim();
48
+ }
49
+ return firstLine.slice(0, 80).trim();
50
+ }
51
+ function bulletsFromBlock(block) {
52
+ const lines = block.split('\n');
53
+ const out = [];
54
+ let current = '';
55
+ for (const line of lines) {
56
+ if (/^[-*]\s+/.test(line)) {
57
+ if (current)
58
+ out.push(current.trim());
59
+ current = line.replace(/^[-*]\s+/, '');
60
+ }
61
+ else if (current && (/^\s{2,}/.test(line) || line.trim() === '')) {
62
+ current += '\n' + line;
63
+ }
64
+ }
65
+ if (current)
66
+ out.push(current.trim());
67
+ return out;
68
+ }
69
+ export function parseMarkdownRules(raw, options) {
70
+ const entries = [];
71
+ const lines = raw.split('\n');
72
+ const sections = [];
73
+ let current = null;
74
+ let paragraph = [];
75
+ const flushPara = () => {
76
+ if (paragraph.length === 0)
77
+ return;
78
+ const block = paragraph.join('\n').trim();
79
+ if (block.length > 0 && current) {
80
+ current.paragraphs.push(block);
81
+ }
82
+ paragraph = [];
83
+ };
84
+ for (const line of lines) {
85
+ const headingMatch = /^(#{1,6})\s+(.*)$/.exec(line);
86
+ if (headingMatch) {
87
+ flushPara();
88
+ if (current)
89
+ sections.push(current);
90
+ current = { heading: headingMatch[2].trim(), level: headingMatch[1].length, paragraphs: [] };
91
+ continue;
92
+ }
93
+ paragraph.push(line);
94
+ }
95
+ flushPara();
96
+ if (current)
97
+ sections.push(current);
98
+ const seen = new Set();
99
+ for (const section of sections) {
100
+ const type = inferType(section.heading);
101
+ const tagsFromHeading = keywordTags(section.heading);
102
+ for (const block of section.paragraphs) {
103
+ const bullets = bulletsFromBlock(block);
104
+ const units = bullets.length > 0 ? bullets : [block];
105
+ for (const unit of units) {
106
+ const title = extractTitle(unit);
107
+ if (!title)
108
+ continue;
109
+ const slugBase = slugify(`${section.heading}-${title}`) || slugify(title);
110
+ let id = `${options.idPrefix}.${slugBase}`;
111
+ let counter = 2;
112
+ while (seen.has(id)) {
113
+ id = `${options.idPrefix}.${slugBase}-${counter}`;
114
+ counter += 1;
115
+ }
116
+ seen.add(id);
117
+ const priority = inferPriority(unit);
118
+ const tags = [...new Set([...tagsFromHeading, ...keywordTags(title)])];
119
+ entries.push({
120
+ id,
121
+ title,
122
+ type,
123
+ priority,
124
+ section: section.heading,
125
+ tags,
126
+ content: unit.trim(),
127
+ origin: options.origin,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ return entries;
133
+ }
@@ -0,0 +1,4 @@
1
+ export declare function slugify(input: string): string;
2
+ /** Pull 2–4 keyword-like tokens from a string for tag inference. */
3
+ export declare function keywordTags(input: string): string[];
4
+ //# sourceMappingURL=slugify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.d.ts","sourceRoot":"","sources":["../../src/parse/slugify.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C;AAmCD,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAWnD"}
@@ -0,0 +1,54 @@
1
+ export function slugify(input) {
2
+ return input
3
+ .toLowerCase()
4
+ .replace(/[^a-z0-9]+/g, '-')
5
+ .replace(/^-+|-+$/g, '')
6
+ .slice(0, 80);
7
+ }
8
+ const STOP_WORDS = new Set([
9
+ 'a',
10
+ 'an',
11
+ 'the',
12
+ 'and',
13
+ 'or',
14
+ 'but',
15
+ 'of',
16
+ 'in',
17
+ 'on',
18
+ 'to',
19
+ 'for',
20
+ 'with',
21
+ 'is',
22
+ 'are',
23
+ 'be',
24
+ 'as',
25
+ 'at',
26
+ 'by',
27
+ 'from',
28
+ 'this',
29
+ 'that',
30
+ 'should',
31
+ 'must',
32
+ 'do',
33
+ 'not',
34
+ 'never',
35
+ 'always',
36
+ 'use',
37
+ 'when',
38
+ 'if',
39
+ ]);
40
+ /** Pull 2–4 keyword-like tokens from a string for tag inference. */
41
+ export function keywordTags(input) {
42
+ const tokens = input
43
+ .toLowerCase()
44
+ .split(/[^a-z0-9]+/)
45
+ .filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
46
+ const out = [];
47
+ for (const t of tokens) {
48
+ if (!out.includes(t))
49
+ out.push(t);
50
+ if (out.length === 4)
51
+ break;
52
+ }
53
+ return out;
54
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@shrkcrft/importer",
3
+ "version": "0.1.0-alpha.2",
4
+ "description": "SharkCraft importer: parse AGENTS.md / CLAUDE.md / .cursor/rules into structured knowledge entries.",
5
+ "license": "MIT",
6
+ "author": "SharkCraft contributors",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "bun": "./src/index.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/shrkcrft/sharkcraft.git",
26
+ "directory": "packages/importer"
27
+ },
28
+ "homepage": "https://github.com/shrkcrft/sharkcraft",
29
+ "bugs": {
30
+ "url": "https://github.com/shrkcrft/sharkcraft/issues"
31
+ },
32
+ "keywords": [
33
+ "sharkcraft",
34
+ "importer",
35
+ "agents-md",
36
+ "claude-md",
37
+ "cursor"
38
+ ],
39
+ "engines": {
40
+ "bun": ">=1.1.0",
41
+ "node": ">=18"
42
+ },
43
+ "scripts": {
44
+ "typecheck": "tsc --noEmit -p tsconfig.json"
45
+ },
46
+ "dependencies": {
47
+ "@shrkcrft/core": "^0.1.0-alpha.2",
48
+ "@shrkcrft/knowledge": "^0.1.0-alpha.2"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }