@skill-map/cli 0.32.0 → 0.34.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 +1336 -488
- package/dist/cli.js.map +1 -1
- package/dist/index.js +712 -108
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +217 -3
- package/dist/kernel/index.js +712 -108
- 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-MHWM2642.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-4X6AAGKZ.js +2 -0
- package/migrations/001_initial.sql +2 -2
- package/package.json +3 -2
- package/dist/ui/chunk-BCQZKYOD.js +0 -1
- package/dist/ui/chunk-LS2NXZQZ.js +0 -135
- package/dist/ui/chunk-WCE7MTK5.js +0 -123
- package/dist/ui/main-LJIHL73M.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
|
|
@@ -506,11 +531,181 @@ var claudeProvider = {
|
|
|
506
531
|
const lower = path.toLowerCase();
|
|
507
532
|
if (lower.startsWith(".claude/agents/")) return "agent";
|
|
508
533
|
if (lower.startsWith(".claude/commands/")) return "command";
|
|
509
|
-
if (
|
|
534
|
+
if (/^\.claude\/skills\/[^/]+\/skill\.md$/.test(lower)) return "skill";
|
|
510
535
|
return null;
|
|
511
536
|
}
|
|
512
537
|
};
|
|
513
538
|
|
|
539
|
+
// plugins/claude/extractors/at-directive/index.ts
|
|
540
|
+
import { posix as pathPosix } from "path";
|
|
541
|
+
|
|
542
|
+
// kernel/util/strip-code-blocks.ts
|
|
543
|
+
var FENCE_RE = /^(?<indent> {0,3})(?<fence>`{3,}|~{3,})/;
|
|
544
|
+
function stripCodeBlocks(input) {
|
|
545
|
+
if (!input) return input;
|
|
546
|
+
const fenceless = stripFences(input);
|
|
547
|
+
return stripInline(fenceless);
|
|
548
|
+
}
|
|
549
|
+
function stripFences(input) {
|
|
550
|
+
const out = [];
|
|
551
|
+
const lines = input.split("\n");
|
|
552
|
+
let openFence = null;
|
|
553
|
+
for (const line of lines) {
|
|
554
|
+
if (openFence) {
|
|
555
|
+
const closer = matchClosingFence(line, openFence);
|
|
556
|
+
if (closer) {
|
|
557
|
+
out.push(blank(line));
|
|
558
|
+
openFence = null;
|
|
559
|
+
} else {
|
|
560
|
+
out.push(blank(line));
|
|
561
|
+
}
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const open = FENCE_RE.exec(line);
|
|
565
|
+
if (open?.groups) {
|
|
566
|
+
openFence = open.groups["fence"];
|
|
567
|
+
out.push(blank(line));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
out.push(line);
|
|
571
|
+
}
|
|
572
|
+
return out.join("\n");
|
|
573
|
+
}
|
|
574
|
+
function matchClosingFence(line, openFence) {
|
|
575
|
+
const m = FENCE_RE.exec(line);
|
|
576
|
+
if (!m?.groups) return false;
|
|
577
|
+
const fence = m.groups["fence"];
|
|
578
|
+
return fence[0] === openFence[0] && fence.length >= openFence.length;
|
|
579
|
+
}
|
|
580
|
+
function stripInline(input) {
|
|
581
|
+
return input.replace(/(`+)([\s\S]*?)\1/g, (_full, ticks, body) => {
|
|
582
|
+
return ticks.replace(/`/g, " ") + blank(body) + ticks.replace(/`/g, " ");
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function blank(s) {
|
|
586
|
+
return s.replace(/[^\s]/g, " ");
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// kernel/trigger-normalize.ts
|
|
590
|
+
function normalizeTrigger(source) {
|
|
591
|
+
let out = source.normalize("NFD");
|
|
592
|
+
out = out.replace(new RegExp("\\p{Mn}+", "gu"), "");
|
|
593
|
+
out = out.toLowerCase();
|
|
594
|
+
out = out.replace(/[-_\s]+/g, " ");
|
|
595
|
+
out = out.replace(/ +/g, " ");
|
|
596
|
+
return out.trim();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// plugins/claude/extractors/at-directive/index.ts
|
|
600
|
+
var ID = "at-directive";
|
|
601
|
+
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;
|
|
602
|
+
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;
|
|
603
|
+
var atDirectiveExtractor = {
|
|
604
|
+
id: ID,
|
|
605
|
+
pluginId: "claude",
|
|
606
|
+
kind: "extractor",
|
|
607
|
+
version: "1.0.0",
|
|
608
|
+
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.",
|
|
609
|
+
scope: "body",
|
|
610
|
+
precondition: { provider: ["claude"] },
|
|
611
|
+
extract(ctx) {
|
|
612
|
+
const seenMentions = /* @__PURE__ */ new Set();
|
|
613
|
+
const seenReferences = /* @__PURE__ */ new Set();
|
|
614
|
+
const body = stripCodeBlocks(ctx.body);
|
|
615
|
+
const sourceDir = pathPosix.dirname(ctx.node.path);
|
|
616
|
+
for (const match of body.matchAll(AT_RE)) {
|
|
617
|
+
const original = match[1];
|
|
618
|
+
const bare = original.slice(1);
|
|
619
|
+
if (bare.startsWith("/")) continue;
|
|
620
|
+
const isReference = bare.startsWith("./") || bare.startsWith("../") || FILE_EXT_RE.test(bare);
|
|
621
|
+
if (isReference) {
|
|
622
|
+
const target = resolveSourceRelative(sourceDir, bare);
|
|
623
|
+
const dedupKey = target.toLowerCase();
|
|
624
|
+
if (seenReferences.has(dedupKey)) continue;
|
|
625
|
+
seenReferences.add(dedupKey);
|
|
626
|
+
ctx.emitLink({
|
|
627
|
+
source: ctx.node.path,
|
|
628
|
+
target,
|
|
629
|
+
kind: "references",
|
|
630
|
+
// 0.85: strong file signal (path prefix `./` / `../` OR
|
|
631
|
+
// a known file extension on the tail). One degree of inference
|
|
632
|
+
// (the runtime still resolves the path).
|
|
633
|
+
confidence: 0.85,
|
|
634
|
+
sources: [ID],
|
|
635
|
+
trigger: {
|
|
636
|
+
originalTrigger: original,
|
|
637
|
+
normalizedTrigger: target
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const normalized = normalizeTrigger(original);
|
|
643
|
+
if (seenMentions.has(normalized)) continue;
|
|
644
|
+
seenMentions.add(normalized);
|
|
645
|
+
ctx.emitLink({
|
|
646
|
+
source: ctx.node.path,
|
|
647
|
+
target: original,
|
|
648
|
+
kind: "mentions",
|
|
649
|
+
// 0.5: genuine ambiguity. A bare `@handle` (no extension, no
|
|
650
|
+
// path prefix) could be an agent, a handle, or generic prose.
|
|
651
|
+
// The runtime decides at invocation time; the extractor leaves
|
|
652
|
+
// the question open.
|
|
653
|
+
confidence: 0.5,
|
|
654
|
+
sources: [ID],
|
|
655
|
+
trigger: {
|
|
656
|
+
originalTrigger: original,
|
|
657
|
+
normalizedTrigger: normalized
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
function resolveSourceRelative(sourceDir, bare) {
|
|
664
|
+
const joined = sourceDir === "." ? bare : `${sourceDir}/${bare}`;
|
|
665
|
+
return pathPosix.normalize(joined);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// plugins/claude/extractors/slash/index.ts
|
|
669
|
+
var ID2 = "slash";
|
|
670
|
+
var SLASH_RE = /(?<![A-Za-z0-9_/.:?#=&])(\/[a-z0-9][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)/gi;
|
|
671
|
+
var slashExtractor = {
|
|
672
|
+
id: ID2,
|
|
673
|
+
pluginId: "claude",
|
|
674
|
+
kind: "extractor",
|
|
675
|
+
version: "1.0.0",
|
|
676
|
+
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.",
|
|
677
|
+
scope: "body",
|
|
678
|
+
precondition: { provider: ["claude"] },
|
|
679
|
+
extract(ctx) {
|
|
680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
681
|
+
const body = stripCodeBlocks(ctx.body);
|
|
682
|
+
for (const match of body.matchAll(SLASH_RE)) {
|
|
683
|
+
const original = match[1];
|
|
684
|
+
const endIdx = (match.index ?? 0) + match[0].length;
|
|
685
|
+
const nextChar = body[endIdx];
|
|
686
|
+
if (nextChar && /[A-Za-z0-9_/-]/.test(nextChar)) continue;
|
|
687
|
+
const normalized = normalizeTrigger(original);
|
|
688
|
+
if (seen.has(normalized)) continue;
|
|
689
|
+
seen.add(normalized);
|
|
690
|
+
ctx.emitLink({
|
|
691
|
+
source: ctx.node.path,
|
|
692
|
+
target: original,
|
|
693
|
+
kind: "invokes",
|
|
694
|
+
// 0.8: clean `/command` match after code-block strip. The
|
|
695
|
+
// post-match path guard above filters URL / file-path noise,
|
|
696
|
+
// so a hit is unambiguous syntax. Resolution against the live
|
|
697
|
+
// skill / command catalog happens downstream.
|
|
698
|
+
confidence: 0.8,
|
|
699
|
+
sources: [ID2],
|
|
700
|
+
trigger: {
|
|
701
|
+
originalTrigger: original,
|
|
702
|
+
normalizedTrigger: normalized
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
514
709
|
// plugins/gemini/providers/gemini/schemas/agent.schema.json
|
|
515
710
|
var agent_schema_default2 = {
|
|
516
711
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -624,7 +819,87 @@ var geminiProvider = {
|
|
|
624
819
|
classify(path) {
|
|
625
820
|
const lower = path.toLowerCase();
|
|
626
821
|
if (lower.startsWith(".gemini/agents/")) return "agent";
|
|
627
|
-
if (
|
|
822
|
+
if (/^\.gemini\/skills\/[^/]+\/skill\.md$/.test(lower)) return "skill";
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// plugins/openai/providers/openai/schemas/agent.schema.json
|
|
828
|
+
var agent_schema_default3 = {
|
|
829
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
830
|
+
$id: "https://skill-map.dev/providers/openai/v1/frontmatter/agent.schema.json",
|
|
831
|
+
title: "FrontmatterCodexAgent",
|
|
832
|
+
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.",
|
|
833
|
+
allOf: [
|
|
834
|
+
{ $ref: "https://skill-map.dev/spec/v0/frontmatter/base.schema.json" }
|
|
835
|
+
],
|
|
836
|
+
type: "object",
|
|
837
|
+
additionalProperties: true,
|
|
838
|
+
properties: {
|
|
839
|
+
name: {
|
|
840
|
+
type: "string",
|
|
841
|
+
minLength: 1,
|
|
842
|
+
description: "Sub-agent identifier. Conventionally matches the filename stem."
|
|
843
|
+
},
|
|
844
|
+
description: {
|
|
845
|
+
type: "string",
|
|
846
|
+
description: "Short description of when this sub-agent applies. Codex surfaces this in the agent picker; skill-map mirrors it in the card."
|
|
847
|
+
},
|
|
848
|
+
model: {
|
|
849
|
+
type: "string",
|
|
850
|
+
description: "Model identifier (`gpt-4o`, `o3-mini`, etc.) the sub-agent runs against."
|
|
851
|
+
},
|
|
852
|
+
instructions: {
|
|
853
|
+
type: "string",
|
|
854
|
+
description: "Multi-line prompt body (TOML triple-quoted string)."
|
|
855
|
+
},
|
|
856
|
+
tools: {
|
|
857
|
+
type: "array",
|
|
858
|
+
items: { type: "string" },
|
|
859
|
+
description: "Tool ids this sub-agent is allowed to call."
|
|
860
|
+
},
|
|
861
|
+
mcp_servers: {
|
|
862
|
+
type: "array",
|
|
863
|
+
items: { type: "string" },
|
|
864
|
+
description: "MCP server ids attached to this sub-agent (`tools: [mcp__<server>__*]` follows the same pattern as Claude)."
|
|
865
|
+
},
|
|
866
|
+
approval_policy: {
|
|
867
|
+
type: "string",
|
|
868
|
+
enum: ["never", "on-request", "untrusted"],
|
|
869
|
+
description: "Codex approval policy for this sub-agent's destructive operations."
|
|
870
|
+
},
|
|
871
|
+
sandbox_mode: {
|
|
872
|
+
type: "string",
|
|
873
|
+
enum: ["read-only", "workspace-write", "danger-full-access"],
|
|
874
|
+
description: "Codex sandbox mode the sub-agent runs under."
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// plugins/openai/providers/openai/index.ts
|
|
880
|
+
var openaiProvider = {
|
|
881
|
+
id: "openai",
|
|
882
|
+
pluginId: "openai",
|
|
883
|
+
kind: "provider",
|
|
884
|
+
version: "1.0.0",
|
|
885
|
+
description: "Walks OpenAI Codex CLI scope conventions (.codex/agents/*.toml).",
|
|
886
|
+
read: { extensions: [".toml"], parser: "toml" },
|
|
887
|
+
kinds: {
|
|
888
|
+
agent: {
|
|
889
|
+
schema: "./schemas/agent.schema.json",
|
|
890
|
+
schemaJson: agent_schema_default3,
|
|
891
|
+
ui: {
|
|
892
|
+
label: "Codex agents",
|
|
893
|
+
// Codex green; distinct from claude / gemini palettes.
|
|
894
|
+
color: "#22c55e",
|
|
895
|
+
colorDark: "#4ade80",
|
|
896
|
+
icon: { kind: "pi", id: "pi-bolt" }
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
classify(path) {
|
|
901
|
+
const lower = path.toLowerCase();
|
|
902
|
+
if (lower.startsWith(".codex/agents/") && lower.endsWith(".toml")) return "agent";
|
|
628
903
|
return null;
|
|
629
904
|
}
|
|
630
905
|
};
|
|
@@ -664,7 +939,7 @@ var agentSkillsProvider = {
|
|
|
664
939
|
}
|
|
665
940
|
},
|
|
666
941
|
classify(path) {
|
|
667
|
-
if (path.toLowerCase()
|
|
942
|
+
if (/^\.agents\/skills\/[^/]+\/skill\.md$/.test(path.toLowerCase())) return "skill";
|
|
668
943
|
return null;
|
|
669
944
|
}
|
|
670
945
|
};
|
|
@@ -722,9 +997,9 @@ var coreMarkdownProvider = {
|
|
|
722
997
|
};
|
|
723
998
|
|
|
724
999
|
// plugins/core/extractors/annotations/index.ts
|
|
725
|
-
var
|
|
1000
|
+
var ID3 = "annotations";
|
|
726
1001
|
var annotationsExtractor = {
|
|
727
|
-
id:
|
|
1002
|
+
id: ID3,
|
|
728
1003
|
pluginId: "core",
|
|
729
1004
|
kind: "extractor",
|
|
730
1005
|
version: "1.0.0",
|
|
@@ -770,128 +1045,17 @@ function link(source, target) {
|
|
|
770
1045
|
source,
|
|
771
1046
|
target,
|
|
772
1047
|
kind: "supersedes",
|
|
773
|
-
confidence:
|
|
774
|
-
sources: [
|
|
1048
|
+
confidence: 1,
|
|
1049
|
+
sources: [ID3]
|
|
775
1050
|
};
|
|
776
1051
|
}
|
|
777
1052
|
|
|
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
|
-
};
|
|
888
|
-
|
|
889
1053
|
// plugins/core/extractors/external-url-counter/index.ts
|
|
890
|
-
var
|
|
1054
|
+
var ID4 = "external-url-counter";
|
|
891
1055
|
var URL_RE = /https?:\/\/[^\s<>"'`)\]]+/g;
|
|
892
1056
|
var TRAILING_PUNCT = /[.,;:!?]+$/;
|
|
893
1057
|
var externalUrlCounterExtractor = {
|
|
894
|
-
id:
|
|
1058
|
+
id: ID4,
|
|
895
1059
|
pluginId: "core",
|
|
896
1060
|
kind: "extractor",
|
|
897
1061
|
version: "1.0.0",
|
|
@@ -937,8 +1101,8 @@ var externalUrlCounterExtractor = {
|
|
|
937
1101
|
source: ctx.node.path,
|
|
938
1102
|
target: normalized,
|
|
939
1103
|
kind: "references",
|
|
940
|
-
confidence:
|
|
941
|
-
sources: [
|
|
1104
|
+
confidence: 0.3,
|
|
1105
|
+
sources: [ID4],
|
|
942
1106
|
trigger: {
|
|
943
1107
|
originalTrigger: original,
|
|
944
1108
|
normalizedTrigger: normalized
|
|
@@ -984,12 +1148,12 @@ function lineFor(lineStarts, offset) {
|
|
|
984
1148
|
}
|
|
985
1149
|
|
|
986
1150
|
// plugins/core/extractors/markdown-link/index.ts
|
|
987
|
-
import { posix as
|
|
988
|
-
var
|
|
1151
|
+
import { posix as pathPosix2 } from "path";
|
|
1152
|
+
var ID5 = "markdown-link";
|
|
989
1153
|
var LINK_RE = /(?<!!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
990
1154
|
var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
991
1155
|
var markdownLinkExtractor = {
|
|
992
|
-
id:
|
|
1156
|
+
id: ID5,
|
|
993
1157
|
pluginId: "core",
|
|
994
1158
|
kind: "extractor",
|
|
995
1159
|
version: "1.0.0",
|
|
@@ -998,7 +1162,7 @@ var markdownLinkExtractor = {
|
|
|
998
1162
|
extract(ctx) {
|
|
999
1163
|
const seen = /* @__PURE__ */ new Set();
|
|
1000
1164
|
const lineStarts = computeLineStarts2(ctx.body);
|
|
1001
|
-
const sourceDir =
|
|
1165
|
+
const sourceDir = pathPosix2.dirname(ctx.node.path);
|
|
1002
1166
|
for (const match of ctx.body.matchAll(LINK_RE)) {
|
|
1003
1167
|
const original = match[2];
|
|
1004
1168
|
const resolved = resolveTarget(sourceDir, original);
|
|
@@ -1010,8 +1174,8 @@ var markdownLinkExtractor = {
|
|
|
1010
1174
|
source: ctx.node.path,
|
|
1011
1175
|
target: resolved,
|
|
1012
1176
|
kind: "references",
|
|
1013
|
-
confidence:
|
|
1014
|
-
sources: [
|
|
1177
|
+
confidence: 0.95,
|
|
1178
|
+
sources: [ID5],
|
|
1015
1179
|
trigger: {
|
|
1016
1180
|
originalTrigger: original,
|
|
1017
1181
|
normalizedTrigger: resolved
|
|
@@ -1030,7 +1194,7 @@ function resolveTarget(sourceDir, raw) {
|
|
|
1030
1194
|
if (URL_SCHEME_RE.test(trimmed)) return null;
|
|
1031
1195
|
if (trimmed.startsWith("/")) return null;
|
|
1032
1196
|
const joined = sourceDir === "." ? trimmed : `${sourceDir}/${trimmed}`;
|
|
1033
|
-
return
|
|
1197
|
+
return pathPosix2.normalize(joined);
|
|
1034
1198
|
}
|
|
1035
1199
|
function computeLineStarts2(body) {
|
|
1036
1200
|
const starts = [0];
|
|
@@ -1050,47 +1214,61 @@ function lineFor2(lineStarts, offset) {
|
|
|
1050
1214
|
return lo + 1;
|
|
1051
1215
|
}
|
|
1052
1216
|
|
|
1053
|
-
// plugins/core/extractors/
|
|
1054
|
-
var
|
|
1055
|
-
var
|
|
1056
|
-
var
|
|
1057
|
-
id:
|
|
1217
|
+
// plugins/core/extractors/mcp-tools/index.ts
|
|
1218
|
+
var ID6 = "mcp-tools";
|
|
1219
|
+
var MCP_PATTERN = /^mcp__([a-z0-9][a-z0-9_-]*)__[a-z0-9_-]+$/i;
|
|
1220
|
+
var mcpToolsExtractor = {
|
|
1221
|
+
id: ID6,
|
|
1058
1222
|
pluginId: "core",
|
|
1059
1223
|
kind: "extractor",
|
|
1060
1224
|
version: "1.0.0",
|
|
1061
|
-
description: "Detects
|
|
1062
|
-
scope: "
|
|
1225
|
+
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.",
|
|
1226
|
+
scope: "frontmatter",
|
|
1063
1227
|
extract(ctx) {
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1228
|
+
const raw = ctx.frontmatter["tools"];
|
|
1229
|
+
if (!Array.isArray(raw)) return;
|
|
1230
|
+
const servers = collectMcpServers(raw);
|
|
1231
|
+
if (servers.size === 0) return;
|
|
1232
|
+
for (const server of servers) {
|
|
1233
|
+
const mcpPath = `mcp://${server}`;
|
|
1234
|
+
ctx.emitNode({
|
|
1235
|
+
path: mcpPath,
|
|
1236
|
+
kind: "mcp",
|
|
1237
|
+
virtual: true,
|
|
1238
|
+
provider: ctx.node.provider,
|
|
1239
|
+
derivedFrom: [ctx.node.path],
|
|
1240
|
+
frontmatter: { name: server }
|
|
1241
|
+
});
|
|
1074
1242
|
ctx.emitLink({
|
|
1075
1243
|
source: ctx.node.path,
|
|
1076
|
-
target:
|
|
1077
|
-
kind: "
|
|
1078
|
-
confidence:
|
|
1079
|
-
sources: [
|
|
1244
|
+
target: mcpPath,
|
|
1245
|
+
kind: "references",
|
|
1246
|
+
confidence: 0.85,
|
|
1247
|
+
sources: [ID6],
|
|
1080
1248
|
trigger: {
|
|
1081
|
-
originalTrigger:
|
|
1082
|
-
normalizedTrigger:
|
|
1249
|
+
originalTrigger: `mcp__${server}__*`,
|
|
1250
|
+
normalizedTrigger: mcpPath
|
|
1083
1251
|
}
|
|
1084
1252
|
});
|
|
1085
1253
|
}
|
|
1086
1254
|
}
|
|
1087
1255
|
};
|
|
1256
|
+
function collectMcpServers(tools) {
|
|
1257
|
+
const out = /* @__PURE__ */ new Set();
|
|
1258
|
+
for (const t of tools) {
|
|
1259
|
+
if (typeof t !== "string" || t.length === 0) continue;
|
|
1260
|
+
const match = MCP_PATTERN.exec(t);
|
|
1261
|
+
if (!match) continue;
|
|
1262
|
+
out.add(match[1].toLowerCase());
|
|
1263
|
+
}
|
|
1264
|
+
return out;
|
|
1265
|
+
}
|
|
1088
1266
|
|
|
1089
1267
|
// plugins/core/extractors/tools-count/index.ts
|
|
1090
|
-
var
|
|
1268
|
+
var ID7 = "tools-count";
|
|
1091
1269
|
var TOOLTIP_MAX = 255;
|
|
1092
1270
|
var toolsCountExtractor = {
|
|
1093
|
-
id:
|
|
1271
|
+
id: ID7,
|
|
1094
1272
|
pluginId: "core",
|
|
1095
1273
|
kind: "extractor",
|
|
1096
1274
|
version: "1.0.0",
|
|
@@ -1133,9 +1311,9 @@ var ANNOTATION_ORPHAN_TEXTS = {
|
|
|
1133
1311
|
};
|
|
1134
1312
|
|
|
1135
1313
|
// plugins/core/analyzers/annotation-orphan/index.ts
|
|
1136
|
-
var
|
|
1314
|
+
var ID8 = "annotation-orphan";
|
|
1137
1315
|
var annotationOrphanAnalyzer = {
|
|
1138
|
-
id:
|
|
1316
|
+
id: ID8,
|
|
1139
1317
|
pluginId: "core",
|
|
1140
1318
|
kind: "analyzer",
|
|
1141
1319
|
version: "1.0.0",
|
|
@@ -1148,7 +1326,7 @@ var annotationOrphanAnalyzer = {
|
|
|
1148
1326
|
for (const orphan of orphans) {
|
|
1149
1327
|
const expectedMdRelative = orphan.relativePath.endsWith(".sm") ? `${orphan.relativePath.slice(0, -".sm".length)}.md` : `${orphan.relativePath}.md`;
|
|
1150
1328
|
issues.push({
|
|
1151
|
-
analyzerId:
|
|
1329
|
+
analyzerId: ID8,
|
|
1152
1330
|
severity: "warn",
|
|
1153
1331
|
nodeIds: [expectedMdRelative],
|
|
1154
1332
|
message: tx(ANNOTATION_ORPHAN_TEXTS.message, {
|
|
@@ -1185,9 +1363,9 @@ var ANNOTATION_STALE_TEXTS = {
|
|
|
1185
1363
|
};
|
|
1186
1364
|
|
|
1187
1365
|
// plugins/core/analyzers/annotation-stale/index.ts
|
|
1188
|
-
var
|
|
1366
|
+
var ID9 = "annotation-stale";
|
|
1189
1367
|
var annotationStaleAnalyzer = {
|
|
1190
|
-
id:
|
|
1368
|
+
id: ID9,
|
|
1191
1369
|
pluginId: "core",
|
|
1192
1370
|
kind: "analyzer",
|
|
1193
1371
|
version: "1.0.0",
|
|
@@ -1222,7 +1400,7 @@ var annotationStaleAnalyzer = {
|
|
|
1222
1400
|
if (status === "fresh") continue;
|
|
1223
1401
|
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
1402
|
issues.push({
|
|
1225
|
-
analyzerId:
|
|
1403
|
+
analyzerId: ID9,
|
|
1226
1404
|
severity: "warn",
|
|
1227
1405
|
nodeIds: [node.path],
|
|
1228
1406
|
message,
|
|
@@ -1249,7 +1427,7 @@ function tooltipFor(status) {
|
|
|
1249
1427
|
}
|
|
1250
1428
|
|
|
1251
1429
|
// plugins/core/analyzers/broken-ref/index.ts
|
|
1252
|
-
import { posix as
|
|
1430
|
+
import { posix as pathPosix3, resolve } from "path";
|
|
1253
1431
|
|
|
1254
1432
|
// plugins/core/analyzers/broken-ref/text.ts
|
|
1255
1433
|
var BROKEN_REF_TEXTS = {
|
|
@@ -1268,9 +1446,9 @@ var BROKEN_REF_TEXTS = {
|
|
|
1268
1446
|
};
|
|
1269
1447
|
|
|
1270
1448
|
// plugins/core/analyzers/broken-ref/index.ts
|
|
1271
|
-
var
|
|
1449
|
+
var ID10 = "broken-ref";
|
|
1272
1450
|
var brokenRefAnalyzer = {
|
|
1273
|
-
id:
|
|
1451
|
+
id: ID10,
|
|
1274
1452
|
pluginId: "core",
|
|
1275
1453
|
kind: "analyzer",
|
|
1276
1454
|
version: "1.0.0",
|
|
@@ -1339,7 +1517,7 @@ function buildIssue(link2, hintCandidates = []) {
|
|
|
1339
1517
|
trigger: link2.trigger?.normalizedTrigger ?? null
|
|
1340
1518
|
};
|
|
1341
1519
|
const issue = {
|
|
1342
|
-
analyzerId:
|
|
1520
|
+
analyzerId: ID10,
|
|
1343
1521
|
severity: "warn",
|
|
1344
1522
|
nodeIds: [link2.source],
|
|
1345
1523
|
message: tx(BROKEN_REF_TEXTS.message, {
|
|
@@ -1388,8 +1566,8 @@ function indexByNormalizedName(nodes) {
|
|
|
1388
1566
|
return out;
|
|
1389
1567
|
}
|
|
1390
1568
|
function basenameWithoutExt(path) {
|
|
1391
|
-
const base =
|
|
1392
|
-
const ext =
|
|
1569
|
+
const base = pathPosix3.basename(path);
|
|
1570
|
+
const ext = pathPosix3.extname(base);
|
|
1393
1571
|
return ext ? base.slice(0, -ext.length) : base;
|
|
1394
1572
|
}
|
|
1395
1573
|
function indexByBasenameWithoutName(nodes) {
|
|
@@ -1433,9 +1611,9 @@ function isPathStyleLink(link2) {
|
|
|
1433
1611
|
}
|
|
1434
1612
|
|
|
1435
1613
|
// plugins/core/analyzers/contribution-orphan/index.ts
|
|
1436
|
-
var
|
|
1614
|
+
var ID11 = "contribution-orphan";
|
|
1437
1615
|
var contributionOrphanAnalyzer = {
|
|
1438
|
-
id:
|
|
1616
|
+
id: ID11,
|
|
1439
1617
|
pluginId: "core",
|
|
1440
1618
|
kind: "analyzer",
|
|
1441
1619
|
version: "0.0.0",
|
|
@@ -1456,9 +1634,9 @@ var JOB_ORPHAN_FILE_TEXTS = {
|
|
|
1456
1634
|
};
|
|
1457
1635
|
|
|
1458
1636
|
// plugins/core/analyzers/job-orphan-file/index.ts
|
|
1459
|
-
var
|
|
1637
|
+
var ID12 = "job-orphan-file";
|
|
1460
1638
|
var jobOrphanFileAnalyzer = {
|
|
1461
|
-
id:
|
|
1639
|
+
id: ID12,
|
|
1462
1640
|
pluginId: "core",
|
|
1463
1641
|
kind: "analyzer",
|
|
1464
1642
|
version: "1.0.0",
|
|
@@ -1470,7 +1648,7 @@ var jobOrphanFileAnalyzer = {
|
|
|
1470
1648
|
const issues = [];
|
|
1471
1649
|
for (const filePath of orphans) {
|
|
1472
1650
|
issues.push({
|
|
1473
|
-
analyzerId:
|
|
1651
|
+
analyzerId: ID12,
|
|
1474
1652
|
severity: "warn",
|
|
1475
1653
|
nodeIds: [filePath],
|
|
1476
1654
|
message: tx(JOB_ORPHAN_FILE_TEXTS.message, { filePath }),
|
|
@@ -1488,9 +1666,9 @@ var LINK_CONFLICT_TEXTS = {
|
|
|
1488
1666
|
};
|
|
1489
1667
|
|
|
1490
1668
|
// plugins/core/analyzers/link-conflict/index.ts
|
|
1491
|
-
var
|
|
1669
|
+
var ID13 = "link-conflict";
|
|
1492
1670
|
var linkConflictAnalyzer = {
|
|
1493
|
-
id:
|
|
1671
|
+
id: ID13,
|
|
1494
1672
|
pluginId: "core",
|
|
1495
1673
|
kind: "analyzer",
|
|
1496
1674
|
version: "1.0.0",
|
|
@@ -1538,7 +1716,7 @@ var linkConflictAnalyzer = {
|
|
|
1538
1716
|
const [source, target] = key.split("\0");
|
|
1539
1717
|
const kindList = variants.map((v) => v.kind).join(" / ");
|
|
1540
1718
|
issues.push({
|
|
1541
|
-
analyzerId:
|
|
1719
|
+
analyzerId: ID13,
|
|
1542
1720
|
severity: "warn",
|
|
1543
1721
|
nodeIds: [source, target],
|
|
1544
1722
|
message: tx(LINK_CONFLICT_TEXTS.message, {
|
|
@@ -1553,14 +1731,7 @@ var linkConflictAnalyzer = {
|
|
|
1553
1731
|
}
|
|
1554
1732
|
};
|
|
1555
1733
|
function rankConfidence(c) {
|
|
1556
|
-
|
|
1557
|
-
case "high":
|
|
1558
|
-
return 2;
|
|
1559
|
-
case "medium":
|
|
1560
|
-
return 1;
|
|
1561
|
-
case "low":
|
|
1562
|
-
return 0;
|
|
1563
|
-
}
|
|
1734
|
+
return c;
|
|
1564
1735
|
}
|
|
1565
1736
|
|
|
1566
1737
|
// kernel/util/trigger-resolve.ts
|
|
@@ -1612,9 +1783,9 @@ function resolveLinkTargetToPath(link2, nameIndex) {
|
|
|
1612
1783
|
}
|
|
1613
1784
|
|
|
1614
1785
|
// plugins/core/analyzers/link-counts/index.ts
|
|
1615
|
-
var
|
|
1786
|
+
var ID14 = "link-counts";
|
|
1616
1787
|
var linkCountsAnalyzer = {
|
|
1617
|
-
id:
|
|
1788
|
+
id: ID14,
|
|
1618
1789
|
pluginId: "core",
|
|
1619
1790
|
kind: "analyzer",
|
|
1620
1791
|
version: "1.0.0",
|
|
@@ -1678,11 +1849,11 @@ function formatBreakdown(byKind, direction) {
|
|
|
1678
1849
|
}
|
|
1679
1850
|
|
|
1680
1851
|
// plugins/core/analyzers/stability/index.ts
|
|
1681
|
-
var
|
|
1852
|
+
var ID15 = "stability";
|
|
1682
1853
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
1683
1854
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
1684
1855
|
var stabilityAnalyzer = {
|
|
1685
|
-
id:
|
|
1856
|
+
id: ID15,
|
|
1686
1857
|
pluginId: "core",
|
|
1687
1858
|
kind: "analyzer",
|
|
1688
1859
|
version: "1.0.0",
|
|
@@ -1714,7 +1885,7 @@ var stabilityAnalyzer = {
|
|
|
1714
1885
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
1715
1886
|
});
|
|
1716
1887
|
issues.push({
|
|
1717
|
-
analyzerId:
|
|
1888
|
+
analyzerId: ID15,
|
|
1718
1889
|
severity: "info",
|
|
1719
1890
|
nodeIds: [node.path],
|
|
1720
1891
|
message: `Node '${node.path}' is marked experimental: API may change.`,
|
|
@@ -1727,7 +1898,7 @@ var stabilityAnalyzer = {
|
|
|
1727
1898
|
severity: "warn"
|
|
1728
1899
|
});
|
|
1729
1900
|
issues.push({
|
|
1730
|
-
analyzerId:
|
|
1901
|
+
analyzerId: ID15,
|
|
1731
1902
|
severity: "warn",
|
|
1732
1903
|
nodeIds: [node.path],
|
|
1733
1904
|
message: `Node '${node.path}' is marked deprecated: avoid in new code.`,
|
|
@@ -1761,9 +1932,9 @@ var SUPERSEDED_TEXTS = {
|
|
|
1761
1932
|
};
|
|
1762
1933
|
|
|
1763
1934
|
// plugins/core/analyzers/superseded/index.ts
|
|
1764
|
-
var
|
|
1935
|
+
var ID16 = "superseded";
|
|
1765
1936
|
var supersededAnalyzer = {
|
|
1766
|
-
id:
|
|
1937
|
+
id: ID16,
|
|
1767
1938
|
pluginId: "core",
|
|
1768
1939
|
kind: "analyzer",
|
|
1769
1940
|
version: "1.0.0",
|
|
@@ -1775,7 +1946,7 @@ var supersededAnalyzer = {
|
|
|
1775
1946
|
const supersededBy = pickSupersededBy(node);
|
|
1776
1947
|
if (supersededBy === null) continue;
|
|
1777
1948
|
issues.push({
|
|
1778
|
-
analyzerId:
|
|
1949
|
+
analyzerId: ID16,
|
|
1779
1950
|
severity: "info",
|
|
1780
1951
|
nodeIds: [node.path],
|
|
1781
1952
|
message: tx(SUPERSEDED_TEXTS.message, {
|
|
@@ -1824,14 +1995,14 @@ var TRIGGER_COLLISION_TEXTS = {
|
|
|
1824
1995
|
};
|
|
1825
1996
|
|
|
1826
1997
|
// plugins/core/analyzers/trigger-collision/index.ts
|
|
1827
|
-
var
|
|
1998
|
+
var ID17 = "trigger-collision";
|
|
1828
1999
|
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
1829
2000
|
"command",
|
|
1830
2001
|
"skill",
|
|
1831
2002
|
"agent"
|
|
1832
2003
|
]);
|
|
1833
2004
|
var triggerCollisionAnalyzer = {
|
|
1834
|
-
id:
|
|
2005
|
+
id: ID17,
|
|
1835
2006
|
pluginId: "core",
|
|
1836
2007
|
kind: "analyzer",
|
|
1837
2008
|
mode: "deterministic",
|
|
@@ -1930,7 +2101,7 @@ function analyzeTriggerBucket(normalized, claims) {
|
|
|
1930
2101
|
part: parts[0]
|
|
1931
2102
|
});
|
|
1932
2103
|
return {
|
|
1933
|
-
analyzerId:
|
|
2104
|
+
analyzerId: ID17,
|
|
1934
2105
|
severity: "error",
|
|
1935
2106
|
nodeIds,
|
|
1936
2107
|
message,
|
|
@@ -1970,10 +2141,10 @@ var UNKNOWN_FIELD_TEXTS = {
|
|
|
1970
2141
|
};
|
|
1971
2142
|
|
|
1972
2143
|
// plugins/core/analyzers/unknown-field/index.ts
|
|
1973
|
-
var
|
|
2144
|
+
var ID18 = "unknown-field";
|
|
1974
2145
|
var RESERVED_ROOT_BLOCKS = /* @__PURE__ */ new Set(["identity", "annotations", "settings", "audit"]);
|
|
1975
2146
|
var unknownFieldAnalyzer = {
|
|
1976
|
-
id:
|
|
2147
|
+
id: ID18,
|
|
1977
2148
|
pluginId: "core",
|
|
1978
2149
|
kind: "analyzer",
|
|
1979
2150
|
version: "1.0.0",
|
|
@@ -2031,7 +2202,7 @@ var unknownFieldAnalyzer = {
|
|
|
2031
2202
|
for (const key of Object.keys(annotations)) {
|
|
2032
2203
|
if (!knownAnnotationKeys.has(key)) {
|
|
2033
2204
|
issues.push({
|
|
2034
|
-
analyzerId:
|
|
2205
|
+
analyzerId: ID18,
|
|
2035
2206
|
severity: "warn",
|
|
2036
2207
|
nodeIds: [node.path],
|
|
2037
2208
|
message: tx(UNKNOWN_FIELD_TEXTS.unknownAnnotationKey, {
|
|
@@ -2058,7 +2229,7 @@ var unknownFieldAnalyzer = {
|
|
|
2058
2229
|
if (validator(value)) continue;
|
|
2059
2230
|
const errors = (validator.errors ?? []).map((e) => `${e.instancePath || "(root)"} ${e.message ?? e.keyword}`).join("; ");
|
|
2060
2231
|
issues.push({
|
|
2061
|
-
analyzerId:
|
|
2232
|
+
analyzerId: ID18,
|
|
2062
2233
|
severity: "warn",
|
|
2063
2234
|
nodeIds: [node.path],
|
|
2064
2235
|
message: tx(UNKNOWN_FIELD_TEXTS.pluginNamespaceInvalid, {
|
|
@@ -2074,7 +2245,7 @@ var unknownFieldAnalyzer = {
|
|
|
2074
2245
|
continue;
|
|
2075
2246
|
}
|
|
2076
2247
|
issues.push({
|
|
2077
|
-
analyzerId:
|
|
2248
|
+
analyzerId: ID18,
|
|
2078
2249
|
severity: "warn",
|
|
2079
2250
|
nodeIds: [node.path],
|
|
2080
2251
|
message: tx(UNKNOWN_FIELD_TEXTS.unknownRootKey, {
|
|
@@ -2367,9 +2538,9 @@ var VALIDATE_ALL_TEXTS = {
|
|
|
2367
2538
|
};
|
|
2368
2539
|
|
|
2369
2540
|
// plugins/core/analyzers/validate-all/index.ts
|
|
2370
|
-
var
|
|
2541
|
+
var ID19 = "validate-all";
|
|
2371
2542
|
var validateAllAnalyzer = {
|
|
2372
|
-
id:
|
|
2543
|
+
id: ID19,
|
|
2373
2544
|
pluginId: "core",
|
|
2374
2545
|
kind: "analyzer",
|
|
2375
2546
|
version: "1.0.0",
|
|
@@ -2432,7 +2603,7 @@ function collectNodeFindings(v, node, out) {
|
|
|
2432
2603
|
const result = v.validate("node", toNodeForSchema(node));
|
|
2433
2604
|
if (result.ok) return;
|
|
2434
2605
|
out.push({
|
|
2435
|
-
analyzerId:
|
|
2606
|
+
analyzerId: ID19,
|
|
2436
2607
|
severity: "error",
|
|
2437
2608
|
nodeIds: [node.path],
|
|
2438
2609
|
message: tx(VALIDATE_ALL_TEXTS.nodeFailure, {
|
|
@@ -2451,7 +2622,7 @@ function collectFrontmatterBaseFindings(node, out) {
|
|
|
2451
2622
|
if (isMissingStringField(fm, "description")) missing.push("description");
|
|
2452
2623
|
if (missing.length === 0) return;
|
|
2453
2624
|
out.push({
|
|
2454
|
-
analyzerId:
|
|
2625
|
+
analyzerId: ID19,
|
|
2455
2626
|
// `warn` (not `error`) so the default `sm scan` exit code stays
|
|
2456
2627
|
// 0 even when nodes are missing frontmatter base fields. Strict
|
|
2457
2628
|
// mode (`sm scan --strict`) still escalates to exit 1. Matches
|
|
@@ -2473,7 +2644,7 @@ function collectLinkFindings(v, link2, out) {
|
|
|
2473
2644
|
const result = v.validate("link", toLinkForSchema(link2));
|
|
2474
2645
|
if (result.ok) return;
|
|
2475
2646
|
out.push({
|
|
2476
|
-
analyzerId:
|
|
2647
|
+
analyzerId: ID19,
|
|
2477
2648
|
severity: "error",
|
|
2478
2649
|
nodeIds: [link2.source],
|
|
2479
2650
|
message: tx(VALIDATE_ALL_TEXTS.linkFailure, {
|
|
@@ -2541,13 +2712,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
2541
2712
|
};
|
|
2542
2713
|
|
|
2543
2714
|
// plugins/core/formatters/ascii/index.ts
|
|
2544
|
-
var
|
|
2715
|
+
var ID20 = "ascii";
|
|
2545
2716
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
2546
2717
|
var asciiFormatter = {
|
|
2547
|
-
id:
|
|
2718
|
+
id: ID20,
|
|
2548
2719
|
pluginId: "core",
|
|
2549
2720
|
kind: "formatter",
|
|
2550
|
-
formatId:
|
|
2721
|
+
formatId: ID20,
|
|
2551
2722
|
version: "1.0.0",
|
|
2552
2723
|
description: "Renders the scan as plain text, grouped by kind, arrows, and issues. Used by `sm scan --format=ascii`.",
|
|
2553
2724
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
@@ -2642,14 +2813,14 @@ function renderSection(out, kind, group) {
|
|
|
2642
2813
|
}
|
|
2643
2814
|
|
|
2644
2815
|
// plugins/core/formatters/json/index.ts
|
|
2645
|
-
var
|
|
2816
|
+
var ID21 = "json";
|
|
2646
2817
|
var jsonFormatter = {
|
|
2647
|
-
id:
|
|
2818
|
+
id: ID21,
|
|
2648
2819
|
pluginId: "core",
|
|
2649
2820
|
kind: "formatter",
|
|
2650
2821
|
version: "1.0.0",
|
|
2651
2822
|
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:
|
|
2823
|
+
formatId: ID21,
|
|
2653
2824
|
format(ctx) {
|
|
2654
2825
|
if (ctx.scanResult !== void 0) {
|
|
2655
2826
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -2788,10 +2959,10 @@ function resolveSpecRoot2() {
|
|
|
2788
2959
|
}
|
|
2789
2960
|
|
|
2790
2961
|
// plugins/core/actions/bump/index.ts
|
|
2791
|
-
var
|
|
2962
|
+
var ID22 = "bump";
|
|
2792
2963
|
var PLUGIN_ID = "core";
|
|
2793
2964
|
var bumpAction = {
|
|
2794
|
-
id:
|
|
2965
|
+
id: ID22,
|
|
2795
2966
|
pluginId: PLUGIN_ID,
|
|
2796
2967
|
kind: "action",
|
|
2797
2968
|
version: "1.0.0",
|
|
@@ -2850,10 +3021,10 @@ function pickCurrentVersion(overlay) {
|
|
|
2850
3021
|
}
|
|
2851
3022
|
|
|
2852
3023
|
// plugins/core/actions/mark-superseded/index.ts
|
|
2853
|
-
var
|
|
3024
|
+
var ID23 = "mark-superseded";
|
|
2854
3025
|
var PLUGIN_ID2 = "core";
|
|
2855
3026
|
var markSupersededAction = {
|
|
2856
|
-
id:
|
|
3027
|
+
id: ID23,
|
|
2857
3028
|
pluginId: PLUGIN_ID2,
|
|
2858
3029
|
kind: "action",
|
|
2859
3030
|
version: "0.0.0",
|
|
@@ -2963,7 +3134,7 @@ var UPDATE_CHECK_TEXTS = {
|
|
|
2963
3134
|
// package.json
|
|
2964
3135
|
var package_default = {
|
|
2965
3136
|
name: "@skill-map/cli",
|
|
2966
|
-
version: "0.
|
|
3137
|
+
version: "0.34.0",
|
|
2967
3138
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
2968
3139
|
license: "MIT",
|
|
2969
3140
|
type: "module",
|
|
@@ -3044,6 +3215,7 @@ var package_default = {
|
|
|
3044
3215
|
"js-yaml": "4.1.1",
|
|
3045
3216
|
kysely: "0.28.17",
|
|
3046
3217
|
semver: "7.7.4",
|
|
3218
|
+
"smol-toml": "1.6.1",
|
|
3047
3219
|
typanion: "3.14.0",
|
|
3048
3220
|
ws: "8.20.0"
|
|
3049
3221
|
},
|
|
@@ -3339,14 +3511,16 @@ var updateCheckHook = {
|
|
|
3339
3511
|
|
|
3340
3512
|
// plugins/built-ins.ts
|
|
3341
3513
|
var claudeProvider2 = { ...claudeProvider, pluginId: "claude" };
|
|
3514
|
+
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "claude" };
|
|
3515
|
+
var slashExtractor2 = { ...slashExtractor, pluginId: "claude" };
|
|
3342
3516
|
var geminiProvider2 = { ...geminiProvider, pluginId: "gemini" };
|
|
3517
|
+
var openaiProvider2 = { ...openaiProvider, pluginId: "openai" };
|
|
3343
3518
|
var agentSkillsProvider2 = { ...agentSkillsProvider, pluginId: "agent-skills" };
|
|
3344
3519
|
var coreMarkdownProvider2 = { ...coreMarkdownProvider, pluginId: "core" };
|
|
3345
3520
|
var annotationsExtractor2 = { ...annotationsExtractor, pluginId: "core" };
|
|
3346
|
-
var atDirectiveExtractor2 = { ...atDirectiveExtractor, pluginId: "core" };
|
|
3347
3521
|
var externalUrlCounterExtractor2 = { ...externalUrlCounterExtractor, pluginId: "core" };
|
|
3348
3522
|
var markdownLinkExtractor2 = { ...markdownLinkExtractor, pluginId: "core" };
|
|
3349
|
-
var
|
|
3523
|
+
var mcpToolsExtractor2 = { ...mcpToolsExtractor, pluginId: "core" };
|
|
3350
3524
|
var toolsCountExtractor2 = { ...toolsCountExtractor, pluginId: "core" };
|
|
3351
3525
|
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core" };
|
|
3352
3526
|
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core" };
|
|
@@ -3371,7 +3545,9 @@ var builtInBundles = [
|
|
|
3371
3545
|
granularity: "bundle",
|
|
3372
3546
|
description: "Claude Code platform integration. Classifies files under `.claude/{agents,commands,skills}` and parses Claude-flavored frontmatter.",
|
|
3373
3547
|
extensions: [
|
|
3374
|
-
claudeProvider2
|
|
3548
|
+
claudeProvider2,
|
|
3549
|
+
atDirectiveExtractor2,
|
|
3550
|
+
slashExtractor2
|
|
3375
3551
|
]
|
|
3376
3552
|
},
|
|
3377
3553
|
{
|
|
@@ -3382,6 +3558,14 @@ var builtInBundles = [
|
|
|
3382
3558
|
geminiProvider2
|
|
3383
3559
|
]
|
|
3384
3560
|
},
|
|
3561
|
+
{
|
|
3562
|
+
id: "openai",
|
|
3563
|
+
granularity: "bundle",
|
|
3564
|
+
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.",
|
|
3565
|
+
extensions: [
|
|
3566
|
+
openaiProvider2
|
|
3567
|
+
]
|
|
3568
|
+
},
|
|
3385
3569
|
{
|
|
3386
3570
|
id: "agent-skills",
|
|
3387
3571
|
granularity: "bundle",
|
|
@@ -3397,10 +3581,9 @@ var builtInBundles = [
|
|
|
3397
3581
|
extensions: [
|
|
3398
3582
|
coreMarkdownProvider2,
|
|
3399
3583
|
annotationsExtractor2,
|
|
3400
|
-
atDirectiveExtractor2,
|
|
3401
3584
|
externalUrlCounterExtractor2,
|
|
3402
3585
|
markdownLinkExtractor2,
|
|
3403
|
-
|
|
3586
|
+
mcpToolsExtractor2,
|
|
3404
3587
|
toolsCountExtractor2,
|
|
3405
3588
|
annotationOrphanAnalyzer2,
|
|
3406
3589
|
annotationStaleAnalyzer2,
|
|
@@ -4885,7 +5068,7 @@ var AsyncMutex = class {
|
|
|
4885
5068
|
this.#locked = true;
|
|
4886
5069
|
return;
|
|
4887
5070
|
}
|
|
4888
|
-
await new Promise((
|
|
5071
|
+
await new Promise((resolve39) => this.#waiters.push(resolve39));
|
|
4889
5072
|
this.#locked = true;
|
|
4890
5073
|
}
|
|
4891
5074
|
unlock() {
|
|
@@ -5945,11 +6128,6 @@ var LINK_KIND_VALUES = Object.freeze([
|
|
|
5945
6128
|
"mentions",
|
|
5946
6129
|
"supersedes"
|
|
5947
6130
|
]);
|
|
5948
|
-
var CONFIDENCE_VALUES = Object.freeze([
|
|
5949
|
-
"high",
|
|
5950
|
-
"medium",
|
|
5951
|
-
"low"
|
|
5952
|
-
]);
|
|
5953
6131
|
var SEVERITY_VALUES = Object.freeze([
|
|
5954
6132
|
"error",
|
|
5955
6133
|
"warn",
|
|
@@ -5981,7 +6159,7 @@ function isLinkKind(s) {
|
|
|
5981
6159
|
return typeof s === "string" && LINK_KIND_VALUES.includes(s);
|
|
5982
6160
|
}
|
|
5983
6161
|
function isConfidence(s) {
|
|
5984
|
-
return typeof s === "
|
|
6162
|
+
return typeof s === "number" && Number.isFinite(s) && s >= 0 && s <= 1;
|
|
5985
6163
|
}
|
|
5986
6164
|
function isSeverity(s) {
|
|
5987
6165
|
return typeof s === "string" && SEVERITY_VALUES.includes(s);
|
|
@@ -5995,7 +6173,7 @@ function parseLinkKind(s, ctx) {
|
|
|
5995
6173
|
function parseConfidence(s, ctx) {
|
|
5996
6174
|
if (isConfidence(s)) return s;
|
|
5997
6175
|
throw new Error(
|
|
5998
|
-
`Invalid Confidence value ${formatValue(s)} at ${ctx}.
|
|
6176
|
+
`Invalid Confidence value ${formatValue(s)} at ${ctx}. Expected a finite number in [0..1].`
|
|
5999
6177
|
);
|
|
6000
6178
|
}
|
|
6001
6179
|
function parseSeverity(s, ctx) {
|
|
@@ -8794,10 +8972,45 @@ var plainParser = {
|
|
|
8794
8972
|
}
|
|
8795
8973
|
};
|
|
8796
8974
|
|
|
8975
|
+
// plugins/core/parsers/toml/index.ts
|
|
8976
|
+
import { parse as parseToml } from "smol-toml";
|
|
8977
|
+
var tomlParser = {
|
|
8978
|
+
id: "toml",
|
|
8979
|
+
parse(raw, _path) {
|
|
8980
|
+
let parsed = {};
|
|
8981
|
+
const issues = [];
|
|
8982
|
+
try {
|
|
8983
|
+
const doc = parseToml(raw);
|
|
8984
|
+
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
|
8985
|
+
parsed = stripPrototypePollution(doc);
|
|
8986
|
+
}
|
|
8987
|
+
} catch (err) {
|
|
8988
|
+
issues.push({
|
|
8989
|
+
code: "frontmatter-parse-error",
|
|
8990
|
+
message: sanitiseParseErrorMessage2(err)
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8993
|
+
const out = {
|
|
8994
|
+
frontmatterRaw: raw,
|
|
8995
|
+
frontmatter: parsed,
|
|
8996
|
+
body: ""
|
|
8997
|
+
};
|
|
8998
|
+
if (issues.length > 0) {
|
|
8999
|
+
return { ...out, issues };
|
|
9000
|
+
}
|
|
9001
|
+
return out;
|
|
9002
|
+
}
|
|
9003
|
+
};
|
|
9004
|
+
function sanitiseParseErrorMessage2(err) {
|
|
9005
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
9006
|
+
return raw.replace(/[-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
9007
|
+
}
|
|
9008
|
+
|
|
8797
9009
|
// kernel/scan/parsers/index.ts
|
|
8798
9010
|
var REGISTRY = /* @__PURE__ */ new Map([
|
|
8799
9011
|
[frontmatterYamlParser.id, frontmatterYamlParser],
|
|
8800
|
-
[plainParser.id, plainParser]
|
|
9012
|
+
[plainParser.id, plainParser],
|
|
9013
|
+
[tomlParser.id, tomlParser]
|
|
8801
9014
|
]);
|
|
8802
9015
|
var FROZEN_IDS = new Set(REGISTRY.keys());
|
|
8803
9016
|
function getParser(id) {
|
|
@@ -9457,9 +9670,42 @@ function trimRedundantPath(message, primary) {
|
|
|
9457
9670
|
}
|
|
9458
9671
|
|
|
9459
9672
|
// cli/commands/config.ts
|
|
9460
|
-
import { existsSync as
|
|
9673
|
+
import { existsSync as existsSync15 } from "fs";
|
|
9461
9674
|
import { Command as Command4, Option as Option4 } from "clipanion";
|
|
9462
9675
|
|
|
9676
|
+
// core/config/active-provider.ts
|
|
9677
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9678
|
+
import { join as join10 } from "path";
|
|
9679
|
+
var DETECTION_RULES = [
|
|
9680
|
+
{ providerId: "claude", marker: ".claude" },
|
|
9681
|
+
{ providerId: "gemini", marker: ".gemini" },
|
|
9682
|
+
{ providerId: "openai", marker: ".codex" },
|
|
9683
|
+
{ providerId: "openai", marker: "AGENTS.md" },
|
|
9684
|
+
{ providerId: "cursor", marker: ".cursor" }
|
|
9685
|
+
];
|
|
9686
|
+
function resolveActiveProvider(cwd) {
|
|
9687
|
+
const detected = detectProvidersFromFilesystem(cwd);
|
|
9688
|
+
const fromConfig = readConfigValue("activeProvider", { cwd });
|
|
9689
|
+
if (typeof fromConfig === "string" && fromConfig.length > 0) {
|
|
9690
|
+
return { resolved: fromConfig, source: "config", detected };
|
|
9691
|
+
}
|
|
9692
|
+
if (detected.length > 0) {
|
|
9693
|
+
return { resolved: detected[0], source: "autodetect", detected };
|
|
9694
|
+
}
|
|
9695
|
+
return { resolved: null, source: "none", detected };
|
|
9696
|
+
}
|
|
9697
|
+
function detectProvidersFromFilesystem(cwd) {
|
|
9698
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9699
|
+
const out = [];
|
|
9700
|
+
for (const rule of DETECTION_RULES) {
|
|
9701
|
+
if (seen.has(rule.providerId)) continue;
|
|
9702
|
+
if (!existsSync14(join10(cwd, rule.marker))) continue;
|
|
9703
|
+
seen.add(rule.providerId);
|
|
9704
|
+
out.push(rule.providerId);
|
|
9705
|
+
}
|
|
9706
|
+
return out;
|
|
9707
|
+
}
|
|
9708
|
+
|
|
9463
9709
|
// cli/util/path-display.ts
|
|
9464
9710
|
import { isAbsolute as isAbsolute4, relative as pathRelative } from "path";
|
|
9465
9711
|
function relativeIfBelow(path, cwd) {
|
|
@@ -9469,6 +9715,42 @@ function relativeIfBelow(path, cwd) {
|
|
|
9469
9715
|
return rel;
|
|
9470
9716
|
}
|
|
9471
9717
|
|
|
9718
|
+
// cli/util/scan-zone-drop.ts
|
|
9719
|
+
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
9720
|
+
|
|
9721
|
+
// cli/commands/db/shared.ts
|
|
9722
|
+
var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
9723
|
+
function assertSafeIdentifier(name) {
|
|
9724
|
+
if (!SAFE_SQL_IDENTIFIER_RE.test(name)) {
|
|
9725
|
+
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
9728
|
+
|
|
9729
|
+
// cli/util/scan-zone-drop.ts
|
|
9730
|
+
function dropScanZone(dbPath) {
|
|
9731
|
+
const db = new DatabaseSync4(dbPath);
|
|
9732
|
+
try {
|
|
9733
|
+
const rows = db.prepare(
|
|
9734
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'scan\\_%' ESCAPE '\\'"
|
|
9735
|
+
).all();
|
|
9736
|
+
for (const r of rows) assertSafeIdentifier(r.name);
|
|
9737
|
+
if (rows.length === 0) {
|
|
9738
|
+
return { tableCount: 0, droppedTables: [] };
|
|
9739
|
+
}
|
|
9740
|
+
db.exec("BEGIN");
|
|
9741
|
+
for (const { name } of rows) {
|
|
9742
|
+
db.exec(`DELETE FROM "${name}"`);
|
|
9743
|
+
}
|
|
9744
|
+
db.exec("COMMIT");
|
|
9745
|
+
return {
|
|
9746
|
+
tableCount: rows.length,
|
|
9747
|
+
droppedTables: rows.map((r) => r.name)
|
|
9748
|
+
};
|
|
9749
|
+
} finally {
|
|
9750
|
+
db.close();
|
|
9751
|
+
}
|
|
9752
|
+
}
|
|
9753
|
+
|
|
9472
9754
|
// cli/i18n/config.texts.ts
|
|
9473
9755
|
var CONFIG_TEXTS = {
|
|
9474
9756
|
unknownKey: "{{glyph}} Unknown config key: {{key}}\n",
|
|
@@ -9511,6 +9793,20 @@ var CONFIG_TEXTS = {
|
|
|
9511
9793
|
* screen what they just opted into.
|
|
9512
9794
|
*/
|
|
9513
9795
|
privacyGateConfirmed: '{{glyph}} Opening disk access for "{{key}}":\n{{paths}}\n',
|
|
9796
|
+
/**
|
|
9797
|
+
* Confirmation printed after `sm config set activeProvider <id>`
|
|
9798
|
+
* succeeds. The lens change atomically drops the scan_* zone (per
|
|
9799
|
+
* `architecture.md` §Active Provider Lens) so the persisted graph
|
|
9800
|
+
* never carries stale node / link rows from the previous lens. We
|
|
9801
|
+
* surface what was cleared so the operator knows their state was
|
|
9802
|
+
* touched and what to do next.
|
|
9803
|
+
*/
|
|
9804
|
+
lensSwitchedCleared: "{{glyph}} Lens switched. Cleared {{tableCount}} scan table(s): {{tableNames}}.\n {{hint}}\n",
|
|
9805
|
+
lensSwitchedClearedHint: "Run `sm scan` to repopulate the graph under the new lens.",
|
|
9806
|
+
/** Same lens-switch announcement when the DB was empty (no rows to clear). */
|
|
9807
|
+
lensSwitchedEmpty: "{{glyph}} Lens switched. Scan zone was already empty.\n {{hint}}\n",
|
|
9808
|
+
/** Lens switch happened before any `sm scan` ran (no DB file on disk yet). */
|
|
9809
|
+
lensSwitchedNoDb: "{{glyph}} Lens switched. Run `sm scan` to populate the graph under the new lens.\n",
|
|
9514
9810
|
// --- list verb (sectioned human renderer) ----------------------------
|
|
9515
9811
|
/** Section heading: ` General`, ` Scan`, … rendered before its rows. */
|
|
9516
9812
|
listSectionHeader: " {{title}}\n",
|
|
@@ -9548,6 +9844,9 @@ function suggestConfigKey(effective, typed, ansi) {
|
|
|
9548
9844
|
hint: ansi.dim(tx(CONFIG_TEXTS.unknownKeySuggestionHint, { suggestions: formatted }))
|
|
9549
9845
|
});
|
|
9550
9846
|
}
|
|
9847
|
+
var KNOWN_DEFAULTLESS_KEY_RESOLVERS = {
|
|
9848
|
+
activeProvider: (cwd) => resolveActiveProvider(cwd).resolved
|
|
9849
|
+
};
|
|
9551
9850
|
function parseCliValue(raw) {
|
|
9552
9851
|
try {
|
|
9553
9852
|
return JSON.parse(raw);
|
|
@@ -9720,6 +10019,11 @@ function formatValueListHuman(value, ansi) {
|
|
|
9720
10019
|
}
|
|
9721
10020
|
return String(value);
|
|
9722
10021
|
}
|
|
10022
|
+
function resolveConfigGetValue(lookupValue, key, cwd) {
|
|
10023
|
+
if (lookupValue !== void 0) return lookupValue;
|
|
10024
|
+
const runtimeResolver = KNOWN_DEFAULTLESS_KEY_RESOLVERS[key];
|
|
10025
|
+
return runtimeResolver ? runtimeResolver(cwd) : void 0;
|
|
10026
|
+
}
|
|
9723
10027
|
var ConfigGetCommand = class extends SmCommand {
|
|
9724
10028
|
static paths = [["config", "get"]];
|
|
9725
10029
|
static usage = Command4.Usage({
|
|
@@ -9734,16 +10038,14 @@ var ConfigGetCommand = class extends SmCommand {
|
|
|
9734
10038
|
strict = Option4.Boolean("--strict", false);
|
|
9735
10039
|
emitElapsed = false;
|
|
9736
10040
|
async run() {
|
|
9737
|
-
const
|
|
9738
|
-
|
|
9739
|
-
this.context.stderr
|
|
9740
|
-
);
|
|
10041
|
+
const ctx = defaultRuntimeContext();
|
|
10042
|
+
const result = tryLoadConfig({ strict: this.strict, ...ctx }, this.context.stderr);
|
|
9741
10043
|
if (!result.ok) return result.exitCode;
|
|
9742
10044
|
const { effective, warnings } = result.loaded;
|
|
9743
10045
|
for (const w of warnings) this.printer.info(w + "\n");
|
|
9744
10046
|
const lookup = safeGetAtPath(effective, this.key, this.context.stderr);
|
|
9745
10047
|
if (!lookup.ok) return lookup.exitCode;
|
|
9746
|
-
const
|
|
10048
|
+
const value = resolveConfigGetValue(lookup.value, this.key, ctx.cwd);
|
|
9747
10049
|
if (value === void 0) {
|
|
9748
10050
|
const ansi = this.ansiFor("stderr");
|
|
9749
10051
|
this.printer.info(
|
|
@@ -9783,10 +10085,8 @@ var ConfigShowCommand = class extends SmCommand {
|
|
|
9783
10085
|
// the value it gates.
|
|
9784
10086
|
// eslint-disable-next-line complexity
|
|
9785
10087
|
async run() {
|
|
9786
|
-
const
|
|
9787
|
-
|
|
9788
|
-
this.context.stderr
|
|
9789
|
-
);
|
|
10088
|
+
const ctx = defaultRuntimeContext();
|
|
10089
|
+
const result = tryLoadConfig({ strict: this.strict, ...ctx }, this.context.stderr);
|
|
9790
10090
|
if (!result.ok) return result.exitCode;
|
|
9791
10091
|
const { effective, sources, warnings } = result.loaded;
|
|
9792
10092
|
for (const w of warnings) this.printer.info(w + "\n");
|
|
@@ -9809,6 +10109,12 @@ var ConfigShowCommand = class extends SmCommand {
|
|
|
9809
10109
|
}
|
|
9810
10110
|
throw err;
|
|
9811
10111
|
}
|
|
10112
|
+
if (value === void 0) {
|
|
10113
|
+
const runtimeResolver = KNOWN_DEFAULTLESS_KEY_RESOLVERS[this.key];
|
|
10114
|
+
if (runtimeResolver) {
|
|
10115
|
+
value = runtimeResolver(ctx.cwd);
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
9812
10118
|
if (value === void 0) {
|
|
9813
10119
|
this.printer.info(tx(CONFIG_TEXTS.unknownKey, { glyph: errGlyphShow, key: this.key }));
|
|
9814
10120
|
return ExitCode.NotFound;
|
|
@@ -9962,8 +10268,44 @@ var ConfigSetCommand = class extends SmCommand {
|
|
|
9962
10268
|
)
|
|
9963
10269
|
})
|
|
9964
10270
|
);
|
|
10271
|
+
if (this.key === "activeProvider") {
|
|
10272
|
+
this.announceLensSwitch(ctx.cwd, ansi);
|
|
10273
|
+
}
|
|
9965
10274
|
return ExitCode.Ok;
|
|
9966
10275
|
}
|
|
10276
|
+
/**
|
|
10277
|
+
* Side effect of `sm config set activeProvider <id>`, atomically
|
|
10278
|
+
* drops the `scan_*` zone so the persisted graph never reflects the
|
|
10279
|
+
* wrong lens (see `architecture.md` §Active Provider Lens). The drop
|
|
10280
|
+
* is non-destructive of `state_*` / `config_*` rows; the operator
|
|
10281
|
+
* runs `sm scan` next to repopulate.
|
|
10282
|
+
*
|
|
10283
|
+
* Silent when no DB file exists on disk yet (fresh project that has
|
|
10284
|
+
* never run `sm scan`), the lens just gets set and the next scan
|
|
10285
|
+
* uses it.
|
|
10286
|
+
*/
|
|
10287
|
+
announceLensSwitch(cwd, ansi) {
|
|
10288
|
+
const dbPath = resolveDbPath({ db: void 0, cwd });
|
|
10289
|
+
const okGlyph = ansi.green("\u2713");
|
|
10290
|
+
if (!existsSync15(dbPath)) {
|
|
10291
|
+
this.printer.info(tx(CONFIG_TEXTS.lensSwitchedNoDb, { glyph: okGlyph }));
|
|
10292
|
+
return;
|
|
10293
|
+
}
|
|
10294
|
+
const result = dropScanZone(dbPath);
|
|
10295
|
+
const hint = ansi.dim(CONFIG_TEXTS.lensSwitchedClearedHint);
|
|
10296
|
+
if (result.tableCount === 0) {
|
|
10297
|
+
this.printer.info(tx(CONFIG_TEXTS.lensSwitchedEmpty, { glyph: okGlyph, hint }));
|
|
10298
|
+
return;
|
|
10299
|
+
}
|
|
10300
|
+
this.printer.info(
|
|
10301
|
+
tx(CONFIG_TEXTS.lensSwitchedCleared, {
|
|
10302
|
+
glyph: okGlyph,
|
|
10303
|
+
tableCount: result.tableCount,
|
|
10304
|
+
tableNames: result.droppedTables.join(", "),
|
|
10305
|
+
hint
|
|
10306
|
+
})
|
|
10307
|
+
);
|
|
10308
|
+
}
|
|
9967
10309
|
};
|
|
9968
10310
|
var ConfigResetCommand = class extends SmCommand {
|
|
9969
10311
|
static paths = [["config", "reset"]];
|
|
@@ -9985,7 +10327,7 @@ var ConfigResetCommand = class extends SmCommand {
|
|
|
9985
10327
|
const path = targetSettingsPath2(target, ctx.cwd);
|
|
9986
10328
|
const ansi = this.ansiFor("stdout");
|
|
9987
10329
|
const okGlyph = ansi.green("\u2713");
|
|
9988
|
-
if (!
|
|
10330
|
+
if (!existsSync15(path)) {
|
|
9989
10331
|
this.printer.data(
|
|
9990
10332
|
tx(CONFIG_TEXTS.unsetNoOverride, {
|
|
9991
10333
|
glyph: okGlyph,
|
|
@@ -10060,16 +10402,16 @@ var CONFIG_COMMANDS = [
|
|
|
10060
10402
|
];
|
|
10061
10403
|
|
|
10062
10404
|
// cli/commands/conformance.ts
|
|
10063
|
-
import { existsSync as
|
|
10405
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
10064
10406
|
import { dirname as dirname12, resolve as resolve21 } from "path";
|
|
10065
10407
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10066
10408
|
import { Command as Command5, Option as Option5 } from "clipanion";
|
|
10067
10409
|
|
|
10068
10410
|
// conformance/index.ts
|
|
10069
10411
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
10070
|
-
import { cpSync, existsSync as
|
|
10412
|
+
import { cpSync, existsSync as existsSync16, mkdtempSync, readdirSync as readdirSync5, readFileSync as readFileSync14, rmSync, statSync as statSync3 } from "fs";
|
|
10071
10413
|
import { tmpdir } from "os";
|
|
10072
|
-
import { isAbsolute as isAbsolute5, join as
|
|
10414
|
+
import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve19 } from "path";
|
|
10073
10415
|
|
|
10074
10416
|
// conformance/i18n/runner.texts.ts
|
|
10075
10417
|
var CONFORMANCE_RUNNER_TEXTS = {
|
|
@@ -10105,9 +10447,9 @@ function disableEnv(setup) {
|
|
|
10105
10447
|
function runConformanceCase(options) {
|
|
10106
10448
|
const raw = readFileSync14(options.casePath, "utf8");
|
|
10107
10449
|
const c = JSON.parse(raw);
|
|
10108
|
-
const fixturesRoot = options.fixturesRoot ??
|
|
10450
|
+
const fixturesRoot = options.fixturesRoot ?? join11(options.specRoot, "conformance", "fixtures");
|
|
10109
10451
|
const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 32);
|
|
10110
|
-
const scope = mkdtempSync(
|
|
10452
|
+
const scope = mkdtempSync(join11(tmpdir(), `sm-conformance-${safeId}-`));
|
|
10111
10453
|
const setupEnv = disableEnv(c.setup);
|
|
10112
10454
|
try {
|
|
10113
10455
|
const priorFailure = runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv);
|
|
@@ -10179,9 +10521,9 @@ function replaceFixture(scope, fixturesRoot, fixture) {
|
|
|
10179
10521
|
assertContained2(fixturesRoot, fixture, "fixture");
|
|
10180
10522
|
for (const entry of readdirSync5(scope)) {
|
|
10181
10523
|
if (entry === KERNEL_SKILL_MAP_DIR) continue;
|
|
10182
|
-
rmSync(
|
|
10524
|
+
rmSync(join11(scope, entry), { recursive: true, force: true });
|
|
10183
10525
|
}
|
|
10184
|
-
const src =
|
|
10526
|
+
const src = join11(fixturesRoot, fixture);
|
|
10185
10527
|
cpSync(src, scope, { recursive: true });
|
|
10186
10528
|
}
|
|
10187
10529
|
function assertContained2(root, rel, label) {
|
|
@@ -10218,7 +10560,7 @@ function evaluateAssertion(a, ctx) {
|
|
|
10218
10560
|
return { ok: false, type: a.type, reason: formatErrorMessage(err) };
|
|
10219
10561
|
}
|
|
10220
10562
|
const abs = resolve19(ctx.scope, a.path);
|
|
10221
|
-
return
|
|
10563
|
+
return existsSync16(abs) ? { ok: true, type: a.type } : {
|
|
10222
10564
|
ok: false,
|
|
10223
10565
|
type: a.type,
|
|
10224
10566
|
reason: tx(CONFORMANCE_RUNNER_TEXTS.fileNotFound, { path: a.path })
|
|
@@ -10231,9 +10573,9 @@ function evaluateAssertion(a, ctx) {
|
|
|
10231
10573
|
} catch (err) {
|
|
10232
10574
|
return { ok: false, type: a.type, reason: formatErrorMessage(err) };
|
|
10233
10575
|
}
|
|
10234
|
-
const fixturePath =
|
|
10576
|
+
const fixturePath = join11(ctx.fixturesRoot, a.fixture);
|
|
10235
10577
|
const targetPath = resolve19(ctx.scope, a.path);
|
|
10236
|
-
if (!
|
|
10578
|
+
if (!existsSync16(targetPath)) {
|
|
10237
10579
|
return {
|
|
10238
10580
|
ok: false,
|
|
10239
10581
|
type: a.type,
|
|
@@ -10414,7 +10756,7 @@ var CONFORMANCE_TEXTS = {
|
|
|
10414
10756
|
};
|
|
10415
10757
|
|
|
10416
10758
|
// cli/util/conformance-scopes.ts
|
|
10417
|
-
import { existsSync as
|
|
10759
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
10418
10760
|
import { dirname as dirname11, resolve as resolve20 } from "path";
|
|
10419
10761
|
import { createRequire as createRequire6 } from "module";
|
|
10420
10762
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -10434,7 +10776,7 @@ function resolveCliWorkspaceRoot() {
|
|
|
10434
10776
|
let cursor = here;
|
|
10435
10777
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
10436
10778
|
const candidate = resolve20(cursor, "plugins");
|
|
10437
|
-
if (
|
|
10779
|
+
if (existsSync17(candidate) && statSync4(candidate).isDirectory()) {
|
|
10438
10780
|
return cursor;
|
|
10439
10781
|
}
|
|
10440
10782
|
const parent = dirname11(cursor);
|
|
@@ -10454,7 +10796,7 @@ function collectProviderScopes(specRoot) {
|
|
|
10454
10796
|
return out;
|
|
10455
10797
|
}
|
|
10456
10798
|
const pluginsRoot = resolve20(workspaceRoot, "plugins");
|
|
10457
|
-
if (!
|
|
10799
|
+
if (!existsSync17(pluginsRoot)) return out;
|
|
10458
10800
|
for (const bundleEntry of readdirSync6(pluginsRoot)) {
|
|
10459
10801
|
const bundleDir = resolve20(pluginsRoot, bundleEntry);
|
|
10460
10802
|
if (!isDir(bundleDir)) continue;
|
|
@@ -10466,7 +10808,7 @@ function collectProviderScopes(specRoot) {
|
|
|
10466
10808
|
}
|
|
10467
10809
|
function isDir(path) {
|
|
10468
10810
|
try {
|
|
10469
|
-
return
|
|
10811
|
+
return existsSync17(path) && statSync4(path).isDirectory();
|
|
10470
10812
|
} catch {
|
|
10471
10813
|
return false;
|
|
10472
10814
|
}
|
|
@@ -10476,10 +10818,10 @@ function collectBundleProviderScopes(providersRoot, specRoot, out) {
|
|
|
10476
10818
|
const providerDir = resolve20(providersRoot, entry);
|
|
10477
10819
|
if (!isDir(providerDir)) continue;
|
|
10478
10820
|
const conformanceDir = resolve20(providerDir, "conformance");
|
|
10479
|
-
if (!
|
|
10821
|
+
if (!existsSync17(conformanceDir)) continue;
|
|
10480
10822
|
const casesDir = resolve20(conformanceDir, "cases");
|
|
10481
10823
|
const fixturesDir = resolve20(conformanceDir, "fixtures");
|
|
10482
|
-
if (!
|
|
10824
|
+
if (!existsSync17(casesDir) || !existsSync17(fixturesDir)) continue;
|
|
10483
10825
|
out.push({
|
|
10484
10826
|
id: `provider:${entry}`,
|
|
10485
10827
|
kind: "provider",
|
|
@@ -10517,7 +10859,7 @@ function selectConformanceScopes(scope) {
|
|
|
10517
10859
|
return [match];
|
|
10518
10860
|
}
|
|
10519
10861
|
function listCaseFiles(scope) {
|
|
10520
|
-
if (!
|
|
10862
|
+
if (!existsSync17(scope.casesDir)) return [];
|
|
10521
10863
|
return readdirSync6(scope.casesDir).filter((entry) => entry.endsWith(".json")).sort().map((entry) => resolve20(scope.casesDir, entry));
|
|
10522
10864
|
}
|
|
10523
10865
|
|
|
@@ -10536,7 +10878,7 @@ function resolveBinary() {
|
|
|
10536
10878
|
let cursor = here;
|
|
10537
10879
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
10538
10880
|
const candidate = resolve21(cursor, "bin", "sm.js");
|
|
10539
|
-
if (
|
|
10881
|
+
if (existsSync18(candidate)) return candidate;
|
|
10540
10882
|
const parent = dirname12(cursor);
|
|
10541
10883
|
if (parent === cursor) break;
|
|
10542
10884
|
cursor = parent;
|
|
@@ -10602,7 +10944,7 @@ var ConformanceRunCommand = class extends SmCommand {
|
|
|
10602
10944
|
return ExitCode.Error;
|
|
10603
10945
|
}
|
|
10604
10946
|
const binary = resolveBinary();
|
|
10605
|
-
if (!
|
|
10947
|
+
if (!existsSync18(binary)) {
|
|
10606
10948
|
if (this.json) {
|
|
10607
10949
|
this.#emitJsonError(
|
|
10608
10950
|
"internal",
|
|
@@ -10794,7 +11136,7 @@ function writeStreamSnippet(stream, header, text) {
|
|
|
10794
11136
|
var CONFORMANCE_COMMANDS = [ConformanceRunCommand];
|
|
10795
11137
|
|
|
10796
11138
|
// cli/commands/db/backup.ts
|
|
10797
|
-
import { dirname as dirname13, join as
|
|
11139
|
+
import { dirname as dirname13, join as join12, resolve as resolve22 } from "path";
|
|
10798
11140
|
import { Command as Command6, Option as Option6 } from "clipanion";
|
|
10799
11141
|
|
|
10800
11142
|
// cli/i18n/db.texts.ts
|
|
@@ -10880,7 +11222,7 @@ var DbBackupCommand = class extends SmCommand {
|
|
|
10880
11222
|
const exit = requireDbOrExit(path, this.context.stderr);
|
|
10881
11223
|
if (exit !== null) return exit;
|
|
10882
11224
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
10883
|
-
const outPath = this.out ? resolve22(this.out) :
|
|
11225
|
+
const outPath = this.out ? resolve22(this.out) : join12(dirname13(path), "backups", `${ts}.db`);
|
|
10884
11226
|
await withSqlite({ databasePath: path, autoMigrate: false }, async (storage) => {
|
|
10885
11227
|
storage.migrations.writeBackup(outPath);
|
|
10886
11228
|
});
|
|
@@ -10991,28 +11333,18 @@ var DbRestoreCommand = class extends SmCommand {
|
|
|
10991
11333
|
this.printer.data(
|
|
10992
11334
|
tx(DB_TEXTS.restoreDone, {
|
|
10993
11335
|
glyph: ansi.green("\u2713"),
|
|
10994
|
-
sourcePath: relativeIfBelow(sourcePath, cwd),
|
|
10995
|
-
target: relativeIfBelow(target, cwd)
|
|
10996
|
-
})
|
|
10997
|
-
);
|
|
10998
|
-
return ExitCode.Ok;
|
|
10999
|
-
}
|
|
11000
|
-
};
|
|
11001
|
-
|
|
11002
|
-
// cli/commands/db/reset.ts
|
|
11003
|
-
import { rm as rm2 } from "fs/promises";
|
|
11004
|
-
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
11005
|
-
import { Command as Command8, Option as Option8 } from "clipanion";
|
|
11006
|
-
|
|
11007
|
-
// cli/commands/db/shared.ts
|
|
11008
|
-
var SAFE_SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
11009
|
-
function assertSafeIdentifier(name) {
|
|
11010
|
-
if (!SAFE_SQL_IDENTIFIER_RE.test(name)) {
|
|
11011
|
-
throw new Error(`refusing to operate on non-identifier table name: ${JSON.stringify(name)}`);
|
|
11336
|
+
sourcePath: relativeIfBelow(sourcePath, cwd),
|
|
11337
|
+
target: relativeIfBelow(target, cwd)
|
|
11338
|
+
})
|
|
11339
|
+
);
|
|
11340
|
+
return ExitCode.Ok;
|
|
11012
11341
|
}
|
|
11013
|
-
}
|
|
11342
|
+
};
|
|
11014
11343
|
|
|
11015
11344
|
// cli/commands/db/reset.ts
|
|
11345
|
+
import { rm as rm2 } from "fs/promises";
|
|
11346
|
+
import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
|
|
11347
|
+
import { Command as Command8, Option as Option8 } from "clipanion";
|
|
11016
11348
|
var DbResetCommand = class extends SmCommand {
|
|
11017
11349
|
static paths = [["db", "reset"]];
|
|
11018
11350
|
static usage = Command8.Usage({
|
|
@@ -11094,7 +11426,7 @@ var DbResetCommand = class extends SmCommand {
|
|
|
11094
11426
|
return ExitCode.Error;
|
|
11095
11427
|
}
|
|
11096
11428
|
}
|
|
11097
|
-
const db = new
|
|
11429
|
+
const db = new DatabaseSync5(path);
|
|
11098
11430
|
try {
|
|
11099
11431
|
const rows = db.prepare(
|
|
11100
11432
|
"SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE 'scan\\_%' ESCAPE '\\'" + (this.state ? " OR name LIKE 'state\\_%' ESCAPE '\\'" : "") + ")"
|
|
@@ -11237,7 +11569,7 @@ var DbBrowserCommand = class extends SmCommand {
|
|
|
11237
11569
|
};
|
|
11238
11570
|
|
|
11239
11571
|
// cli/commands/db/dump.ts
|
|
11240
|
-
import { DatabaseSync as
|
|
11572
|
+
import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
|
|
11241
11573
|
import { Command as Command11, Option as Option10 } from "clipanion";
|
|
11242
11574
|
var DbDumpCommand = class extends SmCommand {
|
|
11243
11575
|
static paths = [["db", "dump"]];
|
|
@@ -11279,7 +11611,7 @@ var DbDumpCommand = class extends SmCommand {
|
|
|
11279
11611
|
}
|
|
11280
11612
|
};
|
|
11281
11613
|
function dumpDatabaseToStream(dbPath, out, tables) {
|
|
11282
|
-
const db = new
|
|
11614
|
+
const db = new DatabaseSync6(dbPath, { readOnly: true });
|
|
11283
11615
|
try {
|
|
11284
11616
|
out.write("PRAGMA foreign_keys=OFF;\n");
|
|
11285
11617
|
out.write("BEGIN TRANSACTION;\n");
|
|
@@ -12544,7 +12876,7 @@ function registeredVerbPaths(cli2) {
|
|
|
12544
12876
|
// cli/commands/hooks.ts
|
|
12545
12877
|
import {
|
|
12546
12878
|
chmodSync,
|
|
12547
|
-
existsSync as
|
|
12879
|
+
existsSync as existsSync19,
|
|
12548
12880
|
mkdirSync as mkdirSync5,
|
|
12549
12881
|
readFileSync as readFileSync17,
|
|
12550
12882
|
statSync as statSync5,
|
|
@@ -12643,7 +12975,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
12643
12975
|
}
|
|
12644
12976
|
const hooksDir = resolve26(repoRoot, ".git", "hooks");
|
|
12645
12977
|
const hookPath = resolve26(hooksDir, "pre-commit");
|
|
12646
|
-
const existing =
|
|
12978
|
+
const existing = existsSync19(hookPath) ? readFileSync17(hookPath, "utf8") : null;
|
|
12647
12979
|
const planned2 = computePlannedHookContent(existing);
|
|
12648
12980
|
if (planned2.kind === "already-installed") {
|
|
12649
12981
|
this.printer.info(tx(HOOKS_TEXTS.alreadyInstalled, { glyph: okGlyph, hookPath }));
|
|
@@ -12669,7 +13001,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
12669
13001
|
return ExitCode.Ok;
|
|
12670
13002
|
}
|
|
12671
13003
|
try {
|
|
12672
|
-
if (!
|
|
13004
|
+
if (!existsSync19(hooksDir)) mkdirSync5(hooksDir, { recursive: true });
|
|
12673
13005
|
writeFileSync2(hookPath, planned2.content, { encoding: "utf8" });
|
|
12674
13006
|
ensureExecutableBit(hookPath);
|
|
12675
13007
|
} catch (err) {
|
|
@@ -12700,7 +13032,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
12700
13032
|
function findGitRepoRoot(cwd) {
|
|
12701
13033
|
let current = cwd;
|
|
12702
13034
|
while (true) {
|
|
12703
|
-
if (
|
|
13035
|
+
if (existsSync19(resolve26(current, ".git"))) return current;
|
|
12704
13036
|
const parent = dirname16(current);
|
|
12705
13037
|
if (parent === current) return null;
|
|
12706
13038
|
current = parent;
|
|
@@ -12722,11 +13054,12 @@ var HOOKS_COMMANDS = [HooksInstallCommand];
|
|
|
12722
13054
|
|
|
12723
13055
|
// cli/commands/init.ts
|
|
12724
13056
|
import { mkdir as mkdir3, readFile as readFile2, writeFile } from "fs/promises";
|
|
12725
|
-
import { join as
|
|
13057
|
+
import { join as join17 } from "path";
|
|
12726
13058
|
import { Command as Command17, Option as Option16 } from "clipanion";
|
|
12727
13059
|
|
|
12728
13060
|
// kernel/orchestrator/index.ts
|
|
12729
|
-
import { existsSync as
|
|
13061
|
+
import { existsSync as existsSync22, statSync as statSync7 } from "fs";
|
|
13062
|
+
import { isAbsolute as isAbsolute7, resolve as resolve27 } from "path";
|
|
12730
13063
|
import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
|
|
12731
13064
|
import cl100k_base from "js-tiktoken/ranks/cl100k_base";
|
|
12732
13065
|
|
|
@@ -12745,12 +13078,22 @@ var ORCHESTRATOR_TEXTS = {
|
|
|
12745
13078
|
runScanRootMissing: "runScan: root path '{{root}}' does not exist or is not a directory"
|
|
12746
13079
|
};
|
|
12747
13080
|
|
|
13081
|
+
// kernel/types.ts
|
|
13082
|
+
var ConfidenceTier = Object.freeze({
|
|
13083
|
+
HIGH: 0.9,
|
|
13084
|
+
MEDIUM: 0.6,
|
|
13085
|
+
LOW: 0.3
|
|
13086
|
+
});
|
|
13087
|
+
|
|
12748
13088
|
// kernel/orchestrator/extractors.ts
|
|
12749
13089
|
async function runExtractorsForNode(opts) {
|
|
12750
13090
|
const internalLinks = [];
|
|
12751
13091
|
const externalLinks = [];
|
|
12752
13092
|
const enrichmentBuffer = /* @__PURE__ */ new Map();
|
|
12753
13093
|
const contributions = [];
|
|
13094
|
+
const signals = [];
|
|
13095
|
+
const virtualNodes = [];
|
|
13096
|
+
const virtualNodePaths = /* @__PURE__ */ new Set();
|
|
12754
13097
|
const validators = loadSchemaValidators();
|
|
12755
13098
|
for (const extractor of opts.extractors) {
|
|
12756
13099
|
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
@@ -12822,6 +13165,18 @@ async function runExtractorsForNode(opts) {
|
|
|
12822
13165
|
emittedAt: Date.now()
|
|
12823
13166
|
});
|
|
12824
13167
|
};
|
|
13168
|
+
const emitSignal = (signal) => {
|
|
13169
|
+
const validated = validateSignal(extractor, signal, opts.emitter);
|
|
13170
|
+
if (!validated) return;
|
|
13171
|
+
signals.push(validated);
|
|
13172
|
+
};
|
|
13173
|
+
const emitNode = (emitted) => {
|
|
13174
|
+
if (virtualNodePaths.has(emitted.path)) return;
|
|
13175
|
+
const node = buildVirtualNode(extractor, emitted, opts.emitter);
|
|
13176
|
+
if (!node) return;
|
|
13177
|
+
virtualNodePaths.add(node.path);
|
|
13178
|
+
virtualNodes.push(node);
|
|
13179
|
+
};
|
|
12825
13180
|
const store = opts.pluginStores?.get(extractor.pluginId);
|
|
12826
13181
|
const ctx = buildExtractorContext(
|
|
12827
13182
|
extractor,
|
|
@@ -12831,6 +13186,8 @@ async function runExtractorsForNode(opts) {
|
|
|
12831
13186
|
emitLink,
|
|
12832
13187
|
enrichNode,
|
|
12833
13188
|
emitContribution,
|
|
13189
|
+
emitSignal,
|
|
13190
|
+
emitNode,
|
|
12834
13191
|
store
|
|
12835
13192
|
);
|
|
12836
13193
|
await extractor.extract(ctx);
|
|
@@ -12839,7 +13196,9 @@ async function runExtractorsForNode(opts) {
|
|
|
12839
13196
|
internalLinks,
|
|
12840
13197
|
externalLinks,
|
|
12841
13198
|
enrichments: Array.from(enrichmentBuffer.values()),
|
|
12842
|
-
contributions
|
|
13199
|
+
contributions,
|
|
13200
|
+
signals,
|
|
13201
|
+
virtualNodes
|
|
12843
13202
|
};
|
|
12844
13203
|
}
|
|
12845
13204
|
function readDeclaredContributions(extension) {
|
|
@@ -12864,7 +13223,7 @@ function emitExtensionError(emitter, qualifiedId2, nodePath, data) {
|
|
|
12864
13223
|
})
|
|
12865
13224
|
);
|
|
12866
13225
|
}
|
|
12867
|
-
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, store) {
|
|
13226
|
+
function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enrichNode, emitContribution, emitSignal, emitNode, store) {
|
|
12868
13227
|
const scope = extractor.scope ?? "both";
|
|
12869
13228
|
const settings = extractor.resolvedSettings ?? {};
|
|
12870
13229
|
return {
|
|
@@ -12875,9 +13234,62 @@ function buildExtractorContext(extractor, node, body, frontmatter, emitLink, enr
|
|
|
12875
13234
|
emitLink,
|
|
12876
13235
|
enrichNode,
|
|
12877
13236
|
emitContribution,
|
|
13237
|
+
emitSignal,
|
|
13238
|
+
emitNode,
|
|
12878
13239
|
...store !== void 0 ? { store } : {}
|
|
12879
13240
|
};
|
|
12880
13241
|
}
|
|
13242
|
+
var VIRTUAL_NODE_PLACEHOLDER_HASH = "0".repeat(64);
|
|
13243
|
+
function buildVirtualNode(extractor, emitted, emitter) {
|
|
13244
|
+
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
13245
|
+
if (typeof emitted.path !== "string" || emitted.path.length === 0) {
|
|
13246
|
+
emitter.emit(
|
|
13247
|
+
makeEvent("extension.error", {
|
|
13248
|
+
kind: "virtual-node-missing-path",
|
|
13249
|
+
extensionId: qualifiedId2,
|
|
13250
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node with no path; dropped.`
|
|
13251
|
+
})
|
|
13252
|
+
);
|
|
13253
|
+
return null;
|
|
13254
|
+
}
|
|
13255
|
+
if (typeof emitted.kind !== "string" || emitted.kind.length === 0) {
|
|
13256
|
+
emitter.emit(
|
|
13257
|
+
makeEvent("extension.error", {
|
|
13258
|
+
kind: "virtual-node-missing-kind",
|
|
13259
|
+
extensionId: qualifiedId2,
|
|
13260
|
+
virtualPath: emitted.path,
|
|
13261
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node at '${emitted.path}' with no kind; dropped.`
|
|
13262
|
+
})
|
|
13263
|
+
);
|
|
13264
|
+
return null;
|
|
13265
|
+
}
|
|
13266
|
+
if (!Array.isArray(emitted.derivedFrom) || emitted.derivedFrom.length === 0) {
|
|
13267
|
+
emitter.emit(
|
|
13268
|
+
makeEvent("extension.error", {
|
|
13269
|
+
kind: "virtual-node-missing-derived-from",
|
|
13270
|
+
extensionId: qualifiedId2,
|
|
13271
|
+
virtualPath: emitted.path,
|
|
13272
|
+
message: `Extractor ${qualifiedId2} emitted a virtual node at '${emitted.path}' with empty derivedFrom; dropped.`
|
|
13273
|
+
})
|
|
13274
|
+
);
|
|
13275
|
+
return null;
|
|
13276
|
+
}
|
|
13277
|
+
const node = {
|
|
13278
|
+
path: emitted.path,
|
|
13279
|
+
kind: emitted.kind,
|
|
13280
|
+
provider: emitted.provider,
|
|
13281
|
+
bodyHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
13282
|
+
frontmatterHash: VIRTUAL_NODE_PLACEHOLDER_HASH,
|
|
13283
|
+
bytes: { frontmatter: 0, body: 0, total: 0 },
|
|
13284
|
+
linksOutCount: 0,
|
|
13285
|
+
linksInCount: 0,
|
|
13286
|
+
externalRefsCount: 0,
|
|
13287
|
+
virtual: true,
|
|
13288
|
+
derivedFrom: [...emitted.derivedFrom]
|
|
13289
|
+
};
|
|
13290
|
+
if (emitted.frontmatter) node.frontmatter = emitted.frontmatter;
|
|
13291
|
+
return node;
|
|
13292
|
+
}
|
|
12881
13293
|
function validateLink(extractor, link2, emitter) {
|
|
12882
13294
|
const knownKinds = ["invokes", "references", "mentions", "supersedes"];
|
|
12883
13295
|
if (!knownKinds.includes(link2.kind)) {
|
|
@@ -12898,9 +13310,68 @@ function validateLink(extractor, link2, emitter) {
|
|
|
12898
13310
|
);
|
|
12899
13311
|
return null;
|
|
12900
13312
|
}
|
|
12901
|
-
const
|
|
13313
|
+
const c = link2.confidence;
|
|
13314
|
+
if (c !== void 0 && (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1)) {
|
|
13315
|
+
const qualifiedId2 = `${extractor.pluginId}/${extractor.id}`;
|
|
13316
|
+
emitter.emit(
|
|
13317
|
+
makeEvent("extension.error", {
|
|
13318
|
+
kind: "link-confidence-out-of-range",
|
|
13319
|
+
extensionId: qualifiedId2,
|
|
13320
|
+
confidence: c,
|
|
13321
|
+
message: `Extractor ${qualifiedId2} emitted a Link with confidence ${String(c)} outside [0..1]; dropped.`
|
|
13322
|
+
})
|
|
13323
|
+
);
|
|
13324
|
+
return null;
|
|
13325
|
+
}
|
|
13326
|
+
const confidence = c ?? ConfidenceTier.MEDIUM;
|
|
12902
13327
|
return { ...link2, confidence };
|
|
12903
13328
|
}
|
|
13329
|
+
var KNOWN_LINK_KINDS = ["invokes", "references", "mentions", "supersedes"];
|
|
13330
|
+
function validateSignal(extractor, signal, emitter) {
|
|
13331
|
+
const qualifiedId2 = qualifiedExtensionId(extractor.pluginId, extractor.id);
|
|
13332
|
+
if (!Array.isArray(signal.candidates) || signal.candidates.length === 0) {
|
|
13333
|
+
emitter.emit(
|
|
13334
|
+
makeEvent("extension.error", {
|
|
13335
|
+
kind: "signal-no-candidates",
|
|
13336
|
+
extensionId: qualifiedId2,
|
|
13337
|
+
signal: { source: signal.source, scope: signal.scope },
|
|
13338
|
+
message: `Extractor ${qualifiedId2} emitted a Signal with no candidates; dropped.`
|
|
13339
|
+
})
|
|
13340
|
+
);
|
|
13341
|
+
return null;
|
|
13342
|
+
}
|
|
13343
|
+
for (const candidate of signal.candidates) {
|
|
13344
|
+
if (!isValidSignalCandidate(qualifiedId2, candidate, emitter)) return null;
|
|
13345
|
+
}
|
|
13346
|
+
return signal;
|
|
13347
|
+
}
|
|
13348
|
+
function isValidSignalCandidate(qualifiedId2, candidate, emitter) {
|
|
13349
|
+
if (!KNOWN_LINK_KINDS.includes(candidate.kind)) {
|
|
13350
|
+
emitter.emit(
|
|
13351
|
+
makeEvent("extension.error", {
|
|
13352
|
+
kind: "signal-candidate-kind-not-declared",
|
|
13353
|
+
extensionId: qualifiedId2,
|
|
13354
|
+
candidateKind: candidate.kind,
|
|
13355
|
+
declaredKinds: KNOWN_LINK_KINDS,
|
|
13356
|
+
message: `Extractor ${qualifiedId2} emitted a Signal candidate with off-enum kind '${String(candidate.kind)}'; dropped.`
|
|
13357
|
+
})
|
|
13358
|
+
);
|
|
13359
|
+
return false;
|
|
13360
|
+
}
|
|
13361
|
+
const c = candidate.confidence;
|
|
13362
|
+
if (typeof c !== "number" || !Number.isFinite(c) || c < 0 || c > 1) {
|
|
13363
|
+
emitter.emit(
|
|
13364
|
+
makeEvent("extension.error", {
|
|
13365
|
+
kind: "signal-candidate-confidence-out-of-range",
|
|
13366
|
+
extensionId: qualifiedId2,
|
|
13367
|
+
confidence: candidate.confidence,
|
|
13368
|
+
message: `Extractor ${qualifiedId2} emitted a Signal candidate with confidence ${String(c)} outside [0..1]; dropped.`
|
|
13369
|
+
})
|
|
13370
|
+
);
|
|
13371
|
+
return false;
|
|
13372
|
+
}
|
|
13373
|
+
return true;
|
|
13374
|
+
}
|
|
12904
13375
|
function dedupeLinks(links) {
|
|
12905
13376
|
const out = /* @__PURE__ */ new Map();
|
|
12906
13377
|
for (const link2 of links) {
|
|
@@ -12915,6 +13386,9 @@ function dedupeLinks(links) {
|
|
|
12915
13386
|
existing.sources = [...existing.sources, src];
|
|
12916
13387
|
}
|
|
12917
13388
|
}
|
|
13389
|
+
if (link2.confidence > existing.confidence) {
|
|
13390
|
+
existing.confidence = link2.confidence;
|
|
13391
|
+
}
|
|
12918
13392
|
continue;
|
|
12919
13393
|
}
|
|
12920
13394
|
out.set(key, link2);
|
|
@@ -12947,7 +13421,9 @@ function recomputeExternalRefsCount(nodes, externalLinks, cachedPaths) {
|
|
|
12947
13421
|
}
|
|
12948
13422
|
}
|
|
12949
13423
|
var EXTERNAL_URL_SCHEME_RE = /^[a-z][a-z0-9+\-.]+:/i;
|
|
13424
|
+
var VIRTUAL_NODE_SCHEME_RE = /^mcp:\/\//i;
|
|
12950
13425
|
function isExternalUrlLink(link2) {
|
|
13426
|
+
if (VIRTUAL_NODE_SCHEME_RE.test(link2.target)) return false;
|
|
12951
13427
|
return EXTERNAL_URL_SCHEME_RE.test(link2.target);
|
|
12952
13428
|
}
|
|
12953
13429
|
|
|
@@ -13106,13 +13582,9 @@ function originatingNodeOf(link2, priorNodePaths) {
|
|
|
13106
13582
|
}
|
|
13107
13583
|
function computeCacheDecision(opts) {
|
|
13108
13584
|
const applicableExtractors = opts.extractors.filter((ex) => {
|
|
13109
|
-
|
|
13110
|
-
if (!
|
|
13111
|
-
return
|
|
13112
|
-
const slashIdx = qualified.indexOf("/");
|
|
13113
|
-
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
13114
|
-
return kindOnly === opts.kind;
|
|
13115
|
-
});
|
|
13585
|
+
if (!matchesKindPrecondition(ex, opts.kind)) return false;
|
|
13586
|
+
if (!matchesProviderPrecondition(ex, opts.provider, opts.activeProvider)) return false;
|
|
13587
|
+
return true;
|
|
13116
13588
|
});
|
|
13117
13589
|
const applicableQualifiedIds = new Set(
|
|
13118
13590
|
applicableExtractors.map((ex) => qualifiedExtensionId(ex.pluginId, ex.id))
|
|
@@ -13126,6 +13598,22 @@ function computeCacheDecision(opts) {
|
|
|
13126
13598
|
fullCacheHit: opts.nodeHashCacheEligible && split.missingExtractors.length === 0
|
|
13127
13599
|
};
|
|
13128
13600
|
}
|
|
13601
|
+
function matchesKindPrecondition(ex, kind) {
|
|
13602
|
+
const kinds = ex.precondition?.kind;
|
|
13603
|
+
if (!kinds || kinds.length === 0) return true;
|
|
13604
|
+
return kinds.some((qualified) => {
|
|
13605
|
+
const slashIdx = qualified.indexOf("/");
|
|
13606
|
+
const kindOnly = slashIdx === -1 ? qualified : qualified.slice(slashIdx + 1);
|
|
13607
|
+
return kindOnly === kind;
|
|
13608
|
+
});
|
|
13609
|
+
}
|
|
13610
|
+
function matchesProviderPrecondition(ex, nodeProvider, activeProvider) {
|
|
13611
|
+
const providers = ex.precondition?.provider;
|
|
13612
|
+
if (!providers || providers.length === 0) return true;
|
|
13613
|
+
if (!providers.includes(nodeProvider)) return false;
|
|
13614
|
+
if (activeProvider === null) return false;
|
|
13615
|
+
return providers.includes(activeProvider);
|
|
13616
|
+
}
|
|
13129
13617
|
function splitLegacy(applicableExtractors, applicableQualifiedIds, nodeHashCacheEligible) {
|
|
13130
13618
|
const cachedQualifiedIds = /* @__PURE__ */ new Set();
|
|
13131
13619
|
const missingExtractors = [];
|
|
@@ -13225,6 +13713,65 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
|
|
|
13225
13713
|
return "obsolete";
|
|
13226
13714
|
}
|
|
13227
13715
|
|
|
13716
|
+
// kernel/orchestrator/lift-mention-confidence.ts
|
|
13717
|
+
function liftMentionConfidence(links, nodes) {
|
|
13718
|
+
if (!links.some((l) => l.kind === "mentions")) return;
|
|
13719
|
+
const byPath3 = /* @__PURE__ */ new Set();
|
|
13720
|
+
for (const node of nodes) byPath3.add(node.path);
|
|
13721
|
+
const byNormalizedName = indexByNormalizedName2(nodes);
|
|
13722
|
+
for (const link2 of links) {
|
|
13723
|
+
if (link2.kind !== "mentions") continue;
|
|
13724
|
+
if (isResolved2(link2, byPath3, byNormalizedName)) {
|
|
13725
|
+
link2.confidence = 1;
|
|
13726
|
+
}
|
|
13727
|
+
}
|
|
13728
|
+
}
|
|
13729
|
+
function isResolved2(link2, byPath3, byNormalizedName) {
|
|
13730
|
+
const normalized = link2.trigger?.normalizedTrigger;
|
|
13731
|
+
if (normalized) {
|
|
13732
|
+
const withoutSigil = normalized.replace(/^[/@]/, "").trim();
|
|
13733
|
+
if (byNormalizedName.has(withoutSigil)) return true;
|
|
13734
|
+
}
|
|
13735
|
+
if (byPath3.has(link2.target)) return true;
|
|
13736
|
+
return false;
|
|
13737
|
+
}
|
|
13738
|
+
function indexByNormalizedName2(nodes) {
|
|
13739
|
+
const out = /* @__PURE__ */ new Map();
|
|
13740
|
+
for (const node of nodes) {
|
|
13741
|
+
const raw = node.frontmatter?.["name"];
|
|
13742
|
+
const name = typeof raw === "string" ? raw : "";
|
|
13743
|
+
if (!name) continue;
|
|
13744
|
+
out.set(normalizeTrigger(name), true);
|
|
13745
|
+
}
|
|
13746
|
+
return out;
|
|
13747
|
+
}
|
|
13748
|
+
|
|
13749
|
+
// kernel/orchestrator/post-walk-transforms.ts
|
|
13750
|
+
var POST_WALK_TRANSFORMS = [
|
|
13751
|
+
{
|
|
13752
|
+
id: "dedupe-links",
|
|
13753
|
+
description: "Collapse identical (source, target, kind, normalizedTrigger) edges across extractors; union sources[] and pick max confidence on merge.",
|
|
13754
|
+
run(links) {
|
|
13755
|
+
return dedupeLinks(links);
|
|
13756
|
+
}
|
|
13757
|
+
},
|
|
13758
|
+
{
|
|
13759
|
+
id: "lift-mention-confidence",
|
|
13760
|
+
description: "Bump resolved `mentions` links to confidence 1.0 once the full node graph is known (post-merge polish).",
|
|
13761
|
+
run(links, nodes) {
|
|
13762
|
+
liftMentionConfidence(links, nodes);
|
|
13763
|
+
}
|
|
13764
|
+
}
|
|
13765
|
+
];
|
|
13766
|
+
function applyPostWalkTransforms(links, nodes, transforms = POST_WALK_TRANSFORMS) {
|
|
13767
|
+
let current = links;
|
|
13768
|
+
for (const transform of transforms) {
|
|
13769
|
+
const next = transform.run(current, nodes);
|
|
13770
|
+
if (next) current = next;
|
|
13771
|
+
}
|
|
13772
|
+
return current;
|
|
13773
|
+
}
|
|
13774
|
+
|
|
13228
13775
|
// kernel/orchestrator/renames.ts
|
|
13229
13776
|
function findHighConfidenceRenames(opts) {
|
|
13230
13777
|
const ops = [];
|
|
@@ -13235,7 +13782,7 @@ function findHighConfidenceRenames(opts) {
|
|
|
13235
13782
|
if (opts.claimedNew.has(toPath)) continue;
|
|
13236
13783
|
const toNode = opts.currentByPath.get(toPath);
|
|
13237
13784
|
if (toNode.bodyHash === fromNode.bodyHash) {
|
|
13238
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
13785
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.HIGH });
|
|
13239
13786
|
opts.claimedDeleted.add(fromPath);
|
|
13240
13787
|
opts.claimedNew.add(toPath);
|
|
13241
13788
|
break;
|
|
@@ -13270,13 +13817,13 @@ function claimSingletonRenames(opts) {
|
|
|
13270
13817
|
const remaining = candidates.filter((p) => !opts.claimedDeleted.has(p));
|
|
13271
13818
|
if (remaining.length === 1) {
|
|
13272
13819
|
const fromPath = remaining[0];
|
|
13273
|
-
ops.push({ from: fromPath, to: toPath, confidence:
|
|
13820
|
+
ops.push({ from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM });
|
|
13274
13821
|
opts.issues.push({
|
|
13275
13822
|
analyzerId: "auto-rename-medium",
|
|
13276
13823
|
severity: "warn",
|
|
13277
13824
|
nodeIds: [toPath],
|
|
13278
13825
|
message: `Auto-rename (medium confidence): ${fromPath} \u2192 ${toPath}`,
|
|
13279
|
-
data: { from: fromPath, to: toPath, confidence:
|
|
13826
|
+
data: { from: fromPath, to: toPath, confidence: ConfidenceTier.MEDIUM }
|
|
13280
13827
|
});
|
|
13281
13828
|
opts.claimedDeleted.add(fromPath);
|
|
13282
13829
|
opts.claimedNew.add(toPath);
|
|
@@ -13368,8 +13915,8 @@ function computeDriftStatus(args2) {
|
|
|
13368
13915
|
}
|
|
13369
13916
|
|
|
13370
13917
|
// kernel/sidecar/discover-orphans.ts
|
|
13371
|
-
import { existsSync as
|
|
13372
|
-
import { join as
|
|
13918
|
+
import { existsSync as existsSync20, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
|
|
13919
|
+
import { join as join13, relative as relative4, sep as sep3 } from "path";
|
|
13373
13920
|
function discoverOrphanSidecars(roots, shouldSkip) {
|
|
13374
13921
|
const out = [];
|
|
13375
13922
|
for (const root of roots) {
|
|
@@ -13385,7 +13932,7 @@ function walk(root, current, shouldSkip, out) {
|
|
|
13385
13932
|
return;
|
|
13386
13933
|
}
|
|
13387
13934
|
for (const entry of entries) {
|
|
13388
|
-
const full =
|
|
13935
|
+
const full = join13(current, entry.name);
|
|
13389
13936
|
const rel = relative4(root, full).split(sep3).join("/");
|
|
13390
13937
|
if (shouldSkip(rel)) continue;
|
|
13391
13938
|
if (entry.isSymbolicLink()) continue;
|
|
@@ -13396,7 +13943,7 @@ function walk(root, current, shouldSkip, out) {
|
|
|
13396
13943
|
if (!entry.isFile()) continue;
|
|
13397
13944
|
if (!entry.name.endsWith(".sm")) continue;
|
|
13398
13945
|
const expectedMd = `${full.slice(0, -".sm".length)}.md`;
|
|
13399
|
-
if (
|
|
13946
|
+
if (existsSync20(expectedMd) && safeIsFile(expectedMd)) continue;
|
|
13400
13947
|
out.push({ sidecarPath: full, relativePath: rel, expectedMdPath: expectedMd });
|
|
13401
13948
|
}
|
|
13402
13949
|
}
|
|
@@ -13410,7 +13957,7 @@ function safeIsFile(path) {
|
|
|
13410
13957
|
|
|
13411
13958
|
// kernel/orchestrator/node-build.ts
|
|
13412
13959
|
import { createHash } from "crypto";
|
|
13413
|
-
import { existsSync as
|
|
13960
|
+
import { existsSync as existsSync21 } from "fs";
|
|
13414
13961
|
import { isAbsolute as isAbsolute6, resolve as resolvePath } from "path";
|
|
13415
13962
|
import "js-tiktoken/lite";
|
|
13416
13963
|
import yaml4 from "js-yaml";
|
|
@@ -13574,11 +14121,11 @@ function resolveSidecarOverlay(relativePath2, nodePathForIssue, roots, liveBodyH
|
|
|
13574
14121
|
}
|
|
13575
14122
|
function resolveAbsoluteMdPath(relativePath2, roots) {
|
|
13576
14123
|
if (isAbsolute6(relativePath2)) {
|
|
13577
|
-
return
|
|
14124
|
+
return existsSync21(relativePath2) ? relativePath2 : null;
|
|
13578
14125
|
}
|
|
13579
14126
|
for (const root of roots) {
|
|
13580
14127
|
const candidate = resolvePath(root, relativePath2);
|
|
13581
|
-
if (
|
|
14128
|
+
if (existsSync21(candidate)) return candidate;
|
|
13582
14129
|
}
|
|
13583
14130
|
return null;
|
|
13584
14131
|
}
|
|
@@ -13714,6 +14261,8 @@ async function processRawNode(raw, provider, wctx, accum, claimedPaths, nextInde
|
|
|
13714
14261
|
const cacheDecision = computeCacheDecision({
|
|
13715
14262
|
extractors: wctx.opts.extractors,
|
|
13716
14263
|
kind,
|
|
14264
|
+
provider: provider.id,
|
|
14265
|
+
activeProvider: wctx.opts.activeProvider,
|
|
13717
14266
|
nodePath: raw.path,
|
|
13718
14267
|
bodyHash,
|
|
13719
14268
|
sidecarAnnotationsHash,
|
|
@@ -13816,6 +14365,10 @@ function mergeExtractResult(extractResult, accum) {
|
|
|
13816
14365
|
accum.enrichmentBuffer.set(`${enr.nodePath}\0${enr.extractorId}`, enr);
|
|
13817
14366
|
}
|
|
13818
14367
|
for (const c of extractResult.contributions) accum.contributionsBuffer.push(c);
|
|
14368
|
+
for (const vn of extractResult.virtualNodes) {
|
|
14369
|
+
if (accum.nodes.some((n) => n.path === vn.path)) continue;
|
|
14370
|
+
accum.nodes.push(vn);
|
|
14371
|
+
}
|
|
13819
14372
|
}
|
|
13820
14373
|
function isPartialCacheHit(ctx) {
|
|
13821
14374
|
return ctx.nodeHashCacheEligible && ctx.cacheDecision.cachedQualifiedIds.size > 0 && ctx.priorNode !== void 0;
|
|
@@ -13923,9 +14476,10 @@ async function runScanInternal(_kernel, options) {
|
|
|
13923
14476
|
priorIndex: setup.priorIndex,
|
|
13924
14477
|
priorExtractorRuns: setup.priorExtractorRuns,
|
|
13925
14478
|
providerFrontmatter: setup.providerFrontmatter,
|
|
13926
|
-
pluginStores: options.pluginStores
|
|
14479
|
+
pluginStores: options.pluginStores,
|
|
14480
|
+
activeProvider: resolveActiveProviderOption(options.activeProvider, options.roots)
|
|
13927
14481
|
});
|
|
13928
|
-
walked.internalLinks =
|
|
14482
|
+
walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes);
|
|
13929
14483
|
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
13930
14484
|
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
13931
14485
|
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
@@ -14041,17 +14595,27 @@ function validateRoots(roots) {
|
|
|
14041
14595
|
throw new Error(ORCHESTRATOR_TEXTS.runScanRootEmptyArray);
|
|
14042
14596
|
}
|
|
14043
14597
|
for (const root of roots) {
|
|
14044
|
-
if (!
|
|
14598
|
+
if (!existsSync22(root) || !statSync7(root).isDirectory()) {
|
|
14045
14599
|
throw new Error(tx(ORCHESTRATOR_TEXTS.runScanRootMissing, { root }));
|
|
14046
14600
|
}
|
|
14047
14601
|
}
|
|
14048
14602
|
}
|
|
14603
|
+
function resolveActiveProviderOption(optionValue, roots) {
|
|
14604
|
+
if (optionValue !== void 0) return optionValue;
|
|
14605
|
+
for (const root of roots) {
|
|
14606
|
+
const absRoot = isAbsolute7(root) ? root : resolve27(root);
|
|
14607
|
+
if (!existsSync22(absRoot)) continue;
|
|
14608
|
+
const detected = resolveActiveProvider(absRoot).resolved;
|
|
14609
|
+
if (detected !== null) return detected;
|
|
14610
|
+
}
|
|
14611
|
+
return null;
|
|
14612
|
+
}
|
|
14049
14613
|
|
|
14050
14614
|
// kernel/scan/watcher.ts
|
|
14051
|
-
import { resolve as
|
|
14615
|
+
import { resolve as resolve28, relative as relative5, sep as sep4 } from "path";
|
|
14052
14616
|
import chokidar from "chokidar";
|
|
14053
14617
|
function createChokidarWatcher(opts) {
|
|
14054
|
-
const absRoots = opts.roots.map((r) =>
|
|
14618
|
+
const absRoots = opts.roots.map((r) => resolve28(opts.cwd, r));
|
|
14055
14619
|
const ignoreFilterOpt = opts.ignoreFilter;
|
|
14056
14620
|
const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
|
|
14057
14621
|
const ignored = getFilter ? (path) => {
|
|
@@ -14270,7 +14834,7 @@ function createKernel() {
|
|
|
14270
14834
|
|
|
14271
14835
|
// kernel/jobs/orphan-files.ts
|
|
14272
14836
|
import { readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
14273
|
-
import { join as
|
|
14837
|
+
import { join as join14, resolve as resolve29 } from "path";
|
|
14274
14838
|
function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
14275
14839
|
let entries;
|
|
14276
14840
|
try {
|
|
@@ -14288,7 +14852,7 @@ function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
|
14288
14852
|
if (!entry.isFile()) continue;
|
|
14289
14853
|
const name = entry.name;
|
|
14290
14854
|
if (!name.endsWith(".md")) continue;
|
|
14291
|
-
const abs =
|
|
14855
|
+
const abs = resolve29(join14(jobsDir, name));
|
|
14292
14856
|
if (!referencedPaths.has(abs)) orphans.push(abs);
|
|
14293
14857
|
}
|
|
14294
14858
|
orphans.sort();
|
|
@@ -14352,7 +14916,48 @@ var SCAN_RUNNER_TEXTS = {
|
|
|
14352
14916
|
* does not exist on disk. Surfaced once per missing root so the
|
|
14353
14917
|
* operator notices a typo without the walker silently swallowing it.
|
|
14354
14918
|
*/
|
|
14355
|
-
referenceWalkMissingRoot: 'scan.referencePaths: configured path "{{path}}" does not exist; skipped.'
|
|
14919
|
+
referenceWalkMissingRoot: 'scan.referencePaths: configured path "{{path}}" does not exist; skipped.',
|
|
14920
|
+
/**
|
|
14921
|
+
* Active-provider bootstrap: filesystem auto-detect found no
|
|
14922
|
+
* markers (`.claude/`, `.gemini/`, `.codex/`, `AGENTS.md`, `.cursor/`)
|
|
14923
|
+
* anywhere under cwd or the effective scan roots. Plain-markdown
|
|
14924
|
+
* projects keep scanning fine; provider-specific extractors silently
|
|
14925
|
+
* no-op for this scan.
|
|
14926
|
+
*/
|
|
14927
|
+
activeProviderNoMarkerWarning: "No provider markers detected (.claude/, .gemini/, .codex/, AGENTS.md, .cursor/). Scanning as universal markdown only; provider-specific link types (e.g. claude @-directives, /-commands) will not appear in the graph. Set `activeProvider` in .skill-map/settings.json or install a provider plugin to enable them.",
|
|
14928
|
+
/**
|
|
14929
|
+
* Active-provider bootstrap: filesystem auto-detect found exactly
|
|
14930
|
+
* one marker and persisted the detected id to project settings.
|
|
14931
|
+
*/
|
|
14932
|
+
activeProviderAutodetected: "Auto-detected activeProvider = {{id}} from filesystem markers; persisted to .skill-map/settings.json.",
|
|
14933
|
+
/**
|
|
14934
|
+
* Active-provider bootstrap: persistence of the auto-detected id
|
|
14935
|
+
* failed (permission, disk full, etc). Non-fatal; the scan
|
|
14936
|
+
* continues with the value in memory for this run.
|
|
14937
|
+
*/
|
|
14938
|
+
activeProviderPersistFailed: "Auto-detected activeProvider = {{id}}, but persisting to .skill-map/settings.json failed: {{message}}. Run `sm config set activeProvider {{id}}` manually to make the choice sticky.",
|
|
14939
|
+
/**
|
|
14940
|
+
* Active-provider bootstrap: ambiguous detection (2+ markers
|
|
14941
|
+
* present), interactive prompt header. Followed by one
|
|
14942
|
+
* `activeProviderPromptOption` per detected provider id.
|
|
14943
|
+
*/
|
|
14944
|
+
activeProviderPromptHeader: "Multiple provider markers detected. Pick the active lens for this project:",
|
|
14945
|
+
activeProviderPromptOption: " {{index}}) {{id}}",
|
|
14946
|
+
activeProviderPromptInput: "Enter the number or provider id: ",
|
|
14947
|
+
/**
|
|
14948
|
+
* Active-provider bootstrap: ambiguous detection under `--yes`. The
|
|
14949
|
+
* caller exits non-zero; this message names the candidates and how
|
|
14950
|
+
* to resolve.
|
|
14951
|
+
*/
|
|
14952
|
+
activeProviderAmbiguousUnderYes: "Multiple provider markers detected ({{candidates}}) and --yes is set. Set the lens explicitly with `sm config set activeProvider <id>` and re-run.",
|
|
14953
|
+
/**
|
|
14954
|
+
* Active lens points at a bundle the operator has disabled (via
|
|
14955
|
+
* `sm plugins disable <id>` or the Settings UI). Classification keeps
|
|
14956
|
+
* running because it's provider-driven, but the lens-gated extractors
|
|
14957
|
+
* for the disabled bundle silently no-op. Without this warning the
|
|
14958
|
+
* graph quietly differs from what the lens implies.
|
|
14959
|
+
*/
|
|
14960
|
+
activeProviderBundleDisabledWarning: 'activeProvider = "{{id}}" but the "{{id}}" plugin bundle is currently disabled; provider-specific extractors will not run. Re-enable the bundle with `sm plugins enable {{id}}` or switch the lens with `sm config set activeProvider <id>` to silence this warning.'
|
|
14356
14961
|
};
|
|
14357
14962
|
|
|
14358
14963
|
// core/runtime/scan-roots.ts
|
|
@@ -14366,7 +14971,7 @@ function resolveScanRoots(inputs) {
|
|
|
14366
14971
|
// core/runtime/reference-paths-walker.ts
|
|
14367
14972
|
import { readdirSync as readdirSync9, statSync as statSync9 } from "fs";
|
|
14368
14973
|
import { homedir as osHomedir2 } from "os";
|
|
14369
|
-
import { isAbsolute as
|
|
14974
|
+
import { isAbsolute as isAbsolute8, join as join15, resolve as resolve30 } from "path";
|
|
14370
14975
|
var REFERENCE_WALK_MAX_FILES = 5e4;
|
|
14371
14976
|
var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
14372
14977
|
"node_modules",
|
|
@@ -14374,10 +14979,10 @@ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
14374
14979
|
SKILL_MAP_DIR
|
|
14375
14980
|
]);
|
|
14376
14981
|
function resolveScanPath(raw, cwd) {
|
|
14377
|
-
if (raw.startsWith("~/")) return
|
|
14378
|
-
if (raw === "~") return
|
|
14379
|
-
if (
|
|
14380
|
-
return
|
|
14982
|
+
if (raw.startsWith("~/")) return resolve30(join15(osHomedir2(), raw.slice(2)));
|
|
14983
|
+
if (raw === "~") return resolve30(osHomedir2());
|
|
14984
|
+
if (isAbsolute8(raw)) return resolve30(raw);
|
|
14985
|
+
return resolve30(cwd, raw);
|
|
14381
14986
|
}
|
|
14382
14987
|
function walkReferencePaths(rawRoots, cwd) {
|
|
14383
14988
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -14406,7 +15011,7 @@ function walkInto(dir, out) {
|
|
|
14406
15011
|
for (const entry of entries) {
|
|
14407
15012
|
if (out.size >= REFERENCE_WALK_MAX_FILES) return true;
|
|
14408
15013
|
if (entry.isSymbolicLink()) continue;
|
|
14409
|
-
const full =
|
|
15014
|
+
const full = join15(dir, entry.name);
|
|
14410
15015
|
if (entry.isDirectory()) {
|
|
14411
15016
|
if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
|
|
14412
15017
|
if (walkInto(full, out)) return true;
|
|
@@ -14424,6 +15029,101 @@ function safeStat(path) {
|
|
|
14424
15029
|
}
|
|
14425
15030
|
}
|
|
14426
15031
|
|
|
15032
|
+
// core/runtime/active-provider-bootstrap.ts
|
|
15033
|
+
import { createInterface as createInterface2 } from "readline";
|
|
15034
|
+
import { isAbsolute as isAbsolute9, join as join16 } from "path";
|
|
15035
|
+
async function bootstrapActiveProvider(opts) {
|
|
15036
|
+
const fromCwd = resolveActiveProvider(opts.cwd);
|
|
15037
|
+
if (fromCwd.source === "config") {
|
|
15038
|
+
return { kind: "ok", activeProvider: fromCwd.resolved, source: "config" };
|
|
15039
|
+
}
|
|
15040
|
+
const detected = aggregateDetected(opts.cwd, opts.effectiveRoots, fromCwd.detected);
|
|
15041
|
+
if (detected.length === 0) {
|
|
15042
|
+
opts.printer.warn(SCAN_RUNNER_TEXTS.activeProviderNoMarkerWarning);
|
|
15043
|
+
return { kind: "ok", activeProvider: null, source: "none" };
|
|
15044
|
+
}
|
|
15045
|
+
if (detected.length === 1) {
|
|
15046
|
+
const picked2 = detected[0];
|
|
15047
|
+
persistActiveProvider(opts.cwd, picked2, opts.printer);
|
|
15048
|
+
return { kind: "ok", activeProvider: picked2, source: "autodetect" };
|
|
15049
|
+
}
|
|
15050
|
+
if (opts.yes) {
|
|
15051
|
+
return { kind: "ambiguous", detected };
|
|
15052
|
+
}
|
|
15053
|
+
const picked = await promptForLens(detected, opts.stdin, opts.stderr);
|
|
15054
|
+
if (picked === null) {
|
|
15055
|
+
return { kind: "ambiguous", detected };
|
|
15056
|
+
}
|
|
15057
|
+
persistActiveProvider(opts.cwd, picked, opts.printer);
|
|
15058
|
+
return { kind: "ok", activeProvider: picked, source: "autodetect" };
|
|
15059
|
+
}
|
|
15060
|
+
function aggregateDetected(cwd, effectiveRoots, cwdDetected) {
|
|
15061
|
+
const out = [];
|
|
15062
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15063
|
+
for (const id of cwdDetected) {
|
|
15064
|
+
if (seen.has(id)) continue;
|
|
15065
|
+
seen.add(id);
|
|
15066
|
+
out.push(id);
|
|
15067
|
+
}
|
|
15068
|
+
for (const root of effectiveRoots) {
|
|
15069
|
+
const absRoot = isAbsolute9(root) ? root : join16(cwd, root);
|
|
15070
|
+
const r = resolveActiveProvider(absRoot);
|
|
15071
|
+
for (const id of r.detected) {
|
|
15072
|
+
if (seen.has(id)) continue;
|
|
15073
|
+
seen.add(id);
|
|
15074
|
+
out.push(id);
|
|
15075
|
+
}
|
|
15076
|
+
}
|
|
15077
|
+
return out;
|
|
15078
|
+
}
|
|
15079
|
+
function persistActiveProvider(cwd, id, printer) {
|
|
15080
|
+
try {
|
|
15081
|
+
writeConfigValue("activeProvider", id, { target: "project", cwd });
|
|
15082
|
+
printer.info(tx(SCAN_RUNNER_TEXTS.activeProviderAutodetected, { id }));
|
|
15083
|
+
} catch (err) {
|
|
15084
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15085
|
+
printer.warn(
|
|
15086
|
+
tx(SCAN_RUNNER_TEXTS.activeProviderPersistFailed, { id, message })
|
|
15087
|
+
);
|
|
15088
|
+
}
|
|
15089
|
+
}
|
|
15090
|
+
function warnIfLensBundleDisabled(args2) {
|
|
15091
|
+
if (args2.activeProvider === null) return;
|
|
15092
|
+
if (args2.resolveEnabled(args2.activeProvider)) return;
|
|
15093
|
+
args2.printer.warn(
|
|
15094
|
+
tx(SCAN_RUNNER_TEXTS.activeProviderBundleDisabledWarning, {
|
|
15095
|
+
id: args2.activeProvider
|
|
15096
|
+
})
|
|
15097
|
+
);
|
|
15098
|
+
}
|
|
15099
|
+
async function promptForLens(detected, stdin, stderr) {
|
|
15100
|
+
const lines = [SCAN_RUNNER_TEXTS.activeProviderPromptHeader];
|
|
15101
|
+
for (let i = 0; i < detected.length; i += 1) {
|
|
15102
|
+
lines.push(
|
|
15103
|
+
tx(SCAN_RUNNER_TEXTS.activeProviderPromptOption, {
|
|
15104
|
+
index: i + 1,
|
|
15105
|
+
id: detected[i]
|
|
15106
|
+
})
|
|
15107
|
+
);
|
|
15108
|
+
}
|
|
15109
|
+
stderr.write(lines.join("\n") + "\n");
|
|
15110
|
+
const rl = createInterface2({ input: stdin, output: stderr });
|
|
15111
|
+
try {
|
|
15112
|
+
const answer = await new Promise(
|
|
15113
|
+
(resolveP) => rl.question(SCAN_RUNNER_TEXTS.activeProviderPromptInput, resolveP)
|
|
15114
|
+
);
|
|
15115
|
+
const trimmed = answer.trim();
|
|
15116
|
+
const asNumber = Number.parseInt(trimmed, 10);
|
|
15117
|
+
if (!Number.isNaN(asNumber) && asNumber >= 1 && asNumber <= detected.length) {
|
|
15118
|
+
return detected[asNumber - 1];
|
|
15119
|
+
}
|
|
15120
|
+
const asId = detected.find((d) => d.toLowerCase() === trimmed.toLowerCase());
|
|
15121
|
+
return asId ?? null;
|
|
15122
|
+
} finally {
|
|
15123
|
+
rl.close();
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
15126
|
+
|
|
14427
15127
|
// core/runtime/scan-runner.ts
|
|
14428
15128
|
async function runScanForCommand(opts) {
|
|
14429
15129
|
const ctx = opts.ctx ?? defaultRuntimeContext();
|
|
@@ -14431,20 +15131,9 @@ async function runScanForCommand(opts) {
|
|
|
14431
15131
|
const kernel = createKernel();
|
|
14432
15132
|
const pluginRuntime = await preparePluginRuntime(opts, opts.printer);
|
|
14433
15133
|
const extensions = registerExtensions(kernel, pluginRuntime, opts);
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
} catch (err) {
|
|
14438
|
-
return { kind: "config-error", message: formatErrorMessage(err) };
|
|
14439
|
-
}
|
|
14440
|
-
const ignoreFilter = buildScanIgnoreFilter(cfg, ctx.cwd);
|
|
14441
|
-
const strict = opts.strict || cfg.scan.strict === true;
|
|
14442
|
-
let effectiveRoots;
|
|
14443
|
-
try {
|
|
14444
|
-
effectiveRoots = resolveScanRoots({ positionalRoots: opts.roots });
|
|
14445
|
-
} catch (err) {
|
|
14446
|
-
return { kind: "config-error", message: formatErrorMessage(err) };
|
|
14447
|
-
}
|
|
15134
|
+
const scanInputs = loadScanInputs(opts, ctx);
|
|
15135
|
+
if ("kind" in scanInputs) return scanInputs;
|
|
15136
|
+
const { cfg, ignoreFilter, strict, effectiveRoots } = scanInputs;
|
|
14448
15137
|
let referenceablePaths;
|
|
14449
15138
|
if (cfg.scan.referencePaths.length > 0) {
|
|
14450
15139
|
const walk2 = walkReferencePaths(cfg.scan.referencePaths, ctx.cwd);
|
|
@@ -14453,6 +15142,9 @@ async function runScanForCommand(opts) {
|
|
|
14453
15142
|
}
|
|
14454
15143
|
const loadPrior = makePriorLoader(opts.noBuiltIns, strict);
|
|
14455
15144
|
const jobsDir = defaultProjectJobsDir(ctx);
|
|
15145
|
+
const lens = await resolveActiveLens(opts, ctx, effectiveRoots, pluginRuntime);
|
|
15146
|
+
if (lens.kind === "ambiguous-provider") return lens;
|
|
15147
|
+
const activeProvider = lens.activeProvider;
|
|
14456
15148
|
const runScanWith = makeScanRunner(
|
|
14457
15149
|
kernel,
|
|
14458
15150
|
opts,
|
|
@@ -14461,11 +15153,37 @@ async function runScanForCommand(opts) {
|
|
|
14461
15153
|
strict,
|
|
14462
15154
|
extensions,
|
|
14463
15155
|
referenceablePaths,
|
|
14464
|
-
ctx.cwd
|
|
15156
|
+
ctx.cwd,
|
|
15157
|
+
activeProvider
|
|
14465
15158
|
);
|
|
14466
15159
|
const willPersist = !opts.noBuiltIns && !opts.dryRun;
|
|
14467
15160
|
return willPersist ? runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith);
|
|
14468
15161
|
}
|
|
15162
|
+
async function resolveActiveLens(opts, ctx, effectiveRoots, pluginRuntime) {
|
|
15163
|
+
const bootstrap = await bootstrapActiveProvider({
|
|
15164
|
+
cwd: ctx.cwd,
|
|
15165
|
+
effectiveRoots,
|
|
15166
|
+
yes: opts.yes ?? false,
|
|
15167
|
+
stdin: opts.stdin ?? process.stdin,
|
|
15168
|
+
stderr: opts.stderr,
|
|
15169
|
+
printer: opts.printer
|
|
15170
|
+
});
|
|
15171
|
+
if (bootstrap.kind === "ambiguous") {
|
|
15172
|
+
return {
|
|
15173
|
+
kind: "ambiguous-provider",
|
|
15174
|
+
detected: bootstrap.detected,
|
|
15175
|
+
message: tx(SCAN_RUNNER_TEXTS.activeProviderAmbiguousUnderYes, {
|
|
15176
|
+
candidates: bootstrap.detected.join(", ")
|
|
15177
|
+
})
|
|
15178
|
+
};
|
|
15179
|
+
}
|
|
15180
|
+
warnIfLensBundleDisabled({
|
|
15181
|
+
activeProvider: bootstrap.activeProvider,
|
|
15182
|
+
resolveEnabled: opts.resolveEnabledOverride ?? pluginRuntime.resolveEnabled,
|
|
15183
|
+
printer: opts.printer
|
|
15184
|
+
});
|
|
15185
|
+
return { kind: "ok", activeProvider: bootstrap.activeProvider };
|
|
15186
|
+
}
|
|
14469
15187
|
function emitReferenceWalkAdvisory(walk2, opts) {
|
|
14470
15188
|
if (walk2.truncated) {
|
|
14471
15189
|
opts.printer.warn(SCAN_RUNNER_TEXTS.referenceWalkTruncated);
|
|
@@ -14499,6 +15217,17 @@ function registerExtensions(kernel, pluginRuntime, opts) {
|
|
|
14499
15217
|
registerEnabledExtensions(kernel, pluginRuntime, registerOpts);
|
|
14500
15218
|
return extensions;
|
|
14501
15219
|
}
|
|
15220
|
+
function loadScanInputs(opts, ctx) {
|
|
15221
|
+
try {
|
|
15222
|
+
const cfg = loadConfig({ strict: opts.strict, ...ctx }).effective;
|
|
15223
|
+
const ignoreFilter = buildScanIgnoreFilter(cfg, ctx.cwd);
|
|
15224
|
+
const strict = opts.strict || cfg.scan.strict === true;
|
|
15225
|
+
const effectiveRoots = resolveScanRoots({ positionalRoots: opts.roots });
|
|
15226
|
+
return { cfg, ignoreFilter, strict, effectiveRoots };
|
|
15227
|
+
} catch (err) {
|
|
15228
|
+
return { kind: "config-error", message: formatErrorMessage(err) };
|
|
15229
|
+
}
|
|
15230
|
+
}
|
|
14502
15231
|
function buildScanIgnoreFilter(cfg, cwd) {
|
|
14503
15232
|
const ignoreFileText = readIgnoreFileText(cwd);
|
|
14504
15233
|
const ignoreFilterOpts = {};
|
|
@@ -14521,7 +15250,7 @@ function makePriorLoader(noBuiltIns, strict) {
|
|
|
14521
15250
|
return loaded;
|
|
14522
15251
|
};
|
|
14523
15252
|
}
|
|
14524
|
-
function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd) {
|
|
15253
|
+
function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider) {
|
|
14525
15254
|
return async (prior, priorExtractorRuns, orphanJobFiles) => {
|
|
14526
15255
|
if (opts.changed && prior === null) {
|
|
14527
15256
|
opts.stderr.write(SCAN_RUNNER_TEXTS.changedNoPriorWarning);
|
|
@@ -14535,6 +15264,7 @@ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, exte
|
|
|
14535
15264
|
referenceablePaths,
|
|
14536
15265
|
cwd: scanCwd,
|
|
14537
15266
|
prior,
|
|
15267
|
+
activeProvider,
|
|
14538
15268
|
...priorExtractorRuns ? { priorExtractorRuns } : {},
|
|
14539
15269
|
...orphanJobFiles ? { orphanJobFiles } : {}
|
|
14540
15270
|
});
|
|
@@ -14555,7 +15285,8 @@ function buildRunScanOptions(args2) {
|
|
|
14555
15285
|
// visible from this caller" (legacy behaviour). The orchestrator
|
|
14556
15286
|
// defaults to `[]` when the field is absent; we always pass the
|
|
14557
15287
|
// array (possibly empty) to keep the wiring uniform.
|
|
14558
|
-
orphanJobFiles: orphanJobFiles ?? []
|
|
15288
|
+
orphanJobFiles: orphanJobFiles ?? [],
|
|
15289
|
+
activeProvider: args2.activeProvider
|
|
14559
15290
|
};
|
|
14560
15291
|
if (args2.extensions) runOptions.extensions = args2.extensions;
|
|
14561
15292
|
if (prior) {
|
|
@@ -14698,7 +15429,7 @@ var InitCommand = class extends SmCommand {
|
|
|
14698
15429
|
async run() {
|
|
14699
15430
|
const ctx = defaultRuntimeContext();
|
|
14700
15431
|
const scopeRoot = ctx.cwd;
|
|
14701
|
-
const skillMapDir =
|
|
15432
|
+
const skillMapDir = join17(scopeRoot, SKILL_MAP_DIR);
|
|
14702
15433
|
const settingsPath = defaultSettingsPath(scopeRoot);
|
|
14703
15434
|
const localPath = defaultLocalSettingsPath(scopeRoot);
|
|
14704
15435
|
const ignorePath = defaultIgnoreFilePath(scopeRoot);
|
|
@@ -14744,7 +15475,7 @@ var InitCommand = class extends SmCommand {
|
|
|
14744
15475
|
const okGlyph = ansi.green("\u2713");
|
|
14745
15476
|
const updated = await ensureGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
|
|
14746
15477
|
if (updated) {
|
|
14747
|
-
const gitignorePath =
|
|
15478
|
+
const gitignorePath = join17(scopeRoot, ".gitignore");
|
|
14748
15479
|
printer.info(
|
|
14749
15480
|
GITIGNORE_ENTRIES.length === 1 ? tx(INIT_TEXTS.gitignoreUpdatedSingular, { glyph: okGlyph, path: gitignorePath }) : tx(INIT_TEXTS.gitignoreUpdatedPlural, {
|
|
14750
15481
|
glyph: okGlyph,
|
|
@@ -14783,7 +15514,7 @@ async function dryRunFileMessage(path) {
|
|
|
14783
15514
|
}
|
|
14784
15515
|
async function writeDryRunGitignorePlan(printer, scopeRoot) {
|
|
14785
15516
|
const wouldAdd = await previewGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
|
|
14786
|
-
const gitignorePath =
|
|
15517
|
+
const gitignorePath = join17(scopeRoot, ".gitignore");
|
|
14787
15518
|
if (wouldAdd.length === 0) {
|
|
14788
15519
|
printer.info(tx(INIT_TEXTS.dryRunWouldLeaveGitignoreUnchanged, { path: gitignorePath }));
|
|
14789
15520
|
} else if (wouldAdd.length === 1) {
|
|
@@ -14822,7 +15553,14 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, ansi) {
|
|
|
14822
15553
|
strict,
|
|
14823
15554
|
stderr,
|
|
14824
15555
|
printer,
|
|
14825
|
-
ctx: { cwd: scopeRoot }
|
|
15556
|
+
ctx: { cwd: scopeRoot },
|
|
15557
|
+
// Init's first scan is a provisioning step, not the user's
|
|
15558
|
+
// primary "show me my graph" call. Don't block waiting for the
|
|
15559
|
+
// operator to disambiguate the lens here; let init complete with
|
|
15560
|
+
// `activeProvider` unset and let the FIRST explicit `sm scan`
|
|
15561
|
+
// surface the prompt. Treat the `ambiguous-provider` outcome below
|
|
15562
|
+
// as a soft hint, not a failure.
|
|
15563
|
+
yes: true
|
|
14826
15564
|
});
|
|
14827
15565
|
const errGlyph = ansi.red("\u2715");
|
|
14828
15566
|
if (outcome.kind === "config-error") {
|
|
@@ -14842,6 +15580,10 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, ansi) {
|
|
|
14842
15580
|
);
|
|
14843
15581
|
return ExitCode.Error;
|
|
14844
15582
|
}
|
|
15583
|
+
if (outcome.kind === "ambiguous-provider") {
|
|
15584
|
+
printer.warn(outcome.message);
|
|
15585
|
+
return ExitCode.Ok;
|
|
15586
|
+
}
|
|
14845
15587
|
const result = outcome.result;
|
|
14846
15588
|
const hasErrors = result.issues.some((i) => i.severity === "error");
|
|
14847
15589
|
printer.info(
|
|
@@ -14858,7 +15600,7 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, ansi) {
|
|
|
14858
15600
|
return hasErrors ? ExitCode.Issues : ExitCode.Ok;
|
|
14859
15601
|
}
|
|
14860
15602
|
async function previewGitignoreEntries(scopeRoot, entries) {
|
|
14861
|
-
const path =
|
|
15603
|
+
const path = join17(scopeRoot, ".gitignore");
|
|
14862
15604
|
const body = await pathExists(path) ? await readFile2(path, "utf8") : "";
|
|
14863
15605
|
const present = new Set(
|
|
14864
15606
|
body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
@@ -14866,7 +15608,7 @@ async function previewGitignoreEntries(scopeRoot, entries) {
|
|
|
14866
15608
|
return entries.filter((entry) => !present.has(entry));
|
|
14867
15609
|
}
|
|
14868
15610
|
async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
14869
|
-
const path =
|
|
15611
|
+
const path = join17(scopeRoot, ".gitignore");
|
|
14870
15612
|
let body = "";
|
|
14871
15613
|
if (await pathExists(path)) {
|
|
14872
15614
|
body = await readFile2(path, "utf8");
|
|
@@ -16431,8 +17173,8 @@ var PLUGINS_TEXTS = {
|
|
|
16431
17173
|
doctorIssueEntry: " {{glyph}} {{id}} {{status}}\n",
|
|
16432
17174
|
doctorIssueBody: " {{line}}\n",
|
|
16433
17175
|
// --- enable / disable -----------------------------------------------
|
|
16434
|
-
toggleBothIdAndAll: "{{glyph}} Pass either
|
|
16435
|
-
toggleNeitherIdNorAll: "{{glyph}} Pass <id> or --all.\n",
|
|
17176
|
+
toggleBothIdAndAll: "{{glyph}} Pass either one or more <id> arguments or --all, not both.\n",
|
|
17177
|
+
toggleNeitherIdNorAll: "{{glyph}} Pass one or more <id> arguments, or --all.\n",
|
|
16436
17178
|
toggleResolveError: "{{error}}",
|
|
16437
17179
|
toggleAppliedSingle: "{{verbPast}}: {{id}}\n",
|
|
16438
17180
|
toggleAppliedManyHeader: "{{verbPast}}: {{count}} plugin(s)\n",
|
|
@@ -16534,9 +17276,9 @@ var PLUGINS_TEXTS = {
|
|
|
16534
17276
|
};
|
|
16535
17277
|
|
|
16536
17278
|
// cli/commands/plugins/shared.ts
|
|
16537
|
-
import { resolve as
|
|
17279
|
+
import { resolve as resolve31 } from "path";
|
|
16538
17280
|
function resolveSearchPaths2(opts, cwd) {
|
|
16539
|
-
if (opts.pluginDir) return [
|
|
17281
|
+
if (opts.pluginDir) return [resolve31(opts.pluginDir)];
|
|
16540
17282
|
return [defaultProjectPluginsDir({ cwd })];
|
|
16541
17283
|
}
|
|
16542
17284
|
async function buildResolver() {
|
|
@@ -17418,7 +18160,7 @@ function buildDoctorJsonEnvelope(args2) {
|
|
|
17418
18160
|
import { Command as Command25, Option as Option24 } from "clipanion";
|
|
17419
18161
|
var TogglePluginsBase = class extends SmCommand {
|
|
17420
18162
|
all = Option24.Boolean("--all", false);
|
|
17421
|
-
|
|
18163
|
+
ids = Option24.Rest({ name: "ids" });
|
|
17422
18164
|
async toggle(enabled) {
|
|
17423
18165
|
const verb = enabled ? "enable" : "disable";
|
|
17424
18166
|
const stderrAnsi = this.ansiFor("stderr");
|
|
@@ -17437,23 +18179,24 @@ var TogglePluginsBase = class extends SmCommand {
|
|
|
17437
18179
|
return ExitCode.Ok;
|
|
17438
18180
|
}
|
|
17439
18181
|
/**
|
|
17440
|
-
* `--all` vs `<id
|
|
17441
|
-
* one must be present; surfaces a directed error on misuse.
|
|
18182
|
+
* `--all` vs `<id>...` mutex check. The two are mutually exclusive
|
|
18183
|
+
* and one must be present; surfaces a directed error on misuse.
|
|
18184
|
+
* Variadic positional accepts one or more ids.
|
|
17442
18185
|
*/
|
|
17443
18186
|
#validateArgs(ansi) {
|
|
17444
18187
|
const errGlyph = ansi.red("\u2715");
|
|
17445
|
-
if (this.all && this.
|
|
18188
|
+
if (this.all && this.ids.length > 0) {
|
|
17446
18189
|
this.printer.error(tx(PLUGINS_TEXTS.toggleBothIdAndAll, { glyph: errGlyph }));
|
|
17447
18190
|
return ExitCode.Error;
|
|
17448
18191
|
}
|
|
17449
|
-
if (!this.all &&
|
|
18192
|
+
if (!this.all && this.ids.length === 0) {
|
|
17450
18193
|
this.printer.error(tx(PLUGINS_TEXTS.toggleNeitherIdNorAll, { glyph: errGlyph }));
|
|
17451
18194
|
return ExitCode.Error;
|
|
17452
18195
|
}
|
|
17453
18196
|
return null;
|
|
17454
18197
|
}
|
|
17455
18198
|
/**
|
|
17456
|
-
* Resolve `<id
|
|
18199
|
+
* Resolve `<id>...` against the catalogue or fan out via `--all`.
|
|
17457
18200
|
* Returns the target list on success, or the exit code on a
|
|
17458
18201
|
* directed-error path (unknown id, granularity mismatch).
|
|
17459
18202
|
*
|
|
@@ -17464,25 +18207,35 @@ var TogglePluginsBase = class extends SmCommand {
|
|
|
17464
18207
|
* the directed error message when they try the bundle id directly,
|
|
17465
18208
|
* so `--all` skips them here too and the real "disable every core
|
|
17466
18209
|
* extension" intent is served by `--no-built-ins` on `sm scan`.
|
|
18210
|
+
*
|
|
18211
|
+
* Variadic mode is all-or-nothing: the first bad id aborts the
|
|
18212
|
+
* batch before any DB write, so the user never lands in a partial
|
|
18213
|
+
* state. Repeated ids in the same call are deduped.
|
|
17467
18214
|
*/
|
|
17468
18215
|
#pickTargets(catalogue, verb, ansi) {
|
|
17469
18216
|
if (this.all) {
|
|
17470
18217
|
return catalogue.filter((b) => b.granularity === "bundle").map((b) => b.id);
|
|
17471
18218
|
}
|
|
17472
|
-
const
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
18219
|
+
const keys = [];
|
|
18220
|
+
for (const rawId of this.ids) {
|
|
18221
|
+
const resolved = resolveToggleTarget(rawId, catalogue, verb, ansi);
|
|
18222
|
+
if ("error" in resolved) {
|
|
18223
|
+
this.printer.error(tx(PLUGINS_TEXTS.toggleResolveError, { error: resolved.error }));
|
|
18224
|
+
return ExitCode.NotFound;
|
|
18225
|
+
}
|
|
18226
|
+
keys.push(resolved.key);
|
|
17476
18227
|
}
|
|
17477
|
-
return [
|
|
18228
|
+
return [...new Set(keys)];
|
|
17478
18229
|
}
|
|
17479
18230
|
/**
|
|
17480
|
-
* Host lock, see `src/kernel/config/locked-plugins.ts`.
|
|
17481
|
-
*
|
|
17482
|
-
* rest. Single-id mode
|
|
18231
|
+
* Host lock, see `src/kernel/config/locked-plugins.ts`. Bulk modes
|
|
18232
|
+
* (`--all` or an explicit batch of >1 ids) silently skip locked
|
|
18233
|
+
* targets so the user can still toggle the rest. Single-id mode
|
|
18234
|
+
* surfaces a directed exit-5 message so the user knows their one
|
|
18235
|
+
* intended target was refused.
|
|
17483
18236
|
*/
|
|
17484
18237
|
#applyLockGate(targets, ansi) {
|
|
17485
|
-
if (this.all) return targets.filter((id) => !isPluginLocked(id));
|
|
18238
|
+
if (this.all || this.ids.length > 1) return targets.filter((id) => !isPluginLocked(id));
|
|
17486
18239
|
const lockedHit = targets.find((id) => isPluginLocked(id));
|
|
17487
18240
|
if (!lockedHit) return targets;
|
|
17488
18241
|
this.printer.error(
|
|
@@ -17538,12 +18291,19 @@ var PluginsEnableCommand = class extends TogglePluginsBase {
|
|
|
17538
18291
|
static paths = [["plugins", "enable"]];
|
|
17539
18292
|
static usage = Command25.Usage({
|
|
17540
18293
|
category: "Plugins",
|
|
17541
|
-
description: "Enable
|
|
18294
|
+
description: "Enable one or more plugins (or --all). Persists in config_plugins.",
|
|
17542
18295
|
details: `
|
|
17543
|
-
Writes a row to config_plugins with enabled=1. Takes
|
|
17544
|
-
over the team-shared baseline at
|
|
17545
|
-
Use sm plugins disable to
|
|
17546
|
-
drops the settings.json
|
|
18296
|
+
Writes a row to config_plugins with enabled=1 per id. Takes
|
|
18297
|
+
precedence over the team-shared baseline at
|
|
18298
|
+
settings.json#/plugins/<id>/enabled. Use sm plugins disable to
|
|
18299
|
+
flip; sm config reset plugins.<id>.enabled drops the settings.json
|
|
18300
|
+
baseline.
|
|
18301
|
+
|
|
18302
|
+
Accepts one or more ids in one call, e.g.
|
|
18303
|
+
'sm plugins enable claude gemini openai'. Batches are
|
|
18304
|
+
all-or-nothing: a single unknown / mismatched id aborts before
|
|
18305
|
+
any write. Repeated ids are deduped. Locked plugins inside a
|
|
18306
|
+
batch are silently skipped.
|
|
17547
18307
|
|
|
17548
18308
|
Granularity: a bundle-granularity plugin (default for user plugins,
|
|
17549
18309
|
and the built-in 'claude' bundle) accepts only the bundle id. An
|
|
@@ -17560,12 +18320,18 @@ var PluginsDisableCommand = class extends TogglePluginsBase {
|
|
|
17560
18320
|
static paths = [["plugins", "disable"]];
|
|
17561
18321
|
static usage = Command25.Usage({
|
|
17562
18322
|
category: "Plugins",
|
|
17563
|
-
description: "Disable
|
|
18323
|
+
description: "Disable one or more plugins (or --all). Persists in config_plugins; does not delete files.",
|
|
17564
18324
|
details: `
|
|
17565
|
-
Writes a row to config_plugins with enabled=0. Discovery
|
|
17566
|
-
surfaces the plugin in sm plugins list, but with
|
|
17567
|
-
its extensions are not imported and the kernel
|
|
17568
|
-
them.
|
|
18325
|
+
Writes a row to config_plugins with enabled=0 per id. Discovery
|
|
18326
|
+
still surfaces the plugin in sm plugins list, but with
|
|
18327
|
+
status=disabled; its extensions are not imported and the kernel
|
|
18328
|
+
will not run them.
|
|
18329
|
+
|
|
18330
|
+
Accepts one or more ids in one call, e.g.
|
|
18331
|
+
'sm plugins disable gemini openai agent-skills'. Batches are
|
|
18332
|
+
all-or-nothing: a single unknown / mismatched id aborts before
|
|
18333
|
+
any write. Repeated ids are deduped. Locked plugins inside a
|
|
18334
|
+
batch are silently skipped.
|
|
17569
18335
|
|
|
17570
18336
|
Granularity: a bundle-granularity plugin (default for user plugins,
|
|
17571
18337
|
and the built-in 'claude' bundle) accepts only the bundle id. An
|
|
@@ -17671,8 +18437,8 @@ function resolveBareToggle(id, catalogue, verb, ansi) {
|
|
|
17671
18437
|
}
|
|
17672
18438
|
|
|
17673
18439
|
// cli/commands/plugins/create.ts
|
|
17674
|
-
import { existsSync as
|
|
17675
|
-
import { join as
|
|
18440
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
18441
|
+
import { join as join18, resolve as resolve32 } from "path";
|
|
17676
18442
|
import { Command as Command26, Option as Option25 } from "clipanion";
|
|
17677
18443
|
var PluginsCreateCommand = class extends SmCommand {
|
|
17678
18444
|
static paths = [["plugins", "create"]];
|
|
@@ -17698,8 +18464,8 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
17698
18464
|
}
|
|
17699
18465
|
const ctx = defaultRuntimeContext();
|
|
17700
18466
|
const baseDir = defaultProjectPluginsDir(ctx);
|
|
17701
|
-
const targetDir = this.at ?
|
|
17702
|
-
if (
|
|
18467
|
+
const targetDir = this.at ? resolve32(this.at) : join18(baseDir, this.pluginId);
|
|
18468
|
+
if (existsSync23(targetDir) && !this.force) {
|
|
17703
18469
|
this.printer.error(
|
|
17704
18470
|
tx(PLUGINS_TEXTS.createRefuseOverwrite, {
|
|
17705
18471
|
glyph: errGlyph,
|
|
@@ -17709,7 +18475,7 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
17709
18475
|
return ExitCode.Error;
|
|
17710
18476
|
}
|
|
17711
18477
|
const extractorName = `${this.pluginId}-extractor`;
|
|
17712
|
-
mkdirSync6(
|
|
18478
|
+
mkdirSync6(join18(targetDir, "extractors", extractorName), { recursive: true });
|
|
17713
18479
|
const specVersion = installedSpecVersion();
|
|
17714
18480
|
const manifest = {
|
|
17715
18481
|
id: this.pluginId,
|
|
@@ -17729,14 +18495,14 @@ var PluginsCreateCommand = class extends SmCommand {
|
|
|
17729
18495
|
}
|
|
17730
18496
|
};
|
|
17731
18497
|
writeFileSync3(
|
|
17732
|
-
|
|
18498
|
+
join18(targetDir, "plugin.json"),
|
|
17733
18499
|
JSON.stringify(manifest, null, 2) + "\n"
|
|
17734
18500
|
);
|
|
17735
18501
|
writeFileSync3(
|
|
17736
|
-
|
|
18502
|
+
join18(targetDir, "extractors", extractorName, "index.js"),
|
|
17737
18503
|
scaffolderExtractorStub(extractorName)
|
|
17738
18504
|
);
|
|
17739
|
-
writeFileSync3(
|
|
18505
|
+
writeFileSync3(join18(targetDir, "README.md"), scaffolderReadme(this.pluginId));
|
|
17740
18506
|
this.printer.data(
|
|
17741
18507
|
tx(PLUGINS_TEXTS.createSuccess, {
|
|
17742
18508
|
targetDir: sanitizeForTerminal(targetDir),
|
|
@@ -17939,7 +18705,7 @@ var PLUGIN_COMMANDS = [
|
|
|
17939
18705
|
|
|
17940
18706
|
// cli/commands/refresh.ts
|
|
17941
18707
|
import { readFile as readFile3 } from "fs/promises";
|
|
17942
|
-
import { resolve as
|
|
18708
|
+
import { resolve as resolve33 } from "path";
|
|
17943
18709
|
import { Command as Command29, Option as Option27 } from "clipanion";
|
|
17944
18710
|
|
|
17945
18711
|
// cli/i18n/refresh.texts.ts
|
|
@@ -18223,7 +18989,7 @@ var RefreshCommand = class extends SmCommand {
|
|
|
18223
18989
|
let body;
|
|
18224
18990
|
try {
|
|
18225
18991
|
assertContained(cwd, node.path);
|
|
18226
|
-
const raw = await readFile3(
|
|
18992
|
+
const raw = await readFile3(resolve33(cwd, node.path), "utf8");
|
|
18227
18993
|
body = stripFrontmatterFence(raw);
|
|
18228
18994
|
} catch (err) {
|
|
18229
18995
|
if (!this.json) {
|
|
@@ -18964,6 +19730,9 @@ var ScanCommand = class extends SmCommand {
|
|
|
18964
19730
|
watch = Option29.Boolean("--watch", false, {
|
|
18965
19731
|
description: "Long-running mode: watch the roots and trigger an incremental scan after each debounced batch of filesystem events. Alias of `sm watch`."
|
|
18966
19732
|
});
|
|
19733
|
+
yes = Option29.Boolean("--yes", false, {
|
|
19734
|
+
description: "Non-interactive mode for ambiguous activeProvider auto-detect. With `--yes`, multiple provider markers (.claude/, .gemini/, .codex/, AGENTS.md, .cursor/) under the scan tree exit non-zero instead of prompting the operator. Set the lens manually via `sm config set activeProvider <id>` and re-run."
|
|
19735
|
+
});
|
|
18967
19736
|
// Each branch in the orchestrator maps to one validation gate
|
|
18968
19737
|
// (--watch alias / --changed mutex / -g mutex / dispatch).
|
|
18969
19738
|
// Splitting per branch scatters the gate from the value it gates.
|
|
@@ -18992,9 +19761,11 @@ var ScanCommand = class extends SmCommand {
|
|
|
18992
19761
|
allowEmpty: this.allowEmpty,
|
|
18993
19762
|
strict: this.strict,
|
|
18994
19763
|
stderr: this.context.stderr,
|
|
19764
|
+
stdin: this.context.stdin,
|
|
18995
19765
|
printer: this.printer,
|
|
18996
19766
|
killSwitches: readConformanceKillSwitches(),
|
|
18997
|
-
colorEnabled
|
|
19767
|
+
colorEnabled,
|
|
19768
|
+
yes: this.yes
|
|
18998
19769
|
});
|
|
18999
19770
|
return outcome.kind === "ok" ? this.renderOutcome(outcome.result, outcome.persistedTo, outcome.dbPath, outcome.strict) : this.renderFailure(outcome);
|
|
19000
19771
|
}
|
|
@@ -19065,6 +19836,12 @@ var ScanCommand = class extends SmCommand {
|
|
|
19065
19836
|
);
|
|
19066
19837
|
return ExitCode.Error;
|
|
19067
19838
|
}
|
|
19839
|
+
if (outcome.kind === "ambiguous-provider") {
|
|
19840
|
+
this.printer.info(
|
|
19841
|
+
tx(SCAN_TEXTS.scanFailure, { glyph: errGlyph, message: outcome.message })
|
|
19842
|
+
);
|
|
19843
|
+
return ExitCode.Error;
|
|
19844
|
+
}
|
|
19068
19845
|
this.printer.info(
|
|
19069
19846
|
tx(SCAN_TEXTS.scanFailure, { glyph: errGlyph, message: outcome.message })
|
|
19070
19847
|
);
|
|
@@ -19147,7 +19924,7 @@ function plural(count, word) {
|
|
|
19147
19924
|
}
|
|
19148
19925
|
|
|
19149
19926
|
// cli/commands/scan-compare.ts
|
|
19150
|
-
import { existsSync as
|
|
19927
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
19151
19928
|
import { Command as Command32, Option as Option30 } from "clipanion";
|
|
19152
19929
|
var ScanCompareCommand = class extends SmCommand {
|
|
19153
19930
|
static paths = [["scan", "compare-with"]];
|
|
@@ -19259,7 +20036,7 @@ var ScanCompareCommand = class extends SmCommand {
|
|
|
19259
20036
|
}
|
|
19260
20037
|
};
|
|
19261
20038
|
function loadAndValidateDump(path) {
|
|
19262
|
-
if (!
|
|
20039
|
+
if (!existsSync24(path)) {
|
|
19263
20040
|
throw new Error(tx(SCAN_TEXTS.compareDumpNotFound, { path }));
|
|
19264
20041
|
}
|
|
19265
20042
|
let raw;
|
|
@@ -19389,7 +20166,7 @@ function renderDeltaIssues(issues) {
|
|
|
19389
20166
|
|
|
19390
20167
|
// cli/commands/serve.ts
|
|
19391
20168
|
import { spawn as spawn2 } from "child_process";
|
|
19392
|
-
import { existsSync as
|
|
20169
|
+
import { existsSync as existsSync30 } from "fs";
|
|
19393
20170
|
import { Command as Command33, Option as Option31 } from "clipanion";
|
|
19394
20171
|
|
|
19395
20172
|
// cli/util/browser-launch.ts
|
|
@@ -19411,7 +20188,7 @@ import { WebSocketServer } from "ws";
|
|
|
19411
20188
|
// server/app.ts
|
|
19412
20189
|
import { Hono } from "hono";
|
|
19413
20190
|
import { bodyLimit } from "hono/body-limit";
|
|
19414
|
-
import { HTTPException as
|
|
20191
|
+
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
19415
20192
|
|
|
19416
20193
|
// core/config/service.ts
|
|
19417
20194
|
var ConfigService = class {
|
|
@@ -19719,7 +20496,20 @@ var SERVER_TEXTS = {
|
|
|
19719
20496
|
// is a synthetic `emitter.error` event; v14.4.a does not yet route
|
|
19720
20497
|
// it through the broadcaster (would re-enter the same stringify
|
|
19721
20498
|
// path), so we degrade to a logged warning.
|
|
19722
|
-
wsBroadcastSerializeFailed: "skill-map server: ws broadcast dropped, failed to serialize event: {{message}}.\n"
|
|
20499
|
+
wsBroadcastSerializeFailed: "skill-map server: ws broadcast dropped, failed to serialize event: {{message}}.\n",
|
|
20500
|
+
// ---- active-provider route (routes/active-provider.ts) -----------
|
|
20501
|
+
//
|
|
20502
|
+
// GET / PUT /api/active-provider. The active provider lens selects
|
|
20503
|
+
// which provider's extractors / classifiers / resolution rules apply
|
|
20504
|
+
// to the whole project (see `architecture.md` §Active Provider Lens).
|
|
20505
|
+
// Changing the lens drops the `scan_*` zone atomically and prompts
|
|
20506
|
+
// the user to re-scan; `state_*` and `config_*` survive.
|
|
20507
|
+
activeProviderBodyNotJson: "Request body must be valid JSON.",
|
|
20508
|
+
activeProviderBodyNotObject: "Request body must be a JSON object.",
|
|
20509
|
+
activeProviderBodyMissing: "Request body must include `activeProvider` (a non-empty string).",
|
|
20510
|
+
activeProviderValueNotString: "`activeProvider` must be a string.",
|
|
20511
|
+
activeProviderValueEmpty: "`activeProvider` cannot be the empty string. Send the id of an enabled provider.",
|
|
20512
|
+
activeProviderPersistFailed: "Could not persist activeProvider: {{message}}"
|
|
19723
20513
|
};
|
|
19724
20514
|
|
|
19725
20515
|
// server/loopback-gate.ts
|
|
@@ -20089,7 +20879,7 @@ function contentTypeFor(format) {
|
|
|
20089
20879
|
}
|
|
20090
20880
|
|
|
20091
20881
|
// server/health.ts
|
|
20092
|
-
import { existsSync as
|
|
20882
|
+
import { existsSync as existsSync25 } from "fs";
|
|
20093
20883
|
var FALLBACK_SCHEMA_VERSION = "1";
|
|
20094
20884
|
function buildHealth(deps) {
|
|
20095
20885
|
return {
|
|
@@ -20097,7 +20887,7 @@ function buildHealth(deps) {
|
|
|
20097
20887
|
schemaVersion: FALLBACK_SCHEMA_VERSION,
|
|
20098
20888
|
specVersion: deps.specVersion,
|
|
20099
20889
|
implVersion: VERSION,
|
|
20100
|
-
db:
|
|
20890
|
+
db: existsSync25(deps.dbPath) ? "present" : "missing",
|
|
20101
20891
|
cwd: deps.cwd,
|
|
20102
20892
|
dbPath: deps.dbPath
|
|
20103
20893
|
};
|
|
@@ -20210,9 +21000,9 @@ import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
|
20210
21000
|
|
|
20211
21001
|
// server/node-body.ts
|
|
20212
21002
|
import { readFile as readFile4 } from "fs/promises";
|
|
20213
|
-
import { isAbsolute as
|
|
21003
|
+
import { isAbsolute as isAbsolute10, resolve as resolvePath2, relative as relativePath, sep as sep5 } from "path";
|
|
20214
21004
|
async function readNodeBody(cwd, relPath) {
|
|
20215
|
-
if (
|
|
21005
|
+
if (isAbsolute10(relPath)) return null;
|
|
20216
21006
|
const absRoot = resolvePath2(cwd);
|
|
20217
21007
|
const absFile = resolvePath2(absRoot, relPath);
|
|
20218
21008
|
const rel = relativePath(absRoot, absFile);
|
|
@@ -20606,11 +21396,6 @@ function registerPluginsRoute(app, deps) {
|
|
|
20606
21396
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
|
|
20607
21397
|
});
|
|
20608
21398
|
}
|
|
20609
|
-
if (granularityOf(handle) !== "extension") {
|
|
20610
|
-
throw new HTTPException8(400, {
|
|
20611
|
-
message: tx(SERVER_TEXTS.pluginsGranularityBundleExpected, { id: bundleId })
|
|
20612
|
-
});
|
|
20613
|
-
}
|
|
20614
21399
|
if (!hasExtension(handle, extensionId)) {
|
|
20615
21400
|
throw new HTTPException8(404, {
|
|
20616
21401
|
message: tx(SERVER_TEXTS.pluginsExtensionUnknown, { bundleId, extensionId })
|
|
@@ -20651,7 +21436,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20651
21436
|
return builtInBundles.map((bundle) => {
|
|
20652
21437
|
const bundleEnabled = resolveEnabled(bundle.id);
|
|
20653
21438
|
const bundleLocked = isPluginLocked(bundle.id);
|
|
20654
|
-
const extensions = bundle.
|
|
21439
|
+
const extensions = bundle.extensions.map((ext) => {
|
|
20655
21440
|
const qualified = qualifiedExtensionId(bundle.id, ext.id);
|
|
20656
21441
|
const extLocked = bundleLocked || isPluginLocked(qualified);
|
|
20657
21442
|
return {
|
|
@@ -20662,7 +21447,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20662
21447
|
...ext.description ? { description: ext.description } : {},
|
|
20663
21448
|
...extLocked ? { locked: true } : {}
|
|
20664
21449
|
};
|
|
20665
|
-
})
|
|
21450
|
+
});
|
|
20666
21451
|
return {
|
|
20667
21452
|
id: bundle.id,
|
|
20668
21453
|
version: firstVersion(bundle.extensions),
|
|
@@ -20672,7 +21457,7 @@ function buildBuiltInItems(resolveEnabled) {
|
|
|
20672
21457
|
source: "built-in",
|
|
20673
21458
|
granularity: bundle.granularity,
|
|
20674
21459
|
description: bundle.description,
|
|
20675
|
-
...extensions ? { extensions } : {},
|
|
21460
|
+
...extensions.length > 0 ? { extensions } : {},
|
|
20676
21461
|
...bundleLocked ? { locked: true } : {}
|
|
20677
21462
|
};
|
|
20678
21463
|
});
|
|
@@ -20705,8 +21490,8 @@ function optionalDiscoveredFields(plugin, extensions) {
|
|
|
20705
21490
|
if (extensions) out.extensions = extensions;
|
|
20706
21491
|
return out;
|
|
20707
21492
|
}
|
|
20708
|
-
function projectExtensionRows(plugin,
|
|
20709
|
-
if (
|
|
21493
|
+
function projectExtensionRows(plugin, _granularity, resolveEnabled, bundleLocked) {
|
|
21494
|
+
if (!plugin.extensions || plugin.extensions.length === 0) return void 0;
|
|
20710
21495
|
return plugin.extensions.map((ext) => {
|
|
20711
21496
|
const description = readInstanceDescription(ext.instance);
|
|
20712
21497
|
const qualified = qualifiedExtensionId(plugin.id, ext.id);
|
|
@@ -20823,13 +21608,6 @@ function validateBulkChange(change, deps) {
|
|
|
20823
21608
|
message: tx(SERVER_TEXTS.pluginsUnknown, { id: bundleId })
|
|
20824
21609
|
};
|
|
20825
21610
|
}
|
|
20826
|
-
if (granularityOf(handle) !== "extension") {
|
|
20827
|
-
return {
|
|
20828
|
-
status: 400,
|
|
20829
|
-
code: "bad-query",
|
|
20830
|
-
message: tx(SERVER_TEXTS.pluginsGranularityBundleExpected, { id: bundleId })
|
|
20831
|
-
};
|
|
20832
|
-
}
|
|
20833
21611
|
if (!hasExtension(handle, extensionId)) {
|
|
20834
21612
|
return {
|
|
20835
21613
|
status: 404,
|
|
@@ -20944,12 +21722,12 @@ var parsePatchBody2 = makeBodyValidator(PATCH_BODY_SCHEMA, {
|
|
|
20944
21722
|
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
20945
21723
|
|
|
20946
21724
|
// server/util/skillmapignore-io.ts
|
|
20947
|
-
import { existsSync as
|
|
20948
|
-
import { resolve as
|
|
21725
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync4 } from "fs";
|
|
21726
|
+
import { resolve as resolve34 } from "path";
|
|
20949
21727
|
var IGNORE_FILENAME2 = ".skillmapignore";
|
|
20950
21728
|
function readPatterns(cwd) {
|
|
20951
|
-
const path =
|
|
20952
|
-
if (!
|
|
21729
|
+
const path = resolve34(cwd, IGNORE_FILENAME2);
|
|
21730
|
+
if (!existsSync26(path)) return [];
|
|
20953
21731
|
let raw;
|
|
20954
21732
|
try {
|
|
20955
21733
|
raw = readFileSync19(path, "utf8");
|
|
@@ -20959,8 +21737,8 @@ function readPatterns(cwd) {
|
|
|
20959
21737
|
return raw.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
20960
21738
|
}
|
|
20961
21739
|
function writePatterns(cwd, nextPatterns) {
|
|
20962
|
-
const path =
|
|
20963
|
-
const prior =
|
|
21740
|
+
const path = resolve34(cwd, IGNORE_FILENAME2);
|
|
21741
|
+
const prior = existsSync26(path) ? safeRead(path) : "";
|
|
20964
21742
|
const content = buildContent(prior, nextPatterns);
|
|
20965
21743
|
writeFileSync4(path, content, "utf8");
|
|
20966
21744
|
}
|
|
@@ -21309,8 +22087,70 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
|
|
|
21309
22087
|
}
|
|
21310
22088
|
});
|
|
21311
22089
|
|
|
21312
|
-
// server/routes/
|
|
22090
|
+
// server/routes/active-provider.ts
|
|
22091
|
+
import { existsSync as existsSync27 } from "fs";
|
|
21313
22092
|
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
22093
|
+
function registerActiveProviderRoute(app, deps) {
|
|
22094
|
+
app.get("/api/active-provider", (c) => {
|
|
22095
|
+
return c.json(buildEnvelope4(deps));
|
|
22096
|
+
});
|
|
22097
|
+
app.patch("/api/active-provider", async (c) => {
|
|
22098
|
+
const body = await parsePatchBody5(c.req.raw);
|
|
22099
|
+
const result = applyLensSwitch(deps, body.activeProvider);
|
|
22100
|
+
deps.configService.reload();
|
|
22101
|
+
return c.json({ ...buildEnvelope4(deps), switch: result });
|
|
22102
|
+
});
|
|
22103
|
+
}
|
|
22104
|
+
function buildEnvelope4(deps) {
|
|
22105
|
+
const r = resolveActiveProvider(deps.runtimeContext.cwd);
|
|
22106
|
+
return {
|
|
22107
|
+
activeProvider: r.resolved,
|
|
22108
|
+
detected: r.detected,
|
|
22109
|
+
source: r.source
|
|
22110
|
+
};
|
|
22111
|
+
}
|
|
22112
|
+
function applyLensSwitch(deps, newValue) {
|
|
22113
|
+
const cwd = deps.runtimeContext.cwd;
|
|
22114
|
+
try {
|
|
22115
|
+
writeConfigValue("activeProvider", newValue, { target: "project", cwd });
|
|
22116
|
+
} catch (err) {
|
|
22117
|
+
throw new HTTPException12(400, {
|
|
22118
|
+
message: tx(SERVER_TEXTS.activeProviderPersistFailed, {
|
|
22119
|
+
message: formatErrorMessage(err)
|
|
22120
|
+
})
|
|
22121
|
+
});
|
|
22122
|
+
}
|
|
22123
|
+
const dbPath = resolveDbPath({ db: void 0, cwd });
|
|
22124
|
+
if (!existsSync27(dbPath)) return { dropped: null };
|
|
22125
|
+
const dropResult = dropScanZone(dbPath);
|
|
22126
|
+
return {
|
|
22127
|
+
dropped: {
|
|
22128
|
+
tableCount: dropResult.tableCount,
|
|
22129
|
+
tableNames: dropResult.droppedTables
|
|
22130
|
+
}
|
|
22131
|
+
};
|
|
22132
|
+
}
|
|
22133
|
+
var PATCH_BODY_SCHEMA4 = {
|
|
22134
|
+
type: "object",
|
|
22135
|
+
additionalProperties: false,
|
|
22136
|
+
required: ["activeProvider"],
|
|
22137
|
+
properties: {
|
|
22138
|
+
activeProvider: { type: "string", minLength: 1 }
|
|
22139
|
+
}
|
|
22140
|
+
};
|
|
22141
|
+
var parsePatchBody5 = makeBodyValidator(PATCH_BODY_SCHEMA4, {
|
|
22142
|
+
notJson: SERVER_TEXTS.activeProviderBodyNotJson,
|
|
22143
|
+
notObject: SERVER_TEXTS.activeProviderBodyNotObject,
|
|
22144
|
+
invalid: SERVER_TEXTS.activeProviderBodyMissing,
|
|
22145
|
+
mapping: {
|
|
22146
|
+
":required": SERVER_TEXTS.activeProviderBodyMissing,
|
|
22147
|
+
"/activeProvider:type:string": SERVER_TEXTS.activeProviderValueNotString,
|
|
22148
|
+
"/activeProvider:minLength": SERVER_TEXTS.activeProviderValueEmpty
|
|
22149
|
+
}
|
|
22150
|
+
});
|
|
22151
|
+
|
|
22152
|
+
// server/routes/scan.ts
|
|
22153
|
+
import { HTTPException as HTTPException13 } from "hono/http-exception";
|
|
21314
22154
|
|
|
21315
22155
|
// server/scan-mutex.ts
|
|
21316
22156
|
var inFlight = null;
|
|
@@ -21318,14 +22158,14 @@ async function withScanMutex(fn) {
|
|
|
21318
22158
|
if (inFlight !== null) {
|
|
21319
22159
|
throw new ScanBusyError();
|
|
21320
22160
|
}
|
|
21321
|
-
let
|
|
22161
|
+
let resolve39;
|
|
21322
22162
|
inFlight = new Promise((r) => {
|
|
21323
|
-
|
|
22163
|
+
resolve39 = r;
|
|
21324
22164
|
});
|
|
21325
22165
|
try {
|
|
21326
22166
|
return await fn();
|
|
21327
22167
|
} finally {
|
|
21328
|
-
|
|
22168
|
+
resolve39();
|
|
21329
22169
|
inFlight = null;
|
|
21330
22170
|
}
|
|
21331
22171
|
}
|
|
@@ -21465,7 +22305,7 @@ function registerScanRoute(app, deps) {
|
|
|
21465
22305
|
}
|
|
21466
22306
|
async function runPersistedScan(c, deps) {
|
|
21467
22307
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
21468
|
-
throw new
|
|
22308
|
+
throw new HTTPException13(400, { message: SERVER_TEXTS.scanPostRequiresFullPipeline });
|
|
21469
22309
|
}
|
|
21470
22310
|
const dbExists = await tryWithSqlite(
|
|
21471
22311
|
{ databasePath: deps.options.dbPath, autoBackup: false },
|
|
@@ -21491,10 +22331,14 @@ async function runPersistedScan(c, deps) {
|
|
|
21491
22331
|
pluginRuntime: deps.pluginRuntime,
|
|
21492
22332
|
resolveEnabledOverride,
|
|
21493
22333
|
printer: bffScanRunnerPrinter,
|
|
21494
|
-
emitterFactory: () => buildBroadcasterEmitter(deps.broadcaster)
|
|
22334
|
+
emitterFactory: () => buildBroadcasterEmitter(deps.broadcaster),
|
|
22335
|
+
// BFF has no TTY; ambiguous activeProvider must be resolved by
|
|
22336
|
+
// the operator via the Settings UI (PATCH /api/active-provider)
|
|
22337
|
+
// before the scan, not via interactive prompt here.
|
|
22338
|
+
yes: true
|
|
21495
22339
|
});
|
|
21496
22340
|
if (outcome.kind !== "ok") {
|
|
21497
|
-
throw new
|
|
22341
|
+
throw new HTTPException13(500, {
|
|
21498
22342
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.scanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
21499
22343
|
});
|
|
21500
22344
|
}
|
|
@@ -21502,7 +22346,7 @@ async function runPersistedScan(c, deps) {
|
|
|
21502
22346
|
});
|
|
21503
22347
|
} catch (err) {
|
|
21504
22348
|
if (err instanceof ScanBusyError) {
|
|
21505
|
-
throw new
|
|
22349
|
+
throw new HTTPException13(409, { message: SERVER_TEXTS.scanPostBusy });
|
|
21506
22350
|
}
|
|
21507
22351
|
throw err;
|
|
21508
22352
|
}
|
|
@@ -21566,7 +22410,7 @@ function groupTagsBySource2(rows) {
|
|
|
21566
22410
|
}
|
|
21567
22411
|
async function runFreshScan(deps) {
|
|
21568
22412
|
if (deps.options.noBuiltIns || deps.options.noPlugins) {
|
|
21569
|
-
throw new
|
|
22413
|
+
throw new HTTPException13(400, { message: SERVER_TEXTS.freshScanRequiresPipeline });
|
|
21570
22414
|
}
|
|
21571
22415
|
const resolveEnabledOverride = await buildBffResolverOverride(deps);
|
|
21572
22416
|
const outcome = await runScanForCommand({
|
|
@@ -21592,10 +22436,13 @@ async function runFreshScan(deps) {
|
|
|
21592
22436
|
// fallback. The fresh-scan response body IS the ScanResult JSON,
|
|
21593
22437
|
// so `data` is never used here; warn/info/error route through
|
|
21594
22438
|
// `log.warn` (same surface the rest of the BFF uses).
|
|
21595
|
-
printer: bffScanRunnerPrinter
|
|
22439
|
+
printer: bffScanRunnerPrinter,
|
|
22440
|
+
// BFF has no TTY; ambiguous activeProvider is the operator's
|
|
22441
|
+
// problem to resolve via the Settings UI, not via prompt here.
|
|
22442
|
+
yes: true
|
|
21596
22443
|
});
|
|
21597
22444
|
if (outcome.kind !== "ok") {
|
|
21598
|
-
throw new
|
|
22445
|
+
throw new HTTPException13(500, {
|
|
21599
22446
|
message: outcome.kind === "guard-trip" ? tx(SERVER_TEXTS.freshScanGuardTrip, { existing: outcome.existing }) : outcome.message
|
|
21600
22447
|
});
|
|
21601
22448
|
}
|
|
@@ -21629,8 +22476,8 @@ function emptyScanResult() {
|
|
|
21629
22476
|
}
|
|
21630
22477
|
|
|
21631
22478
|
// server/routes/sidecar.ts
|
|
21632
|
-
import { HTTPException as
|
|
21633
|
-
import { resolve as
|
|
22479
|
+
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
22480
|
+
import { resolve as resolve35 } from "path";
|
|
21634
22481
|
var STATUS_FRESH = "fresh";
|
|
21635
22482
|
var ENVELOPE_KIND2 = "sidecar.bumped";
|
|
21636
22483
|
var BUMP_BODY_SCHEMA = {
|
|
@@ -21664,13 +22511,13 @@ function registerSidecarRoutes(app, deps) {
|
|
|
21664
22511
|
let absPath;
|
|
21665
22512
|
try {
|
|
21666
22513
|
assertContained(deps.runtimeContext.cwd, node.path);
|
|
21667
|
-
absPath =
|
|
22514
|
+
absPath = resolve35(deps.runtimeContext.cwd, node.path);
|
|
21668
22515
|
} catch (err) {
|
|
21669
|
-
throw new
|
|
22516
|
+
throw new HTTPException14(500, { message: formatErrorMessage(err) });
|
|
21670
22517
|
}
|
|
21671
22518
|
const result = invokeBump2(node, absPath, body);
|
|
21672
22519
|
if (result.report.ok === false && result.report.reason === "fresh") {
|
|
21673
|
-
throw new
|
|
22520
|
+
throw new HTTPException14(409, { message: SERVER_TEXTS.sidecarFreshRefusal });
|
|
21674
22521
|
}
|
|
21675
22522
|
if (result.report.ok === true && result.report.noop === true) {
|
|
21676
22523
|
const envelope2 = {
|
|
@@ -21697,7 +22544,7 @@ function registerSidecarRoutes(app, deps) {
|
|
|
21697
22544
|
}
|
|
21698
22545
|
} catch (err) {
|
|
21699
22546
|
if (err instanceof EConsentRequiredError) throw err;
|
|
21700
|
-
throw new
|
|
22547
|
+
throw new HTTPException14(500, { message: formatErrorMessage(err) });
|
|
21701
22548
|
}
|
|
21702
22549
|
if (body.confirm === true) {
|
|
21703
22550
|
deps.configService.reload();
|
|
@@ -21734,7 +22581,7 @@ async function loadNode(deps, nodePath) {
|
|
|
21734
22581
|
);
|
|
21735
22582
|
const node = persisted?.nodes.find((n) => n.path === nodePath);
|
|
21736
22583
|
if (!node) {
|
|
21737
|
-
throw new
|
|
22584
|
+
throw new HTTPException14(404, {
|
|
21738
22585
|
message: tx(SERVER_TEXTS.nodeNotFound, { path: sanitizeForTerminal(nodePath) })
|
|
21739
22586
|
});
|
|
21740
22587
|
}
|
|
@@ -21742,7 +22589,7 @@ async function loadNode(deps, nodePath) {
|
|
|
21742
22589
|
}
|
|
21743
22590
|
function invokeBump2(node, absPath, body) {
|
|
21744
22591
|
if (!bumpAction.invoke) {
|
|
21745
|
-
throw new
|
|
22592
|
+
throw new HTTPException14(500, { message: SERVER_TEXTS.sidecarBumpInvokeMissing });
|
|
21746
22593
|
}
|
|
21747
22594
|
const input = {};
|
|
21748
22595
|
if (body.force === true) input.force = true;
|
|
@@ -21788,9 +22635,9 @@ function registerUpdateStatusRoute(app, deps) {
|
|
|
21788
22635
|
}
|
|
21789
22636
|
|
|
21790
22637
|
// server/static.ts
|
|
21791
|
-
import { existsSync as
|
|
22638
|
+
import { existsSync as existsSync28 } from "fs";
|
|
21792
22639
|
import { readFile as readFile5 } from "fs/promises";
|
|
21793
|
-
import { extname, join as
|
|
22640
|
+
import { extname, join as join19 } from "path";
|
|
21794
22641
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
21795
22642
|
var INDEX_HTML = "index.html";
|
|
21796
22643
|
var PLACEHOLDER_HTML = `<!doctype html>
|
|
@@ -21842,8 +22689,8 @@ function createSpaFallback(opts) {
|
|
|
21842
22689
|
return async (c, _next) => {
|
|
21843
22690
|
if (c.req.method !== "GET" && c.req.method !== "HEAD") return c.notFound();
|
|
21844
22691
|
if (opts.uiDist === null) return htmlResponse(c, placeholder);
|
|
21845
|
-
const indexPath =
|
|
21846
|
-
if (!
|
|
22692
|
+
const indexPath = join19(opts.uiDist, INDEX_HTML);
|
|
22693
|
+
if (!existsSync28(indexPath)) return htmlResponse(c, placeholder);
|
|
21847
22694
|
return fileResponse(c, indexPath);
|
|
21848
22695
|
};
|
|
21849
22696
|
}
|
|
@@ -21929,13 +22776,13 @@ function attachBroadcasterRoute(app, broadcaster) {
|
|
|
21929
22776
|
|
|
21930
22777
|
// server/app.ts
|
|
21931
22778
|
var BODY_LIMIT_BYTES = 1024 * 1024;
|
|
21932
|
-
var DbMissingError = class extends
|
|
22779
|
+
var DbMissingError = class extends HTTPException15 {
|
|
21933
22780
|
constructor(message) {
|
|
21934
22781
|
super(500, { message });
|
|
21935
22782
|
this.name = "DbMissingError";
|
|
21936
22783
|
}
|
|
21937
22784
|
};
|
|
21938
|
-
var BulkValidationError = class extends
|
|
22785
|
+
var BulkValidationError = class extends HTTPException15 {
|
|
21939
22786
|
id;
|
|
21940
22787
|
code;
|
|
21941
22788
|
constructor(init) {
|
|
@@ -21945,7 +22792,7 @@ var BulkValidationError = class extends HTTPException14 {
|
|
|
21945
22792
|
this.code = init.code;
|
|
21946
22793
|
}
|
|
21947
22794
|
};
|
|
21948
|
-
var LoopbackGateError = class extends
|
|
22795
|
+
var LoopbackGateError = class extends HTTPException15 {
|
|
21949
22796
|
code;
|
|
21950
22797
|
constructor(init) {
|
|
21951
22798
|
super(403, { message: init.message });
|
|
@@ -21965,7 +22812,7 @@ function createApp(deps) {
|
|
|
21965
22812
|
bodyLimit({
|
|
21966
22813
|
maxSize: BODY_LIMIT_BYTES,
|
|
21967
22814
|
onError: () => {
|
|
21968
|
-
throw new
|
|
22815
|
+
throw new HTTPException15(413, { message: tx(SERVER_TEXTS.bodyTooLarge, { maxBytes: String(BODY_LIMIT_BYTES) }) });
|
|
21969
22816
|
}
|
|
21970
22817
|
})
|
|
21971
22818
|
);
|
|
@@ -22006,9 +22853,10 @@ function createApp(deps) {
|
|
|
22006
22853
|
registerUpdateStatusRoute(app, routeDeps);
|
|
22007
22854
|
registerPreferencesRoute(app, routeDeps);
|
|
22008
22855
|
registerProjectPreferencesRoute(app, routeDeps);
|
|
22856
|
+
registerActiveProviderRoute(app, routeDeps);
|
|
22009
22857
|
registerProjectIgnoreRoute(app, routeDeps);
|
|
22010
22858
|
app.all("/api/*", (c) => {
|
|
22011
|
-
throw new
|
|
22859
|
+
throw new HTTPException15(404, {
|
|
22012
22860
|
message: tx(SERVER_TEXTS.unknownApiEndpoint, { path: sanitizeForTerminal(c.req.path) })
|
|
22013
22861
|
});
|
|
22014
22862
|
});
|
|
@@ -22016,7 +22864,7 @@ function createApp(deps) {
|
|
|
22016
22864
|
app.use("*", createStaticHandler({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
22017
22865
|
app.get("*", createSpaFallback({ uiDist: deps.options.uiDist, noUi: deps.options.noUi }));
|
|
22018
22866
|
app.notFound((c) => {
|
|
22019
|
-
throw new
|
|
22867
|
+
throw new HTTPException15(404, {
|
|
22020
22868
|
message: tx(SERVER_TEXTS.unknownPath, { path: sanitizeForTerminal(c.req.path) })
|
|
22021
22869
|
});
|
|
22022
22870
|
});
|
|
@@ -22071,7 +22919,7 @@ function formatError2(err, c) {
|
|
|
22071
22919
|
};
|
|
22072
22920
|
return c.json(envelope, 403);
|
|
22073
22921
|
}
|
|
22074
|
-
if (err instanceof
|
|
22922
|
+
if (err instanceof HTTPException15) {
|
|
22075
22923
|
const status = err.status;
|
|
22076
22924
|
const envelope = {
|
|
22077
22925
|
ok: false,
|
|
@@ -22414,10 +23262,10 @@ function validateNoUi(noUi, uiDist) {
|
|
|
22414
23262
|
}
|
|
22415
23263
|
|
|
22416
23264
|
// server/paths.ts
|
|
22417
|
-
import { existsSync as
|
|
22418
|
-
import { dirname as dirname18, isAbsolute as
|
|
23265
|
+
import { existsSync as existsSync29, statSync as statSync11 } from "fs";
|
|
23266
|
+
import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve36 } from "path";
|
|
22419
23267
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
22420
|
-
var DEFAULT_UI_REL =
|
|
23268
|
+
var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
|
|
22421
23269
|
var PACKAGE_UI_REL = "ui";
|
|
22422
23270
|
var INDEX_HTML2 = "index.html";
|
|
22423
23271
|
function resolveDefaultUiDist(ctx) {
|
|
@@ -22426,13 +23274,13 @@ function resolveDefaultUiDist(ctx) {
|
|
|
22426
23274
|
return walkUpForUi(ctx.cwd);
|
|
22427
23275
|
}
|
|
22428
23276
|
function resolveExplicitUiDist(ctx, raw) {
|
|
22429
|
-
return
|
|
23277
|
+
return isAbsolute11(raw) ? raw : resolve36(ctx.cwd, raw);
|
|
22430
23278
|
}
|
|
22431
23279
|
function isUiBundleDir(path) {
|
|
22432
|
-
if (!
|
|
23280
|
+
if (!existsSync29(path)) return false;
|
|
22433
23281
|
try {
|
|
22434
23282
|
if (!statSync11(path).isDirectory()) return false;
|
|
22435
|
-
return
|
|
23283
|
+
return existsSync29(join20(path, INDEX_HTML2));
|
|
22436
23284
|
} catch {
|
|
22437
23285
|
return false;
|
|
22438
23286
|
}
|
|
@@ -22449,9 +23297,9 @@ function resolvePackageBundledUi() {
|
|
|
22449
23297
|
function resolvePackageBundledUiFrom(here) {
|
|
22450
23298
|
let current = here;
|
|
22451
23299
|
for (let i = 0; i < 8; i++) {
|
|
22452
|
-
const candidate =
|
|
23300
|
+
const candidate = join20(current, PACKAGE_UI_REL);
|
|
22453
23301
|
if (isUiBundleDir(candidate)) return candidate;
|
|
22454
|
-
const distHere =
|
|
23302
|
+
const distHere = join20(current, "dist", PACKAGE_UI_REL);
|
|
22455
23303
|
if (isUiBundleDir(distHere)) return distHere;
|
|
22456
23304
|
const parent = dirname18(current);
|
|
22457
23305
|
if (parent === current) return null;
|
|
@@ -22460,9 +23308,9 @@ function resolvePackageBundledUiFrom(here) {
|
|
|
22460
23308
|
return null;
|
|
22461
23309
|
}
|
|
22462
23310
|
function walkUpForUi(startDir) {
|
|
22463
|
-
let current =
|
|
23311
|
+
let current = resolve36(startDir);
|
|
22464
23312
|
for (let i = 0; i < 64; i++) {
|
|
22465
|
-
const candidate =
|
|
23313
|
+
const candidate = join20(current, DEFAULT_UI_REL);
|
|
22466
23314
|
if (isUiBundleDir(candidate)) return candidate;
|
|
22467
23315
|
const parent = dirname18(current);
|
|
22468
23316
|
if (parent === current) return null;
|
|
@@ -22664,7 +23512,7 @@ var SERVE_TEXTS = {
|
|
|
22664
23512
|
};
|
|
22665
23513
|
|
|
22666
23514
|
// cli/util/serve-banner.ts
|
|
22667
|
-
import { relative as relative7, isAbsolute as
|
|
23515
|
+
import { relative as relative7, isAbsolute as isAbsolute12 } from "path";
|
|
22668
23516
|
var ESC2 = {
|
|
22669
23517
|
reset: "\x1B[0m",
|
|
22670
23518
|
bold: "\x1B[1m",
|
|
@@ -22798,9 +23646,9 @@ function resolveAnsi(colorEnabled) {
|
|
|
22798
23646
|
}
|
|
22799
23647
|
function formatDbPath(dbPath, cwd) {
|
|
22800
23648
|
const safe = sanitizeForTerminal(dbPath);
|
|
22801
|
-
if (!
|
|
23649
|
+
if (!isAbsolute12(safe)) return safe;
|
|
22802
23650
|
const rel = relative7(cwd, safe);
|
|
22803
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
23651
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute12(rel)) {
|
|
22804
23652
|
return safe;
|
|
22805
23653
|
}
|
|
22806
23654
|
return rel;
|
|
@@ -22896,7 +23744,7 @@ var ServeCommand = class extends SmCommand {
|
|
|
22896
23744
|
return ExitCode.Error;
|
|
22897
23745
|
}
|
|
22898
23746
|
const dbPath = resolveDbPath({ db: this.db, ...runtimeCtx });
|
|
22899
|
-
if (this.db !== void 0 && !
|
|
23747
|
+
if (this.db !== void 0 && !existsSync30(dbPath)) {
|
|
22900
23748
|
this.printer.info(
|
|
22901
23749
|
tx(SERVE_TEXTS.dbNotFound, { path: sanitizeForTerminal(dbPath) })
|
|
22902
23750
|
);
|
|
@@ -23307,22 +24155,27 @@ function renderLinksSection(direction, links, ansi) {
|
|
|
23307
24155
|
const aggregated = aggregateLinks(links, projectField);
|
|
23308
24156
|
const headerTpl = direction === "out" ? SHOW_TEXTS.linksOutSection : SHOW_TEXTS.linksInSection;
|
|
23309
24157
|
const kindWidth = Math.max(...aggregated.map((g) => g.kind.length));
|
|
23310
|
-
const
|
|
24158
|
+
const confLabels = aggregated.map((g) => formatConfidence(g.confidence));
|
|
24159
|
+
const confWidth = Math.max(...confLabels.map((l) => l.length));
|
|
23311
24160
|
const lines = [tx(headerTpl, { count: links.length })];
|
|
23312
|
-
|
|
24161
|
+
aggregated.forEach((grp, idx) => {
|
|
23313
24162
|
const dup = grp.rowCount > 1 ? ansi.dim(tx(SHOW_TEXTS.linkDup, { count: grp.rowCount })) : "";
|
|
23314
24163
|
lines.push(
|
|
23315
24164
|
tx(SHOW_TEXTS.linkRow, {
|
|
23316
24165
|
arrow: ansi.dim(arrow),
|
|
23317
24166
|
kind: sanitizeForTerminal(grp.kind).padEnd(kindWidth),
|
|
23318
|
-
confidence: ansi.dim(
|
|
24167
|
+
confidence: ansi.dim(confLabels[idx].padEnd(confWidth)),
|
|
23319
24168
|
endpoint: sanitizeForTerminal(grp.endpoint),
|
|
23320
24169
|
dup
|
|
23321
24170
|
})
|
|
23322
24171
|
);
|
|
23323
|
-
}
|
|
24172
|
+
});
|
|
23324
24173
|
return lines.join("");
|
|
23325
24174
|
}
|
|
24175
|
+
function formatConfidence(c) {
|
|
24176
|
+
if (typeof c !== "number" || !Number.isFinite(c)) return "?";
|
|
24177
|
+
return `${Math.round(c * 100)}%`;
|
|
24178
|
+
}
|
|
23326
24179
|
function renderIssuesSection(issues, nodePath, ansi) {
|
|
23327
24180
|
const lines = [tx(SHOW_TEXTS.issuesSection, { count: issues.length })];
|
|
23328
24181
|
const analyzerWidth = Math.max(
|
|
@@ -23389,18 +24242,13 @@ function aggregateLinks(links, endpointSide) {
|
|
|
23389
24242
|
return a.kind.localeCompare(b.kind);
|
|
23390
24243
|
});
|
|
23391
24244
|
}
|
|
23392
|
-
var CONFIDENCE_RANK = {
|
|
23393
|
-
high: 2,
|
|
23394
|
-
medium: 1,
|
|
23395
|
-
low: 0
|
|
23396
|
-
};
|
|
23397
24245
|
function rankConfidenceForGrouping(c) {
|
|
23398
|
-
return
|
|
24246
|
+
return c;
|
|
23399
24247
|
}
|
|
23400
24248
|
|
|
23401
24249
|
// cli/commands/sidecar.ts
|
|
23402
|
-
import { existsSync as
|
|
23403
|
-
import { resolve as
|
|
24250
|
+
import { existsSync as existsSync31, unlinkSync as unlinkSync2 } from "fs";
|
|
24251
|
+
import { resolve as resolve37 } from "path";
|
|
23404
24252
|
import { Command as Command35, Option as Option33 } from "clipanion";
|
|
23405
24253
|
|
|
23406
24254
|
// cli/i18n/sidecar.texts.ts
|
|
@@ -23551,7 +24399,7 @@ var SidecarRefreshCommand = class extends SmCommand {
|
|
|
23551
24399
|
let absPath;
|
|
23552
24400
|
try {
|
|
23553
24401
|
assertContained(ctx.cwd, node.path);
|
|
23554
|
-
absPath =
|
|
24402
|
+
absPath = resolve37(ctx.cwd, node.path);
|
|
23555
24403
|
} catch (err) {
|
|
23556
24404
|
this.printer.error(
|
|
23557
24405
|
tx(SIDECAR_TEXTS.refreshFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -23832,7 +24680,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23832
24680
|
let absPath;
|
|
23833
24681
|
try {
|
|
23834
24682
|
assertContained(ctx.cwd, node.path);
|
|
23835
|
-
absPath =
|
|
24683
|
+
absPath = resolve37(ctx.cwd, node.path);
|
|
23836
24684
|
} catch (err) {
|
|
23837
24685
|
this.printer.error(
|
|
23838
24686
|
tx(SIDECAR_TEXTS.annotateFailed, { glyph: errGlyph, message: formatErrorMessage(err) })
|
|
@@ -23840,7 +24688,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23840
24688
|
return ExitCode.Error;
|
|
23841
24689
|
}
|
|
23842
24690
|
const sidecarAbsPath = sidecarPathFor(absPath);
|
|
23843
|
-
if (
|
|
24691
|
+
if (existsSync31(sidecarAbsPath) && this.force !== true) {
|
|
23844
24692
|
this.printer.error(
|
|
23845
24693
|
tx(SIDECAR_TEXTS.annotateExists, {
|
|
23846
24694
|
glyph: errGlyph,
|
|
@@ -23850,7 +24698,7 @@ var SidecarAnnotateCommand = class extends SmCommand {
|
|
|
23850
24698
|
);
|
|
23851
24699
|
return ExitCode.Error;
|
|
23852
24700
|
}
|
|
23853
|
-
if (
|
|
24701
|
+
if (existsSync31(sidecarAbsPath) && this.force === true) {
|
|
23854
24702
|
try {
|
|
23855
24703
|
unlinkSync2(sidecarAbsPath);
|
|
23856
24704
|
} catch (err) {
|
|
@@ -24080,8 +24928,8 @@ var STUB_COMMANDS = [
|
|
|
24080
24928
|
];
|
|
24081
24929
|
|
|
24082
24930
|
// cli/commands/tutorial.ts
|
|
24083
|
-
import { cpSync as cpSync2, existsSync as
|
|
24084
|
-
import { dirname as dirname19, join as
|
|
24931
|
+
import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync7, rmSync as rmSync2, statSync as statSync12 } from "fs";
|
|
24932
|
+
import { dirname as dirname19, join as join21, resolve as resolve38 } from "path";
|
|
24085
24933
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
24086
24934
|
import { Command as Command37, Option as Option35 } from "clipanion";
|
|
24087
24935
|
|
|
@@ -24177,9 +25025,9 @@ var TutorialCommand = class extends SmCommand {
|
|
|
24177
25025
|
}
|
|
24178
25026
|
const variant = rawVariant ?? DEFAULT_VARIANT;
|
|
24179
25027
|
const spec = VARIANT_SPECS[variant];
|
|
24180
|
-
const targetDir =
|
|
25028
|
+
const targetDir = join21(ctx.cwd, ".claude", "skills", spec.slug);
|
|
24181
25029
|
const targetDisplay = `.claude/skills/${spec.slug}/`;
|
|
24182
|
-
if (
|
|
25030
|
+
if (existsSync32(targetDir) && !this.force) {
|
|
24183
25031
|
this.printer.error(
|
|
24184
25032
|
tx(TUTORIAL_TEXTS.alreadyExists, {
|
|
24185
25033
|
glyph: errGlyph,
|
|
@@ -24255,14 +25103,14 @@ function resolveSkillSourceDir(variant) {
|
|
|
24255
25103
|
const here = dirname19(fileURLToPath6(import.meta.url));
|
|
24256
25104
|
const candidates = [
|
|
24257
25105
|
// dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
|
|
24258
|
-
|
|
25106
|
+
resolve38(here, "../../..", spec.sourceDir),
|
|
24259
25107
|
// bundled: dist/cli.js → dist/cli/tutorial/<slug> (sibling)
|
|
24260
|
-
|
|
25108
|
+
resolve38(here, "cli/tutorial", spec.slug),
|
|
24261
25109
|
// bundled fallback: any-depth → cli/tutorial/<slug>
|
|
24262
|
-
|
|
25110
|
+
resolve38(here, "../cli/tutorial", spec.slug)
|
|
24263
25111
|
];
|
|
24264
25112
|
for (const candidate of candidates) {
|
|
24265
|
-
if (
|
|
25113
|
+
if (existsSync32(candidate) && statSync12(candidate).isDirectory()) {
|
|
24266
25114
|
cachedSourceDirs.set(variant, candidate);
|
|
24267
25115
|
return candidate;
|
|
24268
25116
|
}
|
|
@@ -24439,7 +25287,7 @@ await lifecycleDispatcher.dispatch(
|
|
|
24439
25287
|
process.exit(exitCode);
|
|
24440
25288
|
function resolveBareDefault() {
|
|
24441
25289
|
const ctx = defaultRuntimeContext();
|
|
24442
|
-
if (
|
|
25290
|
+
if (existsSync33(defaultProjectDbPath(ctx))) {
|
|
24443
25291
|
return ["serve"];
|
|
24444
25292
|
}
|
|
24445
25293
|
process.stderr.write(tx(ENTRY_TEXTS.bareNoProject, { cwd: ctx.cwd }));
|