forge-server 0.1.0
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.
- package/.claude/hooks/worktree-create.sh +64 -0
- package/.claude/hooks/worktree-remove.sh +57 -0
- package/.claude/settings.local.json +29 -0
- package/.forge/knowledge/conventions.yaml +1 -0
- package/.forge/knowledge/decisions.yaml +1 -0
- package/.forge/knowledge/gotchas.yaml +1 -0
- package/.forge/knowledge/patterns.yaml +1 -0
- package/.forge/manifest.yaml +6 -0
- package/CLAUDE.md +144 -0
- package/bin/setup-forge.sh +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/codebase.d.ts +57 -0
- package/dist/context/codebase.d.ts.map +1 -0
- package/dist/context/codebase.js +301 -0
- package/dist/context/codebase.js.map +1 -0
- package/dist/context/injector.d.ts +147 -0
- package/dist/context/injector.d.ts.map +1 -0
- package/dist/context/injector.js +533 -0
- package/dist/context/injector.js.map +1 -0
- package/dist/context/memory.d.ts +32 -0
- package/dist/context/memory.d.ts.map +1 -0
- package/dist/context/memory.js +140 -0
- package/dist/context/memory.js.map +1 -0
- package/dist/context/session-index.d.ts +54 -0
- package/dist/context/session-index.d.ts.map +1 -0
- package/dist/context/session-index.js +265 -0
- package/dist/context/session-index.js.map +1 -0
- package/dist/context/session.d.ts +42 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +121 -0
- package/dist/context/session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +19 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +189 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/embedder.d.ts +45 -0
- package/dist/ingestion/embedder.d.ts.map +1 -0
- package/dist/ingestion/embedder.js +152 -0
- package/dist/ingestion/embedder.js.map +1 -0
- package/dist/ingestion/git-analyzer.d.ts +77 -0
- package/dist/ingestion/git-analyzer.d.ts.map +1 -0
- package/dist/ingestion/git-analyzer.js +437 -0
- package/dist/ingestion/git-analyzer.js.map +1 -0
- package/dist/ingestion/indexer.d.ts +79 -0
- package/dist/ingestion/indexer.d.ts.map +1 -0
- package/dist/ingestion/indexer.js +766 -0
- package/dist/ingestion/indexer.js.map +1 -0
- package/dist/ingestion/markdown-chunker.d.ts +19 -0
- package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
- package/dist/ingestion/markdown-chunker.js +243 -0
- package/dist/ingestion/markdown-chunker.js.map +1 -0
- package/dist/ingestion/markdown-knowledge.d.ts +21 -0
- package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
- package/dist/ingestion/markdown-knowledge.js +129 -0
- package/dist/ingestion/markdown-knowledge.js.map +1 -0
- package/dist/ingestion/parser.d.ts +20 -0
- package/dist/ingestion/parser.d.ts.map +1 -0
- package/dist/ingestion/parser.js +429 -0
- package/dist/ingestion/parser.js.map +1 -0
- package/dist/ingestion/watcher.d.ts +28 -0
- package/dist/ingestion/watcher.d.ts.map +1 -0
- package/dist/ingestion/watcher.js +147 -0
- package/dist/ingestion/watcher.js.map +1 -0
- package/dist/knowledge/hydrator.d.ts +37 -0
- package/dist/knowledge/hydrator.d.ts.map +1 -0
- package/dist/knowledge/hydrator.js +220 -0
- package/dist/knowledge/hydrator.js.map +1 -0
- package/dist/knowledge/registry.d.ts +129 -0
- package/dist/knowledge/registry.d.ts.map +1 -0
- package/dist/knowledge/registry.js +361 -0
- package/dist/knowledge/registry.js.map +1 -0
- package/dist/knowledge/search.d.ts +114 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +428 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +76 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +230 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/learning/confidence.d.ts +30 -0
- package/dist/learning/confidence.d.ts.map +1 -0
- package/dist/learning/confidence.js +165 -0
- package/dist/learning/confidence.js.map +1 -0
- package/dist/learning/patterns.d.ts +52 -0
- package/dist/learning/patterns.d.ts.map +1 -0
- package/dist/learning/patterns.js +290 -0
- package/dist/learning/patterns.js.map +1 -0
- package/dist/learning/trajectory.d.ts +55 -0
- package/dist/learning/trajectory.d.ts.map +1 -0
- package/dist/learning/trajectory.js +200 -0
- package/dist/learning/trajectory.js.map +1 -0
- package/dist/memory/memory-compat.d.ts +100 -0
- package/dist/memory/memory-compat.d.ts.map +1 -0
- package/dist/memory/memory-compat.js +146 -0
- package/dist/memory/memory-compat.js.map +1 -0
- package/dist/memory/observation-store.d.ts +57 -0
- package/dist/memory/observation-store.d.ts.map +1 -0
- package/dist/memory/observation-store.js +154 -0
- package/dist/memory/observation-store.js.map +1 -0
- package/dist/memory/session-tracker.d.ts +81 -0
- package/dist/memory/session-tracker.d.ts.map +1 -0
- package/dist/memory/session-tracker.js +262 -0
- package/dist/memory/session-tracker.js.map +1 -0
- package/dist/pipeline/engine.d.ts +179 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +691 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/events.d.ts +54 -0
- package/dist/pipeline/events.d.ts.map +1 -0
- package/dist/pipeline/events.js +157 -0
- package/dist/pipeline/events.js.map +1 -0
- package/dist/pipeline/parallel.d.ts +83 -0
- package/dist/pipeline/parallel.d.ts.map +1 -0
- package/dist/pipeline/parallel.js +277 -0
- package/dist/pipeline/parallel.js.map +1 -0
- package/dist/pipeline/state-machine.d.ts +65 -0
- package/dist/pipeline/state-machine.d.ts.map +1 -0
- package/dist/pipeline/state-machine.js +176 -0
- package/dist/pipeline/state-machine.js.map +1 -0
- package/dist/query/graph-queries.d.ts +84 -0
- package/dist/query/graph-queries.d.ts.map +1 -0
- package/dist/query/graph-queries.js +216 -0
- package/dist/query/graph-queries.js.map +1 -0
- package/dist/query/hybrid-search.d.ts +34 -0
- package/dist/query/hybrid-search.d.ts.map +1 -0
- package/dist/query/hybrid-search.js +263 -0
- package/dist/query/hybrid-search.js.map +1 -0
- package/dist/query/intent-detector.d.ts +35 -0
- package/dist/query/intent-detector.d.ts.map +1 -0
- package/dist/query/intent-detector.js +115 -0
- package/dist/query/intent-detector.js.map +1 -0
- package/dist/query/ranking.d.ts +57 -0
- package/dist/query/ranking.d.ts.map +1 -0
- package/dist/query/ranking.js +109 -0
- package/dist/query/ranking.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/falkordb-store.d.ts +73 -0
- package/dist/storage/falkordb-store.d.ts.map +1 -0
- package/dist/storage/falkordb-store.js +346 -0
- package/dist/storage/falkordb-store.js.map +1 -0
- package/dist/storage/file-cache.d.ts +32 -0
- package/dist/storage/file-cache.d.ts.map +1 -0
- package/dist/storage/file-cache.js +115 -0
- package/dist/storage/file-cache.js.map +1 -0
- package/dist/storage/interfaces.d.ts +151 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +7 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/qdrant-store.d.ts +110 -0
- package/dist/storage/qdrant-store.d.ts.map +1 -0
- package/dist/storage/qdrant-store.js +467 -0
- package/dist/storage/qdrant-store.js.map +1 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +136 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +35 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +132 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/collaboration-tools.d.ts +111 -0
- package/dist/tools/collaboration-tools.d.ts.map +1 -0
- package/dist/tools/collaboration-tools.js +174 -0
- package/dist/tools/collaboration-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +293 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +437 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +129 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +237 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/ingestion-tools.d.ts +96 -0
- package/dist/tools/ingestion-tools.d.ts.map +1 -0
- package/dist/tools/ingestion-tools.js +90 -0
- package/dist/tools/ingestion-tools.js.map +1 -0
- package/dist/tools/learning-tools.d.ts +168 -0
- package/dist/tools/learning-tools.d.ts.map +1 -0
- package/dist/tools/learning-tools.js +158 -0
- package/dist/tools/learning-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +183 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +197 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/phase-tools.d.ts +954 -0
- package/dist/tools/phase-tools.d.ts.map +1 -0
- package/dist/tools/phase-tools.js +1215 -0
- package/dist/tools/phase-tools.js.map +1 -0
- package/dist/tools/pipeline-tools.d.ts +140 -0
- package/dist/tools/pipeline-tools.d.ts.map +1 -0
- package/dist/tools/pipeline-tools.js +162 -0
- package/dist/tools/pipeline-tools.js.map +1 -0
- package/dist/tools/registration-tools.d.ts +220 -0
- package/dist/tools/registration-tools.d.ts.map +1 -0
- package/dist/tools/registration-tools.js +391 -0
- package/dist/tools/registration-tools.js.map +1 -0
- package/dist/util/circuit-breaker.d.ts +75 -0
- package/dist/util/circuit-breaker.d.ts.map +1 -0
- package/dist/util/circuit-breaker.js +159 -0
- package/dist/util/circuit-breaker.js.map +1 -0
- package/dist/util/config.d.ts +23 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +164 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +45 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/token-counter.d.ts +24 -0
- package/dist/util/token-counter.d.ts.map +1 -0
- package/dist/util/token-counter.js +48 -0
- package/dist/util/token-counter.js.map +1 -0
- package/dist/util/types.d.ts +525 -0
- package/dist/util/types.d.ts.map +1 -0
- package/dist/util/types.js +5 -0
- package/dist/util/types.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
- package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
- package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
- package/package.json +39 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/README.md +134 -0
- package/plugin/agents/architect.md +367 -0
- package/plugin/agents/backend-specialist.md +263 -0
- package/plugin/agents/brainstormer.md +122 -0
- package/plugin/agents/data-specialist.md +266 -0
- package/plugin/agents/designer.md +408 -0
- package/plugin/agents/frontend-specialist.md +241 -0
- package/plugin/agents/inspector.md +406 -0
- package/plugin/agents/knowledge-keeper.md +443 -0
- package/plugin/agents/platform-engineer.md +326 -0
- package/plugin/agents/product-manager.md +268 -0
- package/plugin/agents/product-owner.md +438 -0
- package/plugin/agents/pulse-checker.md +73 -0
- package/plugin/agents/qa-strategist.md +500 -0
- package/plugin/agents/self-improver.md +310 -0
- package/plugin/agents/strategist.md +360 -0
- package/plugin/agents/supervisor.md +380 -0
- package/plugin/commands/brainstorm.md +25 -0
- package/plugin/commands/forge.md +88 -0
- package/plugin/docs/atlassian-integration.md +110 -0
- package/plugin/docs/workflow.md +126 -0
- package/plugin/skills/agent-development/.skillfish.json +10 -0
- package/plugin/skills/agent-development/SKILL.md +415 -0
- package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
- package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
- package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
- package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
- package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
- package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
- package/plugin/skills/agent-handoff/SKILL.md +335 -0
- package/plugin/skills/anti-stub/SKILL.md +317 -0
- package/plugin/skills/brainstorm/SKILL.md +31 -0
- package/plugin/skills/debugging/SKILL.md +276 -0
- package/plugin/skills/fix/SKILL.md +62 -0
- package/plugin/skills/frontend-design/.skillfish.json +10 -0
- package/plugin/skills/frontend-design/SKILL.md +42 -0
- package/plugin/skills/gotchas/SKILL.md +61 -0
- package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
- package/plugin/skills/history/SKILL.md +58 -0
- package/plugin/skills/impact/SKILL.md +59 -0
- package/plugin/skills/implementation-execution/SKILL.md +291 -0
- package/plugin/skills/index-repo/SKILL.md +55 -0
- package/plugin/skills/interviewing/SKILL.md +225 -0
- package/plugin/skills/knowledge-curation/SKILL.md +393 -0
- package/plugin/skills/learn/SKILL.md +69 -0
- package/plugin/skills/mcp-integration/.skillfish.json +10 -0
- package/plugin/skills/mcp-integration/SKILL.md +554 -0
- package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
- package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
- package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
- package/plugin/skills/mcp-integration/references/authentication.md +549 -0
- package/plugin/skills/mcp-integration/references/server-types.md +536 -0
- package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
- package/plugin/skills/nestjs/.skillfish.json +10 -0
- package/plugin/skills/nestjs/SKILL.md +669 -0
- package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
- package/plugin/skills/nestjs/reference.md +1447 -0
- package/plugin/skills/nestjs/workflow-optimization.md +229 -0
- package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
- package/plugin/skills/project-discovery/SKILL.md +304 -0
- package/plugin/skills/search/SKILL.md +56 -0
- package/plugin/skills/security-audit/SKILL.md +362 -0
- package/plugin/skills/skill-development/.skillfish.json +10 -0
- package/plugin/skills/skill-development/SKILL.md +637 -0
- package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
- package/plugin/skills/tdd/SKILL.md +273 -0
- package/plugin/skills/terminal-presentation/SKILL.md +395 -0
- package/plugin/skills/test-strategy/SKILL.md +365 -0
- package/plugin/skills/verification-protocol/SKILL.md +256 -0
- package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
- package/plugin/skills/visual-explainer/LICENSE +21 -0
- package/plugin/skills/visual-explainer/README.md +137 -0
- package/plugin/skills/visual-explainer/SKILL.md +352 -0
- package/plugin/skills/visual-explainer/banner.png +0 -0
- package/plugin/skills/visual-explainer/package.json +11 -0
- package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
- package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
- package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
- package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
- package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
- package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
- package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
- package/plugin/skills/visual-explainer/references/libraries.md +470 -0
- package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
- package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
- package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
- package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
- package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
- package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
- package/src/cli.ts +655 -0
- package/src/context/.gitkeep +0 -0
- package/src/context/codebase.ts +393 -0
- package/src/context/injector.ts +797 -0
- package/src/context/memory.ts +187 -0
- package/src/context/session-index.ts +327 -0
- package/src/context/session.ts +152 -0
- package/src/index.ts +47 -0
- package/src/ingestion/.gitkeep +0 -0
- package/src/ingestion/chunker.ts +277 -0
- package/src/ingestion/embedder.ts +167 -0
- package/src/ingestion/git-analyzer.ts +545 -0
- package/src/ingestion/indexer.ts +984 -0
- package/src/ingestion/markdown-chunker.ts +337 -0
- package/src/ingestion/markdown-knowledge.ts +175 -0
- package/src/ingestion/parser.ts +475 -0
- package/src/ingestion/watcher.ts +182 -0
- package/src/knowledge/.gitkeep +0 -0
- package/src/knowledge/hydrator.ts +246 -0
- package/src/knowledge/registry.ts +463 -0
- package/src/knowledge/search.ts +565 -0
- package/src/knowledge/store.ts +262 -0
- package/src/learning/.gitkeep +0 -0
- package/src/learning/confidence.ts +193 -0
- package/src/learning/patterns.ts +360 -0
- package/src/learning/trajectory.ts +268 -0
- package/src/memory/.gitkeep +0 -0
- package/src/memory/memory-compat.ts +233 -0
- package/src/memory/observation-store.ts +224 -0
- package/src/memory/session-tracker.ts +332 -0
- package/src/pipeline/.gitkeep +0 -0
- package/src/pipeline/engine.ts +1139 -0
- package/src/pipeline/events.ts +253 -0
- package/src/pipeline/parallel.ts +394 -0
- package/src/pipeline/state-machine.ts +199 -0
- package/src/query/.gitkeep +0 -0
- package/src/query/graph-queries.ts +262 -0
- package/src/query/hybrid-search.ts +337 -0
- package/src/query/intent-detector.ts +131 -0
- package/src/query/ranking.ts +161 -0
- package/src/server.ts +352 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/falkordb-store.ts +388 -0
- package/src/storage/file-cache.ts +141 -0
- package/src/storage/interfaces.ts +201 -0
- package/src/storage/qdrant-store.ts +557 -0
- package/src/storage/schema.ts +139 -0
- package/src/storage/sqlite.ts +168 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/collaboration-tools.ts +208 -0
- package/src/tools/context-tools.ts +493 -0
- package/src/tools/graph-tools.ts +295 -0
- package/src/tools/ingestion-tools.ts +122 -0
- package/src/tools/learning-tools.ts +181 -0
- package/src/tools/memory-tools.ts +234 -0
- package/src/tools/phase-tools.ts +1452 -0
- package/src/tools/pipeline-tools.ts +188 -0
- package/src/tools/registration-tools.ts +450 -0
- package/src/util/.gitkeep +0 -0
- package/src/util/circuit-breaker.ts +193 -0
- package/src/util/config.ts +177 -0
- package/src/util/logger.ts +53 -0
- package/src/util/token-counter.ts +52 -0
- package/src/util/types.ts +710 -0
- package/tests/context/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/knowledge/.gitkeep +0 -0
- package/tests/learning/.gitkeep +0 -0
- package/tests/pipeline/.gitkeep +0 -0
- package/tests/tools/.gitkeep +0 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +10 -0
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/README.md +43 -0
- package/vscode-extension/out/edge-collector.js +274 -0
- package/vscode-extension/out/edge-collector.js.map +1 -0
- package/vscode-extension/out/extension.js +264 -0
- package/vscode-extension/out/extension.js.map +1 -0
- package/vscode-extension/out/forge-client.js +318 -0
- package/vscode-extension/out/forge-client.js.map +1 -0
- package/vscode-extension/package-lock.json +59 -0
- package/vscode-extension/package.json +71 -0
- package/vscode-extension/src/edge-collector.ts +320 -0
- package/vscode-extension/src/extension.ts +269 -0
- package/vscode-extension/src/forge-client.ts +364 -0
- package/vscode-extension/tsconfig.json +19 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section-aware markdown chunker.
|
|
3
|
+
* Replaces chunkFixed for .md/.mdx files during indexing.
|
|
4
|
+
*
|
|
5
|
+
* Strategy:
|
|
6
|
+
* 1. Parse markdown into a section tree by headings (H1 → H2 → H3)
|
|
7
|
+
* 2. Each section becomes a chunk with heading breadcrumb as context header
|
|
8
|
+
* 3. Code blocks stay attached to their parent section
|
|
9
|
+
* 4. Sections exceeding 1024 tokens split at sub-heading or paragraph boundaries
|
|
10
|
+
* 5. Sections under ~30 tokens merge with the next sibling
|
|
11
|
+
* 6. Frontmatter (YAML between ---) becomes its own chunk
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CodeChunk } from '../util/types.js';
|
|
15
|
+
import { estimateTokens } from '../util/token-counter.js';
|
|
16
|
+
import { makeChunk } from './chunker.js';
|
|
17
|
+
|
|
18
|
+
const SECTION_MAX_TOKENS = 1024;
|
|
19
|
+
const SECTION_MIN_TOKENS = 30;
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Section tree types
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
interface MarkdownSection {
|
|
26
|
+
heading: string; // The heading text (e.g. "## Authentication")
|
|
27
|
+
level: number; // Heading level (1-6), 0 for root/frontmatter
|
|
28
|
+
breadcrumb: string; // Full path: "Authentication > JWT Flow"
|
|
29
|
+
lines: string[]; // Body lines (excluding heading line itself)
|
|
30
|
+
startLine: number; // 1-based line number of heading
|
|
31
|
+
endLine: number; // 1-based line number of last body line
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Public API
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Chunk a markdown file into section-aware chunks.
|
|
40
|
+
* Each section gets a dk-forge context header with heading breadcrumb.
|
|
41
|
+
*/
|
|
42
|
+
export function chunkMarkdown(
|
|
43
|
+
content: string,
|
|
44
|
+
filePath: string,
|
|
45
|
+
repoId: string,
|
|
46
|
+
indexedAt: number,
|
|
47
|
+
): CodeChunk[] {
|
|
48
|
+
const allLines = content.split('\n');
|
|
49
|
+
const chunks: CodeChunk[] = [];
|
|
50
|
+
|
|
51
|
+
// Extract frontmatter first
|
|
52
|
+
const { frontmatter, bodyStartLine } = extractFrontmatter(allLines);
|
|
53
|
+
if (frontmatter) {
|
|
54
|
+
const fmContent = `// [dk-forge] file: ${filePath} | section: "frontmatter" | lines: 1-${bodyStartLine - 1}\n` +
|
|
55
|
+
frontmatter;
|
|
56
|
+
chunks.push(makeChunk(
|
|
57
|
+
fmContent, filePath, repoId, 'markdown',
|
|
58
|
+
1, bodyStartLine - 1, 'markdown', indexedAt, 'frontmatter',
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse body into sections
|
|
63
|
+
const bodyLines = allLines.slice(bodyStartLine - 1);
|
|
64
|
+
const sections = parseSections(bodyLines, bodyStartLine);
|
|
65
|
+
|
|
66
|
+
// Merge tiny sections with their next sibling
|
|
67
|
+
const merged = mergeTinySections(sections);
|
|
68
|
+
|
|
69
|
+
// Emit chunks, splitting oversized sections
|
|
70
|
+
for (const section of merged) {
|
|
71
|
+
const sectionChunks = sectionToChunks(section, filePath, repoId, indexedAt);
|
|
72
|
+
chunks.push(...sectionChunks);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return chunks;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Frontmatter extraction
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function extractFrontmatter(lines: string[]): { frontmatter: string | null; bodyStartLine: number } {
|
|
83
|
+
if (lines.length === 0 || lines[0]!.trim() !== '---') {
|
|
84
|
+
return { frontmatter: null, bodyStartLine: 1 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Find closing ---
|
|
88
|
+
for (let i = 1; i < lines.length; i++) {
|
|
89
|
+
if (lines[i]!.trim() === '---') {
|
|
90
|
+
const fmLines = lines.slice(0, i + 1);
|
|
91
|
+
return {
|
|
92
|
+
frontmatter: fmLines.join('\n'),
|
|
93
|
+
bodyStartLine: i + 2, // 1-based, line after closing ---
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// No closing --- found, treat entire file as body
|
|
99
|
+
return { frontmatter: null, bodyStartLine: 1 };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Section parsing
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
107
|
+
|
|
108
|
+
function parseSections(lines: string[], lineOffset: number): MarkdownSection[] {
|
|
109
|
+
const sections: MarkdownSection[] = [];
|
|
110
|
+
const headingStack: Array<{ level: number; text: string }> = [];
|
|
111
|
+
|
|
112
|
+
let currentSection: MarkdownSection | null = null;
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i]!;
|
|
116
|
+
const match = HEADING_RE.exec(line);
|
|
117
|
+
|
|
118
|
+
if (match) {
|
|
119
|
+
// Flush current section
|
|
120
|
+
if (currentSection) {
|
|
121
|
+
currentSection.endLine = lineOffset + i - 1;
|
|
122
|
+
sections.push(currentSection);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const level = match[1]!.length;
|
|
126
|
+
const headingText = match[2]!.trim();
|
|
127
|
+
|
|
128
|
+
// Update heading stack for breadcrumb
|
|
129
|
+
while (headingStack.length > 0 && headingStack[headingStack.length - 1]!.level >= level) {
|
|
130
|
+
headingStack.pop();
|
|
131
|
+
}
|
|
132
|
+
headingStack.push({ level, text: headingText });
|
|
133
|
+
|
|
134
|
+
const breadcrumb = headingStack.map(h => h.text).join(' > ');
|
|
135
|
+
|
|
136
|
+
currentSection = {
|
|
137
|
+
heading: headingText,
|
|
138
|
+
level,
|
|
139
|
+
breadcrumb,
|
|
140
|
+
lines: [],
|
|
141
|
+
startLine: lineOffset + i,
|
|
142
|
+
endLine: lineOffset + i, // Will be updated
|
|
143
|
+
};
|
|
144
|
+
} else {
|
|
145
|
+
if (currentSection) {
|
|
146
|
+
currentSection.lines.push(line);
|
|
147
|
+
} else {
|
|
148
|
+
// Content before first heading — create a root section
|
|
149
|
+
if (line.trim()) {
|
|
150
|
+
currentSection = {
|
|
151
|
+
heading: '',
|
|
152
|
+
level: 0,
|
|
153
|
+
breadcrumb: '',
|
|
154
|
+
lines: [line],
|
|
155
|
+
startLine: lineOffset + i,
|
|
156
|
+
endLine: lineOffset + i,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Flush final section
|
|
164
|
+
if (currentSection) {
|
|
165
|
+
currentSection.endLine = lineOffset + lines.length - 1;
|
|
166
|
+
sections.push(currentSection);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return sections;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Merge tiny sections
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
function mergeTinySections(sections: MarkdownSection[]): MarkdownSection[] {
|
|
177
|
+
if (sections.length <= 1) return sections;
|
|
178
|
+
|
|
179
|
+
const result: MarkdownSection[] = [];
|
|
180
|
+
let i = 0;
|
|
181
|
+
|
|
182
|
+
while (i < sections.length) {
|
|
183
|
+
const section = sections[i]!;
|
|
184
|
+
const bodyText = section.lines.join('\n');
|
|
185
|
+
const tokens = estimateTokens(bodyText);
|
|
186
|
+
|
|
187
|
+
if (tokens < SECTION_MIN_TOKENS && i + 1 < sections.length) {
|
|
188
|
+
// Merge with next section
|
|
189
|
+
const next = sections[i + 1]!;
|
|
190
|
+
const merged: MarkdownSection = {
|
|
191
|
+
heading: section.heading || next.heading,
|
|
192
|
+
level: Math.min(section.level, next.level) || section.level || next.level,
|
|
193
|
+
breadcrumb: section.breadcrumb || next.breadcrumb,
|
|
194
|
+
lines: [
|
|
195
|
+
...(section.heading ? [`# ${section.heading}`, ''] : []),
|
|
196
|
+
...section.lines,
|
|
197
|
+
'',
|
|
198
|
+
...(next.heading ? [`${'#'.repeat(next.level)} ${next.heading}`, ''] : []),
|
|
199
|
+
...next.lines,
|
|
200
|
+
],
|
|
201
|
+
startLine: section.startLine,
|
|
202
|
+
endLine: next.endLine,
|
|
203
|
+
};
|
|
204
|
+
// Replace next with merged, re-check merged against following
|
|
205
|
+
sections[i + 1] = merged;
|
|
206
|
+
i++;
|
|
207
|
+
} else {
|
|
208
|
+
result.push(section);
|
|
209
|
+
i++;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Section to chunks (with splitting for oversized)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
function sectionToChunks(
|
|
221
|
+
section: MarkdownSection,
|
|
222
|
+
filePath: string,
|
|
223
|
+
repoId: string,
|
|
224
|
+
indexedAt: number,
|
|
225
|
+
): CodeChunk[] {
|
|
226
|
+
const bodyText = section.lines.join('\n');
|
|
227
|
+
const headerComment = buildSectionHeader(filePath, section.breadcrumb, section.startLine, section.endLine);
|
|
228
|
+
const fullContent = headerComment + (section.heading ? `${'#'.repeat(section.level || 1)} ${section.heading}\n\n` : '') + bodyText;
|
|
229
|
+
|
|
230
|
+
const tokens = estimateTokens(fullContent);
|
|
231
|
+
|
|
232
|
+
if (tokens <= SECTION_MAX_TOKENS) {
|
|
233
|
+
return [makeChunk(
|
|
234
|
+
fullContent, filePath, repoId, 'markdown',
|
|
235
|
+
section.startLine, section.endLine,
|
|
236
|
+
'markdown', indexedAt,
|
|
237
|
+
section.heading || undefined,
|
|
238
|
+
)];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Split oversized section at paragraph boundaries
|
|
242
|
+
return splitOversizedSection(section, filePath, repoId, indexedAt);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function splitOversizedSection(
|
|
246
|
+
section: MarkdownSection,
|
|
247
|
+
filePath: string,
|
|
248
|
+
repoId: string,
|
|
249
|
+
indexedAt: number,
|
|
250
|
+
): CodeChunk[] {
|
|
251
|
+
const chunks: CodeChunk[] = [];
|
|
252
|
+
const paragraphs = splitIntoParagraphs(section.lines);
|
|
253
|
+
|
|
254
|
+
let currentLines: string[] = [];
|
|
255
|
+
let currentTokens = 0;
|
|
256
|
+
let chunkStartLine = section.startLine;
|
|
257
|
+
let partIndex = 0;
|
|
258
|
+
|
|
259
|
+
const flush = (endLine: number) => {
|
|
260
|
+
if (currentLines.length === 0) return;
|
|
261
|
+
const headerComment = buildSectionHeader(filePath, section.breadcrumb, chunkStartLine, endLine);
|
|
262
|
+
const content = headerComment + currentLines.join('\n');
|
|
263
|
+
const entityName = section.heading
|
|
264
|
+
? `${section.heading}_part${partIndex++}`
|
|
265
|
+
: `section_part${partIndex++}`;
|
|
266
|
+
chunks.push(makeChunk(
|
|
267
|
+
content, filePath, repoId, 'markdown',
|
|
268
|
+
chunkStartLine, endLine,
|
|
269
|
+
'markdown', indexedAt, entityName,
|
|
270
|
+
));
|
|
271
|
+
currentLines = [];
|
|
272
|
+
currentTokens = 0;
|
|
273
|
+
chunkStartLine = endLine + 1;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
for (const para of paragraphs) {
|
|
277
|
+
const paraTokens = estimateTokens(para.text);
|
|
278
|
+
|
|
279
|
+
if (currentTokens + paraTokens > SECTION_MAX_TOKENS && currentLines.length > 0) {
|
|
280
|
+
flush(chunkStartLine + currentLines.length - 1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
currentLines.push(...para.lines);
|
|
284
|
+
currentTokens += paraTokens;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (currentLines.length > 0) {
|
|
288
|
+
flush(section.endLine);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return chunks;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface Paragraph {
|
|
295
|
+
lines: string[];
|
|
296
|
+
text: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function splitIntoParagraphs(lines: string[]): Paragraph[] {
|
|
300
|
+
const paragraphs: Paragraph[] = [];
|
|
301
|
+
let currentLines: string[] = [];
|
|
302
|
+
|
|
303
|
+
for (const line of lines) {
|
|
304
|
+
if (line.trim() === '' && currentLines.length > 0) {
|
|
305
|
+
paragraphs.push({
|
|
306
|
+
lines: [...currentLines, ''],
|
|
307
|
+
text: currentLines.join('\n'),
|
|
308
|
+
});
|
|
309
|
+
currentLines = [];
|
|
310
|
+
} else {
|
|
311
|
+
currentLines.push(line);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (currentLines.length > 0) {
|
|
316
|
+
paragraphs.push({
|
|
317
|
+
lines: currentLines,
|
|
318
|
+
text: currentLines.join('\n'),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return paragraphs;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Header builder
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
function buildSectionHeader(
|
|
330
|
+
filePath: string,
|
|
331
|
+
breadcrumb: string,
|
|
332
|
+
startLine: number,
|
|
333
|
+
endLine: number,
|
|
334
|
+
): string {
|
|
335
|
+
const sectionStr = breadcrumb ? ` | section: "${breadcrumb}"` : '';
|
|
336
|
+
return `// [dk-forge] file: ${filePath}${sectionStr} | lines: ${startLine}-${endLine}\n`;
|
|
337
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown knowledge extractor.
|
|
3
|
+
* Scans markdown document headings for keywords that signal knowledge items
|
|
4
|
+
* (gotchas, patterns, decisions, conventions) and extracts matching sections.
|
|
5
|
+
*
|
|
6
|
+
* Used during indexing to automatically populate the knowledge collection
|
|
7
|
+
* from documentation files without manual YAML curation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import type { KnowledgeItem, KnowledgeCategory } from '../util/types.js';
|
|
12
|
+
|
|
13
|
+
const MAX_CONTENT_LENGTH = 2000;
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Heading keyword patterns
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
interface CategoryMatcher {
|
|
20
|
+
category: KnowledgeCategory;
|
|
21
|
+
keywords: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const CATEGORY_MATCHERS: CategoryMatcher[] = [
|
|
25
|
+
{
|
|
26
|
+
category: 'gotcha',
|
|
27
|
+
keywords: ['gotcha', 'warning', 'caveat', 'pitfall', 'watch out', 'known issue'],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
category: 'pattern',
|
|
31
|
+
keywords: ['pattern', 'best practice', 'convention', 'approach'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
category: 'decision',
|
|
35
|
+
keywords: ['decision', 'adr', 'why we', 'trade-off', 'tradeoff', 'we chose'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
category: 'convention',
|
|
39
|
+
keywords: ['convention', 'rule', 'standard', 'naming'],
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Public API
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract knowledge items from a markdown file's content.
|
|
49
|
+
* Scans headings for category keywords and returns matching sections
|
|
50
|
+
* as KnowledgeItem objects with deterministic IDs.
|
|
51
|
+
*
|
|
52
|
+
* @param content - Raw markdown file content
|
|
53
|
+
* @param filePath - Absolute path to the file (used for ID generation)
|
|
54
|
+
* @param repoId - Repository ID (not used in item directly but available for caller context)
|
|
55
|
+
* @param stackTags - Stack tags from repo manifest to attach to items
|
|
56
|
+
*/
|
|
57
|
+
export function extractKnowledge(
|
|
58
|
+
content: string,
|
|
59
|
+
filePath: string,
|
|
60
|
+
_repoId: string,
|
|
61
|
+
stackTags: string[] = [],
|
|
62
|
+
): KnowledgeItem[] {
|
|
63
|
+
const items: KnowledgeItem[] = [];
|
|
64
|
+
const sections = parseIntoSections(content);
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
|
|
67
|
+
for (const section of sections) {
|
|
68
|
+
const category = matchCategory(section.heading);
|
|
69
|
+
if (!category) continue;
|
|
70
|
+
|
|
71
|
+
const id = generateDeterministicId(filePath, section.heading, category);
|
|
72
|
+
const body = section.body.slice(0, MAX_CONTENT_LENGTH).trim();
|
|
73
|
+
|
|
74
|
+
if (!body) continue;
|
|
75
|
+
|
|
76
|
+
items.push({
|
|
77
|
+
id,
|
|
78
|
+
title: section.heading,
|
|
79
|
+
content: body,
|
|
80
|
+
stack_tags: stackTags,
|
|
81
|
+
confidence: 0.6,
|
|
82
|
+
source: 'agent',
|
|
83
|
+
source_phase: null,
|
|
84
|
+
source_agent: 'indexer',
|
|
85
|
+
created_at: now,
|
|
86
|
+
updated_at: now,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return items;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Section parsing
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
interface HeadingSection {
|
|
98
|
+
heading: string;
|
|
99
|
+
level: number;
|
|
100
|
+
body: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
104
|
+
|
|
105
|
+
function parseIntoSections(content: string): HeadingSection[] {
|
|
106
|
+
const lines = content.split('\n');
|
|
107
|
+
const sections: HeadingSection[] = [];
|
|
108
|
+
let current: HeadingSection | null = null;
|
|
109
|
+
let bodyLines: string[] = [];
|
|
110
|
+
|
|
111
|
+
const flush = () => {
|
|
112
|
+
if (current) {
|
|
113
|
+
current.body = bodyLines.join('\n');
|
|
114
|
+
sections.push(current);
|
|
115
|
+
bodyLines = [];
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const match = HEADING_RE.exec(line);
|
|
121
|
+
if (match) {
|
|
122
|
+
flush();
|
|
123
|
+
current = {
|
|
124
|
+
heading: match[2]!.trim(),
|
|
125
|
+
level: match[1]!.length,
|
|
126
|
+
body: '',
|
|
127
|
+
};
|
|
128
|
+
} else if (current) {
|
|
129
|
+
bodyLines.push(line);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
flush();
|
|
134
|
+
return sections;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Category matching
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
function matchCategory(heading: string): KnowledgeCategory | null {
|
|
142
|
+
const lower = heading.toLowerCase();
|
|
143
|
+
|
|
144
|
+
for (const matcher of CATEGORY_MATCHERS) {
|
|
145
|
+
for (const keyword of matcher.keywords) {
|
|
146
|
+
if (lower.includes(keyword)) {
|
|
147
|
+
return matcher.category;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Deterministic ID generation
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate a deterministic ID from filePath + heading.
|
|
161
|
+
* Format: `<category>-<hash-first-12>` to match the convention used by
|
|
162
|
+
* KnowledgeHydrator._categoryFromId().
|
|
163
|
+
*
|
|
164
|
+
* Idempotent: re-indexing the same file produces the same IDs, so Qdrant
|
|
165
|
+
* upserts overwrite rather than duplicate.
|
|
166
|
+
*/
|
|
167
|
+
function generateDeterministicId(
|
|
168
|
+
filePath: string,
|
|
169
|
+
heading: string,
|
|
170
|
+
category: KnowledgeCategory,
|
|
171
|
+
): string {
|
|
172
|
+
const input = `${filePath}::${heading}`;
|
|
173
|
+
const hash = createHash('sha256').update(input).digest('hex').slice(0, 12);
|
|
174
|
+
return `${category}-auto-${hash}`;
|
|
175
|
+
}
|