@skill-map/cli 0.31.0 → 0.33.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/dist/cli/tutorial/sm-tutorial/SKILL.md +27 -7
- package/dist/cli.js +1366 -501
- package/dist/cli.js.map +1 -1
- package/dist/index.js +206 -15
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +199 -4
- package/dist/kernel/index.js +206 -15
- package/dist/kernel/index.js.map +1 -1
- package/dist/migrations/001_initial.sql +2 -2
- package/dist/ui/chunk-2QZDJSJN.js +1 -0
- package/dist/ui/chunk-5CFY2K3Y.js +135 -0
- package/dist/ui/{chunk-YQIWQVJ6.js → chunk-L3OLNVKI.js} +9 -9
- package/dist/ui/chunk-OKFHCQNJ.js +123 -0
- package/dist/ui/{chunk-47OZB7LR.js → chunk-TKV6TXTI.js} +1 -1
- package/dist/ui/{chunk-WJLIYGWJ.js → chunk-UK5YFHL3.js} +1 -1
- package/dist/ui/{chunk-FEPH4VNB.js → chunk-UMCC32EJ.js} +3 -3
- package/dist/ui/{chunk-VDQLDTTR.js → chunk-YZ7KCL3G.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-H7FURBYT.js +2 -0
- package/migrations/001_initial.sql +2 -2
- package/package.json +3 -2
- package/dist/ui/chunk-LNRQ7VKE.js +0 -1
- package/dist/ui/chunk-LS2NXZQZ.js +0 -135
- package/dist/ui/chunk-PVKIT7DW.js +0 -123
- package/dist/ui/main-N23S66NJ.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// cli/entry.ts
|
|
2
|
-
import { existsSync as
|
|
2
|
+
import { existsSync as existsSync33 } from "fs";
|
|
3
3
|
import { Builtins, Cli as Cli2 } from "clipanion";
|
|
4
4
|
|
|
5
5
|
// kernel/adapters/in-memory-progress.ts
|
|
@@ -494,6 +494,31 @@ var claudeProvider = {
|
|
|
494
494
|
colorDark: "#34d399",
|
|
495
495
|
icon: { kind: "pi", id: "pi-bolt" }
|
|
496
496
|
}
|
|
497
|
+
},
|
|
498
|
+
/**
|
|
499
|
+
* Phase 5 of the active-lens migration: MCP servers surface as
|
|
500
|
+
* synthetic / virtual nodes (no filesystem path; identifier is
|
|
501
|
+
* `mcp://<name>`) derived from a config file (`settings.json`
|
|
502
|
+
* for Claude). Per-skill / per-agent references appear as
|
|
503
|
+
* `tools: [mcp__<server>__<tool>, ...]` entries in frontmatter;
|
|
504
|
+
* the `core/mcp-tools` extractor turns each match into one
|
|
505
|
+
* MCP node (idempotent dedup by path) plus a `references` link
|
|
506
|
+
* from the source skill / agent to that node.
|
|
507
|
+
*
|
|
508
|
+
* `schema` is provider-agnostic enough that we reuse the skill
|
|
509
|
+
* schema for now (mcp nodes have `name` + `description` at most,
|
|
510
|
+
* which the base skill schema accepts). A dedicated schema lands
|
|
511
|
+
* if MCP nodes grow Claude-specific metadata.
|
|
512
|
+
*/
|
|
513
|
+
mcp: {
|
|
514
|
+
schema: "./schemas/skill.schema.json",
|
|
515
|
+
schemaJson: skill_schema_default,
|
|
516
|
+
ui: {
|
|
517
|
+
label: "MCP servers",
|
|
518
|
+
color: "#8b5cf6",
|
|
519
|
+
colorDark: "#a78bfa",
|
|
520
|
+
icon: { kind: "pi", id: "pi-server" }
|
|
521
|
+
}
|
|
497
522
|
}
|
|
498
523
|
},
|
|
499
524
|
// Auxiliary schemas the per-kind schemas $ref by $id. AJV needs them
|
|
@@ -511,6 +536,166 @@ var claudeProvider = {
|
|
|
511
536
|
}
|
|
512
537
|
};
|
|
513
538
|
|
|
539
|
+
// kernel/util/strip-code-blocks.ts
|
|
540
|
+
var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
|
|
541
|
+
function stripCodeBlocks(input) {
|
|
542
|
+
if (!input) return input;
|
|
543
|
+
const fenceless = stripFences(input);
|
|
544
|
+
return stripInline(fenceless);
|
|
545
|
+
}
|
|
546
|
+
function stripFences(input) {
|
|
547
|
+
const out = [];
|
|
548
|
+
const lines = input.split("\n");
|
|
549
|
+
let openFence = null;
|
|
550
|
+
for (const line of lines) {
|
|
551
|
+
if (openFence) {
|
|
552
|
+
const closer = matchClosingFence(line, openFence);
|
|
553
|
+
if (closer) {
|
|
554
|
+
out.push(blank(line));
|
|
555
|
+
openFence = null;
|
|
556
|
+
} else {
|
|
557
|
+
out.push(blank(line));
|
|
558
|
+
}
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
const open = FENCE_RE.exec(line);
|
|
562
|
+
if (open?.groups) {
|
|
563
|
+
openFence = open.groups["fence"];
|
|
564
|
+
out.push(blank(line));
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
out.push(line);
|
|
568
|
+
}
|
|
569
|
+
return out.join("\n");
|
|
570
|
+
}
|
|
571
|
+
function matchClosingFence(line, openFence) {
|
|
572
|
+
const m = FENCE_RE.exec(line);
|
|
573
|
+
if (!m?.groups) return false;
|
|
574
|
+
const fence = m.groups["fence"];
|
|
575
|
+
return fence[0] === openFence[0] && fence.length >= openFence.length;
|
|
576
|
+
}
|
|
577
|
+
function stripInline(input) {
|
|
578
|
+
return input.replace(/(`+)([\s\S]*?)\1/g, (_full, ticks, body) => {
|
|
579
|
+
return ticks.replace(/`/g, " ") + blank(body) + ticks.replace(/`/g, " ");
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function blank(s) {
|
|
583
|
+
return s.replace(/[^\s]/g, " ");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// kernel/trigger-normalize.ts
|
|
587
|
+
function normalizeTrigger(source) {
|
|
588
|
+
let out = source.normalize("NFD");
|
|
589
|
+
out = out.replace(new RegExp("\\p{Mn}+", "gu"), "");
|
|
590
|
+
out = out.toLowerCase();
|
|
591
|
+
out = out.replace(/[-_\s]+/g, " ");
|
|
592
|
+
out = out.replace(/ +/g, " ");
|
|
593
|
+
return out.trim();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// plugins/claude/extractors/at-directive/index.ts
|
|
597
|
+
var ID = "at-directive";
|
|
598
|
+
var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
599
|
+
var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
|
|
600
|
+
var atDirectiveExtractor = {
|
|
601
|
+
id: ID,
|
|
602
|
+
pluginId: "claude",
|
|
603
|
+
kind: "extractor",
|
|
604
|
+
version: "1.0.0",
|
|
605
|
+
description: "Detects `@<token>` directives in a node's body using Claude Code interpretation rules. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link. Gated by `precondition.provider: ['claude']` so Gemini / Cursor / Codex apply their own at-directive flavours via their own extractors.",
|
|
606
|
+
scope: "body",
|
|
607
|
+
precondition: { provider: ["claude"] },
|
|
608
|
+
extract(ctx) {
|
|
609
|
+
const seenMentions = /* @__PURE__ */ new Set();
|
|
610
|
+
const seenReferences = /* @__PURE__ */ new Set();
|
|
611
|
+
const body = stripCodeBlocks(ctx.body);
|
|
612
|
+
for (const match of body.matchAll(AT_RE)) {
|
|
613
|
+
const original = match[1];
|
|
614
|
+
const bare = original.slice(1);
|
|
615
|
+
const isReference = bare.startsWith("./") || bare.startsWith("../") || bare.startsWith("/") || FILE_EXT_RE.test(bare);
|
|
616
|
+
if (isReference) {
|
|
617
|
+
const target = bare.replace(/^\.\//, "");
|
|
618
|
+
if (seenReferences.has(target)) continue;
|
|
619
|
+
seenReferences.add(target);
|
|
620
|
+
ctx.emitLink({
|
|
621
|
+
source: ctx.node.path,
|
|
622
|
+
target,
|
|
623
|
+
kind: "references",
|
|
624
|
+
// 0.85: strong file signal (path prefix `./` / `../` / `/` OR
|
|
625
|
+
// a known file extension on the tail). One degree of inference
|
|
626
|
+
// (the runtime still resolves the path).
|
|
627
|
+
confidence: 0.85,
|
|
628
|
+
sources: [ID],
|
|
629
|
+
trigger: {
|
|
630
|
+
originalTrigger: original,
|
|
631
|
+
normalizedTrigger: target.toLowerCase()
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const normalized = normalizeTrigger(original);
|
|
637
|
+
if (seenMentions.has(normalized)) continue;
|
|
638
|
+
seenMentions.add(normalized);
|
|
639
|
+
ctx.emitLink({
|
|
640
|
+
source: ctx.node.path,
|
|
641
|
+
target: original,
|
|
642
|
+
kind: "mentions",
|
|
643
|
+
// 0.5: genuine ambiguity. A bare `@handle` (no extension, no
|
|
644
|
+
// path prefix) could be an agent, a handle, or generic prose.
|
|
645
|
+
// The runtime decides at invocation time; the extractor leaves
|
|
646
|
+
// the question open.
|
|
647
|
+
confidence: 0.5,
|
|
648
|
+
sources: [ID],
|
|
649
|
+
trigger: {
|
|
650
|
+
originalTrigger: original,
|
|
651
|
+
normalizedTrigger: normalized
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// plugins/claude/extractors/slash/index.ts
|
|
659
|
+
var ID2 = "slash";
|
|
660
|
+
var SLASH_RE = /(?<![A-Za-z0-9_/.:?#])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
661
|
+
var slashExtractor = {
|
|
662
|
+
id: ID2,
|
|
663
|
+
pluginId: "claude",
|
|
664
|
+
kind: "extractor",
|
|
665
|
+
version: "1.0.0",
|
|
666
|
+
description: "Detects `/command` invocations in a node's body using Claude Code routing rules and turns each one into an arrow between nodes in the graph. Gated by `precondition.provider: ['claude']` so Gemini / Cursor / Codex apply their own slash flavours (Gemini has 4 routing separators, Codex deprecated user slash commands, etc.) via their own extractors.",
|
|
667
|
+
scope: "body",
|
|
668
|
+
precondition: { provider: ["claude"] },
|
|
669
|
+
extract(ctx) {
|
|
670
|
+
const seen = /* @__PURE__ */ new Set();
|
|
671
|
+
const body = stripCodeBlocks(ctx.body);
|
|
672
|
+
for (const match of body.matchAll(SLASH_RE)) {
|
|
673
|
+
const original = match[1];
|
|
674
|
+
const endIdx = (match.index ?? 0) + match[0].length;
|
|
675
|
+
const nextChar = body[endIdx];
|
|
676
|
+
if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
|
|
677
|
+
const normalized = normalizeTrigger(original);
|
|
678
|
+
if (seen.has(normalized)) continue;
|
|
679
|
+
seen.add(normalized);
|
|
680
|
+
ctx.emitLink({
|
|
681
|
+
source: ctx.node.path,
|
|
682
|
+
target: original,
|
|
683
|
+
kind: "invokes",
|
|
684
|
+
// 0.8: clean `/command` match after code-block strip. The
|
|
685
|
+
// post-match path guard above filters URL / file-path noise,
|
|
686
|
+
// so a hit is unambiguous syntax. Resolution against the live
|
|
687
|
+
// skill / command catalog happens downstream.
|
|
688
|
+
confidence: 0.8,
|
|
689
|
+
sources: [ID2],
|
|
690
|
+
trigger: {
|
|
691
|
+
originalTrigger: original,
|
|
692
|
+
normalizedTrigger: normalized
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
514
699
|
// plugins/gemini/providers/gemini/schemas/agent.schema.json
|
|
515
700
|
var agent_schema_default2 = {
|
|
516
701
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -629,6 +814,86 @@ var geminiProvider = {
|
|
|
629
814
|
}
|
|
630
815
|
};
|
|
631
816
|
|
|
817
|
+
// plugins/openai/providers/openai/schemas/agent.schema.json
|
|
818
|
+
var agent_schema_default3 = {
|
|
819
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
820
|
+
$id: "https://skill-map.dev/providers/openai/v1/frontmatter/agent.schema.json",
|
|
821
|
+
title: "FrontmatterCodexAgent",
|
|
822
|
+
description: "Frontmatter shape for nodes classified as `agent` by the OpenAI Codex Provider. Codex sub-agents live as TOML files under `.codex/agents/<name>.toml`; the entire file IS the agent definition (no markdown body). The TOML parser feeds the parsed root object into `frontmatter`, so this schema validates the same shape skill-map's other providers carry on per-kind frontmatter. Mirrors Codex's documented sub-agent fields (https://github.com/openai/codex) with `additionalProperties: true` so future additions flow through unchanged.",
|
|
823
|
+
allOf: [
|
|
824
|
+
{ $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
|
|
825
|
+
],
|
|
826
|
+
type: "object",
|
|
827
|
+
additionalProperties: true,
|
|
828
|
+
properties: {
|
|
829
|
+
name: {
|
|
830
|
+
type: "string",
|
|
831
|
+
minLength: 1,
|
|
832
|
+
description: "Sub-agent identifier. Conventionally matches the filename stem."
|
|
833
|
+
},
|
|
834
|
+
description: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "Short description of when this sub-agent applies. Codex surfaces this in the agent picker; skill-map mirrors it in the card."
|
|
837
|
+
},
|
|
838
|
+
model: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "Model identifier (`gpt-4o`, `o3-mini`, etc.) the sub-agent runs against."
|
|
841
|
+
},
|
|
842
|
+
instructions: {
|
|
843
|
+
type: "string",
|
|
844
|
+
description: "Multi-line prompt body (TOML triple-quoted string)."
|
|
845
|
+
},
|
|
846
|
+
tools: {
|
|
847
|
+
type: "array",
|
|
848
|
+
items: { type: "string" },
|
|
849
|
+
description: "Tool ids this sub-agent is allowed to call."
|
|
850
|
+
},
|
|
851
|
+
mcp_servers: {
|
|
852
|
+
type: "array",
|
|
853
|
+
items: { type: "string" },
|
|
854
|
+
description: "MCP server ids attached to this sub-agent (`tools: [mcp__<server>__*]` follows the same pattern as Claude)."
|
|
855
|
+
},
|
|
856
|
+
approval_policy: {
|
|
857
|
+
type: "string",
|
|
858
|
+
enum: ["never", "on-request", "untrusted"],
|
|
859
|
+
description: "Codex approval policy for this sub-agent's destructive operations."
|
|
860
|
+
},
|
|
861
|
+
sandbox_mode: {
|
|
862
|
+
type: "string",
|
|
863
|
+
enum: ["read-only", "workspace-write", "danger-full-access"],
|
|
864
|
+
description: "Codex sandbox mode the sub-agent runs under."
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// plugins/openai/providers/openai/index.ts
|
|
870
|
+
var openaiProvider = {
|
|
871
|
+
id: "openai",
|
|
872
|
+
pluginId: "openai",
|
|
873
|
+
kind: "provider",
|
|
874
|
+
version: "1.0.0",
|
|
875
|
+
description: "Walks OpenAI Codex CLI scope conventions (.codex/agents/*.toml).",
|
|
876
|
+
read: { extensions: [".toml"], parser: "toml" },
|
|
877
|
+
kinds: {
|
|
878
|
+
agent: {
|
|
879
|
+
schema: "./schemas/agent.schema.json",
|
|
880
|
+
schemaJson: agent_schema_default3,
|
|
881
|
+
ui: {
|
|
882
|
+
label: "Codex agents",
|
|
883
|
+
// Codex green; distinct from claude / gemini palettes.
|
|
884
|
+
color: "#22c55e",
|
|
885
|
+
colorDark: "#4ade80",
|
|
886
|
+
icon: { kind: "pi", id: "pi-bolt" }
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
classify(path) {
|
|
891
|
+
const lower = path.toLowerCase();
|
|
892
|
+
if (lower.startsWith(".codex/agents/") && lower.endsWith(".toml")) return "agent";
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
632
897
|
// plugins/agent-skills/providers/agent-skills/schemas/skill.schema.json
|
|
633
898
|
var skill_schema_default3 = {
|
|
634
899
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -722,9 +987,9 @@ var coreMarkdownProvider = {
|
|
|
722
987
|
};
|
|
723
988
|
|
|
724
989
|
// plugins/core/extractors/annotations/index.ts
|
|
725
|
-
var
|
|
990
|
+
var ID3 = "annotations";
|
|
726
991
|
var annotationsExtractor = {
|
|
727
|
-
id:
|
|
992
|
+
id: ID3,
|
|
728
993
|
pluginId: "core",
|
|
729
994
|
kind: "extractor",
|
|
730
995
|
version: "1.0.0",
|
|
@@ -751,147 +1016,36 @@ function processBlock(block, sourcePath, emit) {
|
|
|
751
1016
|
if (typeof supersededBy === "string" && supersededBy.length > 0) {
|
|
752
1017
|
emit(supersededBy, sourcePath);
|
|
753
1018
|
}
|
|
754
|
-
}
|
|
755
|
-
function pickAnnotations(node) {
|
|
756
|
-
const sidecar = node.sidecar;
|
|
757
|
-
if (!sidecar || sidecar.present !== true) return null;
|
|
758
|
-
const ann = sidecar.annotations;
|
|
759
|
-
if (ann && typeof ann === "object" && !Array.isArray(ann)) {
|
|
760
|
-
return ann;
|
|
761
|
-
}
|
|
762
|
-
return null;
|
|
763
|
-
}
|
|
764
|
-
function stringArray(value) {
|
|
765
|
-
if (!Array.isArray(value)) return [];
|
|
766
|
-
return value.filter((v) => typeof v === "string" && v.length > 0);
|
|
767
|
-
}
|
|
768
|
-
function link(source, target) {
|
|
769
|
-
return {
|
|
770
|
-
source,
|
|
771
|
-
target,
|
|
772
|
-
kind: "supersedes",
|
|
773
|
-
confidence:
|
|
774
|
-
sources: [
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// kernel/util/strip-code-blocks.ts
|
|
779
|
-
var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
|
|
780
|
-
function stripCodeBlocks(input) {
|
|
781
|
-
if (!input) return input;
|
|
782
|
-
const fenceless = stripFences(input);
|
|
783
|
-
return stripInline(fenceless);
|
|
784
|
-
}
|
|
785
|
-
function stripFences(input) {
|
|
786
|
-
const out = [];
|
|
787
|
-
const lines = input.split("\n");
|
|
788
|
-
let openFence = null;
|
|
789
|
-
for (const line of lines) {
|
|
790
|
-
if (openFence) {
|
|
791
|
-
const closer = matchClosingFence(line, openFence);
|
|
792
|
-
if (closer) {
|
|
793
|
-
out.push(blank(line));
|
|
794
|
-
openFence = null;
|
|
795
|
-
} else {
|
|
796
|
-
out.push(blank(line));
|
|
797
|
-
}
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
const open = FENCE_RE.exec(line);
|
|
801
|
-
if (open?.groups) {
|
|
802
|
-
openFence = open.groups["fence"];
|
|
803
|
-
out.push(blank(line));
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
out.push(line);
|
|
807
|
-
}
|
|
808
|
-
return out.join("\n");
|
|
809
|
-
}
|
|
810
|
-
function matchClosingFence(line, openFence) {
|
|
811
|
-
const m = FENCE_RE.exec(line);
|
|
812
|
-
if (!m?.groups) return false;
|
|
813
|
-
const fence = m.groups["fence"];
|
|
814
|
-
return fence[0] === openFence[0] && fence.length >= openFence.length;
|
|
815
|
-
}
|
|
816
|
-
function stripInline(input) {
|
|
817
|
-
return input.replace(/(`+)([\s\S]*?)\1/g, (_full, ticks, body) => {
|
|
818
|
-
return ticks.replace(/`/g, " ") + blank(body) + ticks.replace(/`/g, " ");
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
function blank(s) {
|
|
822
|
-
return s.replace(/[^\s]/g, " ");
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// kernel/trigger-normalize.ts
|
|
826
|
-
function normalizeTrigger(source) {
|
|
827
|
-
let out = source.normalize("NFD");
|
|
828
|
-
out = out.replace(new RegExp("\\p{Mn}+", "gu"), "");
|
|
829
|
-
out = out.toLowerCase();
|
|
830
|
-
out = out.replace(/[-_\s]+/g, " ");
|
|
831
|
-
out = out.replace(/ +/g, " ");
|
|
832
|
-
return out.trim();
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// plugins/core/extractors/at-directive/index.ts
|
|
836
|
-
var ID2 = "at-directive";
|
|
837
|
-
var AT_RE = /(?:^|[^A-Za-z0-9_@])(@(?:\.{1,2}\/|\/)?[a-z0-9](?:[a-z0-9_\-./]*[a-z0-9_])?(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
838
|
-
var FILE_EXT_RE = /\.(md|mdx|js|jsx|ts|tsx|json|yml|yaml|toml|txt|html|css|scss|less|py|rb|go|rs|java|c|cpp|h|hpp|sh|sql|svg|png|jpg|jpeg|gif|webp|pdf)$/i;
|
|
839
|
-
var atDirectiveExtractor = {
|
|
840
|
-
id: ID2,
|
|
841
|
-
pluginId: "core",
|
|
842
|
-
kind: "extractor",
|
|
843
|
-
version: "1.0.0",
|
|
844
|
-
description: "Detects `@<token>` directives in a node's body. A bare handle (e.g. `@team`) becomes a `mentions` link; a file-flavoured token (e.g. `@docs/api.md`, `@./readme.md`) becomes a `references` link, matching how Claude Code / Gemini CLI / Cursor read the same syntax.",
|
|
845
|
-
scope: "body",
|
|
846
|
-
extract(ctx) {
|
|
847
|
-
const seenMentions = /* @__PURE__ */ new Set();
|
|
848
|
-
const seenReferences = /* @__PURE__ */ new Set();
|
|
849
|
-
const body = stripCodeBlocks(ctx.body);
|
|
850
|
-
for (const match of body.matchAll(AT_RE)) {
|
|
851
|
-
const original = match[1];
|
|
852
|
-
const bare = original.slice(1);
|
|
853
|
-
const isReference = bare.startsWith("./") || bare.startsWith("../") || bare.startsWith("/") || FILE_EXT_RE.test(bare);
|
|
854
|
-
if (isReference) {
|
|
855
|
-
const target = bare.replace(/^\.\//, "");
|
|
856
|
-
if (seenReferences.has(target)) continue;
|
|
857
|
-
seenReferences.add(target);
|
|
858
|
-
ctx.emitLink({
|
|
859
|
-
source: ctx.node.path,
|
|
860
|
-
target,
|
|
861
|
-
kind: "references",
|
|
862
|
-
confidence: "medium",
|
|
863
|
-
sources: [ID2],
|
|
864
|
-
trigger: {
|
|
865
|
-
originalTrigger: original,
|
|
866
|
-
normalizedTrigger: target.toLowerCase()
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
const normalized = normalizeTrigger(original);
|
|
872
|
-
if (seenMentions.has(normalized)) continue;
|
|
873
|
-
seenMentions.add(normalized);
|
|
874
|
-
ctx.emitLink({
|
|
875
|
-
source: ctx.node.path,
|
|
876
|
-
target: original,
|
|
877
|
-
kind: "mentions",
|
|
878
|
-
confidence: "medium",
|
|
879
|
-
sources: [ID2],
|
|
880
|
-
trigger: {
|
|
881
|
-
originalTrigger: original,
|
|
882
|
-
normalizedTrigger: normalized
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
};
|
|
1019
|
+
}
|
|
1020
|
+
function pickAnnotations(node) {
|
|
1021
|
+
const sidecar = node.sidecar;
|
|
1022
|
+
if (!sidecar || sidecar.present !== true) return null;
|
|
1023
|
+
const ann = sidecar.annotations;
|
|
1024
|
+
if (ann && typeof ann === "object" && !Array.isArray(ann)) {
|
|
1025
|
+
return ann;
|
|
1026
|
+
}
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
function stringArray(value) {
|
|
1030
|
+
if (!Array.isArray(value)) return [];
|
|
1031
|
+
return value.filter((v) => typeof v === "string" && v.length > 0);
|
|
1032
|
+
}
|
|
1033
|
+
function link(source, target) {
|
|
1034
|
+
return {
|
|
1035
|
+
source,
|
|
1036
|
+
target,
|
|
1037
|
+
kind: "supersedes",
|
|
1038
|
+
confidence: 1,
|
|
1039
|
+
sources: [ID3]
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
888
1042
|
|
|
889
1043
|
// plugins/core/extractors/external-url-counter/index.ts
|
|
890
|
-
var
|
|
1044
|
+
var ID4 = "external-url-counter";
|
|
891
1045
|
var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
|
|
892
1046
|
var TRAILING_PUNCT = /[.,;:!?]+$/;
|
|
893
1047
|
var externalUrlCounterExtractor = {
|
|
894
|
-
id:
|
|
1048
|
+
id: ID4,
|
|
895
1049
|
pluginId: "core",
|
|
896
1050
|
kind: "extractor",
|
|
897
1051
|
version: "1.0.0",
|
|
@@ -937,8 +1091,8 @@ var externalUrlCounterExtractor = {
|
|
|
937
1091
|
source: ctx.node.path,
|
|
938
1092
|
target: normalized,
|
|
939
1093
|
kind: "references",
|
|
940
|
-
confidence:
|
|
941
|
-
sources: [
|
|
1094
|
+
confidence: 0.3,
|
|
1095
|
+
sources: [ID4],
|
|
942
1096
|
trigger: {
|
|
943
1097
|
originalTrigger: original,
|
|
944
1098
|
normalizedTrigger: normalized
|
|
@@ -985,11 +1139,11 @@ function lineFor(lineStarts, offset) {
|
|
|
985
1139
|
|
|
986
1140
|
// plugins/core/extractors/markdown-link/index.ts
|
|
987
1141
|
import { posix as pathPosix } from "path";
|
|
988
|
-
var
|
|
1142
|
+
var ID5 = "markdown-link";
|
|
989
1143
|
var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
990
1144
|
var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
991
1145
|
var markdownLinkExtractor = {
|
|
992
|
-
id:
|
|
1146
|
+
id: ID5,
|
|
993
1147
|
pluginId: "core",
|
|
994
1148
|
kind: "extractor",
|
|
995
1149
|
version: "1.0.0",
|
|
@@ -1010,8 +1164,8 @@ var markdownLinkExtractor = {
|
|
|
1010
1164
|
source: ctx.node.path,
|
|
1011
1165
|
target: resolved,
|
|
1012
1166
|
kind: "references",
|
|
1013
|
-
confidence:
|
|
1014
|
-
sources: [
|
|
1167
|
+
confidence: 0.95,
|
|
1168
|
+
sources: [ID5],
|
|
1015
1169
|
trigger: {
|
|
1016
1170
|
originalTrigger: original,
|
|
1017
1171
|
normalizedTrigger: resolved
|
|
@@ -1050,47 +1204,61 @@ function lineFor2(lineStarts, offset) {
|
|
|
1050
1204
|
return lo + 1;
|
|
1051
1205
|
}
|
|
1052
1206
|
|
|
1053
|
-
// plugins/core/extractors/
|
|
1054
|
-
var
|
|
1055
|
-
var
|
|
1056
|
-
var
|
|
1057
|
-
id:
|
|
1207
|
+
// plugins/core/extractors/mcp-tools/index.ts
|
|
1208
|
+
var ID6 = "mcp-tools";
|
|
1209
|
+
var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
|
|
1210
|
+
var mcpToolsExtractor = {
|
|
1211
|
+
id: ID6,
|
|
1058
1212
|
pluginId: "core",
|
|
1059
1213
|
kind: "extractor",
|
|
1060
1214
|
version: "1.0.0",
|
|
1061
|
-
description: "Detects
|
|
1062
|
-
scope: "
|
|
1215
|
+
description: "Detects `tools: [mcp__<server>__<tool>]` entries in a node's frontmatter and turns each unique server into an MCP node + a reference edge from the source.",
|
|
1216
|
+
scope: "frontmatter",
|
|
1063
1217
|
extract(ctx) {
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1218
|
+
const raw = ctx.frontmatter["tools"];
|
|
1219
|
+
if (!Array.isArray(raw)) return;
|
|
1220
|
+
const servers = collectMcpServers(raw);
|
|
1221
|
+
if (servers.size === 0) return;
|
|
1222
|
+
for (const server of servers) {
|
|
1223
|
+
const mcpPath = `mcp://${server}`;
|
|
1224
|
+
ctx.emitNode({
|
|
1225
|
+
path: mcpPath,
|
|
1226
|
+
kind: "mcp",
|
|
1227
|
+
virtual: true,
|
|
1228
|
+
provider: ctx.node.provider,
|
|
1229
|
+
derivedFrom: [ctx.node.path],
|
|
1230
|
+
frontmatter: { name: server }
|
|
1231
|
+
});
|
|
1074
1232
|
ctx.emitLink({
|
|
1075
1233
|
source: ctx.node.path,
|
|
1076
|
-
target:
|
|
1077
|
-
kind: "
|
|
1078
|
-
confidence:
|
|
1079
|
-
sources: [
|
|
1234
|
+
target: mcpPath,
|
|
1235
|
+
kind: "references",
|
|
1236
|
+
confidence: 0.85,
|
|
1237
|
+
sources: [ID6],
|
|
1080
1238
|
trigger: {
|
|
1081
|
-
originalTrigger:
|
|
1082
|
-
normalizedTrigger:
|
|
1239
|
+
originalTrigger: `mcp__${server}__*`,
|
|
1240
|
+
normalizedTrigger: mcpPath
|
|
1083
1241
|
}
|
|
1084
1242
|
});
|
|
1085
1243
|
}
|
|
1086
1244
|
}
|
|
1087
1245
|
};
|
|
1246
|
+
function collectMcpServers(tools) {
|
|
1247
|
+
const out = /* @__PURE__ */ new Set();
|
|
1248
|
+
for (const t of tools) {
|
|
1249
|
+
if (typeof t !== "string" || t.length === 0) continue;
|
|
1250
|
+
const match = MCP_PATTERN.exec(t);
|
|
1251
|
+
if (!match) continue;
|
|
1252
|
+
out.add(match[1].toLowerCase());
|
|
1253
|
+
}
|
|
1254
|
+
return out;
|
|
1255
|
+
}
|
|
1088
1256
|
|
|
1089
1257
|
// plugins/core/extractors/tools-count/index.ts
|
|
1090
|
-
var
|
|
1258
|
+
var ID7 = "tools-count";
|
|
1091
1259
|
var TOOLTIP_MAX = 255;
|
|
1092
1260
|
var toolsCountExtractor = {
|
|
1093
|
-
id:
|
|
1261
|
+
id: ID7,
|
|
1094
1262
|
pluginId: "core",
|
|
1095
1263
|
kind: "extractor",
|
|
1096
1264
|
version: "1.0.0",
|
|
@@ -1133,9 +1301,9 @@ var ANNOTATION_ORPHAN_TEXTS = {
|
|
|
1133
1301
|
};
|
|
1134
1302
|
|
|
1135
1303
|
// plugins/core/analyzers/annotation-orphan/index.ts
|
|
1136
|
-
var
|
|
1304
|
+
var ID8 = "annotation-orphan";
|
|
1137
1305
|
var annotationOrphanAnalyzer = {
|
|
1138
|
-
id:
|
|
1306
|
+
id: ID8,
|
|
1139
1307
|
pluginId: "core",
|
|
1140
1308
|
kind: "analyzer",
|
|
1141
1309
|
version: "1.0.0",
|
|
@@ -1148,7 +1316,7 @@ var annotationOrphanAnalyzer = {
|
|
|
1148
1316
|
for (const orphan of orphans) {
|
|
1149
1317
|
const expectedMdRelative = orphan.relativePath.endsWith(".sm") ? `${orphan.relativePath.slice(0, -".sm".length)}.md` : `${orphan.relativePath}.md`;
|
|
1150
1318
|
issues.push({
|
|
1151
|
-
analyzerId:
|
|
1319
|
+
analyzerId: ID8,
|
|
1152
1320
|
severity: "warn",
|
|
1153
1321
|
nodeIds: [expectedMdRelative],
|
|
1154
1322
|
message: tx(ANNOTATION_ORPHAN_TEXTS.message, {
|
|
@@ -1185,9 +1353,9 @@ var ANNOTATION_STALE_TEXTS = {
|
|
|
1185
1353
|
};
|
|
1186
1354
|
|
|
1187
1355
|
// plugins/core/analyzers/annotation-stale/index.ts
|
|
1188
|
-
var
|
|
1356
|
+
var ID9 = "annotation-stale";
|
|
1189
1357
|
var annotationStaleAnalyzer = {
|
|
1190
|
-
id:
|
|
1358
|
+
id: ID9,
|
|
1191
1359
|
pluginId: "core",
|
|
1192
1360
|
kind: "analyzer",
|
|
1193
1361
|
version: "1.0.0",
|
|
@@ -1222,7 +1390,7 @@ var annotationStaleAnalyzer = {
|
|
|
1222
1390
|
if (status === "fresh") continue;
|
|
1223
1391
|
const message = status === "stale-body" ? tx(ANNOTATION_STALE_TEXTS.bodyDrift, { path: node.path }) : status === "stale-frontmatter" ? tx(ANNOTATION_STALE_TEXTS.frontmatterDrift, { path: node.path }) : tx(ANNOTATION_STALE_TEXTS.bothDrift, { path: node.path });
|
|
1224
1392
|
issues.push({
|
|
1225
|
-
analyzerId:
|
|
1393
|
+
analyzerId: ID9,
|
|
1226
1394
|
severity: "warn",
|
|
1227
1395
|
nodeIds: [node.path],
|
|
1228
1396
|
message,
|
|
@@ -1268,9 +1436,9 @@ var BROKEN_REF_TEXTS = {
|
|
|
1268
1436
|
};
|
|
1269
1437
|
|
|
1270
1438
|
// plugins/core/analyzers/broken-ref/index.ts
|
|
1271
|
-
var
|
|
1439
|
+
var ID10 = "broken-ref";
|
|
1272
1440
|
var brokenRefAnalyzer = {
|
|
1273
|
-
id:
|
|
1441
|
+
id: ID10,
|
|
1274
1442
|
pluginId: "core",
|
|
1275
1443
|
kind: "analyzer",
|
|
1276
1444
|
version: "1.0.0",
|
|
@@ -1339,7 +1507,7 @@ function buildIssue(link2, hintCandidates = []) {
|
|
|
1339
1507
|
trigger: link2.trigger?.normalizedTrigger ?? null
|
|
1340
1508
|
};
|
|
1341
1509
|
const issue = {
|
|
1342
|
-
analyzerId:
|
|
1510
|
+
analyzerId: ID10,
|
|
1343
1511
|
severity: "warn",
|
|
1344
1512
|
nodeIds: [link2.source],
|
|
1345
1513
|
message: tx(BROKEN_REF_TEXTS.message, {
|
|
@@ -1433,9 +1601,9 @@ function isPathStyleLink(link2) {
|
|
|
1433
1601
|
}
|
|
1434
1602
|
|
|
1435
1603
|
// plugins/core/analyzers/contribution-orphan/index.ts
|
|
1436
|
-
var
|
|
1604
|
+
var ID11 = "contribution-orphan";
|
|
1437
1605
|
var contributionOrphanAnalyzer = {
|
|
1438
|
-
id:
|
|
1606
|
+
id: ID11,
|
|
1439
1607
|
pluginId: "core",
|
|
1440
1608
|
kind: "analyzer",
|
|
1441
1609
|
version: "0.0.0",
|
|
@@ -1456,9 +1624,9 @@ var JOB_ORPHAN_FILE_TEXTS = {
|
|
|
1456
1624
|
};
|
|
1457
1625
|
|
|
1458
1626
|
// plugins/core/analyzers/job-orphan-file/index.ts
|
|
1459
|
-
var
|
|
1627
|
+
var ID12 = "job-orphan-file";
|
|
1460
1628
|
var jobOrphanFileAnalyzer = {
|
|
1461
|
-
id:
|
|
1629
|
+
id: ID12,
|
|
1462
1630
|
pluginId: "core",
|
|
1463
1631
|
kind: "analyzer",
|
|
1464
1632
|
version: "1.0.0",
|
|
@@ -1470,7 +1638,7 @@ var jobOrphanFileAnalyzer = {
|
|
|
1470
1638
|
const issues = [];
|
|
1471
1639
|
for (const filePath of orphans) {
|
|
1472
1640
|
issues.push({
|
|
1473
|
-
analyzerId:
|
|
1641
|
+
analyzerId: ID12,
|
|
1474
1642
|
severity: "warn",
|
|
1475
1643
|
nodeIds: [filePath],
|
|
1476
1644
|
message: tx(JOB_ORPHAN_FILE_TEXTS.message, { filePath }),
|
|
@@ -1488,9 +1656,9 @@ var LINK_CONFLICT_TEXTS = {
|
|
|
1488
1656
|
};
|
|
1489
1657
|
|
|
1490
1658
|
// plugins/core/analyzers/link-conflict/index.ts
|
|
1491
|
-
var
|
|
1659
|
+
var ID13 = "link-conflict";
|
|
1492
1660
|
var linkConflictAnalyzer = {
|
|
1493
|
-
id:
|
|
1661
|
+
id: ID13,
|
|
1494
1662
|
pluginId: "core",
|
|
1495
1663
|
kind: "analyzer",
|
|
1496
1664
|
version: "1.0.0",
|
|
@@ -1538,7 +1706,7 @@ var linkConflictAnalyzer = {
|
|
|
1538
1706
|
const [source, target] = key.split("\0");
|
|
1539
1707
|
const kindList = variants.map((v) => v.kind).join(" / ");
|
|
1540
1708
|
issues.push({
|
|
1541
|
-
analyzerId:
|
|
1709
|
+
analyzerId: ID13,
|
|
1542
1710
|
severity: "warn",
|
|
1543
1711
|
nodeIds: [source, target],
|
|
1544
1712
|
message: tx(LINK_CONFLICT_TEXTS.message, {
|
|
@@ -1553,14 +1721,7 @@ var linkConflictAnalyzer = {
|
|
|
1553
1721
|
}
|
|
1554
1722
|
};
|
|
1555
1723
|
function rankConfidence(c) {
|
|
1556
|
-
|
|
1557
|
-
case "high":
|
|
1558
|
-
return 2;
|
|
1559
|
-
case "medium":
|
|
1560
|
-
return 1;
|
|
1561
|
-
case "low":
|
|
1562
|
-
return 0;
|
|
1563
|
-
}
|
|
1724
|
+
return c;
|
|
1564
1725
|
}
|
|
1565
1726
|
|
|
1566
1727
|
// kernel/util/trigger-resolve.ts
|
|
@@ -1612,9 +1773,9 @@ function resolveLinkTargetToPath(link2, nameIndex) {
|
|
|
1612
1773
|
}
|
|
1613
1774
|
|
|
1614
1775
|
// plugins/core/analyzers/link-counts/index.ts
|
|
1615
|
-
var
|
|
1776
|
+
var ID14 = "link-counts";
|
|
1616
1777
|
var linkCountsAnalyzer = {
|
|
1617
|
-
id:
|
|
1778
|
+
id: ID14,
|
|
1618
1779
|
pluginId: "core",
|
|
1619
1780
|
kind: "analyzer",
|
|
1620
1781
|
version: "1.0.0",
|
|
@@ -1678,11 +1839,11 @@ function formatBreakdown(byKind, direction) {
|
|
|
1678
1839
|
}
|
|
1679
1840
|
|
|
1680
1841
|
// plugins/core/analyzers/stability/index.ts
|
|
1681
|
-
var
|
|
1842
|
+
var ID15 = "stability";
|
|
1682
1843
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
1683
1844
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
1684
1845
|
var stabilityAnalyzer = {
|
|
1685
|
-
id:
|
|
1846
|
+
id: ID15,
|
|
1686
1847
|
pluginId: "core",
|
|
1687
1848
|
kind: "analyzer",
|
|
1688
1849
|
version: "1.0.0",
|
|
@@ -1714,7 +1875,7 @@ var stabilityAnalyzer = {
|
|
|
1714
1875
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
1715
1876
|
});
|
|
1716
1877
|
issues.push({
|
|
1717
|
-
analyzerId:
|
|
1878
|
+
analyzerId: ID15,
|
|
1718
1879
|
severity: "info",
|
|
1719
1880
|
nodeIds: [node.path],
|
|
1720
1881
|
message: `Node '${node.path}' is marked experimental: API may change.`,
|
|
@@ -1727,7 +1888,7 @@ var stabilityAnalyzer = {
|
|
|
1727
1888
|
severity: "warn"
|
|
1728
1889
|
});
|
|
1729
1890
|
issues.push({
|
|
1730
|
-
analyzerId:
|
|
1891
|
+
analyzerId: ID15,
|
|
1731
1892
|
severity: "warn",
|
|
1732
1893
|
nodeIds: [node.path],
|
|
1733
1894
|
message: `Node '${node.path}' is marked deprecated: avoid in new code.`,
|
|
@@ -1761,9 +1922,9 @@ var SUPERSEDED_TEXTS = {
|
|
|
1761
1922
|
};
|
|
1762
1923
|
|
|
1763
1924
|
// plugins/core/analyzers/superseded/index.ts
|
|
1764
|
-
var
|
|
1925
|
+
var ID16 = "superseded";
|
|
1765
1926
|
var supersededAnalyzer = {
|
|
1766
|
-
id:
|
|
1927
|
+
id: ID16,
|
|
1767
1928
|
pluginId: "core",
|
|
1768
1929
|
kind: "analyzer",
|
|
1769
1930
|
version: "1.0.0",
|
|
@@ -1775,7 +1936,7 @@ var supersededAnalyzer = {
|
|
|
1775
1936
|
const supersededBy = pickSupersededBy(node);
|
|
1776
1937
|
if (supersededBy === null) continue;
|
|
1777
1938
|
issues.push({
|
|
1778
|
-
analyzerId:
|
|
1939
|
+
analyzerId: ID16,
|
|
1779
1940
|
severity: "info",
|
|
1780
1941
|
nodeIds: [node.path],
|
|
1781
1942
|
message: tx(SUPERSEDED_TEXTS.message, {
|
|
@@ -1824,14 +1985,14 @@ var TRIGGER_COLLISION_TEXTS = {
|
|
|
1824
1985
|
};
|
|
1825
1986
|
|
|
1826
1987
|
// plugins/core/analyzers/trigger-collision/index.ts
|
|
1827
|
-
var
|
|
1988
|
+
var ID17 = "trigger-collision";
|
|
1828
1989
|
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
1829
1990
|
"command",
|
|
1830
1991
|
"skill",
|
|
1831
1992
|
"agent"
|
|
1832
1993
|
]);
|
|
1833
1994
|
var triggerCollisionAnalyzer = {
|
|
1834
|
-
id:
|
|
1995
|
+
id: ID17,
|
|
1835
1996
|
pluginId: "core",
|
|
1836
1997
|
kind: "analyzer",
|
|
1837
1998
|
mode: "deterministic",
|
|
@@ -1930,7 +2091,7 @@ function analyzeTriggerBucket(normalized, claims) {
|
|
|
1930
2091
|
part: parts[0]
|
|
1931
2092
|
});
|
|
1932
2093
|
return {
|
|
1933
|
-
analyzerId:
|
|
2094
|
+
analyzerId: ID17,
|
|
1934
2095
|
severity: "error",
|
|
1935
2096
|
nodeIds,
|
|
1936
2097
|
message,
|
|
@@ -1970,10 +2131,10 @@ var UNKNOWN_FIELD_TEXTS = {
|
|
|
1970
2131
|
};
|
|
1971
2132
|
|
|
1972
2133
|
// plugins/core/analyzers/unknown-field/index.ts
|
|
1973
|
-
var
|
|
2134
|
+
var ID18 = "unknown-field";
|
|
1974
2135
|
var RESERVED_ROOT_BLOCKS = /* @__PURE__ */ new Set(["identity", "annotations", "settings", "audit"]);
|
|
1975
2136
|
var unknownFieldAnalyzer = {
|
|
1976
|
-
id:
|
|
2137
|
+
id: ID18,
|
|
1977
2138
|
pluginId: "core",
|
|
1978
2139
|
kind: "analyzer",
|
|
1979
2140
|
version: "1.0.0",
|
|
@@ -2031,7 +2192,7 @@ var unknownFieldAnalyzer = {
|
|
|
2031
2192
|
for (const key of Object.keys(annotations)) {
|
|
2032
2193
|
if (!knownAnnotationKeys.has(key)) {
|
|
2033
2194
|
issues.push({
|
|
2034
|
-
analyzerId:
|
|
2195
|
+
analyzerId: ID18,
|
|
2035
2196
|
severity: "warn",
|
|
2036
2197
|
nodeIds: [node.path],
|
|
2037
2198
|
message: tx(UNKNOWN_FIELD_TEXTS.unknownAnnotationKey, {
|
|
@@ -2058,7 +2219,7 @@ var unknownFieldAnalyzer = {
|
|
|
2058
2219
|
if (validator(value)) continue;
|
|
2059
2220
|
const errors = (validator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
2060
2221
|
issues.push({
|
|
2061
|
-
analyzerId:
|
|
2222
|
+
analyzerId: ID18,
|
|
2062
2223
|
severity: "warn",
|
|
2063
2224
|
nodeIds: [node.path],
|
|
2064
2225
|
message: tx(UNKNOWN_FIELD_TEXTS.pluginNamespaceInvalid, {
|
|
@@ -2074,7 +2235,7 @@ var unknownFieldAnalyzer = {
|
|
|
2074
2235
|
continue;
|
|
2075
2236
|
}
|
|
2076
2237
|
issues.push({
|
|
2077
|
-
analyzerId:
|
|
2238
|
+
analyzerId: ID18,
|
|
2078
2239
|
severity: "warn",
|
|
2079
2240
|
nodeIds: [node.path],
|
|
2080
2241
|
message: tx(UNKNOWN_FIELD_TEXTS.unknownRootKey, {
|
|
@@ -2367,9 +2528,9 @@ var VALIDATE_ALL_TEXTS = {
|
|
|
2367
2528
|
};
|
|
2368
2529
|
|
|
2369
2530
|
// plugins/core/analyzers/validate-all/index.ts
|
|
2370
|
-
var
|
|
2531
|
+
var ID19 = "validate-all";
|
|
2371
2532
|
var validateAllAnalyzer = {
|
|
2372
|
-
id:
|
|
2533
|
+
id: ID19,
|
|
2373
2534
|
pluginId: "core",
|
|
2374
2535
|
kind: "analyzer",
|
|
2375
2536
|
version: "1.0.0",
|
|
@@ -2432,7 +2593,7 @@ function collectNodeFindings(v, node, out) {
|
|
|
2432
2593
|
const result = v.validate("node", toNodeForSchema(node));
|
|
2433
2594
|
if (result.ok) return;
|
|
2434
2595
|
out.push({
|
|
2435
|
-
analyzerId:
|
|
2596
|
+
analyzerId: ID19,
|
|
2436
2597
|
severity: "error",
|
|
2437
2598
|
nodeIds: [node.path],
|
|
2438
2599
|
message: tx(VALIDATE_ALL_TEXTS.nodeFailure, {
|
|
@@ -2451,7 +2612,7 @@ function collectFrontmatterBaseFindings(node, out) {
|
|
|
2451
2612
|
if (isMissingStringField(fm, "description")) missing.push("description");
|
|
2452
2613
|
if (missing.length === 0) return;
|
|
2453
2614
|
out.push({
|
|
2454
|
-
analyzerId:
|
|
2615
|
+
analyzerId: ID19,
|
|
2455
2616
|
// `warn` (not `error`) so the default `sm scan` exit code stays
|
|
2456
2617
|
// 0 even when nodes are missing frontmatter base fields. Strict
|
|
2457
2618
|
// mode (`sm scan --strict`) still escalates to exit 1. Matches
|
|
@@ -2473,7 +2634,7 @@ function collectLinkFindings(v, link2, out) {
|
|
|
2473
2634
|
const result = v.validate("link", toLinkForSchema(link2));
|
|
2474
2635
|
if (result.ok) return;
|
|
2475
2636
|
out.push({
|
|
2476
|
-
analyzerId:
|
|
2637
|
+
analyzerId: ID19,
|
|
2477
2638
|
severity: "error",
|
|
2478
2639
|
nodeIds: [link2.source],
|
|
2479
2640
|
message: tx(VALIDATE_ALL_TEXTS.linkFailure, {
|
|
@@ -2541,13 +2702,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
2541
2702
|
};
|
|
2542
2703
|
|
|
2543
2704
|
// plugins/core/formatters/ascii/index.ts
|
|
2544
|
-
var
|
|
2705
|
+
var ID20 = "ascii";
|
|
2545
2706
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
2546
2707
|
var asciiFormatter = {
|
|
2547
|
-
id:
|
|
2708
|
+
id: ID20,
|
|
2548
2709
|
pluginId: "core",
|
|
2549
2710
|
kind: "formatter",
|
|
2550
|
-
formatId:
|
|
2711
|
+
formatId: ID20,
|
|
2551
2712
|
version: "1.0.0",
|
|
2552
2713
|
description: "Renders the scan as plain text, grouped by kind, arrows, and issues. Used by `sm scan --format=ascii`.",
|
|
2553
2714
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
@@ -2642,14 +2803,14 @@ function renderSection(out, kind, group) {
|
|
|
2642
2803
|
}
|
|
2643
2804
|
|
|
2644
2805
|
// plugins/core/formatters/json/index.ts
|
|
2645
|
-
var
|
|
2806
|
+
var ID21 = "json";
|
|
2646
2807
|
var jsonFormatter = {
|
|
2647
|
-
id:
|
|
2808
|
+
id: ID21,
|
|
2648
2809
|
pluginId: "core",
|
|
2649
2810
|
kind: "formatter",
|
|
2650
2811
|
version: "1.0.0",
|
|
2651
2812
|
description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json` when the full ScanResult is available). Used by `sm graph --format json` and `GET /api/graph?format=json`.",
|
|
2652
|
-
formatId:
|
|
2813
|
+
formatId: ID21,
|
|
2653
2814
|
format(ctx) {
|
|
2654
2815
|
if (ctx.scanResult !== void 0) {
|
|
2655
2816
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -2788,10 +2949,10 @@ function resolveSpecRoot2() {
|
|
|
2788
2949
|
}
|
|
2789
2950
|
|
|
2790
2951
|
// plugins/core/actions/bump/index.ts
|
|
2791
|
-
var
|
|
2952
|
+
var ID22 = "bump";
|
|
2792
2953
|
var PLUGIN_ID = "core";
|
|
2793
2954
|
var bumpAction = {
|
|
2794
|
-
id:
|
|
2955
|
+
id: ID22,
|
|
2795
2956
|
pluginId: PLUGIN_ID,
|
|
2796
2957
|
kind: "action",
|
|
2797
2958
|
version: "1.0.0",
|
|
@@ -2850,10 +3011,10 @@ function pickCurrentVersion(overlay) {
|
|
|
2850
3011
|
}
|
|
2851
3012
|
|
|
2852
3013
|
// plugins/core/actions/mark-superseded/index.ts
|
|
2853
|
-
var
|
|
3014
|
+
var ID23 = "mark-superseded";
|
|
2854
3015
|
var PLUGIN_ID2 = "core";
|
|
2855
3016
|
var markSupersededAction = {
|
|
2856
|
-
id:
|
|
3017
|
+
id: ID23,
|
|
2857
3018
|
pluginId: PLUGIN_ID2,
|
|
2858
3019
|
kind: "action",
|
|
2859
3020
|
version: "0.0.0",
|
|
@@ -2963,7 +3124,7 @@ var UPDATE_CHECK_TEXTS = {
|
|
|
2963
3124
|
// package.json
|
|
2964
3125
|
var package_default = {
|
|
2965
3126
|
name: "@skill-map/cli",
|
|
2966
|
-
version: "0.
|
|
3127
|
+
version: "0.33.0",
|
|
2967
3128
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
2968
3129
|
license: "MIT",
|
|
2969
3130
|
type: "module",
|
|
@@ -3044,6 +3205,7 @@ var package_default = {
|
|
|
3044
3205
|
"js-yaml": "4.1.1",
|
|
3045
3206
|
kysely: "0.28.17",
|
|
3046
3207
|
semver: "7.7.4",
|
|
3208
|
+
"smol-toml": "1.6.1",
|
|
3047
3209
|
typanion: "3.14.0",
|
|
3048
3210
|
ws: "8.20.0"
|
|
3049
3211
|
},
|
|
@@ -3339,14 +3501,16 @@ var updateCheckHook = {
|
|
|
3339
3501
|
|
|
3340
3502
|
// plugins/built-ins.ts
|
|
3341
3503
|
var claudeProvider2 = { ...claudeProvider, pluginId: "claude" };
|
|
3504
|
+
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude" };
|
|
3505
|
+
var slashExtractor2 = { ...slashExtractor, pluginId: "claude" };
|
|
3342
3506
|
var geminiProvider2 = { ...geminiProvider, pluginId: "gemini" };
|
|
3507
|
+
var openaiProvider2 = { ...openaiProvider, pluginId: "openai" };
|
|
3343
3508
|
var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills" };
|
|
3344
3509
|
var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core" };
|
|
3345
3510
|
var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core" };
|
|
3346
|
-
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "core" };
|
|
3347
3511
|
var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core" };
|
|
3348
3512
|
var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core" };
|
|
3349
|
-
var
|
|
3513
|
+
var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core" };
|
|
3350
3514
|
var toolsCountExtractor2 = { ...toolsCountExtractor, pluginId: "core" };
|
|
3351
3515
|
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core" };
|
|
3352
3516
|
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core" };
|
|
@@ -3371,7 +3535,9 @@ var builtInBundles = [
|
|
|
3371
3535
|
granularity: "bundle",
|
|
3372
3536
|
description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and parses Claude-flavored frontmatter.",
|
|
3373
3537
|
extensions: [
|
|
3374
|
-
claudeProvider2
|
|
3538
|
+
claudeProvider2,
|
|
3539
|
+
atDirectiveExtractor2,
|
|
3540
|
+
slashExtractor2
|
|
3375
3541
|
]
|
|
3376
3542
|
},
|
|
3377
3543
|
{
|
|
@@ -3382,6 +3548,14 @@ var builtInBundles = [
|
|
|
3382
3548
|
geminiProvider2
|
|
3383
3549
|
]
|
|
3384
3550
|
},
|
|
3551
|
+
{
|
|
3552
|
+
id: "openai",
|
|
3553
|
+
granularity: "bundle",
|
|
3554
|
+
description: "OpenAI Codex CLI platform integration. Classifies TOML sub-agent definitions under `.codex/agents/*.toml` and (future) walks the hierarchical AGENTS.md cascade. Provider for the active-lens `openai` runtime.",
|
|
3555
|
+
extensions: [
|
|
3556
|
+
openaiProvider2
|
|
3557
|
+
]
|
|
3558
|
+
},
|
|
3385
3559
|
{
|
|
3386
3560
|
id: "agent-skills",
|
|
3387
3561
|
granularity: "bundle",
|
|
@@ -3397,10 +3571,9 @@ var builtInBundles = [
|
|
|
3397
3571
|
extensions: [
|
|
3398
3572
|
coreMarkdownProvider2,
|
|
3399
3573
|
annotationsExtractor2,
|
|
3400
|
-
atDirectiveExtractor2,
|
|
3401
3574
|
externalUrlCounterExtractor2,
|
|
3402
3575
|
markdownLinkExtractor2,
|
|
3403
|
-
|
|
3576
|
+
mcpToolsExtractor2,
|
|
3404
3577
|
toolsCountExtractor2,
|
|
3405
3578
|
annotationOrphanAnalyzer2,
|
|
3406
3579
|
annotationStaleAnalyzer2,
|
|
@@ -3893,7 +4066,6 @@ var defaults_default = {
|
|
|
3893
4066
|
watch: {
|
|
3894
4067
|
debounceMs: 300
|
|
3895
4068
|
},
|
|
3896
|
-
extraFolders: [],
|
|
3897
4069
|
referencePaths: []
|
|
3898
4070
|
},
|
|
3899
4071
|
plugins: {},
|
|
@@ -3919,7 +4091,6 @@ var defaults_default = {
|
|
|
3919
4091
|
// kernel/config/loader.ts
|
|
3920
4092
|
var PROJECT_LOCAL_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
3921
4093
|
"allowEditSmFiles",
|
|
3922
|
-
"scan.extraFolders",
|
|
3923
4094
|
"scan.referencePaths"
|
|
3924
4095
|
]);
|
|
3925
4096
|
var DEFAULTS = defaults_default;
|
|
@@ -4258,7 +4429,6 @@ function writeJsonAtomic(path, content) {
|
|
|
4258
4429
|
|
|
4259
4430
|
// core/config/helper.ts
|
|
4260
4431
|
var PRIVACY_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
4261
|
-
"scan.extraFolders",
|
|
4262
4432
|
"scan.referencePaths"
|
|
4263
4433
|
]);
|
|
4264
4434
|
var ProjectLocalOnlyKeyError = class extends Error {
|
|
@@ -4330,19 +4500,17 @@ var ConfigValidationError = class extends Error {
|
|
|
4330
4500
|
function projectPathExposure(inputs) {
|
|
4331
4501
|
const empty = { expandsSurface: false, exposedPaths: [] };
|
|
4332
4502
|
if (!PRIVACY_SENSITIVE_KEYS.has(inputs.key)) return empty;
|
|
4333
|
-
if (inputs.key
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
}
|
|
4345
|
-
return empty;
|
|
4503
|
+
if (inputs.key !== "scan.referencePaths") return empty;
|
|
4504
|
+
if (!Array.isArray(inputs.value)) return empty;
|
|
4505
|
+
const before = readConfigValue(inputs.key, {
|
|
4506
|
+
cwd: inputs.cwd,
|
|
4507
|
+
default: []
|
|
4508
|
+
}) ?? [];
|
|
4509
|
+
const beforeSet = new Set(before);
|
|
4510
|
+
const added = inputs.value.filter((entry) => typeof entry === "string").filter((entry) => !beforeSet.has(entry));
|
|
4511
|
+
const exposed = added.map((entry) => resolveScanPathForExposure(entry, inputs.cwd)).filter((abs) => abs !== null && !isUnderProject(abs, inputs.cwd));
|
|
4512
|
+
if (exposed.length === 0) return empty;
|
|
4513
|
+
return { expandsSurface: true, exposedPaths: exposed };
|
|
4346
4514
|
}
|
|
4347
4515
|
function resolveScanPathForExposure(raw, cwd) {
|
|
4348
4516
|
if (raw.startsWith("~/")) return resolve6(join4(osHomedir(), raw.slice(2)));
|
|
@@ -4890,7 +5058,7 @@ var AsyncMutex = class {
|
|
|
4890
5058
|
this.#locked = true;
|
|
4891
5059
|
return;
|
|
4892
5060
|
}
|
|
4893
|
-
await new Promise((
|
|
5061
|
+
await new Promise((resolve38) => this.#waiters.push(resolve38));
|
|
4894
5062
|
this.#locked = true;
|
|
4895
5063
|
}
|
|
4896
5064
|
unlock() {
|
|
@@ -5950,11 +6118,6 @@ var LINK_KIND_VALUES = Object.freeze([
|
|
|
5950
6118
|
"mentions",
|
|
5951
6119
|
"supersedes"
|
|
5952
6120
|
]);
|
|
5953
|
-
var CONFIDENCE_VALUES = Object.freeze([
|
|
5954
|
-
"high",
|
|
5955
|
-
"medium",
|
|
5956
|
-
"low"
|
|
5957
|
-
]);
|
|
5958
6121
|
var SEVERITY_VALUES = Object.freeze([
|
|
5959
6122
|
"error",
|
|
5960
6123
|
"warn",
|
|
@@ -5986,7 +6149,7 @@ function isLinkKind(s) {
|
|
|
5986
6149
|
return typeof s === "string" && LINK_KIND_VALUES.includes(s);
|
|
5987
6150
|
}
|
|
5988
6151
|
function isConfidence(s) {
|
|
5989
|
-
return typeof s === "
|
|
6152
|
+
return typeof s === "number" && Number.isFinite(s) && s >= 0 && s <= 1;
|
|
5990
6153
|
}
|
|
5991
6154
|
function isSeverity(s) {
|
|
5992
6155
|
return typeof s === "string" && SEVERITY_VALUES.includes(s);
|
|
@@ -6000,7 +6163,7 @@ function parseLinkKind(s, ctx) {
|
|
|
6000
6163
|
function parseConfidence(s, ctx) {
|
|
6001
6164
|
if (isConfidence(s)) return s;
|
|
6002
6165
|
throw new Error(
|
|
6003
|
-
`Invalid Confidence value ${formatValue(s)} at ${ctx}.
|
|
6166
|
+
`Invalid Confidence value ${formatValue(s)} at ${ctx}. Expected a finite number in [0..1].`
|
|
6004
6167
|
);
|
|
6005
6168
|
}
|
|
6006
6169
|
function parseSeverity(s, ctx) {
|
|
@@ -8081,9 +8244,9 @@ function providerKindFailure(opts, status, fileName, errDescription) {
|
|
|
8081
8244
|
}
|
|
8082
8245
|
};
|
|
8083
8246
|
}
|
|
8084
|
-
function isDirectorySafe(path,
|
|
8247
|
+
function isDirectorySafe(path, statSync13) {
|
|
8085
8248
|
try {
|
|
8086
|
-
return
|
|
8249
|
+
return statSync13(path).isDirectory();
|
|
8087
8250
|
} catch {
|
|
8088
8251
|
return false;
|
|
8089
8252
|
}
|
|
@@ -8799,10 +8962,45 @@ var plainParser = {
|
|
|
8799
8962
|
}
|
|
8800
8963
|
};
|
|
8801
8964
|
|
|
8965
|
+
// plugins/core/parsers/toml/index.ts
|
|
8966
|
+
import { parse as parseToml } from "smol-toml";
|
|
8967
|
+
var tomlParser = {
|
|
8968
|
+
id: "toml",
|
|
8969
|
+
parse(raw, _path) {
|
|
8970
|
+
let parsed = {};
|
|
8971
|
+
const issues = [];
|
|
8972
|
+
try {
|
|
8973
|
+
const doc = parseToml(raw);
|
|
8974
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
8975
|
+
parsed = stripPrototypePollution(doc);
|
|
8976
|
+
}
|
|
8977
|
+
} catch (err) {
|
|
8978
|
+
issues.push({
|
|
8979
|
+
code: "frontmatter-parse-error",
|
|
8980
|
+
message: sanitiseParseErrorMessage2(err)
|
|
8981
|
+
});
|
|
8982
|
+
}
|
|
8983
|
+
const out = {
|
|
8984
|
+
frontmatterRaw: raw,
|
|
8985
|
+
frontmatter: parsed,
|
|
8986
|
+
body: ""
|
|
8987
|
+
};
|
|
8988
|
+
if (issues.length > 0) {
|
|
8989
|
+
return { ...out, issues };
|
|
8990
|
+
}
|
|
8991
|
+
return out;
|
|
8992
|
+
}
|
|
8993
|
+
};
|
|
8994
|
+
function sanitiseParseErrorMessage2(err) {
|
|
8995
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
8996
|
+
return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
8997
|
+
}
|
|
8998
|
+
|
|
8802
8999
|
// kernel/scan/parsers/index.ts
|
|
8803
9000
|
var REGISTRY = /* @__PURE__ */ new Map([
|
|
8804
9001
|
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
8805
|
-
[plainParser.id, plainParser]
|
|
9002
|
+
[plainParser.id, plainParser],
|
|
9003
|
+
[tomlParser.id, tomlParser]
|
|
8806
9004
|
]);
|
|
8807
9005
|
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
8808
9006
|
function getParser(id) {
|
|
@@ -9474,6 +9672,42 @@ function relativeIfBelow(path, cwd) {
|
|
|
9474
9672
|
return rel;
|
|
9475
9673
|
}
|
|
9476
9674
|
|
|
9675
|
+
// cli/util/scan-zone-drop.ts
|
|
9676
|
+
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
9677
|
+
|
|
9678
|
+
// cli/commands/db/shared.ts
|
|
9679
|
+
var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
9680
|
+
function assertSafeIdentifier(name) {
|
|
9681
|
+
if (!SAFE_SQL_IDENTIFIER_RE.test(name)) {
|
|
9682
|
+
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
9683
|
+
}
|
|
9684
|
+
}
|
|
9685
|
+
|
|
9686
|
+
// cli/util/scan-zone-drop.ts
|
|
9687
|
+
function dropScanZone(dbPath) {
|
|
9688
|
+
const db = new DatabaseSync4(dbPath);
|
|
9689
|
+
try {
|
|
9690
|
+
const rows = db.prepare(
|
|
9691
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'scan\\_%' ESCAPE '\\'"
|
|
9692
|
+
).all();
|
|
9693
|
+
for (const r of rows) assertSafeIdentifier(r.name);
|
|
9694
|
+
if (rows.length === 0) {
|
|
9695
|
+
return { tableCount: 0, droppedTables: [] };
|
|
9696
|
+
}
|
|
9697
|
+
db.exec("BEGIN");
|
|
9698
|
+
for (const { name } of rows) {
|
|
9699
|
+
db.exec(`DELETE FROM "${name}"`);
|
|
9700
|
+
}
|
|
9701
|
+
db.exec("COMMIT");
|
|
9702
|
+
return {
|
|
9703
|
+
tableCount: rows.length,
|
|
9704
|
+
droppedTables: rows.map((r) => r.name)
|
|
9705
|
+
};
|
|
9706
|
+
} finally {
|
|
9707
|
+
db.close();
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
|
|
9477
9711
|
// cli/i18n/config.texts.ts
|
|
9478
9712
|
var CONFIG_TEXTS = {
|
|
9479
9713
|
unknownKey: "{{glyph}} Unknown config key: {{key}}\n",
|
|
@@ -9516,6 +9750,20 @@ var CONFIG_TEXTS = {
|
|
|
9516
9750
|
* screen what they just opted into.
|
|
9517
9751
|
*/
|
|
9518
9752
|
privacyGateConfirmed: '{{glyph}} Opening disk access for "{{key}}":\n{{paths}}\n',
|
|
9753
|
+
/**
|
|
9754
|
+
* Confirmation printed after `sm config set activeProvider <id>`
|
|
9755
|
+
* succeeds. The lens change atomically drops the scan_* zone (per
|
|
9756
|
+
* `architecture.md` §Active Provider Lens) so the persisted graph
|
|
9757
|
+
* never carries stale node / link rows from the previous lens. We
|
|
9758
|
+
* surface what was cleared so the operator knows their state was
|
|
9759
|
+
* touched and what to do next.
|
|
9760
|
+
*/
|
|
9761
|
+
lensSwitchedCleared: "{{glyph}} Lens switched. Cleared {{tableCount}} scan table(s): {{tableNames}}.\n {{hint}}\n",
|
|
9762
|
+
lensSwitchedClearedHint: "Run `sm scan` to repopulate the graph under the new lens.",
|
|
9763
|
+
/** Same lens-switch announcement when the DB was empty (no rows to clear). */
|
|
9764
|
+
lensSwitchedEmpty: "{{glyph}} Lens switched. Scan zone was already empty.\n {{hint}}\n",
|
|
9765
|
+
/** Lens switch happened before any `sm scan` ran (no DB file on disk yet). */
|
|
9766
|
+
lensSwitchedNoDb: "{{glyph}} Lens switched. Run `sm scan` to populate the graph under the new lens.\n",
|
|
9519
9767
|
// --- list verb (sectioned human renderer) ----------------------------
|
|
9520
9768
|
/** Section heading: ` General`, ` Scan`, … rendered before its rows. */
|
|
9521
9769
|
listSectionHeader: " {{title}}\n",
|
|
@@ -9879,7 +10127,7 @@ var ConfigSetCommand = class extends SmCommand {
|
|
|
9879
10127
|
key = Option4.String({ required: true });
|
|
9880
10128
|
value = Option4.String({ required: true });
|
|
9881
10129
|
yes = Option4.Boolean("--yes", false, {
|
|
9882
|
-
description: "Confirm a privacy-sensitive write that opens disk access outside the project (scan.
|
|
10130
|
+
description: "Confirm a privacy-sensitive write that opens disk access outside the project (scan.referencePaths)."
|
|
9883
10131
|
});
|
|
9884
10132
|
// CLI orchestrator: each branch is one validation gate (forbidden
|
|
9885
10133
|
// segment / privacy guard / schema violation) or output dispatch.
|
|
@@ -9967,8 +10215,44 @@ var ConfigSetCommand = class extends SmCommand {
|
|
|
9967
10215
|
)
|
|
9968
10216
|
})
|
|
9969
10217
|
);
|
|
10218
|
+
if (this.key === "activeProvider") {
|
|
10219
|
+
this.announceLensSwitch(ctx.cwd, ansi);
|
|
10220
|
+
}
|
|
9970
10221
|
return ExitCode.Ok;
|
|
9971
10222
|
}
|
|
10223
|
+
/**
|
|
10224
|
+
* Side effect of `sm config set activeProvider <id>`, atomically
|
|
10225
|
+
* drops the `scan_*` zone so the persisted graph never reflects the
|
|
10226
|
+
* wrong lens (see `architecture.md` §Active Provider Lens). The drop
|
|
10227
|
+
* is non-destructive of `state_*` / `config_*` rows; the operator
|
|
10228
|
+
* runs `sm scan` next to repopulate.
|
|
10229
|
+
*
|
|
10230
|
+
* Silent when no DB file exists on disk yet (fresh project that has
|
|
10231
|
+
* never run `sm scan`), the lens just gets set and the next scan
|
|
10232
|
+
* uses it.
|
|
10233
|
+
*/
|
|
10234
|
+
announceLensSwitch(cwd, ansi) {
|
|
10235
|
+
const dbPath = resolveDbPath({ db: void 0, cwd });
|
|
10236
|
+
const okGlyph = ansi.green("\u2713");
|
|
10237
|
+
if (!existsSync14(dbPath)) {
|
|
10238
|
+
this.printer.info(tx(CONFIG_TEXTS.lensSwitchedNoDb, { glyph: okGlyph }));
|
|
10239
|
+
return;
|
|
10240
|
+
}
|
|
10241
|
+
const result = dropScanZone(dbPath);
|
|
10242
|
+
const hint = ansi.dim(CONFIG_TEXTS.lensSwitchedClearedHint);
|
|
10243
|
+
if (result.tableCount === 0) {
|
|
10244
|
+
this.printer.info(tx(CONFIG_TEXTS.lensSwitchedEmpty, { glyph: okGlyph, hint }));
|
|
10245
|
+
return;
|
|
10246
|
+
}
|
|
10247
|
+
this.printer.info(
|
|
10248
|
+
tx(CONFIG_TEXTS.lensSwitchedCleared, {
|
|
10249
|
+
glyph: okGlyph,
|
|
10250
|
+
tableCount: result.tableCount,
|
|
10251
|
+
tableNames: result.droppedTables.join(", "),
|
|
10252
|
+
hint
|
|
10253
|
+
})
|
|
10254
|
+
);
|
|
10255
|
+
}
|
|
9972
10256
|
};
|
|
9973
10257
|
var ConfigResetCommand = class extends SmCommand {
|
|
9974
10258
|
static paths = [["config", "reset"]];
|
|
@@ -11006,18 +11290,8 @@ var DbRestoreCommand = class extends SmCommand {
|
|
|
11006
11290
|
|
|
11007
11291
|
// cli/commands/db/reset.ts
|
|
11008
11292
|
import { rm as rm2 } from "fs/promises";
|
|
11009
|
-
import { DatabaseSync as
|
|
11293
|
+
import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
|
|
11010
11294
|
import { Command as Command8, Option as Option8 } from "clipanion";
|
|
11011
|
-
|
|
11012
|
-
// cli/commands/db/shared.ts
|
|
11013
|
-
var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
11014
|
-
function assertSafeIdentifier(name) {
|
|
11015
|
-
if (!SAFE_SQL_IDENTIFIER_RE.test(name)) {
|
|
11016
|
-
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
11017
|
-
}
|
|
11018
|
-
}
|
|
11019
|
-
|
|
11020
|
-
// cli/commands/db/reset.ts
|
|
11021
11295
|
var DbResetCommand = class extends SmCommand {
|
|
11022
11296
|
static paths = [["db", "reset"]];
|
|
11023
11297
|
static usage = Command8.Usage({
|
|
@@ -11099,7 +11373,7 @@ var DbResetCommand = class extends SmCommand {
|
|
|
11099
11373
|
return ExitCode.Error;
|
|
11100
11374
|
}
|
|
11101
11375
|
}
|
|
11102
|
-
const db = new
|
|
11376
|
+
const db = new DatabaseSync5(path);
|
|
11103
11377
|
try {
|
|
11104
11378
|
const rows = db.prepare(
|
|
11105
11379
|
"SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE 'scan\\_%' ESCAPE '\\'" + (this.state ? " OR name LIKE 'state\\_%' ESCAPE '\\'" : "") + ")"
|
|
@@ -11242,7 +11516,7 @@ var DbBrowserCommand = class extends SmCommand {
|
|
|
11242
11516
|
};
|
|
11243
11517
|
|
|
11244
11518
|
// cli/commands/db/dump.ts
|
|
11245
|
-
import { DatabaseSync as
|
|
11519
|
+
import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
|
|
11246
11520
|
import { Command as Command11, Option as Option10 } from "clipanion";
|
|
11247
11521
|
var DbDumpCommand = class extends SmCommand {
|
|
11248
11522
|
static paths = [["db", "dump"]];
|
|
@@ -11284,7 +11558,7 @@ var DbDumpCommand = class extends SmCommand {
|
|
|
11284
11558
|
}
|
|
11285
11559
|
};
|
|
11286
11560
|
function dumpDatabaseToStream(dbPath, out, tables) {
|
|
11287
|
-
const db = new
|
|
11561
|
+
const db = new DatabaseSync6(dbPath, { readOnly: true });
|
|
11288
11562
|
try {
|
|
11289
11563
|
out.write("PRAGMA foreign_keys=OFF;\n");
|
|
11290
11564
|
out.write("BEGIN TRANSACTION;\n");
|
|
@@ -12750,12 +13024,22 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
12750
13024
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
12751
13025
|
};
|
|
12752
13026
|
|
|
13027
|
+
// kernel/types.ts
|
|
13028
|
+
var ConfidenceTier = Object.freeze({
|
|
13029
|
+
HIGH: 0.9,
|
|
13030
|
+
MEDIUM: 0.6,
|
|
13031
|
+
LOW: 0.3
|
|
13032
|
+
});
|
|
13033
|
+
|
|
12753
13034
|
// kernel/orchestrator/extractors.ts
|
|
12754
13035
|
async function runExtractorsForNode(opts) {
|
|
12755
13036
|
const internalLinks = [];
|
|
12756
13037
|
const externalLinks = [];
|
|
12757
13038
|
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
12758
13039
|
const contributions = [];
|
|
13040
|
+
const signals = [];
|
|
13041
|
+
const virtualNodes = [];
|
|
13042
|
+
const virtualNodePaths = /* @__PURE__ */ new Set();
|
|
12759
13043
|
const validators = loadSchemaValidators();
|
|
12760
13044
|
for (const extractor of opts.extractors) {
|
|
12761
13045
|
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
@@ -12827,6 +13111,18 @@ async function runExtractorsForNode(opts) {
|
|
|
12827
13111
|
emittedAt: Date.now()
|
|
12828
13112
|
});
|
|
12829
13113
|
};
|
|
13114
|
+
const emitSignal = (signal) => {
|
|
13115
|
+
const validated = validateSignal(extractor, signal, opts.emitter);
|
|
13116
|
+
if (!validated) return;
|
|
13117
|
+
signals.push(validated);
|
|
13118
|
+
};
|
|
13119
|
+
const emitNode = (emitted) => {
|
|
13120
|
+
if (virtualNodePaths.has(emitted.path)) return;
|
|
13121
|
+
const node = buildVirtualNode(extractor, emitted, opts.emitter);
|
|
13122
|
+
if (!node) return;
|
|
13123
|
+
virtualNodePaths.add(node.path);
|
|
13124
|
+
virtualNodes.push(node);
|
|
13125
|
+
};
|
|
12830
13126
|
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
12831
13127
|
const ctx = buildExtractorContext(
|
|
12832
13128
|
extractor,
|
|
@@ -12836,6 +13132,8 @@ async function runExtractorsForNode(opts) {
|
|
|
12836
13132
|
emitLink,
|
|
12837
13133
|
enrichNode,
|
|
12838
13134
|
emitContribution,
|
|
13135
|
+
emitSignal,
|
|
13136
|
+
emitNode,
|
|
12839
13137
|
store
|
|
12840
13138
|
);
|
|
12841
13139
|
await extractor.extract(ctx);
|
|
@@ -12844,7 +13142,9 @@ async function runExtractorsForNode(opts) {
|
|
|
12844
13142
|
internalLinks,
|
|
12845
13143
|
externalLinks,
|
|
12846
13144
|
enrichments: Array.from(enrichmentBuffer.values()),
|
|
12847
|
-
contributions
|
|
13145
|
+
contributions,
|
|
13146
|
+
signals,
|
|
13147
|
+
virtualNodes
|
|
12848
13148
|
};
|
|
12849
13149
|
}
|
|
12850
13150
|
function readDeclaredContributions(extension) {
|
|
@@ -12869,7 +13169,7 @@ function emitExtensionError(emitter, qualifiedId2, nodePath, data) {
|
|
|
12869
13169
|
})
|
|
12870
13170
|
);
|
|
12871
13171
|
}
|
|
12872
|
-
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
13172
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
|
|
12873
13173
|
const scope = extractor.scope ?? "both";
|
|
12874
13174
|
const settings = extractor.resolvedSettings ?? {};
|
|
12875
13175
|
return {
|
|
@@ -12880,9 +13180,62 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
|
|
|
12880
13180
|
emitLink,
|
|
12881
13181
|
enrichNode,
|
|
12882
13182
|
emitContribution,
|
|
13183
|
+
emitSignal,
|
|
13184
|
+
emitNode,
|
|
12883
13185
|
...store !== void 0 ? { store } : {}
|
|
12884
13186
|
};
|
|
12885
13187
|
}
|
|
13188
|
+
var VIRTUAL_NODE_PLACEHOLDER_HASH = "0".repeat(64);
|
|
13189
|
+
function buildVirtualNode(extractor, emitted, emitter) {
|
|
13190
|
+
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
13191
|
+
if (typeof emitted.path !== "string" || emitted.path.length === 0) {
|
|
13192
|
+
emitter.emit(
|
|
13193
|
+
makeEvent("extension.error", {
|
|
13194
|
+
kind: "virtual-node-missing-path",
|
|
13195
|
+
extensionId: qualifiedId2,
|
|
13196
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node with no path; dropped.`
|
|
13197
|
+
})
|
|
13198
|
+
);
|
|
13199
|
+
return null;
|
|
13200
|
+
}
|
|
13201
|
+
if (typeof emitted.kind !== "string" || emitted.kind.length === 0) {
|
|
13202
|
+
emitter.emit(
|
|
13203
|
+
makeEvent("extension.error", {
|
|
13204
|
+
kind: "virtual-node-missing-kind",
|
|
13205
|
+
extensionId: qualifiedId2,
|
|
13206
|
+
virtualPath: emitted.path,
|
|
13207
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node at '${emitted.path}' with no kind; dropped.`
|
|
13208
|
+
})
|
|
13209
|
+
);
|
|
13210
|
+
return null;
|
|
13211
|
+
}
|
|
13212
|
+
if (!Array.isArray(emitted.derivedFrom) || emitted.derivedFrom.length === 0) {
|
|
13213
|
+
emitter.emit(
|
|
13214
|
+
makeEvent("extension.error", {
|
|
13215
|
+
kind: "virtual-node-missing-derived-from",
|
|
13216
|
+
extensionId: qualifiedId2,
|
|
13217
|
+
virtualPath: emitted.path,
|
|
13218
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node at '${emitted.path}' with empty derivedFrom; dropped.`
|
|
13219
|
+
})
|
|
13220
|
+
);
|
|
13221
|
+
return null;
|
|
13222
|
+
}
|
|
13223
|
+
const node = {
|
|
13224
|
+
path: emitted.path,
|
|
13225
|
+
kind: emitted.kind,
|
|
13226
|
+
provider: emitted.provider,
|
|
13227
|
+
bodyHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
13228
|
+
frontmatterHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
13229
|
+
bytes: { frontmatter: 0, body: 0, total: 0 },
|
|
13230
|
+
linksOutCount: 0,
|
|
13231
|
+
linksInCount: 0,
|
|
13232
|
+
externalRefsCount: 0,
|
|
13233
|
+
virtual: true,
|
|
13234
|
+
derivedFrom: [...emitted.derivedFrom]
|
|
13235
|
+
};
|
|
13236
|
+
if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
|
|
13237
|
+
return node;
|
|
13238
|
+
}
|
|
12886
13239
|
function validateLink(extractor, link2, emitter) {
|
|
12887
13240
|
const knownKinds = ["invokes", "references", "mentions", "supersedes"];
|
|
12888
13241
|
if (!knownKinds.includes(link2.kind)) {
|
|
@@ -12903,9 +13256,68 @@ function validateLink(extractor, link2, emitter) {
|
|
|
12903
13256
|
);
|
|
12904
13257
|
return null;
|
|
12905
13258
|
}
|
|
12906
|
-
const
|
|
13259
|
+
const c = link2.confidence;
|
|
13260
|
+
if (c !== void 0 && (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
13261
|
+
const qualifiedId2 = `${extractor.pluginId}/${extractor.id}`;
|
|
13262
|
+
emitter.emit(
|
|
13263
|
+
makeEvent("extension.error", {
|
|
13264
|
+
kind: "link-confidence-out-of-range",
|
|
13265
|
+
extensionId: qualifiedId2,
|
|
13266
|
+
confidence: c,
|
|
13267
|
+
message: `Extractor ${qualifiedId2} emitted a Link with confidence ${String(c)} outside [0..1]; dropped.`
|
|
13268
|
+
})
|
|
13269
|
+
);
|
|
13270
|
+
return null;
|
|
13271
|
+
}
|
|
13272
|
+
const confidence = c ?? ConfidenceTier.MEDIUM;
|
|
12907
13273
|
return { ...link2, confidence };
|
|
12908
13274
|
}
|
|
13275
|
+
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
|
|
13276
|
+
function validateSignal(extractor, signal, emitter) {
|
|
13277
|
+
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
13278
|
+
if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
|
|
13279
|
+
emitter.emit(
|
|
13280
|
+
makeEvent("extension.error", {
|
|
13281
|
+
kind: "signal-no-candidates",
|
|
13282
|
+
extensionId: qualifiedId2,
|
|
13283
|
+
signal: { source: signal.source, scope: signal.scope },
|
|
13284
|
+
message: `Extractor ${qualifiedId2} emitted a Signal with no candidates; dropped.`
|
|
13285
|
+
})
|
|
13286
|
+
);
|
|
13287
|
+
return null;
|
|
13288
|
+
}
|
|
13289
|
+
for (const candidate of signal.candidates) {
|
|
13290
|
+
if (!isValidSignalCandidate(qualifiedId2, candidate, emitter)) return null;
|
|
13291
|
+
}
|
|
13292
|
+
return signal;
|
|
13293
|
+
}
|
|
13294
|
+
function isValidSignalCandidate(qualifiedId2, candidate, emitter) {
|
|
13295
|
+
if (!KNOWN_LINK_KINDS.includes(candidate.kind)) {
|
|
13296
|
+
emitter.emit(
|
|
13297
|
+
makeEvent("extension.error", {
|
|
13298
|
+
kind: "signal-candidate-kind-not-declared",
|
|
13299
|
+
extensionId: qualifiedId2,
|
|
13300
|
+
candidateKind: candidate.kind,
|
|
13301
|
+
declaredKinds: KNOWN_LINK_KINDS,
|
|
13302
|
+
message: `Extractor ${qualifiedId2} emitted a Signal candidate with off-enum kind '${String(candidate.kind)}'; dropped.`
|
|
13303
|
+
})
|
|
13304
|
+
);
|
|
13305
|
+
return false;
|
|
13306
|
+
}
|
|
13307
|
+
const c = candidate.confidence;
|
|
13308
|
+
if (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1) {
|
|
13309
|
+
emitter.emit(
|
|
13310
|
+
makeEvent("extension.error", {
|
|
13311
|
+
kind: "signal-candidate-confidence-out-of-range",
|
|
13312
|
+
extensionId: qualifiedId2,
|
|
13313
|
+
confidence: candidate.confidence,
|
|
13314
|
+
message: `Extractor ${qualifiedId2} emitted a Signal candidate with confidence ${String(c)} outside [0..1]; dropped.`
|
|
13315
|
+
})
|
|
13316
|
+
);
|
|
13317
|
+
return false;
|
|
13318
|
+
}
|
|
13319
|
+
return true;
|
|
13320
|
+
}
|
|
12909
13321
|
function dedupeLinks(links) {
|
|
12910
13322
|
const out = /* @__PURE__ */ new Map();
|
|
12911
13323
|
for (const link2 of links) {
|
|
@@ -12952,7 +13364,9 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
|
12952
13364
|
}
|
|
12953
13365
|
}
|
|
12954
13366
|
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
13367
|
+
var VIRTUAL_NODE_SCHEME_RE = /^mcp:\/\//i;
|
|
12955
13368
|
function isExternalUrlLink(link2) {
|
|
13369
|
+
if (VIRTUAL_NODE_SCHEME_RE.test(link2.target)) return false;
|
|
12956
13370
|
return EXTERNAL_URL_SCHEME_RE.test(link2.target);
|
|
12957
13371
|
}
|
|
12958
13372
|
|
|
@@ -13111,13 +13525,9 @@ function originatingNodeOf(link2, priorNodePaths) {
|
|
|
13111
13525
|
}
|
|
13112
13526
|
function computeCacheDecision(opts) {
|
|
13113
13527
|
const applicableExtractors = opts.extractors.filter((ex) => {
|
|
13114
|
-
|
|
13115
|
-
if (!
|
|
13116
|
-
return
|
|
13117
|
-
const slashIdx = qualified.indexOf("/");
|
|
13118
|
-
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
13119
|
-
return kindOnly === opts.kind;
|
|
13120
|
-
});
|
|
13528
|
+
if (!matchesKindPrecondition(ex, opts.kind)) return false;
|
|
13529
|
+
if (!matchesProviderPrecondition(ex, opts.provider)) return false;
|
|
13530
|
+
return true;
|
|
13121
13531
|
});
|
|
13122
13532
|
const applicableQualifiedIds = new Set(
|
|
13123
13533
|
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
@@ -13131,6 +13541,20 @@ function computeCacheDecision(opts) {
|
|
|
13131
13541
|
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
13132
13542
|
};
|
|
13133
13543
|
}
|
|
13544
|
+
function matchesKindPrecondition(ex, kind) {
|
|
13545
|
+
const kinds = ex.precondition?.kind;
|
|
13546
|
+
if (!kinds || kinds.length === 0) return true;
|
|
13547
|
+
return kinds.some((qualified) => {
|
|
13548
|
+
const slashIdx = qualified.indexOf("/");
|
|
13549
|
+
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
13550
|
+
return kindOnly === kind;
|
|
13551
|
+
});
|
|
13552
|
+
}
|
|
13553
|
+
function matchesProviderPrecondition(ex, provider) {
|
|
13554
|
+
const providers = ex.precondition?.provider;
|
|
13555
|
+
if (!providers || providers.length === 0) return true;
|
|
13556
|
+
return providers.includes(provider);
|
|
13557
|
+
}
|
|
13134
13558
|
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
13135
13559
|
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
13136
13560
|
const missingExtractors = [];
|
|
@@ -13240,7 +13664,7 @@ function findHighConfidenceRenames(opts) {
|
|
|
13240
13664
|
if (opts.claimedNew.has(toPath)) continue;
|
|
13241
13665
|
const toNode = opts.currentByPath.get(toPath);
|
|
13242
13666
|
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
13243
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
13667
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.HIGH });
|
|
13244
13668
|
opts.claimedDeleted.add(fromPath);
|
|
13245
13669
|
opts.claimedNew.add(toPath);
|
|
13246
13670
|
break;
|
|
@@ -13275,13 +13699,13 @@ function claimSingletonRenames(opts) {
|
|
|
13275
13699
|
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
13276
13700
|
if (remaining.length === 1) {
|
|
13277
13701
|
const fromPath = remaining[0];
|
|
13278
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
13702
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM });
|
|
13279
13703
|
opts.issues.push({
|
|
13280
13704
|
analyzerId: "auto-rename-medium",
|
|
13281
13705
|
severity: "warn",
|
|
13282
13706
|
nodeIds: [toPath],
|
|
13283
13707
|
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
13284
|
-
data: { from: fromPath, to: toPath, confidence:
|
|
13708
|
+
data: { from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM }
|
|
13285
13709
|
});
|
|
13286
13710
|
opts.claimedDeleted.add(fromPath);
|
|
13287
13711
|
opts.claimedNew.add(toPath);
|
|
@@ -13719,6 +14143,7 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
|
|
|
13719
14143
|
const cacheDecision = computeCacheDecision({
|
|
13720
14144
|
extractors: wctx.opts.extractors,
|
|
13721
14145
|
kind,
|
|
14146
|
+
provider: provider.id,
|
|
13722
14147
|
nodePath: raw.path,
|
|
13723
14148
|
bodyHash,
|
|
13724
14149
|
sidecarAnnotationsHash,
|
|
@@ -13821,6 +14246,10 @@ function mergeExtractResult(extractResult, accum) {
|
|
|
13821
14246
|
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
13822
14247
|
}
|
|
13823
14248
|
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
14249
|
+
for (const vn of extractResult.virtualNodes) {
|
|
14250
|
+
if (accum.nodes.some((n) => n.path === vn.path)) continue;
|
|
14251
|
+
accum.nodes.push(vn);
|
|
14252
|
+
}
|
|
13824
14253
|
}
|
|
13825
14254
|
function isPartialCacheHit(ctx) {
|
|
13826
14255
|
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|
|
@@ -14345,13 +14774,7 @@ var SCAN_RUNNER_TEXTS = {
|
|
|
14345
14774
|
* DB-resident prior `ScanResult` fails `scan-result.schema.json`
|
|
14346
14775
|
* validation.
|
|
14347
14776
|
*/
|
|
14348
|
-
priorSchemaValidationFailed: "prior scan-result loaded from DB failed schema validation: {{errors}}. Run `sm db backup` then re-scan without --strict to rebuild from disk.",
|
|
14349
|
-
/**
|
|
14350
|
-
* Honest disclosure when the scan surface expanded beyond the cwd
|
|
14351
|
-
* via `scan.extraFolders`. The list of paths makes it obvious which
|
|
14352
|
-
* extra folders the operator just opted into.
|
|
14353
|
-
*/
|
|
14354
|
-
includingExtraFoldersAdvisory: "Including extra folders: {{paths}}",
|
|
14777
|
+
priorSchemaValidationFailed: "prior scan-result loaded from DB failed schema validation: {{errors}}. Run `sm db backup` then re-scan without --strict to rebuild from disk.",
|
|
14355
14778
|
/**
|
|
14356
14779
|
* Reference-paths walker hit `REFERENCE_WALK_MAX_FILES` and stopped
|
|
14357
14780
|
* early. The set may be incomplete for link validation; `core/broken-ref`
|
|
@@ -14366,6 +14789,14 @@ var SCAN_RUNNER_TEXTS = {
|
|
|
14366
14789
|
referenceWalkMissingRoot: 'scan.referencePaths: configured path "{{path}}" does not exist; skipped.'
|
|
14367
14790
|
};
|
|
14368
14791
|
|
|
14792
|
+
// core/runtime/scan-roots.ts
|
|
14793
|
+
function resolveScanRoots(inputs) {
|
|
14794
|
+
if (inputs.positionalRoots.length > 0) {
|
|
14795
|
+
return inputs.positionalRoots.slice();
|
|
14796
|
+
}
|
|
14797
|
+
return ["."];
|
|
14798
|
+
}
|
|
14799
|
+
|
|
14369
14800
|
// core/runtime/reference-paths-walker.ts
|
|
14370
14801
|
import { readdirSync as readdirSync9, statSync as statSync9 } from "fs";
|
|
14371
14802
|
import { homedir as osHomedir2 } from "os";
|
|
@@ -14427,26 +14858,6 @@ function safeStat(path) {
|
|
|
14427
14858
|
}
|
|
14428
14859
|
}
|
|
14429
14860
|
|
|
14430
|
-
// core/runtime/scan-roots.ts
|
|
14431
|
-
function resolveScanRoots(inputs) {
|
|
14432
|
-
if (inputs.positionalRoots.length > 0) {
|
|
14433
|
-
return {
|
|
14434
|
-
roots: inputs.positionalRoots.slice(),
|
|
14435
|
-
fromExtra: []
|
|
14436
|
-
};
|
|
14437
|
-
}
|
|
14438
|
-
const cwdRoot = ".";
|
|
14439
|
-
const extra = inputs.extraFolders.map((r) => resolveScanPath(r, inputs.cwd));
|
|
14440
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14441
|
-
const out = [cwdRoot];
|
|
14442
|
-
for (const root of extra) {
|
|
14443
|
-
if (seen.has(root)) continue;
|
|
14444
|
-
seen.add(root);
|
|
14445
|
-
out.push(root);
|
|
14446
|
-
}
|
|
14447
|
-
return { roots: out, fromExtra: extra };
|
|
14448
|
-
}
|
|
14449
|
-
|
|
14450
14861
|
// core/runtime/scan-runner.ts
|
|
14451
14862
|
async function runScanForCommand(opts) {
|
|
14452
14863
|
const ctx = opts.ctx ?? defaultRuntimeContext();
|
|
@@ -14464,13 +14875,7 @@ async function runScanForCommand(opts) {
|
|
|
14464
14875
|
const strict = opts.strict || cfg.scan.strict === true;
|
|
14465
14876
|
let effectiveRoots;
|
|
14466
14877
|
try {
|
|
14467
|
-
|
|
14468
|
-
positionalRoots: opts.roots,
|
|
14469
|
-
cwd: ctx.cwd,
|
|
14470
|
-
extraFolders: cfg.scan.extraFolders
|
|
14471
|
-
});
|
|
14472
|
-
effectiveRoots = resolution.roots;
|
|
14473
|
-
emitRootsAdvisory(resolution.fromExtra, opts);
|
|
14878
|
+
effectiveRoots = resolveScanRoots({ positionalRoots: opts.roots });
|
|
14474
14879
|
} catch (err) {
|
|
14475
14880
|
return { kind: "config-error", message: formatErrorMessage(err) };
|
|
14476
14881
|
}
|
|
@@ -14495,12 +14900,6 @@ async function runScanForCommand(opts) {
|
|
|
14495
14900
|
const willPersist = !opts.noBuiltIns && !opts.dryRun;
|
|
14496
14901
|
return willPersist ? runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith);
|
|
14497
14902
|
}
|
|
14498
|
-
function emitRootsAdvisory(fromExtra, opts) {
|
|
14499
|
-
if (fromExtra.length === 0) return;
|
|
14500
|
-
opts.printer.info(
|
|
14501
|
-
tx(SCAN_RUNNER_TEXTS.includingExtraFoldersAdvisory, { paths: fromExtra.join(", ") }) + "\n"
|
|
14502
|
-
);
|
|
14503
|
-
}
|
|
14504
14903
|
function emitReferenceWalkAdvisory(walk2, opts) {
|
|
14505
14904
|
if (walk2.truncated) {
|
|
14506
14905
|
opts.printer.warn(SCAN_RUNNER_TEXTS.referenceWalkTruncated);
|
|
@@ -18518,6 +18917,13 @@ function createWatcherRuntime(opts) {
|
|
|
18518
18917
|
strict,
|
|
18519
18918
|
emitter
|
|
18520
18919
|
};
|
|
18920
|
+
if (cfg.scan.referencePaths.length > 0) {
|
|
18921
|
+
const walk2 = walkReferencePaths(cfg.scan.referencePaths, cwd);
|
|
18922
|
+
if (walk2.paths.size > 0) {
|
|
18923
|
+
runOptions.referenceablePaths = walk2.paths;
|
|
18924
|
+
runOptions.cwd = cwd;
|
|
18925
|
+
}
|
|
18926
|
+
}
|
|
18521
18927
|
if (composed) runOptions.extensions = composed;
|
|
18522
18928
|
if (priorState) {
|
|
18523
18929
|
runOptions.priorSnapshot = priorState.snapshot;
|
|
@@ -18952,12 +19358,11 @@ var ScanCommand = class extends SmCommand {
|
|
|
18952
19358
|
the prior snapshot from the DB, reuse unchanged nodes, and only
|
|
18953
19359
|
reprocess new / modified files.
|
|
18954
19360
|
|
|
18955
|
-
Scans honour scan.
|
|
18956
|
-
only
|
|
18957
|
-
(walk the configured dirs for link-validation only; files there
|
|
18958
|
-
are not indexed). Both are
|
|
19361
|
+
Scans honour scan.referencePaths (walk the configured dirs for
|
|
19362
|
+
link-validation only; files there are not indexed). The key is
|
|
18959
19363
|
privacy-sensitive; see "sm config set --help" for the --yes
|
|
18960
|
-
gate.
|
|
19364
|
+
gate. To extend the indexed scan beyond cwd, pass extra roots
|
|
19365
|
+
positionally.
|
|
18961
19366
|
`,
|
|
18962
19367
|
examples: [
|
|
18963
19368
|
["Scan the current directory", "$0 scan"],
|
|
@@ -19418,7 +19823,7 @@ function renderDeltaIssues(issues) {
|
|
|
19418
19823
|
|
|
19419
19824
|
// cli/commands/serve.ts
|
|
19420
19825
|
import { spawn as spawn2 } from "child_process";
|
|
19421
|
-
import { existsSync as
|
|
19826
|
+
import { existsSync as existsSync30 } from "fs";
|
|
19422
19827
|
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
19423
19828
|
|
|
19424
19829
|
// cli/util/browser-launch.ts
|
|
@@ -19440,7 +19845,7 @@ import { WebSocketServer } from "ws";
|
|
|
19440
19845
|
// server/app.ts
|
|
19441
19846
|
import { Hono } from "hono";
|
|
19442
19847
|
import { bodyLimit } from "hono/body-limit";
|
|
19443
|
-
import { HTTPException as
|
|
19848
|
+
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
19444
19849
|
|
|
19445
19850
|
// core/config/service.ts
|
|
19446
19851
|
var ConfigService = class {
|
|
@@ -19682,13 +20087,60 @@ var SERVER_TEXTS = {
|
|
|
19682
20087
|
// silently widen the scan surface.
|
|
19683
20088
|
projectPrefsBodyNotJson: "Request body must be valid JSON.",
|
|
19684
20089
|
projectPrefsBodyNotObject: "Request body must be a JSON object.",
|
|
19685
|
-
projectPrefsBodyEmpty: "Request body must contain a `scan` block with
|
|
20090
|
+
projectPrefsBodyEmpty: "Request body must contain a `scan` block with `referencePaths`.",
|
|
19686
20091
|
projectPrefsConfirmNotBoolean: "`confirm` must be a boolean.",
|
|
19687
|
-
projectPrefsScanNotObject: '`scan` must be an object (e.g. `{"scan": {"
|
|
20092
|
+
projectPrefsScanNotObject: '`scan` must be an object (e.g. `{"scan": {"referencePaths": ["~/Documents"]}}`).',
|
|
19688
20093
|
projectPrefsListNotArray: "`{{key}}` must be an array of strings.",
|
|
19689
20094
|
projectPrefsListEntryNotString: "`{{key}}` entries must be strings.",
|
|
19690
20095
|
projectPrefsConfirmRequired: "This change opens disk access outside the project: {{paths}}. Re-issue the request with `confirm: true` to proceed.",
|
|
19691
20096
|
projectPrefsPersistFailed: "Could not persist `{{key}}`: {{message}}",
|
|
20097
|
+
// Returned for every NEW entry that does not resolve to an existing
|
|
20098
|
+
// directory on disk. The list is comma-separated; pre-existing
|
|
20099
|
+
// entries are not re-validated.
|
|
20100
|
+
projectPrefsPathNotFound: "These folders do not exist on disk: {{paths}}. Add only paths that already exist.",
|
|
20101
|
+
// AJV `pattern` violation, an entry contains a comma. The UI rejects
|
|
20102
|
+
// comma input client-side; this message is the server-side safety
|
|
20103
|
+
// net (defense in depth).
|
|
20104
|
+
projectPrefsEntryHasComma: "Folder entries must not contain commas. Add one folder per entry.",
|
|
20105
|
+
// Server-stderr advisories emitted by `PATCH /api/project-preferences`
|
|
20106
|
+
// after a successful write. The operator running `sm serve` sees
|
|
20107
|
+
// each add / remove on the console without opening the config file.
|
|
20108
|
+
// `{{detail}}` is composed in JS (see `formatPathDetail`) so the
|
|
20109
|
+
// single template covers all three path shapes (home / relative /
|
|
20110
|
+
// absolute) without a template explosion.
|
|
20111
|
+
projectPrefsPathAdded: "project-prefs: + {{key}} {{detail}}",
|
|
20112
|
+
projectPrefsPathRemoved: "project-prefs: - {{key}} {{detail}}",
|
|
20113
|
+
// `PATCH /api/project-preferences` mutated `scan.*` and the
|
|
20114
|
+
// post-write `watcherService.restart()` call threw. The on-disk
|
|
20115
|
+
// write itself succeeded; the operator sees this advisory and
|
|
20116
|
+
// restarts the server to pick up the new root list manually.
|
|
20117
|
+
projectPrefsWatcherRestartFailed: "project-prefs: watcher restart after scan-config write failed ({{message}}). Restart `sm serve` to pick up the new roots.",
|
|
20118
|
+
// ---- project-ignore route (routes/project-ignore.ts) -------------------
|
|
20119
|
+
//
|
|
20120
|
+
// GET / PATCH /api/project-ignore. Backing is the project-root
|
|
20121
|
+
// `.skillmapignore` file (gitignore-syntax). Comments and blank
|
|
20122
|
+
// lines are preserved on write; only the active pattern list is
|
|
20123
|
+
// exchanged over the wire. No privacy gate, the patterns narrow the
|
|
20124
|
+
// scan surface and never widen disk access.
|
|
20125
|
+
projectIgnoreBodyNotJson: "Request body must be valid JSON.",
|
|
20126
|
+
projectIgnoreBodyNotObject: "Request body must be a JSON object.",
|
|
20127
|
+
projectIgnoreBodyEmpty: "Request body must contain a `patterns` array.",
|
|
20128
|
+
projectIgnoreListNotArray: "`patterns` must be an array of strings.",
|
|
20129
|
+
projectIgnoreEntryNotString: "`patterns` entries must be strings.",
|
|
20130
|
+
// AJV `minLength: 1` on each pattern after the route trims server-side;
|
|
20131
|
+
// surfaces when the operator sends `" "` or an empty string.
|
|
20132
|
+
projectIgnorePatternEmpty: "Pattern entries must not be empty or whitespace-only.",
|
|
20133
|
+
// AJV `pattern` violation: a single pattern carried a newline,
|
|
20134
|
+
// carriage return, or other ASCII control character. The UI rejects
|
|
20135
|
+
// these client-side; this message is the server-side safety net.
|
|
20136
|
+
projectIgnorePatternHasControlChar: "Pattern entries must be a single line without control characters.",
|
|
20137
|
+
// Duplicate detection runs after trim; the UI rejects duplicates
|
|
20138
|
+
// client-side, this is the server-side safety net.
|
|
20139
|
+
projectIgnorePatternDuplicate: 'Duplicate pattern: "{{pattern}}". Each pattern must be unique.',
|
|
20140
|
+
projectIgnorePersistFailed: "Could not persist `.skillmapignore`: {{message}}",
|
|
20141
|
+
projectIgnorePatternAdded: "project-ignore: + {{pattern}}",
|
|
20142
|
+
projectIgnorePatternRemoved: "project-ignore: - {{pattern}}",
|
|
20143
|
+
projectIgnoreWatcherRestartFailed: "project-ignore: watcher restart after `.skillmapignore` write failed ({{message}}). Restart `sm serve` to pick up the new filter.",
|
|
19692
20144
|
// A connected client's outbound buffer exceeded the backpressure
|
|
19693
20145
|
// threshold. The broadcaster closes the client with code 1009 and
|
|
19694
20146
|
// unregisters it. Logged so operators can spot a wedged consumer.
|
|
@@ -19701,7 +20153,20 @@ var SERVER_TEXTS = {
|
|
|
19701
20153
|
// is a synthetic `emitter.error` event; v14.4.a does not yet route
|
|
19702
20154
|
// it through the broadcaster (would re-enter the same stringify
|
|
19703
20155
|
// path), so we degrade to a logged warning.
|
|
19704
|
-
wsBroadcastSerializeFailed: "skill-map server: ws broadcast dropped, failed to serialize event: {{message}}.\n"
|
|
20156
|
+
wsBroadcastSerializeFailed: "skill-map server: ws broadcast dropped, failed to serialize event: {{message}}.\n",
|
|
20157
|
+
// ---- active-provider route (routes/active-provider.ts) -----------
|
|
20158
|
+
//
|
|
20159
|
+
// GET / PUT /api/active-provider. The active provider lens selects
|
|
20160
|
+
// which provider's extractors / classifiers / resolution rules apply
|
|
20161
|
+
// to the whole project (see `architecture.md` §Active Provider Lens).
|
|
20162
|
+
// Changing the lens drops the `scan_*` zone atomically and prompts
|
|
20163
|
+
// the user to re-scan; `state_*` and `config_*` survive.
|
|
20164
|
+
activeProviderBodyNotJson: "Request body must be valid JSON.",
|
|
20165
|
+
activeProviderBodyNotObject: "Request body must be a JSON object.",
|
|
20166
|
+
activeProviderBodyMissing: "Request body must include `activeProvider` (a non-empty string).",
|
|
20167
|
+
activeProviderValueNotString: "`activeProvider` must be a string.",
|
|
20168
|
+
activeProviderValueEmpty: "`activeProvider` cannot be the empty string. Send the id of an enabled provider.",
|
|
20169
|
+
activeProviderPersistFailed: "Could not persist activeProvider: {{message}}"
|
|
19705
20170
|
};
|
|
19706
20171
|
|
|
19707
20172
|
// server/loopback-gate.ts
|
|
@@ -20588,11 +21053,6 @@ function registerPluginsRoute(app, deps) {
|
|
|
20588
21053
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
|
|
20589
21054
|
});
|
|
20590
21055
|
}
|
|
20591
|
-
if (granularityOf(handle) !== "extension") {
|
|
20592
|
-
throw new HTTPException8(400, {
|
|
20593
|
-
message: tx(SERVER_TEXTS.pluginsGranularityBundleExpected, { id: bundleId })
|
|
20594
|
-
});
|
|
20595
|
-
}
|
|
20596
21056
|
if (!hasExtension(handle, extensionId)) {
|
|
20597
21057
|
throw new HTTPException8(404, {
|
|
20598
21058
|
message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
|
|
@@ -20633,7 +21093,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20633
21093
|
return builtInBundles.map((bundle) => {
|
|
20634
21094
|
const bundleEnabled = resolveEnabled(bundle.id);
|
|
20635
21095
|
const bundleLocked = isPluginLocked(bundle.id);
|
|
20636
|
-
const extensions = bundle.
|
|
21096
|
+
const extensions = bundle.extensions.map((ext) => {
|
|
20637
21097
|
const qualified = qualifiedExtensionId(bundle.id, ext.id);
|
|
20638
21098
|
const extLocked = bundleLocked || isPluginLocked(qualified);
|
|
20639
21099
|
return {
|
|
@@ -20644,7 +21104,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20644
21104
|
...ext.description ? { description: ext.description } : {},
|
|
20645
21105
|
...extLocked ? { locked: true } : {}
|
|
20646
21106
|
};
|
|
20647
|
-
})
|
|
21107
|
+
});
|
|
20648
21108
|
return {
|
|
20649
21109
|
id: bundle.id,
|
|
20650
21110
|
version: firstVersion(bundle.extensions),
|
|
@@ -20654,7 +21114,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20654
21114
|
source: "built-in",
|
|
20655
21115
|
granularity: bundle.granularity,
|
|
20656
21116
|
description: bundle.description,
|
|
20657
|
-
...extensions ? { extensions } : {},
|
|
21117
|
+
...extensions.length > 0 ? { extensions } : {},
|
|
20658
21118
|
...bundleLocked ? { locked: true } : {}
|
|
20659
21119
|
};
|
|
20660
21120
|
});
|
|
@@ -20687,8 +21147,8 @@ function optionalDiscoveredFields(plugin, extensions) {
|
|
|
20687
21147
|
if (extensions) out.extensions = extensions;
|
|
20688
21148
|
return out;
|
|
20689
21149
|
}
|
|
20690
|
-
function projectExtensionRows(plugin,
|
|
20691
|
-
if (
|
|
21150
|
+
function projectExtensionRows(plugin, _granularity, resolveEnabled, bundleLocked) {
|
|
21151
|
+
if (!plugin.extensions || plugin.extensions.length === 0) return void 0;
|
|
20692
21152
|
return plugin.extensions.map((ext) => {
|
|
20693
21153
|
const description = readInstanceDescription(ext.instance);
|
|
20694
21154
|
const qualified = qualifiedExtensionId(plugin.id, ext.id);
|
|
@@ -20805,13 +21265,6 @@ function validateBulkChange(change, deps) {
|
|
|
20805
21265
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
|
|
20806
21266
|
};
|
|
20807
21267
|
}
|
|
20808
|
-
if (granularityOf(handle) !== "extension") {
|
|
20809
|
-
return {
|
|
20810
|
-
status: 400,
|
|
20811
|
-
code: "bad-query",
|
|
20812
|
-
message: tx(SERVER_TEXTS.pluginsGranularityBundleExpected, { id: bundleId })
|
|
20813
|
-
};
|
|
20814
|
-
}
|
|
20815
21268
|
if (!hasExtension(handle, extensionId)) {
|
|
20816
21269
|
return {
|
|
20817
21270
|
status: 404,
|
|
@@ -20922,26 +21375,203 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
|
|
|
20922
21375
|
}
|
|
20923
21376
|
});
|
|
20924
21377
|
|
|
20925
|
-
// server/routes/project-
|
|
21378
|
+
// server/routes/project-ignore.ts
|
|
20926
21379
|
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
20927
|
-
|
|
20928
|
-
|
|
21380
|
+
|
|
21381
|
+
// server/util/skillmapignore-io.ts
|
|
21382
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "fs";
|
|
21383
|
+
import { resolve as resolve33 } from "path";
|
|
21384
|
+
var IGNORE_FILENAME2 = ".skillmapignore";
|
|
21385
|
+
function readPatterns(cwd) {
|
|
21386
|
+
const path = resolve33(cwd, IGNORE_FILENAME2);
|
|
21387
|
+
if (!existsSync25(path)) return [];
|
|
21388
|
+
let raw;
|
|
21389
|
+
try {
|
|
21390
|
+
raw = readFileSync19(path, "utf8");
|
|
21391
|
+
} catch {
|
|
21392
|
+
return [];
|
|
21393
|
+
}
|
|
21394
|
+
return raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
21395
|
+
}
|
|
21396
|
+
function writePatterns(cwd, nextPatterns) {
|
|
21397
|
+
const path = resolve33(cwd, IGNORE_FILENAME2);
|
|
21398
|
+
const prior = existsSync25(path) ? safeRead(path) : "";
|
|
21399
|
+
const content = buildContent(prior, nextPatterns);
|
|
21400
|
+
writeFileSync4(path, content, "utf8");
|
|
21401
|
+
}
|
|
21402
|
+
function safeRead(path) {
|
|
21403
|
+
try {
|
|
21404
|
+
return readFileSync19(path, "utf8");
|
|
21405
|
+
} catch {
|
|
21406
|
+
return "";
|
|
21407
|
+
}
|
|
21408
|
+
}
|
|
21409
|
+
function buildContent(prior, nextPatterns) {
|
|
21410
|
+
const wanted = new Set(nextPatterns);
|
|
21411
|
+
const kept = /* @__PURE__ */ new Set();
|
|
21412
|
+
const outLines = [];
|
|
21413
|
+
for (const line of splitLines(prior)) {
|
|
21414
|
+
pushPriorLine(line, wanted, kept, outLines);
|
|
21415
|
+
}
|
|
21416
|
+
appendNewPatterns(nextPatterns, kept, outLines);
|
|
21417
|
+
return outLines.length === 0 ? "" : outLines.join("\n") + "\n";
|
|
21418
|
+
}
|
|
21419
|
+
function splitLines(prior) {
|
|
21420
|
+
if (prior.length === 0) return [];
|
|
21421
|
+
const lines = prior.split(/\r?\n/);
|
|
21422
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
21423
|
+
return lines;
|
|
21424
|
+
}
|
|
21425
|
+
function pushPriorLine(line, wanted, kept, outLines) {
|
|
21426
|
+
const trimmed = line.trim();
|
|
21427
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) {
|
|
21428
|
+
outLines.push(line);
|
|
21429
|
+
return;
|
|
21430
|
+
}
|
|
21431
|
+
if (wanted.has(trimmed)) {
|
|
21432
|
+
outLines.push(trimmed);
|
|
21433
|
+
kept.add(trimmed);
|
|
21434
|
+
}
|
|
21435
|
+
}
|
|
21436
|
+
function appendNewPatterns(nextPatterns, kept, outLines) {
|
|
21437
|
+
for (const p of nextPatterns) {
|
|
21438
|
+
if (kept.has(p)) continue;
|
|
21439
|
+
outLines.push(p);
|
|
21440
|
+
kept.add(p);
|
|
21441
|
+
}
|
|
21442
|
+
}
|
|
21443
|
+
|
|
21444
|
+
// server/routes/project-ignore.ts
|
|
21445
|
+
function registerProjectIgnoreRoute(app, deps) {
|
|
21446
|
+
app.get("/api/project-ignore", (c) => {
|
|
20929
21447
|
return c.json(buildEnvelope2(deps));
|
|
20930
21448
|
});
|
|
20931
|
-
app.patch("/api/project-
|
|
21449
|
+
app.patch("/api/project-ignore", async (c) => {
|
|
20932
21450
|
const body = await parsePatchBody3(c.req.raw);
|
|
20933
|
-
applyPatch2(deps, body);
|
|
21451
|
+
await applyPatch2(deps, body);
|
|
20934
21452
|
return c.json(buildEnvelope2(deps));
|
|
20935
21453
|
});
|
|
20936
21454
|
}
|
|
20937
21455
|
function buildEnvelope2(deps) {
|
|
21456
|
+
const cwd = deps.runtimeContext.cwd;
|
|
21457
|
+
return { patterns: readPatterns(cwd) };
|
|
21458
|
+
}
|
|
21459
|
+
async function applyPatch2(deps, body) {
|
|
21460
|
+
const cwd = deps.runtimeContext.cwd;
|
|
21461
|
+
const trimmed = [];
|
|
21462
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21463
|
+
for (const raw of body.patterns) {
|
|
21464
|
+
const t = raw.trim();
|
|
21465
|
+
if (t.length === 0) {
|
|
21466
|
+
throw new HTTPException10(400, {
|
|
21467
|
+
message: SERVER_TEXTS.projectIgnorePatternEmpty
|
|
21468
|
+
});
|
|
21469
|
+
}
|
|
21470
|
+
if (seen.has(t)) {
|
|
21471
|
+
throw new HTTPException10(400, {
|
|
21472
|
+
message: tx(SERVER_TEXTS.projectIgnorePatternDuplicate, { pattern: t })
|
|
21473
|
+
});
|
|
21474
|
+
}
|
|
21475
|
+
seen.add(t);
|
|
21476
|
+
trimmed.push(t);
|
|
21477
|
+
}
|
|
21478
|
+
const before = readPatterns(cwd);
|
|
21479
|
+
try {
|
|
21480
|
+
writePatterns(cwd, trimmed);
|
|
21481
|
+
} catch (err) {
|
|
21482
|
+
throw new HTTPException10(400, {
|
|
21483
|
+
message: tx(SERVER_TEXTS.projectIgnorePersistFailed, {
|
|
21484
|
+
message: formatErrorMessage(err)
|
|
21485
|
+
})
|
|
21486
|
+
});
|
|
21487
|
+
}
|
|
21488
|
+
logPatternChanges(before, trimmed);
|
|
21489
|
+
if (arrayChanged(before, trimmed)) await maybeRestartWatcher(deps);
|
|
21490
|
+
}
|
|
21491
|
+
function arrayChanged(before, next) {
|
|
21492
|
+
if (before.length !== next.length) return true;
|
|
21493
|
+
const beforeSet = new Set(before);
|
|
21494
|
+
for (const p of next) {
|
|
21495
|
+
if (!beforeSet.has(p)) return true;
|
|
21496
|
+
}
|
|
21497
|
+
return false;
|
|
21498
|
+
}
|
|
21499
|
+
function logPatternChanges(before, next) {
|
|
21500
|
+
const beforeSet = new Set(before);
|
|
21501
|
+
const nextSet = new Set(next);
|
|
21502
|
+
for (const p of next) {
|
|
21503
|
+
if (beforeSet.has(p)) continue;
|
|
21504
|
+
log.warn(
|
|
21505
|
+
tx(SERVER_TEXTS.projectIgnorePatternAdded, {
|
|
21506
|
+
pattern: sanitizeForTerminal(p)
|
|
21507
|
+
})
|
|
21508
|
+
);
|
|
21509
|
+
}
|
|
21510
|
+
for (const p of before) {
|
|
21511
|
+
if (nextSet.has(p)) continue;
|
|
21512
|
+
log.warn(
|
|
21513
|
+
tx(SERVER_TEXTS.projectIgnorePatternRemoved, {
|
|
21514
|
+
pattern: sanitizeForTerminal(p)
|
|
21515
|
+
})
|
|
21516
|
+
);
|
|
21517
|
+
}
|
|
21518
|
+
}
|
|
21519
|
+
async function maybeRestartWatcher(deps) {
|
|
21520
|
+
const watcher = deps.watcherHolder.current;
|
|
21521
|
+
if (!watcher) return;
|
|
21522
|
+
try {
|
|
21523
|
+
await watcher.restart();
|
|
21524
|
+
} catch (err) {
|
|
21525
|
+
log.warn(
|
|
21526
|
+
tx(SERVER_TEXTS.projectIgnoreWatcherRestartFailed, {
|
|
21527
|
+
message: formatErrorMessage(err)
|
|
21528
|
+
})
|
|
21529
|
+
);
|
|
21530
|
+
}
|
|
21531
|
+
}
|
|
21532
|
+
var PATCH_BODY_SCHEMA2 = {
|
|
21533
|
+
type: "object",
|
|
21534
|
+
additionalProperties: false,
|
|
21535
|
+
required: ["patterns"],
|
|
21536
|
+
properties: {
|
|
21537
|
+
patterns: {
|
|
21538
|
+
type: "array",
|
|
21539
|
+
items: {
|
|
21540
|
+
type: "string",
|
|
21541
|
+
pattern: "^[^\\n\\r\\x00-\\x1F\\x7F]+$"
|
|
21542
|
+
}
|
|
21543
|
+
}
|
|
21544
|
+
}
|
|
21545
|
+
};
|
|
21546
|
+
var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
|
|
21547
|
+
notJson: SERVER_TEXTS.projectIgnoreBodyNotJson,
|
|
21548
|
+
notObject: SERVER_TEXTS.projectIgnoreBodyNotObject,
|
|
21549
|
+
invalid: SERVER_TEXTS.projectIgnoreBodyEmpty,
|
|
21550
|
+
mapping: {
|
|
21551
|
+
"/patterns:required": SERVER_TEXTS.projectIgnoreBodyEmpty,
|
|
21552
|
+
"/patterns:type:array": SERVER_TEXTS.projectIgnoreListNotArray,
|
|
21553
|
+
"/patterns/*:type:string": SERVER_TEXTS.projectIgnoreEntryNotString,
|
|
21554
|
+
"/patterns/*:pattern": SERVER_TEXTS.projectIgnorePatternHasControlChar
|
|
21555
|
+
}
|
|
21556
|
+
});
|
|
21557
|
+
|
|
21558
|
+
// server/routes/project-preferences.ts
|
|
21559
|
+
import { statSync as statSync10 } from "fs";
|
|
21560
|
+
import { HTTPException as HTTPException11 } from "hono/http-exception";
|
|
21561
|
+
function registerProjectPreferencesRoute(app, deps) {
|
|
21562
|
+
app.get("/api/project-preferences", (c) => {
|
|
21563
|
+
return c.json(buildEnvelope3(deps));
|
|
21564
|
+
});
|
|
21565
|
+
app.patch("/api/project-preferences", async (c) => {
|
|
21566
|
+
const body = await parsePatchBody4(c.req.raw);
|
|
21567
|
+
await applyPatch3(deps, body);
|
|
21568
|
+
return c.json(buildEnvelope3(deps));
|
|
21569
|
+
});
|
|
21570
|
+
}
|
|
21571
|
+
function buildEnvelope3(deps) {
|
|
20938
21572
|
const cwd = deps.runtimeContext.cwd;
|
|
20939
21573
|
return {
|
|
20940
21574
|
scan: {
|
|
20941
|
-
extraFolders: readConfigValue("scan.extraFolders", {
|
|
20942
|
-
cwd,
|
|
20943
|
-
default: []
|
|
20944
|
-
}) ?? [],
|
|
20945
21575
|
referencePaths: readConfigValue("scan.referencePaths", {
|
|
20946
21576
|
cwd,
|
|
20947
21577
|
default: []
|
|
@@ -20949,46 +21579,138 @@ function buildEnvelope2(deps) {
|
|
|
20949
21579
|
}
|
|
20950
21580
|
};
|
|
20951
21581
|
}
|
|
20952
|
-
function
|
|
21582
|
+
async function applyPatch3(deps, body) {
|
|
20953
21583
|
const writes = collectWrites(body);
|
|
20954
21584
|
if (writes.length === 0) return;
|
|
20955
21585
|
const cwd = deps.runtimeContext.cwd;
|
|
21586
|
+
const missingPaths = collectMissingPaths(writes, cwd);
|
|
21587
|
+
if (missingPaths.length > 0) {
|
|
21588
|
+
throw new HTTPException11(400, {
|
|
21589
|
+
message: tx(SERVER_TEXTS.projectPrefsPathNotFound, {
|
|
21590
|
+
paths: missingPaths.join(", ")
|
|
21591
|
+
})
|
|
21592
|
+
});
|
|
21593
|
+
}
|
|
20956
21594
|
const exposures = writes.map((w) => projectPathExposure({ key: w.key, value: w.value, cwd })).filter((e) => e.expandsSurface);
|
|
20957
21595
|
if (exposures.length > 0 && body.confirm !== true) {
|
|
20958
21596
|
const exposed = exposures.flatMap((e) => e.exposedPaths);
|
|
20959
|
-
throw new
|
|
21597
|
+
throw new HTTPException11(412, {
|
|
20960
21598
|
message: tx(SERVER_TEXTS.projectPrefsConfirmRequired, {
|
|
20961
21599
|
paths: exposed.join(", ")
|
|
20962
21600
|
})
|
|
20963
21601
|
});
|
|
20964
21602
|
}
|
|
21603
|
+
let scanSurfaceMutated = false;
|
|
20965
21604
|
for (const w of writes) {
|
|
20966
|
-
|
|
20967
|
-
writeConfigValue(w.key, w.value, { target: "project-local", cwd });
|
|
20968
|
-
} catch (err) {
|
|
20969
|
-
const status = err instanceof ConfigValidationError ? 400 : 400;
|
|
20970
|
-
throw new HTTPException10(status, {
|
|
20971
|
-
message: tx(SERVER_TEXTS.projectPrefsPersistFailed, {
|
|
20972
|
-
key: w.key,
|
|
20973
|
-
message: formatErrorMessage(err)
|
|
20974
|
-
})
|
|
20975
|
-
});
|
|
20976
|
-
}
|
|
21605
|
+
if (runWrite(w, cwd)) scanSurfaceMutated = true;
|
|
20977
21606
|
}
|
|
21607
|
+
if (scanSurfaceMutated) await maybeRestartWatcher2(deps);
|
|
20978
21608
|
deps.configService.reload();
|
|
20979
21609
|
}
|
|
20980
21610
|
function collectWrites(body) {
|
|
20981
21611
|
if (!body.scan) return [];
|
|
20982
21612
|
const out = [];
|
|
20983
|
-
if (Array.isArray(body.scan.extraFolders)) {
|
|
20984
|
-
out.push({ key: "scan.extraFolders", value: body.scan.extraFolders });
|
|
20985
|
-
}
|
|
20986
21613
|
if (Array.isArray(body.scan.referencePaths)) {
|
|
20987
21614
|
out.push({ key: "scan.referencePaths", value: body.scan.referencePaths });
|
|
20988
21615
|
}
|
|
20989
21616
|
return out;
|
|
20990
21617
|
}
|
|
20991
|
-
|
|
21618
|
+
function collectMissingPaths(writes, cwd) {
|
|
21619
|
+
const missing = [];
|
|
21620
|
+
for (const w of writes) {
|
|
21621
|
+
if (!Array.isArray(w.value)) continue;
|
|
21622
|
+
const current = readConfigValue(w.key, { cwd, default: [] }) ?? [];
|
|
21623
|
+
const currentSet = new Set(current);
|
|
21624
|
+
for (const entry of w.value) {
|
|
21625
|
+
if (currentSet.has(entry)) continue;
|
|
21626
|
+
if (!isExistingDirectory(entry, cwd)) missing.push(entry);
|
|
21627
|
+
}
|
|
21628
|
+
}
|
|
21629
|
+
return missing;
|
|
21630
|
+
}
|
|
21631
|
+
async function maybeRestartWatcher2(deps) {
|
|
21632
|
+
const watcher = deps.watcherHolder.current;
|
|
21633
|
+
if (!watcher) return;
|
|
21634
|
+
try {
|
|
21635
|
+
await watcher.restart();
|
|
21636
|
+
} catch (err) {
|
|
21637
|
+
log.warn(
|
|
21638
|
+
tx(SERVER_TEXTS.projectPrefsWatcherRestartFailed, {
|
|
21639
|
+
message: formatErrorMessage(err)
|
|
21640
|
+
})
|
|
21641
|
+
);
|
|
21642
|
+
}
|
|
21643
|
+
}
|
|
21644
|
+
function runWrite(w, cwd) {
|
|
21645
|
+
const before = readConfigValue(w.key, { cwd, default: [] }) ?? [];
|
|
21646
|
+
try {
|
|
21647
|
+
writeConfigValue(w.key, w.value, { target: "project-local", cwd });
|
|
21648
|
+
} catch (err) {
|
|
21649
|
+
throw new HTTPException11(400, {
|
|
21650
|
+
message: tx(SERVER_TEXTS.projectPrefsPersistFailed, {
|
|
21651
|
+
key: w.key,
|
|
21652
|
+
message: formatErrorMessage(err)
|
|
21653
|
+
})
|
|
21654
|
+
});
|
|
21655
|
+
}
|
|
21656
|
+
logPathChanges(w.key, before, w.value, cwd);
|
|
21657
|
+
return arrayChanged2(before, w.value);
|
|
21658
|
+
}
|
|
21659
|
+
function arrayChanged2(before, nextValue) {
|
|
21660
|
+
if (!Array.isArray(nextValue)) return false;
|
|
21661
|
+
const next = nextValue;
|
|
21662
|
+
if (before.length !== next.length) return true;
|
|
21663
|
+
const beforeSet = new Set(before);
|
|
21664
|
+
for (const p of next) {
|
|
21665
|
+
if (!beforeSet.has(p)) return true;
|
|
21666
|
+
}
|
|
21667
|
+
return false;
|
|
21668
|
+
}
|
|
21669
|
+
function logPathChanges(key, before, nextValue, cwd) {
|
|
21670
|
+
if (!Array.isArray(nextValue)) return;
|
|
21671
|
+
const next = nextValue;
|
|
21672
|
+
const beforeSet = new Set(before);
|
|
21673
|
+
const nextSet = new Set(next);
|
|
21674
|
+
for (const path of next) {
|
|
21675
|
+
if (beforeSet.has(path)) continue;
|
|
21676
|
+
log.warn(
|
|
21677
|
+
tx(SERVER_TEXTS.projectPrefsPathAdded, {
|
|
21678
|
+
key,
|
|
21679
|
+
detail: formatPathDetail(path, cwd)
|
|
21680
|
+
})
|
|
21681
|
+
);
|
|
21682
|
+
}
|
|
21683
|
+
for (const path of before) {
|
|
21684
|
+
if (nextSet.has(path)) continue;
|
|
21685
|
+
log.warn(
|
|
21686
|
+
tx(SERVER_TEXTS.projectPrefsPathRemoved, {
|
|
21687
|
+
key,
|
|
21688
|
+
detail: formatPathDetail(path, cwd)
|
|
21689
|
+
})
|
|
21690
|
+
);
|
|
21691
|
+
}
|
|
21692
|
+
}
|
|
21693
|
+
function formatPathDetail(path, cwd) {
|
|
21694
|
+
const safePath = sanitizeForTerminal(path);
|
|
21695
|
+
if (path.startsWith("~/") || path === "~") {
|
|
21696
|
+
const abs2 = sanitizeForTerminal(resolveScanPath(path, cwd));
|
|
21697
|
+
return `${safePath} (home) \u2192 ${abs2}`;
|
|
21698
|
+
}
|
|
21699
|
+
if (path.startsWith("/")) {
|
|
21700
|
+
return `${safePath} (absolute)`;
|
|
21701
|
+
}
|
|
21702
|
+
const abs = sanitizeForTerminal(resolveScanPath(path, cwd));
|
|
21703
|
+
return `${safePath} (relative) \u2192 ${abs}`;
|
|
21704
|
+
}
|
|
21705
|
+
function isExistingDirectory(entry, cwd) {
|
|
21706
|
+
const abs = resolveScanPath(entry, cwd);
|
|
21707
|
+
try {
|
|
21708
|
+
return statSync10(abs).isDirectory();
|
|
21709
|
+
} catch {
|
|
21710
|
+
return false;
|
|
21711
|
+
}
|
|
21712
|
+
}
|
|
21713
|
+
var PATCH_BODY_SCHEMA3 = {
|
|
20992
21714
|
type: "object",
|
|
20993
21715
|
additionalProperties: false,
|
|
20994
21716
|
required: ["scan"],
|
|
@@ -20999,13 +21721,15 @@ var PATCH_BODY_SCHEMA2 = {
|
|
|
20999
21721
|
additionalProperties: false,
|
|
21000
21722
|
minProperties: 1,
|
|
21001
21723
|
properties: {
|
|
21002
|
-
|
|
21003
|
-
|
|
21724
|
+
referencePaths: {
|
|
21725
|
+
type: "array",
|
|
21726
|
+
items: { type: "string", pattern: "^[^,]+$" }
|
|
21727
|
+
}
|
|
21004
21728
|
}
|
|
21005
21729
|
}
|
|
21006
21730
|
}
|
|
21007
21731
|
};
|
|
21008
|
-
var
|
|
21732
|
+
var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
|
|
21009
21733
|
notJson: SERVER_TEXTS.projectPrefsBodyNotJson,
|
|
21010
21734
|
notObject: SERVER_TEXTS.projectPrefsBodyNotObject,
|
|
21011
21735
|
invalid: SERVER_TEXTS.projectPrefsBodyEmpty,
|
|
@@ -21014,15 +21738,111 @@ var parsePatchBody3 = makeBodyValidator(PATCH_BODY_SCHEMA2, {
|
|
|
21014
21738
|
"/scan:minProperties": SERVER_TEXTS.projectPrefsBodyEmpty,
|
|
21015
21739
|
"/scan:type:object": SERVER_TEXTS.projectPrefsScanNotObject,
|
|
21016
21740
|
"/confirm:type:boolean": SERVER_TEXTS.projectPrefsConfirmNotBoolean,
|
|
21017
|
-
"/scan/extraFolders:type:array": tx(SERVER_TEXTS.projectPrefsListNotArray, { key: "scan.extraFolders" }),
|
|
21018
21741
|
"/scan/referencePaths:type:array": tx(SERVER_TEXTS.projectPrefsListNotArray, { key: "scan.referencePaths" }),
|
|
21019
|
-
"/scan/
|
|
21020
|
-
"/scan/referencePaths/*:
|
|
21742
|
+
"/scan/referencePaths/*:type:string": tx(SERVER_TEXTS.projectPrefsListEntryNotString, { key: "scan.referencePaths" }),
|
|
21743
|
+
"/scan/referencePaths/*:pattern": SERVER_TEXTS.projectPrefsEntryHasComma
|
|
21744
|
+
}
|
|
21745
|
+
});
|
|
21746
|
+
|
|
21747
|
+
// server/routes/active-provider.ts
|
|
21748
|
+
import { existsSync as existsSync27 } from "fs";
|
|
21749
|
+
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
21750
|
+
|
|
21751
|
+
// core/config/active-provider.ts
|
|
21752
|
+
import { existsSync as existsSync26 } from "fs";
|
|
21753
|
+
import { join as join17 } from "path";
|
|
21754
|
+
var DETECTION_RULES = [
|
|
21755
|
+
{ providerId: "claude", marker: ".claude" },
|
|
21756
|
+
{ providerId: "gemini", marker: ".gemini" },
|
|
21757
|
+
{ providerId: "openai", marker: ".codex" },
|
|
21758
|
+
{ providerId: "openai", marker: "AGENTS.md" },
|
|
21759
|
+
{ providerId: "cursor", marker: ".cursor" }
|
|
21760
|
+
];
|
|
21761
|
+
function resolveActiveProvider(cwd) {
|
|
21762
|
+
const detected = detectProvidersFromFilesystem(cwd);
|
|
21763
|
+
const fromConfig = readConfigValue("activeProvider", { cwd });
|
|
21764
|
+
if (typeof fromConfig === "string" && fromConfig.length > 0) {
|
|
21765
|
+
return { resolved: fromConfig, source: "config", detected };
|
|
21766
|
+
}
|
|
21767
|
+
if (detected.length > 0) {
|
|
21768
|
+
return { resolved: detected[0], source: "autodetect", detected };
|
|
21769
|
+
}
|
|
21770
|
+
return { resolved: null, source: "none", detected };
|
|
21771
|
+
}
|
|
21772
|
+
function detectProvidersFromFilesystem(cwd) {
|
|
21773
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21774
|
+
const out = [];
|
|
21775
|
+
for (const rule of DETECTION_RULES) {
|
|
21776
|
+
if (seen.has(rule.providerId)) continue;
|
|
21777
|
+
if (!existsSync26(join17(cwd, rule.marker))) continue;
|
|
21778
|
+
seen.add(rule.providerId);
|
|
21779
|
+
out.push(rule.providerId);
|
|
21780
|
+
}
|
|
21781
|
+
return out;
|
|
21782
|
+
}
|
|
21783
|
+
|
|
21784
|
+
// server/routes/active-provider.ts
|
|
21785
|
+
function registerActiveProviderRoute(app, deps) {
|
|
21786
|
+
app.get("/api/active-provider", (c) => {
|
|
21787
|
+
return c.json(buildEnvelope4(deps));
|
|
21788
|
+
});
|
|
21789
|
+
app.patch("/api/active-provider", async (c) => {
|
|
21790
|
+
const body = await parsePatchBody5(c.req.raw);
|
|
21791
|
+
const result = applyLensSwitch(deps, body.activeProvider);
|
|
21792
|
+
deps.configService.reload();
|
|
21793
|
+
return c.json({ ...buildEnvelope4(deps), switch: result });
|
|
21794
|
+
});
|
|
21795
|
+
}
|
|
21796
|
+
function buildEnvelope4(deps) {
|
|
21797
|
+
const r = resolveActiveProvider(deps.runtimeContext.cwd);
|
|
21798
|
+
return {
|
|
21799
|
+
activeProvider: r.resolved,
|
|
21800
|
+
detected: r.detected,
|
|
21801
|
+
source: r.source
|
|
21802
|
+
};
|
|
21803
|
+
}
|
|
21804
|
+
function applyLensSwitch(deps, newValue) {
|
|
21805
|
+
const cwd = deps.runtimeContext.cwd;
|
|
21806
|
+
try {
|
|
21807
|
+
writeConfigValue("activeProvider", newValue, { target: "project", cwd });
|
|
21808
|
+
} catch (err) {
|
|
21809
|
+
throw new HTTPException12(400, {
|
|
21810
|
+
message: tx(SERVER_TEXTS.activeProviderPersistFailed, {
|
|
21811
|
+
message: formatErrorMessage(err)
|
|
21812
|
+
})
|
|
21813
|
+
});
|
|
21814
|
+
}
|
|
21815
|
+
const dbPath = resolveDbPath({ db: void 0, cwd });
|
|
21816
|
+
if (!existsSync27(dbPath)) return { dropped: null };
|
|
21817
|
+
const dropResult = dropScanZone(dbPath);
|
|
21818
|
+
return {
|
|
21819
|
+
dropped: {
|
|
21820
|
+
tableCount: dropResult.tableCount,
|
|
21821
|
+
tableNames: dropResult.droppedTables
|
|
21822
|
+
}
|
|
21823
|
+
};
|
|
21824
|
+
}
|
|
21825
|
+
var PATCH_BODY_SCHEMA4 = {
|
|
21826
|
+
type: "object",
|
|
21827
|
+
additionalProperties: false,
|
|
21828
|
+
required: ["activeProvider"],
|
|
21829
|
+
properties: {
|
|
21830
|
+
activeProvider: { type: "string", minLength: 1 }
|
|
21831
|
+
}
|
|
21832
|
+
};
|
|
21833
|
+
var parsePatchBody5 = makeBodyValidator(PATCH_BODY_SCHEMA4, {
|
|
21834
|
+
notJson: SERVER_TEXTS.activeProviderBodyNotJson,
|
|
21835
|
+
notObject: SERVER_TEXTS.activeProviderBodyNotObject,
|
|
21836
|
+
invalid: SERVER_TEXTS.activeProviderBodyMissing,
|
|
21837
|
+
mapping: {
|
|
21838
|
+
":required": SERVER_TEXTS.activeProviderBodyMissing,
|
|
21839
|
+
"/activeProvider:type:string": SERVER_TEXTS.activeProviderValueNotString,
|
|
21840
|
+
"/activeProvider:minLength": SERVER_TEXTS.activeProviderValueEmpty
|
|
21021
21841
|
}
|
|
21022
21842
|
});
|
|
21023
21843
|
|
|
21024
21844
|
// server/routes/scan.ts
|
|
21025
|
-
import { HTTPException as
|
|
21845
|
+
import { HTTPException as HTTPException13 } from "hono/http-exception";
|
|
21026
21846
|
|
|
21027
21847
|
// server/scan-mutex.ts
|
|
21028
21848
|
var inFlight = null;
|
|
@@ -21030,14 +21850,14 @@ async function withScanMutex(fn) {
|
|
|
21030
21850
|
if (inFlight !== null) {
|
|
21031
21851
|
throw new ScanBusyError();
|
|
21032
21852
|
}
|
|
21033
|
-
let
|
|
21853
|
+
let resolve38;
|
|
21034
21854
|
inFlight = new Promise((r) => {
|
|
21035
|
-
|
|
21855
|
+
resolve38 = r;
|
|
21036
21856
|
});
|
|
21037
21857
|
try {
|
|
21038
21858
|
return await fn();
|
|
21039
21859
|
} finally {
|
|
21040
|
-
|
|
21860
|
+
resolve38();
|
|
21041
21861
|
inFlight = null;
|
|
21042
21862
|
}
|
|
21043
21863
|
}
|
|
@@ -21077,61 +21897,80 @@ function buildWatcherErrorEvent(data) {
|
|
|
21077
21897
|
}
|
|
21078
21898
|
|
|
21079
21899
|
// server/watcher.ts
|
|
21080
|
-
var WATCH_ROOT = ".";
|
|
21081
21900
|
function createWatcherService(opts) {
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
|
|
21087
|
-
|
|
21088
|
-
|
|
21089
|
-
|
|
21090
|
-
|
|
21091
|
-
|
|
21092
|
-
|
|
21093
|
-
|
|
21094
|
-
|
|
21095
|
-
|
|
21096
|
-
|
|
21901
|
+
let currentRuntime = null;
|
|
21902
|
+
const buildRuntimeOpts = () => {
|
|
21903
|
+
const runtimeOpts = {
|
|
21904
|
+
dbPath: opts.options.dbPath,
|
|
21905
|
+
roots: ["."],
|
|
21906
|
+
runtimeContext: opts.runtimeContext,
|
|
21907
|
+
noBuiltIns: opts.options.noBuiltIns,
|
|
21908
|
+
noPlugins: opts.options.noPlugins,
|
|
21909
|
+
emitterFactory: () => buildBroadcasterEmitter(opts.broadcaster),
|
|
21910
|
+
runInitialBatch: true,
|
|
21911
|
+
// BFF ordering: subscribe first so edits arriving during the
|
|
21912
|
+
// initial scan queue against the armed chokidar and fire a
|
|
21913
|
+
// follow-up batch.
|
|
21914
|
+
subscribeBeforeInitial: true,
|
|
21915
|
+
failOnInitialBatchError: false,
|
|
21916
|
+
events: {
|
|
21917
|
+
onBatch: (outcome) => {
|
|
21918
|
+
if (outcome.kind === "error") {
|
|
21919
|
+
log.warn(
|
|
21920
|
+
tx(SERVER_TEXTS.watcherBatchFailed, {
|
|
21921
|
+
message: sanitizeForTerminal(outcome.message)
|
|
21922
|
+
})
|
|
21923
|
+
);
|
|
21924
|
+
}
|
|
21925
|
+
},
|
|
21926
|
+
onWatcherError: (message) => {
|
|
21097
21927
|
log.warn(
|
|
21098
|
-
tx(SERVER_TEXTS.
|
|
21099
|
-
message: sanitizeForTerminal(
|
|
21928
|
+
tx(SERVER_TEXTS.watcherError, {
|
|
21929
|
+
message: sanitizeForTerminal(message)
|
|
21930
|
+
})
|
|
21931
|
+
);
|
|
21932
|
+
opts.broadcaster.broadcast(buildWatcherErrorEvent({ message }));
|
|
21933
|
+
},
|
|
21934
|
+
onPluginWarning: (message) => {
|
|
21935
|
+
log.warn(sanitizeForTerminal(message));
|
|
21936
|
+
},
|
|
21937
|
+
onReady: (info) => {
|
|
21938
|
+
opts.broadcaster.broadcast(
|
|
21939
|
+
buildWatcherStartedEvent({ roots: info.roots, debounceMs: info.debounceMs })
|
|
21940
|
+
);
|
|
21941
|
+
log.info(
|
|
21942
|
+
tx(SERVER_TEXTS.watcherReady, {
|
|
21943
|
+
roots: info.roots.join(","),
|
|
21944
|
+
debounceMs: String(info.debounceMs)
|
|
21100
21945
|
})
|
|
21101
21946
|
);
|
|
21102
21947
|
}
|
|
21103
|
-
},
|
|
21104
|
-
onWatcherError: (message) => {
|
|
21105
|
-
log.warn(
|
|
21106
|
-
tx(SERVER_TEXTS.watcherError, {
|
|
21107
|
-
message: sanitizeForTerminal(message)
|
|
21108
|
-
})
|
|
21109
|
-
);
|
|
21110
|
-
opts.broadcaster.broadcast(buildWatcherErrorEvent({ message }));
|
|
21111
|
-
},
|
|
21112
|
-
onPluginWarning: (message) => {
|
|
21113
|
-
log.warn(sanitizeForTerminal(message));
|
|
21114
|
-
},
|
|
21115
|
-
onReady: (info) => {
|
|
21116
|
-
opts.broadcaster.broadcast(
|
|
21117
|
-
buildWatcherStartedEvent({ roots: info.roots, debounceMs: info.debounceMs })
|
|
21118
|
-
);
|
|
21119
|
-
log.info(
|
|
21120
|
-
tx(SERVER_TEXTS.watcherReady, {
|
|
21121
|
-
roots: info.roots.join(","),
|
|
21122
|
-
debounceMs: String(info.debounceMs)
|
|
21123
|
-
})
|
|
21124
|
-
);
|
|
21125
21948
|
}
|
|
21949
|
+
};
|
|
21950
|
+
if (opts.debounceMsOverride !== void 0) {
|
|
21951
|
+
runtimeOpts.debounceMsOverride = opts.debounceMsOverride;
|
|
21126
21952
|
}
|
|
21953
|
+
return runtimeOpts;
|
|
21127
21954
|
};
|
|
21128
|
-
if (opts.debounceMsOverride !== void 0) {
|
|
21129
|
-
runtimeOpts.debounceMsOverride = opts.debounceMsOverride;
|
|
21130
|
-
}
|
|
21131
|
-
const handle = createWatcherRuntime(runtimeOpts);
|
|
21132
21955
|
return {
|
|
21133
|
-
start
|
|
21134
|
-
|
|
21956
|
+
async start() {
|
|
21957
|
+
currentRuntime = createWatcherRuntime(buildRuntimeOpts());
|
|
21958
|
+
await currentRuntime.start();
|
|
21959
|
+
},
|
|
21960
|
+
async stop() {
|
|
21961
|
+
if (currentRuntime) {
|
|
21962
|
+
await currentRuntime.stop();
|
|
21963
|
+
currentRuntime = null;
|
|
21964
|
+
}
|
|
21965
|
+
},
|
|
21966
|
+
async restart() {
|
|
21967
|
+
if (currentRuntime) {
|
|
21968
|
+
await currentRuntime.stop();
|
|
21969
|
+
currentRuntime = null;
|
|
21970
|
+
}
|
|
21971
|
+
currentRuntime = createWatcherRuntime(buildRuntimeOpts());
|
|
21972
|
+
await currentRuntime.start();
|
|
21973
|
+
}
|
|
21135
21974
|
};
|
|
21136
21975
|
}
|
|
21137
21976
|
function buildBroadcasterEmitter(broadcaster) {
|
|
@@ -21158,7 +21997,7 @@ function registerScanRoute(app, deps) {
|
|
|
21158
21997
|
}
|
|
21159
21998
|
async function runPersistedScan(c, deps) {
|
|
21160
21999
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
21161
|
-
throw new
|
|
22000
|
+
throw new HTTPException13(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
|
|
21162
22001
|
}
|
|
21163
22002
|
const dbExists = await tryWithSqlite(
|
|
21164
22003
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
@@ -21187,7 +22026,7 @@ async function runPersistedScan(c, deps) {
|
|
|
21187
22026
|
emitterFactory: () => buildBroadcasterEmitter(deps.broadcaster)
|
|
21188
22027
|
});
|
|
21189
22028
|
if (outcome.kind !== "ok") {
|
|
21190
|
-
throw new
|
|
22029
|
+
throw new HTTPException13(500, {
|
|
21191
22030
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.scanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
21192
22031
|
});
|
|
21193
22032
|
}
|
|
@@ -21195,7 +22034,7 @@ async function runPersistedScan(c, deps) {
|
|
|
21195
22034
|
});
|
|
21196
22035
|
} catch (err) {
|
|
21197
22036
|
if (err instanceof ScanBusyError) {
|
|
21198
|
-
throw new
|
|
22037
|
+
throw new HTTPException13(409, { message: SERVER_TEXTS.scanPostBusy });
|
|
21199
22038
|
}
|
|
21200
22039
|
throw err;
|
|
21201
22040
|
}
|
|
@@ -21259,7 +22098,7 @@ function groupTagsBySource2(rows) {
|
|
|
21259
22098
|
}
|
|
21260
22099
|
async function runFreshScan(deps) {
|
|
21261
22100
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
21262
|
-
throw new
|
|
22101
|
+
throw new HTTPException13(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
|
|
21263
22102
|
}
|
|
21264
22103
|
const resolveEnabledOverride = await buildBffResolverOverride(deps);
|
|
21265
22104
|
const outcome = await runScanForCommand({
|
|
@@ -21288,7 +22127,7 @@ async function runFreshScan(deps) {
|
|
|
21288
22127
|
printer: bffScanRunnerPrinter
|
|
21289
22128
|
});
|
|
21290
22129
|
if (outcome.kind !== "ok") {
|
|
21291
|
-
throw new
|
|
22130
|
+
throw new HTTPException13(500, {
|
|
21292
22131
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.freshScanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
21293
22132
|
});
|
|
21294
22133
|
}
|
|
@@ -21322,8 +22161,8 @@ function emptyScanResult() {
|
|
|
21322
22161
|
}
|
|
21323
22162
|
|
|
21324
22163
|
// server/routes/sidecar.ts
|
|
21325
|
-
import { HTTPException as
|
|
21326
|
-
import { resolve as
|
|
22164
|
+
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
22165
|
+
import { resolve as resolve34 } from "path";
|
|
21327
22166
|
var STATUS_FRESH = "fresh";
|
|
21328
22167
|
var ENVELOPE_KIND2 = "sidecar.bumped";
|
|
21329
22168
|
var BUMP_BODY_SCHEMA = {
|
|
@@ -21357,13 +22196,13 @@ function registerSidecarRoutes(app, deps) {
|
|
|
21357
22196
|
let absPath;
|
|
21358
22197
|
try {
|
|
21359
22198
|
assertContained(deps.runtimeContext.cwd, node.path);
|
|
21360
|
-
absPath =
|
|
22199
|
+
absPath = resolve34(deps.runtimeContext.cwd, node.path);
|
|
21361
22200
|
} catch (err) {
|
|
21362
|
-
throw new
|
|
22201
|
+
throw new HTTPException14(500, { message: formatErrorMessage(err) });
|
|
21363
22202
|
}
|
|
21364
22203
|
const result = invokeBump2(node, absPath, body);
|
|
21365
22204
|
if (result.report.ok === false && result.report.reason === "fresh") {
|
|
21366
|
-
throw new
|
|
22205
|
+
throw new HTTPException14(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
|
|
21367
22206
|
}
|
|
21368
22207
|
if (result.report.ok === true && result.report.noop === true) {
|
|
21369
22208
|
const envelope2 = {
|
|
@@ -21390,7 +22229,7 @@ function registerSidecarRoutes(app, deps) {
|
|
|
21390
22229
|
}
|
|
21391
22230
|
} catch (err) {
|
|
21392
22231
|
if (err instanceof EConsentRequiredError) throw err;
|
|
21393
|
-
throw new
|
|
22232
|
+
throw new HTTPException14(500, { message: formatErrorMessage(err) });
|
|
21394
22233
|
}
|
|
21395
22234
|
if (body.confirm === true) {
|
|
21396
22235
|
deps.configService.reload();
|
|
@@ -21427,7 +22266,7 @@ async function loadNode(deps, nodePath) {
|
|
|
21427
22266
|
);
|
|
21428
22267
|
const node = persisted?.nodes.find((n) => n.path === nodePath);
|
|
21429
22268
|
if (!node) {
|
|
21430
|
-
throw new
|
|
22269
|
+
throw new HTTPException14(404, {
|
|
21431
22270
|
message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
|
|
21432
22271
|
});
|
|
21433
22272
|
}
|
|
@@ -21435,7 +22274,7 @@ async function loadNode(deps, nodePath) {
|
|
|
21435
22274
|
}
|
|
21436
22275
|
function invokeBump2(node, absPath, body) {
|
|
21437
22276
|
if (!bumpAction.invoke) {
|
|
21438
|
-
throw new
|
|
22277
|
+
throw new HTTPException14(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
|
|
21439
22278
|
}
|
|
21440
22279
|
const input = {};
|
|
21441
22280
|
if (body.force === true) input.force = true;
|
|
@@ -21481,9 +22320,9 @@ function registerUpdateStatusRoute(app, deps) {
|
|
|
21481
22320
|
}
|
|
21482
22321
|
|
|
21483
22322
|
// server/static.ts
|
|
21484
|
-
import { existsSync as
|
|
22323
|
+
import { existsSync as existsSync28 } from "fs";
|
|
21485
22324
|
import { readFile as readFile5 } from "fs/promises";
|
|
21486
|
-
import { extname, join as
|
|
22325
|
+
import { extname, join as join18 } from "path";
|
|
21487
22326
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
21488
22327
|
var INDEX_HTML = "index.html";
|
|
21489
22328
|
var PLACEHOLDER_HTML = `<!doctype html>
|
|
@@ -21535,8 +22374,8 @@ function createSpaFallback(opts) {
|
|
|
21535
22374
|
return async (c, _next) => {
|
|
21536
22375
|
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
|
|
21537
22376
|
if (opts.uiDist === null) return htmlResponse(c, placeholder);
|
|
21538
|
-
const indexPath =
|
|
21539
|
-
if (!
|
|
22377
|
+
const indexPath = join18(opts.uiDist, INDEX_HTML);
|
|
22378
|
+
if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
|
|
21540
22379
|
return fileResponse(c, indexPath);
|
|
21541
22380
|
};
|
|
21542
22381
|
}
|
|
@@ -21622,13 +22461,13 @@ function attachBroadcasterRoute(app, broadcaster) {
|
|
|
21622
22461
|
|
|
21623
22462
|
// server/app.ts
|
|
21624
22463
|
var BODY_LIMIT_BYTES = 1024 * 1024;
|
|
21625
|
-
var DbMissingError = class extends
|
|
22464
|
+
var DbMissingError = class extends HTTPException15 {
|
|
21626
22465
|
constructor(message) {
|
|
21627
22466
|
super(500, { message });
|
|
21628
22467
|
this.name = "DbMissingError";
|
|
21629
22468
|
}
|
|
21630
22469
|
};
|
|
21631
|
-
var BulkValidationError = class extends
|
|
22470
|
+
var BulkValidationError = class extends HTTPException15 {
|
|
21632
22471
|
id;
|
|
21633
22472
|
code;
|
|
21634
22473
|
constructor(init) {
|
|
@@ -21638,7 +22477,7 @@ var BulkValidationError = class extends HTTPException13 {
|
|
|
21638
22477
|
this.code = init.code;
|
|
21639
22478
|
}
|
|
21640
22479
|
};
|
|
21641
|
-
var LoopbackGateError = class extends
|
|
22480
|
+
var LoopbackGateError = class extends HTTPException15 {
|
|
21642
22481
|
code;
|
|
21643
22482
|
constructor(init) {
|
|
21644
22483
|
super(403, { message: init.message });
|
|
@@ -21658,7 +22497,7 @@ function createApp(deps) {
|
|
|
21658
22497
|
bodyLimit({
|
|
21659
22498
|
maxSize: BODY_LIMIT_BYTES,
|
|
21660
22499
|
onError: () => {
|
|
21661
|
-
throw new
|
|
22500
|
+
throw new HTTPException15(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
|
|
21662
22501
|
}
|
|
21663
22502
|
})
|
|
21664
22503
|
);
|
|
@@ -21682,7 +22521,8 @@ function createApp(deps) {
|
|
|
21682
22521
|
kindRegistry: deps.kindRegistry,
|
|
21683
22522
|
contributionsRegistry: deps.contributionsRegistry,
|
|
21684
22523
|
pluginRuntime: deps.pluginRuntime,
|
|
21685
|
-
configService
|
|
22524
|
+
configService,
|
|
22525
|
+
watcherHolder: deps.watcherHolder
|
|
21686
22526
|
};
|
|
21687
22527
|
registerScanRoute(app, { ...routeDeps, broadcaster: deps.broadcaster });
|
|
21688
22528
|
registerNodesRoutes(app, routeDeps);
|
|
@@ -21698,8 +22538,10 @@ function createApp(deps) {
|
|
|
21698
22538
|
registerUpdateStatusRoute(app, routeDeps);
|
|
21699
22539
|
registerPreferencesRoute(app, routeDeps);
|
|
21700
22540
|
registerProjectPreferencesRoute(app, routeDeps);
|
|
22541
|
+
registerActiveProviderRoute(app, routeDeps);
|
|
22542
|
+
registerProjectIgnoreRoute(app, routeDeps);
|
|
21701
22543
|
app.all("/api/*", (c) => {
|
|
21702
|
-
throw new
|
|
22544
|
+
throw new HTTPException15(404, {
|
|
21703
22545
|
message: tx(SERVER_TEXTS.unknownApiEndpoint, { path: sanitizeForTerminal(c.req.path) })
|
|
21704
22546
|
});
|
|
21705
22547
|
});
|
|
@@ -21707,7 +22549,7 @@ function createApp(deps) {
|
|
|
21707
22549
|
app.use("*", createStaticHandler({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
21708
22550
|
app.get("*", createSpaFallback({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
21709
22551
|
app.notFound((c) => {
|
|
21710
|
-
throw new
|
|
22552
|
+
throw new HTTPException15(404, {
|
|
21711
22553
|
message: tx(SERVER_TEXTS.unknownPath, { path: sanitizeForTerminal(c.req.path) })
|
|
21712
22554
|
});
|
|
21713
22555
|
});
|
|
@@ -21762,7 +22604,7 @@ function formatError2(err, c) {
|
|
|
21762
22604
|
};
|
|
21763
22605
|
return c.json(envelope, 403);
|
|
21764
22606
|
}
|
|
21765
|
-
if (err instanceof
|
|
22607
|
+
if (err instanceof HTTPException15) {
|
|
21766
22608
|
const status = err.status;
|
|
21767
22609
|
const envelope = {
|
|
21768
22610
|
ok: false,
|
|
@@ -22105,10 +22947,10 @@ function validateNoUi(noUi, uiDist) {
|
|
|
22105
22947
|
}
|
|
22106
22948
|
|
|
22107
22949
|
// server/paths.ts
|
|
22108
|
-
import { existsSync as
|
|
22109
|
-
import { dirname as dirname18, isAbsolute as isAbsolute9, join as
|
|
22950
|
+
import { existsSync as existsSync29, statSync as statSync11 } from "fs";
|
|
22951
|
+
import { dirname as dirname18, isAbsolute as isAbsolute9, join as join19, resolve as resolve35 } from "path";
|
|
22110
22952
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
22111
|
-
var DEFAULT_UI_REL =
|
|
22953
|
+
var DEFAULT_UI_REL = join19("ui", "dist", "ui", "browser");
|
|
22112
22954
|
var PACKAGE_UI_REL = "ui";
|
|
22113
22955
|
var INDEX_HTML2 = "index.html";
|
|
22114
22956
|
function resolveDefaultUiDist(ctx) {
|
|
@@ -22117,13 +22959,13 @@ function resolveDefaultUiDist(ctx) {
|
|
|
22117
22959
|
return walkUpForUi(ctx.cwd);
|
|
22118
22960
|
}
|
|
22119
22961
|
function resolveExplicitUiDist(ctx, raw) {
|
|
22120
|
-
return isAbsolute9(raw) ? raw :
|
|
22962
|
+
return isAbsolute9(raw) ? raw : resolve35(ctx.cwd, raw);
|
|
22121
22963
|
}
|
|
22122
22964
|
function isUiBundleDir(path) {
|
|
22123
|
-
if (!
|
|
22965
|
+
if (!existsSync29(path)) return false;
|
|
22124
22966
|
try {
|
|
22125
|
-
if (!
|
|
22126
|
-
return
|
|
22967
|
+
if (!statSync11(path).isDirectory()) return false;
|
|
22968
|
+
return existsSync29(join19(path, INDEX_HTML2));
|
|
22127
22969
|
} catch {
|
|
22128
22970
|
return false;
|
|
22129
22971
|
}
|
|
@@ -22140,9 +22982,9 @@ function resolvePackageBundledUi() {
|
|
|
22140
22982
|
function resolvePackageBundledUiFrom(here) {
|
|
22141
22983
|
let current = here;
|
|
22142
22984
|
for (let i = 0; i < 8; i++) {
|
|
22143
|
-
const candidate =
|
|
22985
|
+
const candidate = join19(current, PACKAGE_UI_REL);
|
|
22144
22986
|
if (isUiBundleDir(candidate)) return candidate;
|
|
22145
|
-
const distHere =
|
|
22987
|
+
const distHere = join19(current, "dist", PACKAGE_UI_REL);
|
|
22146
22988
|
if (isUiBundleDir(distHere)) return distHere;
|
|
22147
22989
|
const parent = dirname18(current);
|
|
22148
22990
|
if (parent === current) return null;
|
|
@@ -22151,9 +22993,9 @@ function resolvePackageBundledUiFrom(here) {
|
|
|
22151
22993
|
return null;
|
|
22152
22994
|
}
|
|
22153
22995
|
function walkUpForUi(startDir) {
|
|
22154
|
-
let current =
|
|
22996
|
+
let current = resolve35(startDir);
|
|
22155
22997
|
for (let i = 0; i < 64; i++) {
|
|
22156
|
-
const candidate =
|
|
22998
|
+
const candidate = join19(current, DEFAULT_UI_REL);
|
|
22157
22999
|
if (isUiBundleDir(candidate)) return candidate;
|
|
22158
23000
|
const parent = dirname18(current);
|
|
22159
23001
|
if (parent === current) return null;
|
|
@@ -22169,6 +23011,7 @@ async function createServer(options, extra = {}) {
|
|
|
22169
23011
|
const broadcaster = new WsBroadcaster();
|
|
22170
23012
|
const { pluginRuntime, kindRegistry } = await assemblePluginRuntime(options, runtimeContext);
|
|
22171
23013
|
const { kernel, contributionsRegistry } = assembleKernel(pluginRuntime, options.noBuiltIns);
|
|
23014
|
+
const watcherHolder = { current: null };
|
|
22172
23015
|
const app = createApp({
|
|
22173
23016
|
options,
|
|
22174
23017
|
specVersion,
|
|
@@ -22177,6 +23020,7 @@ async function createServer(options, extra = {}) {
|
|
|
22177
23020
|
kindRegistry,
|
|
22178
23021
|
contributionsRegistry,
|
|
22179
23022
|
pluginRuntime,
|
|
23023
|
+
watcherHolder,
|
|
22180
23024
|
kernel
|
|
22181
23025
|
});
|
|
22182
23026
|
const wss = new WebSocketServer({ noServer: true });
|
|
@@ -22196,6 +23040,7 @@ async function createServer(options, extra = {}) {
|
|
|
22196
23040
|
try {
|
|
22197
23041
|
await candidate.start();
|
|
22198
23042
|
watcherService = candidate;
|
|
23043
|
+
watcherHolder.current = candidate;
|
|
22199
23044
|
} catch (err) {
|
|
22200
23045
|
const message = formatErrorMessage(err);
|
|
22201
23046
|
log.warn(
|
|
@@ -22390,7 +23235,8 @@ function renderBanner(input) {
|
|
|
22390
23235
|
dbDisplay,
|
|
22391
23236
|
pathDisplay: formatCwdPath(input.cwd),
|
|
22392
23237
|
browserLine,
|
|
22393
|
-
colorEnabled: input.colorEnabled
|
|
23238
|
+
colorEnabled: input.colorEnabled,
|
|
23239
|
+
referencePaths: input.referencePaths ?? []
|
|
22394
23240
|
});
|
|
22395
23241
|
}
|
|
22396
23242
|
function resolveColorEnabled(opts) {
|
|
@@ -22447,11 +23293,23 @@ function renderFiglet(input) {
|
|
|
22447
23293
|
lines.push(` ${dimOpen}Server${dimClose} ${greenUnderline}${input.url}${greenUnderlineClose}`);
|
|
22448
23294
|
lines.push(` ${dimOpen}Path${dimClose} ${input.pathDisplay}`);
|
|
22449
23295
|
lines.push(` ${dimOpen}DB${dimClose} ${input.dbDisplay}`);
|
|
23296
|
+
lines.push(...renderListRows("Refs", input.referencePaths, dimOpen, dimClose));
|
|
22450
23297
|
lines.push("");
|
|
22451
23298
|
lines.push(` ${dimOpen}${input.browserLine}${dimClose}`);
|
|
22452
23299
|
lines.push("");
|
|
22453
23300
|
return lines.join("\n") + "\n";
|
|
22454
23301
|
}
|
|
23302
|
+
function renderListRows(label, values, dimOpen, dimClose) {
|
|
23303
|
+
if (values.length === 0) return [];
|
|
23304
|
+
const out = [];
|
|
23305
|
+
const labelPad = " ".repeat(Math.max(1, 9 - label.length));
|
|
23306
|
+
const continuationPad = " ".repeat(11);
|
|
23307
|
+
out.push(` ${dimOpen}${label}${dimClose}${labelPad}${sanitizeForTerminal(values[0])}`);
|
|
23308
|
+
for (let i = 1; i < values.length; i += 1) {
|
|
23309
|
+
out.push(`${continuationPad}${sanitizeForTerminal(values[i])}`);
|
|
23310
|
+
}
|
|
23311
|
+
return out;
|
|
23312
|
+
}
|
|
22455
23313
|
var EMPTY_ANSI = {
|
|
22456
23314
|
dimOpen: "",
|
|
22457
23315
|
dimClose: "",
|
|
@@ -22571,7 +23429,7 @@ var ServeCommand = class extends SmCommand {
|
|
|
22571
23429
|
return ExitCode.Error;
|
|
22572
23430
|
}
|
|
22573
23431
|
const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
|
|
22574
|
-
if (this.db !== void 0 && !
|
|
23432
|
+
if (this.db !== void 0 && !existsSync30(dbPath)) {
|
|
22575
23433
|
this.printer.info(
|
|
22576
23434
|
tx(SERVE_TEXTS.dbNotFound, { path: sanitizeForTerminal(dbPath) })
|
|
22577
23435
|
);
|
|
@@ -22647,6 +23505,12 @@ var ServeCommand = class extends SmCommand {
|
|
|
22647
23505
|
noColorFlag: this.noColor,
|
|
22648
23506
|
env: process.env
|
|
22649
23507
|
});
|
|
23508
|
+
let referencePaths = [];
|
|
23509
|
+
try {
|
|
23510
|
+
const cfg = loadConfig({ cwd: runtimeCtx.cwd }).effective;
|
|
23511
|
+
referencePaths = cfg.scan.referencePaths;
|
|
23512
|
+
} catch {
|
|
23513
|
+
}
|
|
22650
23514
|
this.printer.info(
|
|
22651
23515
|
renderBanner({
|
|
22652
23516
|
version: VERSION,
|
|
@@ -22656,7 +23520,8 @@ var ServeCommand = class extends SmCommand {
|
|
|
22656
23520
|
cwd: runtimeCtx.cwd,
|
|
22657
23521
|
openBrowser: validation.options.open,
|
|
22658
23522
|
isTTY,
|
|
22659
|
-
colorEnabled
|
|
23523
|
+
colorEnabled,
|
|
23524
|
+
referencePaths
|
|
22660
23525
|
})
|
|
22661
23526
|
);
|
|
22662
23527
|
if (validation.options.open) {
|
|
@@ -22975,22 +23840,27 @@ function renderLinksSection(direction, links, ansi) {
|
|
|
22975
23840
|
const aggregated = aggregateLinks(links, projectField);
|
|
22976
23841
|
const headerTpl = direction === "out" ? SHOW_TEXTS.linksOutSection : SHOW_TEXTS.linksInSection;
|
|
22977
23842
|
const kindWidth = Math.max(...aggregated.map((g) => g.kind.length));
|
|
22978
|
-
const
|
|
23843
|
+
const confLabels = aggregated.map((g) => formatConfidence(g.confidence));
|
|
23844
|
+
const confWidth = Math.max(...confLabels.map((l) => l.length));
|
|
22979
23845
|
const lines = [tx(headerTpl, { count: links.length })];
|
|
22980
|
-
|
|
23846
|
+
aggregated.forEach((grp, idx) => {
|
|
22981
23847
|
const dup = grp.rowCount > 1 ? ansi.dim(tx(SHOW_TEXTS.linkDup, { count: grp.rowCount })) : "";
|
|
22982
23848
|
lines.push(
|
|
22983
23849
|
tx(SHOW_TEXTS.linkRow, {
|
|
22984
23850
|
arrow: ansi.dim(arrow),
|
|
22985
23851
|
kind: sanitizeForTerminal(grp.kind).padEnd(kindWidth),
|
|
22986
|
-
confidence: ansi.dim(
|
|
23852
|
+
confidence: ansi.dim(confLabels[idx].padEnd(confWidth)),
|
|
22987
23853
|
endpoint: sanitizeForTerminal(grp.endpoint),
|
|
22988
23854
|
dup
|
|
22989
23855
|
})
|
|
22990
23856
|
);
|
|
22991
|
-
}
|
|
23857
|
+
});
|
|
22992
23858
|
return lines.join("");
|
|
22993
23859
|
}
|
|
23860
|
+
function formatConfidence(c) {
|
|
23861
|
+
if (typeof c !== "number" || !Number.isFinite(c)) return "?";
|
|
23862
|
+
return `${Math.round(c * 100)}%`;
|
|
23863
|
+
}
|
|
22994
23864
|
function renderIssuesSection(issues, nodePath, ansi) {
|
|
22995
23865
|
const lines = [tx(SHOW_TEXTS.issuesSection, { count: issues.length })];
|
|
22996
23866
|
const analyzerWidth = Math.max(
|
|
@@ -23057,18 +23927,13 @@ function aggregateLinks(links, endpointSide) {
|
|
|
23057
23927
|
return a.kind.localeCompare(b.kind);
|
|
23058
23928
|
});
|
|
23059
23929
|
}
|
|
23060
|
-
var CONFIDENCE_RANK = {
|
|
23061
|
-
high: 2,
|
|
23062
|
-
medium: 1,
|
|
23063
|
-
low: 0
|
|
23064
|
-
};
|
|
23065
23930
|
function rankConfidenceForGrouping(c) {
|
|
23066
|
-
return
|
|
23931
|
+
return c;
|
|
23067
23932
|
}
|
|
23068
23933
|
|
|
23069
23934
|
// cli/commands/sidecar.ts
|
|
23070
|
-
import { existsSync as
|
|
23071
|
-
import { resolve as
|
|
23935
|
+
import { existsSync as existsSync31, unlinkSync as unlinkSync2 } from "fs";
|
|
23936
|
+
import { resolve as resolve36 } from "path";
|
|
23072
23937
|
import { Command as Command35, Option as Option33 } from "clipanion";
|
|
23073
23938
|
|
|
23074
23939
|
// cli/i18n/sidecar.texts.ts
|
|
@@ -23219,7 +24084,7 @@ var SidecarRefreshCommand = class extends SmCommand {
|
|
|
23219
24084
|
let absPath;
|
|
23220
24085
|
try {
|
|
23221
24086
|
assertContained(ctx.cwd, node.path);
|
|
23222
|
-
absPath =
|
|
24087
|
+
absPath = resolve36(ctx.cwd, node.path);
|
|
23223
24088
|
} catch (err) {
|
|
23224
24089
|
this.printer.error(
|
|
23225
24090
|
tx(SIDECAR_TEXTS.refreshFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -23500,7 +24365,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23500
24365
|
let absPath;
|
|
23501
24366
|
try {
|
|
23502
24367
|
assertContained(ctx.cwd, node.path);
|
|
23503
|
-
absPath =
|
|
24368
|
+
absPath = resolve36(ctx.cwd, node.path);
|
|
23504
24369
|
} catch (err) {
|
|
23505
24370
|
this.printer.error(
|
|
23506
24371
|
tx(SIDECAR_TEXTS.annotateFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -23508,7 +24373,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23508
24373
|
return ExitCode.Error;
|
|
23509
24374
|
}
|
|
23510
24375
|
const sidecarAbsPath = sidecarPathFor(absPath);
|
|
23511
|
-
if (
|
|
24376
|
+
if (existsSync31(sidecarAbsPath) && this.force !== true) {
|
|
23512
24377
|
this.printer.error(
|
|
23513
24378
|
tx(SIDECAR_TEXTS.annotateExists, {
|
|
23514
24379
|
glyph: errGlyph,
|
|
@@ -23518,7 +24383,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23518
24383
|
);
|
|
23519
24384
|
return ExitCode.Error;
|
|
23520
24385
|
}
|
|
23521
|
-
if (
|
|
24386
|
+
if (existsSync31(sidecarAbsPath) && this.force === true) {
|
|
23522
24387
|
try {
|
|
23523
24388
|
unlinkSync2(sidecarAbsPath);
|
|
23524
24389
|
} catch (err) {
|
|
@@ -23748,8 +24613,8 @@ var STUB_COMMANDS = [
|
|
|
23748
24613
|
];
|
|
23749
24614
|
|
|
23750
24615
|
// cli/commands/tutorial.ts
|
|
23751
|
-
import { cpSync as cpSync2, existsSync as
|
|
23752
|
-
import { dirname as dirname19, join as
|
|
24616
|
+
import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync7, rmSync as rmSync2, statSync as statSync12 } from "fs";
|
|
24617
|
+
import { dirname as dirname19, join as join20, resolve as resolve37 } from "path";
|
|
23753
24618
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
23754
24619
|
import { Command as Command37, Option as Option35 } from "clipanion";
|
|
23755
24620
|
|
|
@@ -23845,9 +24710,9 @@ var TutorialCommand = class extends SmCommand {
|
|
|
23845
24710
|
}
|
|
23846
24711
|
const variant = rawVariant ?? DEFAULT_VARIANT;
|
|
23847
24712
|
const spec = VARIANT_SPECS[variant];
|
|
23848
|
-
const targetDir =
|
|
24713
|
+
const targetDir = join20(ctx.cwd, ".claude", "skills", spec.slug);
|
|
23849
24714
|
const targetDisplay = `.claude/skills/${spec.slug}/`;
|
|
23850
|
-
if (
|
|
24715
|
+
if (existsSync32(targetDir) && !this.force) {
|
|
23851
24716
|
this.printer.error(
|
|
23852
24717
|
tx(TUTORIAL_TEXTS.alreadyExists, {
|
|
23853
24718
|
glyph: errGlyph,
|
|
@@ -23923,14 +24788,14 @@ function resolveSkillSourceDir(variant) {
|
|
|
23923
24788
|
const here = dirname19(fileURLToPath6(import.meta.url));
|
|
23924
24789
|
const candidates = [
|
|
23925
24790
|
// dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
|
|
23926
|
-
|
|
24791
|
+
resolve37(here, "../../..", spec.sourceDir),
|
|
23927
24792
|
// bundled: dist/cli.js → dist/cli/tutorial/<slug> (sibling)
|
|
23928
|
-
|
|
24793
|
+
resolve37(here, "cli/tutorial", spec.slug),
|
|
23929
24794
|
// bundled fallback: any-depth → cli/tutorial/<slug>
|
|
23930
|
-
|
|
24795
|
+
resolve37(here, "../cli/tutorial", spec.slug)
|
|
23931
24796
|
];
|
|
23932
24797
|
for (const candidate of candidates) {
|
|
23933
|
-
if (
|
|
24798
|
+
if (existsSync32(candidate) && statSync12(candidate).isDirectory()) {
|
|
23934
24799
|
cachedSourceDirs.set(variant, candidate);
|
|
23935
24800
|
return candidate;
|
|
23936
24801
|
}
|
|
@@ -24107,7 +24972,7 @@ await lifecycleDispatcher.dispatch(
|
|
|
24107
24972
|
process.exit(exitCode);
|
|
24108
24973
|
function resolveBareDefault() {
|
|
24109
24974
|
const ctx = defaultRuntimeContext();
|
|
24110
|
-
if (
|
|
24975
|
+
if (existsSync33(defaultProjectDbPath(ctx))) {
|
|
24111
24976
|
return ["serve"];
|
|
24112
24977
|
}
|
|
24113
24978
|
process.stderr.write(tx(ENTRY_TEXTS.bareNoProject, { cwd: ctx.cwd }));
|