@systemdox/mcp-server 0.1.51 → 0.1.53
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.js +131 -19
- package/dist/http.js +121 -17
- package/dist/index.js +121 -17
- package/dist/lib/context-builder.js +98 -11
- package/dist/lib/scoring.d.ts +5 -0
- package/dist/lib/spec-label.d.ts +5 -0
- package/dist/lib/team-setup-types.d.ts +1 -1
- package/dist/server.js +121 -17
- package/dist/tools/get-docs.js +14 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -592,9 +592,9 @@ function localDate(now) {
|
|
|
592
592
|
return `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}`;
|
|
593
593
|
}
|
|
594
594
|
function conflictCopyName(path, date, taken) {
|
|
595
|
-
const
|
|
595
|
+
const stem2 = path.replace(/\.md$/, "");
|
|
596
596
|
for (let n = 1; ; n++) {
|
|
597
|
-
const name = `${
|
|
597
|
+
const name = `${stem2}.local-${date}${n === 1 ? "" : `-${n}`}.md`;
|
|
598
598
|
if (!taken.has(name.toLowerCase())) return name;
|
|
599
599
|
}
|
|
600
600
|
}
|
|
@@ -2857,6 +2857,21 @@ var init_get_guardrails = __esm({
|
|
|
2857
2857
|
}
|
|
2858
2858
|
});
|
|
2859
2859
|
|
|
2860
|
+
// src/lib/spec-label.ts
|
|
2861
|
+
function isDecisionDraft(name) {
|
|
2862
|
+
return name.startsWith(DECISION_PREFIX);
|
|
2863
|
+
}
|
|
2864
|
+
function specLabel(spec) {
|
|
2865
|
+
return isDecisionDraft(spec.name) ? "decision record" : `${spec.requirementCount} requirements`;
|
|
2866
|
+
}
|
|
2867
|
+
var DECISION_PREFIX;
|
|
2868
|
+
var init_spec_label = __esm({
|
|
2869
|
+
"src/lib/spec-label.ts"() {
|
|
2870
|
+
"use strict";
|
|
2871
|
+
DECISION_PREFIX = "ADR: ";
|
|
2872
|
+
}
|
|
2873
|
+
});
|
|
2874
|
+
|
|
2860
2875
|
// src/tools/get-specs.ts
|
|
2861
2876
|
import { z as z2 } from "zod";
|
|
2862
2877
|
function registerGetSpecs(server, apiClient2) {
|
|
@@ -2923,7 +2938,7 @@ ${spec.taskContent}`);
|
|
|
2923
2938
|
};
|
|
2924
2939
|
}
|
|
2925
2940
|
const text3 = filtered.map(
|
|
2926
|
-
(s) => `- **${s.name}** (${s.draftId}) \u2014 ${s.status}, ${s
|
|
2941
|
+
(s) => `- **${s.name}** (${s.draftId}) \u2014 ${s.status}, ${specLabel(s)}` + (s.repoFullName ? `, repo: ${s.repoFullName}` : "")
|
|
2927
2942
|
).join("\n");
|
|
2928
2943
|
return {
|
|
2929
2944
|
content: [
|
|
@@ -2941,11 +2956,16 @@ ${text3}`
|
|
|
2941
2956
|
var init_get_specs = __esm({
|
|
2942
2957
|
"src/tools/get-specs.ts"() {
|
|
2943
2958
|
"use strict";
|
|
2959
|
+
init_spec_label();
|
|
2944
2960
|
}
|
|
2945
2961
|
});
|
|
2946
2962
|
|
|
2947
2963
|
// src/tools/get-docs.ts
|
|
2948
2964
|
import { z as z3 } from "zod";
|
|
2965
|
+
function literalPrefix(pattern) {
|
|
2966
|
+
const wildcard = pattern.indexOf("*");
|
|
2967
|
+
return wildcard < 0 ? pattern : pattern.slice(0, wildcard);
|
|
2968
|
+
}
|
|
2949
2969
|
function matchGlob(path, pattern) {
|
|
2950
2970
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
2951
2971
|
return new RegExp(`^${regex}$`).test(path);
|
|
@@ -3006,11 +3026,17 @@ ${doc.content}`;
|
|
|
3006
3026
|
const params = {};
|
|
3007
3027
|
if (repo) params.repo = repo;
|
|
3008
3028
|
if (search) params.q = search;
|
|
3029
|
+
if (path) {
|
|
3030
|
+
params.limit = PATH_FILTER_LIMIT;
|
|
3031
|
+
const prefix = literalPrefix(path);
|
|
3032
|
+
if (prefix) params.pathPrefix = prefix;
|
|
3033
|
+
}
|
|
3009
3034
|
const response = await apiClient2.get(
|
|
3010
3035
|
"/v1/docs",
|
|
3011
3036
|
params
|
|
3012
3037
|
);
|
|
3013
3038
|
let items = response.items;
|
|
3039
|
+
const unread = path ? response.total - response.items.length : 0;
|
|
3014
3040
|
if (path) {
|
|
3015
3041
|
items = items.filter((item2) => matchGlob(item2.path, path));
|
|
3016
3042
|
}
|
|
@@ -3040,16 +3066,20 @@ ${doc.content}`;
|
|
|
3040
3066
|
|
|
3041
3067
|
${text3}
|
|
3042
3068
|
|
|
3043
|
-
|
|
3069
|
+
` + (unread > 0 ? `${unread} more were not read. Start the path with a folder to narrow it.
|
|
3070
|
+
|
|
3071
|
+
` : "") + "Use doc_id for full content, or doc_id + section to fetch one section."
|
|
3044
3072
|
}
|
|
3045
3073
|
]
|
|
3046
3074
|
};
|
|
3047
3075
|
}
|
|
3048
3076
|
);
|
|
3049
3077
|
}
|
|
3078
|
+
var PATH_FILTER_LIMIT;
|
|
3050
3079
|
var init_get_docs = __esm({
|
|
3051
3080
|
"src/tools/get-docs.ts"() {
|
|
3052
3081
|
"use strict";
|
|
3082
|
+
PATH_FILTER_LIMIT = "1000";
|
|
3053
3083
|
}
|
|
3054
3084
|
});
|
|
3055
3085
|
|
|
@@ -3152,7 +3182,9 @@ ${deps}`);
|
|
|
3152
3182
|
}
|
|
3153
3183
|
if (ctx.adrs.length > 0) {
|
|
3154
3184
|
const adrs = ctx.adrs.map((a) => `- ${a.title}${a.repo ? ` (${a.repo})` : ""}`).join("\n");
|
|
3155
|
-
|
|
3185
|
+
const total = ctx.adrTotal ?? ctx.adrs.length;
|
|
3186
|
+
const count = total > ctx.adrs.length ? `${ctx.adrs.length} newest of ${total}` : `${ctx.adrs.length}`;
|
|
3187
|
+
sections.push(`## Architecture Decision Records (${count})
|
|
3156
3188
|
|
|
3157
3189
|
${adrs}`);
|
|
3158
3190
|
}
|
|
@@ -3581,6 +3613,32 @@ function scoreSeverity(item2) {
|
|
|
3581
3613
|
if (!item2.severity) return 0.5;
|
|
3582
3614
|
return SEVERITY_SCORES[item2.severity] ?? 0.5;
|
|
3583
3615
|
}
|
|
3616
|
+
function stem(word) {
|
|
3617
|
+
return word.length > 3 ? word.replace(/s$/, "") : word;
|
|
3618
|
+
}
|
|
3619
|
+
function stems(text3) {
|
|
3620
|
+
return new Set(
|
|
3621
|
+
text3.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).map(stem)
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
function titleShare(title, keywords) {
|
|
3625
|
+
const wanted = stems(keywords.join(" "));
|
|
3626
|
+
if (wanted.size === 0) return 0;
|
|
3627
|
+
const held = stems(title);
|
|
3628
|
+
let matches = 0;
|
|
3629
|
+
for (const word of wanted) {
|
|
3630
|
+
if (held.has(word)) matches++;
|
|
3631
|
+
}
|
|
3632
|
+
return matches / wanted.size;
|
|
3633
|
+
}
|
|
3634
|
+
function scoreReference(item2, keywords) {
|
|
3635
|
+
const share = titleShare(item2.title, keywords);
|
|
3636
|
+
const score = Math.max(
|
|
3637
|
+
item2.relevance ?? 0,
|
|
3638
|
+
share >= MIN_TITLE_SHARE ? share : 0
|
|
3639
|
+
);
|
|
3640
|
+
return RECORD_PATH.test(item2.path ?? "") ? score * RECORD_FACTOR : score;
|
|
3641
|
+
}
|
|
3584
3642
|
function extractKeywords(text3) {
|
|
3585
3643
|
const stopWords = /* @__PURE__ */ new Set([
|
|
3586
3644
|
"the",
|
|
@@ -3677,7 +3735,8 @@ function scoreItems(items, taskDescription, filePaths) {
|
|
|
3677
3735
|
const keywordScore = scoreKeywordMatch(item2, keywords);
|
|
3678
3736
|
const recencyScore = scoreRecency(item2);
|
|
3679
3737
|
const severityScore = scoreSeverity(item2);
|
|
3680
|
-
const
|
|
3738
|
+
const isReference2 = item2.type === "spec" || item2.type === "doc";
|
|
3739
|
+
const score = isReference2 && keywords.length > 0 ? scoreReference(item2, keywords) : pathScore * WEIGHT_PATH + keywordScore * WEIGHT_KEYWORD + recencyScore * WEIGHT_RECENCY + severityScore * WEIGHT_SEVERITY;
|
|
3681
3740
|
return {
|
|
3682
3741
|
type: item2.type,
|
|
3683
3742
|
id: item2.id,
|
|
@@ -3686,7 +3745,7 @@ function scoreItems(items, taskDescription, filePaths) {
|
|
|
3686
3745
|
score,
|
|
3687
3746
|
tokenEstimate: estimateTokens(item2.content)
|
|
3688
3747
|
};
|
|
3689
|
-
}).sort((a, b) => b.score - a.score);
|
|
3748
|
+
}).filter((item2) => item2.score > 0).sort((a, b) => b.score - a.score);
|
|
3690
3749
|
}
|
|
3691
3750
|
function selectWithinBudget(items, maxTokens) {
|
|
3692
3751
|
const selected = [];
|
|
@@ -3698,7 +3757,7 @@ function selectWithinBudget(items, maxTokens) {
|
|
|
3698
3757
|
}
|
|
3699
3758
|
return { selected, tokensUsed, totalItems: items.length };
|
|
3700
3759
|
}
|
|
3701
|
-
var WEIGHT_PATH, WEIGHT_KEYWORD, WEIGHT_RECENCY, WEIGHT_SEVERITY, SEVERITY_SCORES, CHARS_PER_TOKEN;
|
|
3760
|
+
var WEIGHT_PATH, WEIGHT_KEYWORD, WEIGHT_RECENCY, WEIGHT_SEVERITY, SEVERITY_SCORES, MIN_TITLE_SHARE, RECORD_PATH, RECORD_FACTOR, CHARS_PER_TOKEN;
|
|
3702
3761
|
var init_scoring = __esm({
|
|
3703
3762
|
"src/lib/scoring.ts"() {
|
|
3704
3763
|
"use strict";
|
|
@@ -3713,6 +3772,9 @@ var init_scoring = __esm({
|
|
|
3713
3772
|
medium: 0.5,
|
|
3714
3773
|
low: 0.25
|
|
3715
3774
|
};
|
|
3775
|
+
MIN_TITLE_SHARE = 1 / 3;
|
|
3776
|
+
RECORD_PATH = /(^|\/)changelog\//;
|
|
3777
|
+
RECORD_FACTOR = 0.5;
|
|
3716
3778
|
CHARS_PER_TOKEN = 4;
|
|
3717
3779
|
}
|
|
3718
3780
|
});
|
|
@@ -3736,7 +3798,7 @@ ${g.fix}
|
|
|
3736
3798
|
}
|
|
3737
3799
|
function formatSpecHeadline(s) {
|
|
3738
3800
|
return `### ${s.name}
|
|
3739
|
-
\`draft_id: ${s.draftId}\` \xB7 ${s.status} \xB7 ${s
|
|
3801
|
+
\`draft_id: ${s.draftId}\` \xB7 ${s.status} \xB7 ${specLabel(s)}`;
|
|
3740
3802
|
}
|
|
3741
3803
|
function formatDocHeadline(d) {
|
|
3742
3804
|
return `### ${d.title}
|
|
@@ -3747,6 +3809,21 @@ function formatStack(entries2) {
|
|
|
3747
3809
|
(e) => `- ${e.name}${e.version ? ` v${e.version}` : ""}${e.notes ? ` \u2014 ${e.notes}` : ""}`
|
|
3748
3810
|
).join("\n");
|
|
3749
3811
|
}
|
|
3812
|
+
function selectStack(entries2, words, maxTokens) {
|
|
3813
|
+
const named = (e) => {
|
|
3814
|
+
const entry = `${e.name} ${e.notes ?? ""}`.toLowerCase();
|
|
3815
|
+
return words.some((w) => entry.includes(w));
|
|
3816
|
+
};
|
|
3817
|
+
const ordered = [...entries2.filter(named), ...entries2.filter((e) => !named(e))];
|
|
3818
|
+
const shown = [];
|
|
3819
|
+
for (const entry of ordered) {
|
|
3820
|
+
if (shown.length > 0 && estimateTokens(formatStack([...shown, entry])) > maxTokens) {
|
|
3821
|
+
break;
|
|
3822
|
+
}
|
|
3823
|
+
shown.push(entry);
|
|
3824
|
+
}
|
|
3825
|
+
return { text: formatStack(shown), hidden: entries2.length - shown.length };
|
|
3826
|
+
}
|
|
3750
3827
|
function formatRules(rules) {
|
|
3751
3828
|
const byCategory = /* @__PURE__ */ new Map();
|
|
3752
3829
|
for (const r of rules) {
|
|
@@ -3768,6 +3845,21 @@ function formatRules(rules) {
|
|
|
3768
3845
|
|
|
3769
3846
|
${text3.trim()}`;
|
|
3770
3847
|
}
|
|
3848
|
+
async function findDocs(api, taskDescription) {
|
|
3849
|
+
const query = taskDescription.trim().slice(0, MAX_QUERY_CHARS);
|
|
3850
|
+
if (query) {
|
|
3851
|
+
try {
|
|
3852
|
+
const found = await api.get("/v1/docs", {
|
|
3853
|
+
q: query,
|
|
3854
|
+
limit: String(SEARCH_RESULTS)
|
|
3855
|
+
});
|
|
3856
|
+
if (found.items.length > 0) return { ...found, searched: true };
|
|
3857
|
+
} catch {
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
const listed = await api.get("/v1/docs");
|
|
3861
|
+
return { ...listed, searched: false };
|
|
3862
|
+
}
|
|
3771
3863
|
async function fetchSpecBody(api, draftId) {
|
|
3772
3864
|
const spec = await api.get(
|
|
3773
3865
|
`/v1/specs/${encodeURIComponent(draftId)}`
|
|
@@ -3846,7 +3938,9 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3846
3938
|
const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
|
|
3847
3939
|
api.get("/v1/guardrails").catch(orEmpty([])),
|
|
3848
3940
|
api.get("/v1/specs").catch(orEmpty([])),
|
|
3849
|
-
api
|
|
3941
|
+
findDocs(api, taskDescription).catch(
|
|
3942
|
+
orEmpty({ items: [], searched: false })
|
|
3943
|
+
),
|
|
3850
3944
|
api.get("/v1/ai/settings/stack").catch(orEmpty([])),
|
|
3851
3945
|
api.get("/v1/standards/rules").catch(orEmpty([])),
|
|
3852
3946
|
// Capability states — FAIL-OPEN: an unreadable config (older backend, a
|
|
@@ -3859,6 +3953,8 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3859
3953
|
const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
|
|
3860
3954
|
const refs = /* @__PURE__ */ new Map();
|
|
3861
3955
|
const key = (type, id) => `${type}:${id}`;
|
|
3956
|
+
const docTitles = new Set(docs.items.map((d) => d.title));
|
|
3957
|
+
const isCopyOfDoc = (s) => isDecisionDraft(s.name) && docTitles.has(s.name.slice("ADR: ".length));
|
|
3862
3958
|
const scorable = [
|
|
3863
3959
|
...guardrails.map((g) => ({
|
|
3864
3960
|
type: "guardrail",
|
|
@@ -3869,12 +3965,12 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3869
3965
|
severity: g.severity,
|
|
3870
3966
|
updatedAt: g.updatedAt
|
|
3871
3967
|
})),
|
|
3872
|
-
...specs.filter((s) => s.status !== "archived").map((s) => {
|
|
3968
|
+
...specs.filter((s) => s.status !== "archived" && !isCopyOfDoc(s)).map((s) => {
|
|
3873
3969
|
refs.set(key("spec", s.draftId), {
|
|
3874
3970
|
type: "spec",
|
|
3875
3971
|
id: s.draftId,
|
|
3876
3972
|
title: s.name,
|
|
3877
|
-
listLine: `- **${s.name}** \u2014 \`draft_id: ${s.draftId}\` (${s.status}, ${s
|
|
3973
|
+
listLine: `- **${s.name}** \u2014 \`draft_id: ${s.draftId}\` (${s.status}, ${specLabel(s)})`,
|
|
3878
3974
|
fetchHint: `get_specs(draft_id="${s.draftId}")`
|
|
3879
3975
|
});
|
|
3880
3976
|
return {
|
|
@@ -3885,7 +3981,7 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3885
3981
|
updatedAt: s.updatedAt
|
|
3886
3982
|
};
|
|
3887
3983
|
}),
|
|
3888
|
-
...docs.items.map((d) => {
|
|
3984
|
+
...docs.items.map((d, position) => {
|
|
3889
3985
|
const section = selectSection(d.outline, taskDescription);
|
|
3890
3986
|
const pointer = section ? formatSectionPointer(d.repo_full_name, d.path, section) : void 0;
|
|
3891
3987
|
refs.set(key("doc", d.id), {
|
|
@@ -3904,7 +4000,9 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3904
4000
|
title: d.title,
|
|
3905
4001
|
content: formatDocHeadline(d),
|
|
3906
4002
|
category: d.doc_type === "adr" ? "architecture" : void 0,
|
|
3907
|
-
updatedAt: d.synced_at
|
|
4003
|
+
updatedAt: d.synced_at,
|
|
4004
|
+
path: d.path,
|
|
4005
|
+
relevance: docs.searched ? 1 - position / (2 * docs.items.length) : void 0
|
|
3908
4006
|
};
|
|
3909
4007
|
})
|
|
3910
4008
|
];
|
|
@@ -3912,9 +4010,16 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
3912
4010
|
const sections = [];
|
|
3913
4011
|
let overhead = 0;
|
|
3914
4012
|
if (stack.length > 0) {
|
|
4013
|
+
const { text: text3, hidden } = selectStack(
|
|
4014
|
+
stack,
|
|
4015
|
+
[...extractKeywords(taskDescription), ...detectLanguages(filePaths)],
|
|
4016
|
+
Math.floor(budget * STACK_SHARE)
|
|
4017
|
+
);
|
|
3915
4018
|
const section = `## Tech Stack
|
|
3916
4019
|
|
|
3917
|
-
${
|
|
4020
|
+
${text3}` + (hidden > 0 ? `
|
|
4021
|
+
|
|
4022
|
+
_${hidden} more with \`get_tech_stack\`._` : "");
|
|
3918
4023
|
sections.push(section);
|
|
3919
4024
|
overhead += estimateTokens(section);
|
|
3920
4025
|
}
|
|
@@ -4033,9 +4138,10 @@ _Highest-ranked items \u2014 docs are scoped to their most relevant section. Eac
|
|
|
4033
4138
|
const tailLines = [];
|
|
4034
4139
|
for (const meta of tailCandidates) {
|
|
4035
4140
|
const cost = estimateTokens(meta.listLine);
|
|
4036
|
-
|
|
4141
|
+
const named = blocks.length + tailLines.length;
|
|
4142
|
+
if (cost > remaining && named >= MIN_REFERENCES) break;
|
|
4037
4143
|
tailLines.push(meta.listLine);
|
|
4038
|
-
remaining
|
|
4144
|
+
remaining = Math.max(0, remaining - cost);
|
|
4039
4145
|
}
|
|
4040
4146
|
if (tailLines.length > 0) {
|
|
4041
4147
|
sections.push(
|
|
@@ -4069,11 +4175,13 @@ _Ranked lower \u2014 fetch with \`get_specs\` / \`get_docs\` if you need them._
|
|
|
4069
4175
|
_Context: ${itemsWithBody} full text, ${itemsListed} listed, ${stats.itemsOmitted} omitted \xB7 ~${tokensUsed} of ${budget} tokens` + (bodyFailures > 0 ? ` \xB7 ${bodyFailures} body fetch(es) failed` : "") + (failures.length > 0 ? ` \xB7 ${failures.length} of 5 reads failed` : "") + `_`;
|
|
4070
4176
|
return { text: body + footer, stats };
|
|
4071
4177
|
}
|
|
4072
|
-
var DEFAULT_BUDGET, RESERVE_TOKENS, GUARDRAIL_SHARE, MIN_BODY_TOKENS, MAX_HYDRATED, MAX_TAIL, TAIL_SHARE, TRUNCATION_NOTICE_TOKENS, SECTION_STOP_WORDS;
|
|
4178
|
+
var DEFAULT_BUDGET, RESERVE_TOKENS, GUARDRAIL_SHARE, MIN_BODY_TOKENS, MAX_HYDRATED, MAX_TAIL, TAIL_SHARE, TRUNCATION_NOTICE_TOKENS, STACK_SHARE, MIN_REFERENCES, SEARCH_RESULTS, MAX_QUERY_CHARS, SECTION_STOP_WORDS;
|
|
4073
4179
|
var init_context_builder = __esm({
|
|
4074
4180
|
"src/lib/context-builder.ts"() {
|
|
4075
4181
|
"use strict";
|
|
4182
|
+
init_language();
|
|
4076
4183
|
init_scoring();
|
|
4184
|
+
init_spec_label();
|
|
4077
4185
|
init_scoring();
|
|
4078
4186
|
DEFAULT_BUDGET = 4e3;
|
|
4079
4187
|
RESERVE_TOKENS = 200;
|
|
@@ -4083,6 +4191,10 @@ var init_context_builder = __esm({
|
|
|
4083
4191
|
MAX_TAIL = 10;
|
|
4084
4192
|
TAIL_SHARE = 0.15;
|
|
4085
4193
|
TRUNCATION_NOTICE_TOKENS = 30;
|
|
4194
|
+
STACK_SHARE = 0.1;
|
|
4195
|
+
MIN_REFERENCES = 3;
|
|
4196
|
+
SEARCH_RESULTS = 30;
|
|
4197
|
+
MAX_QUERY_CHARS = 300;
|
|
4086
4198
|
SECTION_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
4087
4199
|
"the",
|
|
4088
4200
|
"a",
|
|
@@ -5234,7 +5346,7 @@ var init_server = __esm({
|
|
|
5234
5346
|
init_create_prompt_template();
|
|
5235
5347
|
init_skills2();
|
|
5236
5348
|
init_team_setup();
|
|
5237
|
-
SERVER_VERSION = true ? "0.1.
|
|
5349
|
+
SERVER_VERSION = true ? "0.1.53" : "0.0.0-dev";
|
|
5238
5350
|
}
|
|
5239
5351
|
});
|
|
5240
5352
|
|
package/dist/http.js
CHANGED
|
@@ -851,6 +851,17 @@ ${text3}`
|
|
|
851
851
|
|
|
852
852
|
// src/tools/get-specs.ts
|
|
853
853
|
import { z as z2 } from "zod";
|
|
854
|
+
|
|
855
|
+
// src/lib/spec-label.ts
|
|
856
|
+
var DECISION_PREFIX = "ADR: ";
|
|
857
|
+
function isDecisionDraft(name) {
|
|
858
|
+
return name.startsWith(DECISION_PREFIX);
|
|
859
|
+
}
|
|
860
|
+
function specLabel(spec) {
|
|
861
|
+
return isDecisionDraft(spec.name) ? "decision record" : `${spec.requirementCount} requirements`;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/tools/get-specs.ts
|
|
854
865
|
function registerGetSpecs(server, apiClient) {
|
|
855
866
|
server.registerTool(
|
|
856
867
|
"get_specs",
|
|
@@ -915,7 +926,7 @@ ${spec.taskContent}`);
|
|
|
915
926
|
};
|
|
916
927
|
}
|
|
917
928
|
const text3 = filtered.map(
|
|
918
|
-
(s) => `- **${s.name}** (${s.draftId}) \u2014 ${s.status}, ${s
|
|
929
|
+
(s) => `- **${s.name}** (${s.draftId}) \u2014 ${s.status}, ${specLabel(s)}` + (s.repoFullName ? `, repo: ${s.repoFullName}` : "")
|
|
919
930
|
).join("\n");
|
|
920
931
|
return {
|
|
921
932
|
content: [
|
|
@@ -933,6 +944,11 @@ ${text3}`
|
|
|
933
944
|
|
|
934
945
|
// src/tools/get-docs.ts
|
|
935
946
|
import { z as z3 } from "zod";
|
|
947
|
+
var PATH_FILTER_LIMIT = "1000";
|
|
948
|
+
function literalPrefix(pattern) {
|
|
949
|
+
const wildcard = pattern.indexOf("*");
|
|
950
|
+
return wildcard < 0 ? pattern : pattern.slice(0, wildcard);
|
|
951
|
+
}
|
|
936
952
|
function matchGlob(path, pattern) {
|
|
937
953
|
const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
938
954
|
return new RegExp(`^${regex}$`).test(path);
|
|
@@ -993,11 +1009,17 @@ ${doc.content}`;
|
|
|
993
1009
|
const params = {};
|
|
994
1010
|
if (repo) params.repo = repo;
|
|
995
1011
|
if (search) params.q = search;
|
|
1012
|
+
if (path) {
|
|
1013
|
+
params.limit = PATH_FILTER_LIMIT;
|
|
1014
|
+
const prefix = literalPrefix(path);
|
|
1015
|
+
if (prefix) params.pathPrefix = prefix;
|
|
1016
|
+
}
|
|
996
1017
|
const response = await apiClient.get(
|
|
997
1018
|
"/v1/docs",
|
|
998
1019
|
params
|
|
999
1020
|
);
|
|
1000
1021
|
let items = response.items;
|
|
1022
|
+
const unread = path ? response.total - response.items.length : 0;
|
|
1001
1023
|
if (path) {
|
|
1002
1024
|
items = items.filter((item) => matchGlob(item.path, path));
|
|
1003
1025
|
}
|
|
@@ -1027,7 +1049,9 @@ ${doc.content}`;
|
|
|
1027
1049
|
|
|
1028
1050
|
${text3}
|
|
1029
1051
|
|
|
1030
|
-
|
|
1052
|
+
` + (unread > 0 ? `${unread} more were not read. Start the path with a folder to narrow it.
|
|
1053
|
+
|
|
1054
|
+
` : "") + "Use doc_id for full content, or doc_id + section to fetch one section."
|
|
1031
1055
|
}
|
|
1032
1056
|
]
|
|
1033
1057
|
};
|
|
@@ -1128,7 +1152,9 @@ ${deps}`);
|
|
|
1128
1152
|
}
|
|
1129
1153
|
if (ctx.adrs.length > 0) {
|
|
1130
1154
|
const adrs = ctx.adrs.map((a) => `- ${a.title}${a.repo ? ` (${a.repo})` : ""}`).join("\n");
|
|
1131
|
-
|
|
1155
|
+
const total = ctx.adrTotal ?? ctx.adrs.length;
|
|
1156
|
+
const count = total > ctx.adrs.length ? `${ctx.adrs.length} newest of ${total}` : `${ctx.adrs.length}`;
|
|
1157
|
+
sections.push(`## Architecture Decision Records (${count})
|
|
1132
1158
|
|
|
1133
1159
|
${adrs}`);
|
|
1134
1160
|
}
|
|
@@ -1500,6 +1526,9 @@ var SEVERITY_SCORES = {
|
|
|
1500
1526
|
medium: 0.5,
|
|
1501
1527
|
low: 0.25
|
|
1502
1528
|
};
|
|
1529
|
+
var MIN_TITLE_SHARE = 1 / 3;
|
|
1530
|
+
var RECORD_PATH = /(^|\/)changelog\//;
|
|
1531
|
+
var RECORD_FACTOR = 0.5;
|
|
1503
1532
|
var CHARS_PER_TOKEN = 4;
|
|
1504
1533
|
function estimateTokens(text3) {
|
|
1505
1534
|
return Math.ceil(text3.length / CHARS_PER_TOKEN);
|
|
@@ -1552,6 +1581,32 @@ function scoreSeverity(item) {
|
|
|
1552
1581
|
if (!item.severity) return 0.5;
|
|
1553
1582
|
return SEVERITY_SCORES[item.severity] ?? 0.5;
|
|
1554
1583
|
}
|
|
1584
|
+
function stem(word) {
|
|
1585
|
+
return word.length > 3 ? word.replace(/s$/, "") : word;
|
|
1586
|
+
}
|
|
1587
|
+
function stems(text3) {
|
|
1588
|
+
return new Set(
|
|
1589
|
+
text3.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).map(stem)
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
function titleShare(title, keywords) {
|
|
1593
|
+
const wanted = stems(keywords.join(" "));
|
|
1594
|
+
if (wanted.size === 0) return 0;
|
|
1595
|
+
const held = stems(title);
|
|
1596
|
+
let matches = 0;
|
|
1597
|
+
for (const word of wanted) {
|
|
1598
|
+
if (held.has(word)) matches++;
|
|
1599
|
+
}
|
|
1600
|
+
return matches / wanted.size;
|
|
1601
|
+
}
|
|
1602
|
+
function scoreReference(item, keywords) {
|
|
1603
|
+
const share = titleShare(item.title, keywords);
|
|
1604
|
+
const score = Math.max(
|
|
1605
|
+
item.relevance ?? 0,
|
|
1606
|
+
share >= MIN_TITLE_SHARE ? share : 0
|
|
1607
|
+
);
|
|
1608
|
+
return RECORD_PATH.test(item.path ?? "") ? score * RECORD_FACTOR : score;
|
|
1609
|
+
}
|
|
1555
1610
|
function extractKeywords(text3) {
|
|
1556
1611
|
const stopWords = /* @__PURE__ */ new Set([
|
|
1557
1612
|
"the",
|
|
@@ -1648,7 +1703,8 @@ function scoreItems(items, taskDescription, filePaths) {
|
|
|
1648
1703
|
const keywordScore = scoreKeywordMatch(item, keywords);
|
|
1649
1704
|
const recencyScore = scoreRecency(item);
|
|
1650
1705
|
const severityScore = scoreSeverity(item);
|
|
1651
|
-
const
|
|
1706
|
+
const isReference = item.type === "spec" || item.type === "doc";
|
|
1707
|
+
const score = isReference && keywords.length > 0 ? scoreReference(item, keywords) : pathScore * WEIGHT_PATH + keywordScore * WEIGHT_KEYWORD + recencyScore * WEIGHT_RECENCY + severityScore * WEIGHT_SEVERITY;
|
|
1652
1708
|
return {
|
|
1653
1709
|
type: item.type,
|
|
1654
1710
|
id: item.id,
|
|
@@ -1657,7 +1713,7 @@ function scoreItems(items, taskDescription, filePaths) {
|
|
|
1657
1713
|
score,
|
|
1658
1714
|
tokenEstimate: estimateTokens(item.content)
|
|
1659
1715
|
};
|
|
1660
|
-
}).sort((a, b) => b.score - a.score);
|
|
1716
|
+
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score);
|
|
1661
1717
|
}
|
|
1662
1718
|
function selectWithinBudget(items, maxTokens) {
|
|
1663
1719
|
const selected = [];
|
|
@@ -1679,6 +1735,10 @@ var MAX_HYDRATED = 8;
|
|
|
1679
1735
|
var MAX_TAIL = 10;
|
|
1680
1736
|
var TAIL_SHARE = 0.15;
|
|
1681
1737
|
var TRUNCATION_NOTICE_TOKENS = 30;
|
|
1738
|
+
var STACK_SHARE = 0.1;
|
|
1739
|
+
var MIN_REFERENCES = 3;
|
|
1740
|
+
var SEARCH_RESULTS = 30;
|
|
1741
|
+
var MAX_QUERY_CHARS = 300;
|
|
1682
1742
|
function formatGuardrail(g) {
|
|
1683
1743
|
let text3 = `## ${g.title} [${g.severity}]
|
|
1684
1744
|
|
|
@@ -1697,7 +1757,7 @@ ${g.fix}
|
|
|
1697
1757
|
}
|
|
1698
1758
|
function formatSpecHeadline(s) {
|
|
1699
1759
|
return `### ${s.name}
|
|
1700
|
-
\`draft_id: ${s.draftId}\` \xB7 ${s.status} \xB7 ${s
|
|
1760
|
+
\`draft_id: ${s.draftId}\` \xB7 ${s.status} \xB7 ${specLabel(s)}`;
|
|
1701
1761
|
}
|
|
1702
1762
|
function formatDocHeadline(d) {
|
|
1703
1763
|
return `### ${d.title}
|
|
@@ -1708,6 +1768,21 @@ function formatStack(entries2) {
|
|
|
1708
1768
|
(e) => `- ${e.name}${e.version ? ` v${e.version}` : ""}${e.notes ? ` \u2014 ${e.notes}` : ""}`
|
|
1709
1769
|
).join("\n");
|
|
1710
1770
|
}
|
|
1771
|
+
function selectStack(entries2, words, maxTokens) {
|
|
1772
|
+
const named = (e) => {
|
|
1773
|
+
const entry = `${e.name} ${e.notes ?? ""}`.toLowerCase();
|
|
1774
|
+
return words.some((w) => entry.includes(w));
|
|
1775
|
+
};
|
|
1776
|
+
const ordered = [...entries2.filter(named), ...entries2.filter((e) => !named(e))];
|
|
1777
|
+
const shown = [];
|
|
1778
|
+
for (const entry of ordered) {
|
|
1779
|
+
if (shown.length > 0 && estimateTokens(formatStack([...shown, entry])) > maxTokens) {
|
|
1780
|
+
break;
|
|
1781
|
+
}
|
|
1782
|
+
shown.push(entry);
|
|
1783
|
+
}
|
|
1784
|
+
return { text: formatStack(shown), hidden: entries2.length - shown.length };
|
|
1785
|
+
}
|
|
1711
1786
|
function formatRules(rules) {
|
|
1712
1787
|
const byCategory = /* @__PURE__ */ new Map();
|
|
1713
1788
|
for (const r of rules) {
|
|
@@ -1729,6 +1804,21 @@ function formatRules(rules) {
|
|
|
1729
1804
|
|
|
1730
1805
|
${text3.trim()}`;
|
|
1731
1806
|
}
|
|
1807
|
+
async function findDocs(api, taskDescription) {
|
|
1808
|
+
const query = taskDescription.trim().slice(0, MAX_QUERY_CHARS);
|
|
1809
|
+
if (query) {
|
|
1810
|
+
try {
|
|
1811
|
+
const found = await api.get("/v1/docs", {
|
|
1812
|
+
q: query,
|
|
1813
|
+
limit: String(SEARCH_RESULTS)
|
|
1814
|
+
});
|
|
1815
|
+
if (found.items.length > 0) return { ...found, searched: true };
|
|
1816
|
+
} catch {
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
const listed = await api.get("/v1/docs");
|
|
1820
|
+
return { ...listed, searched: false };
|
|
1821
|
+
}
|
|
1732
1822
|
async function fetchSpecBody(api, draftId) {
|
|
1733
1823
|
const spec = await api.get(
|
|
1734
1824
|
`/v1/specs/${encodeURIComponent(draftId)}`
|
|
@@ -1829,7 +1919,9 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1829
1919
|
const [rawGuardrails, rawSpecs, docs, stack, rules, docRepos] = await Promise.all([
|
|
1830
1920
|
api.get("/v1/guardrails").catch(orEmpty([])),
|
|
1831
1921
|
api.get("/v1/specs").catch(orEmpty([])),
|
|
1832
|
-
api
|
|
1922
|
+
findDocs(api, taskDescription).catch(
|
|
1923
|
+
orEmpty({ items: [], searched: false })
|
|
1924
|
+
),
|
|
1833
1925
|
api.get("/v1/ai/settings/stack").catch(orEmpty([])),
|
|
1834
1926
|
api.get("/v1/standards/rules").catch(orEmpty([])),
|
|
1835
1927
|
// Capability states — FAIL-OPEN: an unreadable config (older backend, a
|
|
@@ -1842,6 +1934,8 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1842
1934
|
const specs = disabled("requirements") && disabled("design") && disabled("specs") ? [] : rawSpecs;
|
|
1843
1935
|
const refs = /* @__PURE__ */ new Map();
|
|
1844
1936
|
const key = (type, id) => `${type}:${id}`;
|
|
1937
|
+
const docTitles = new Set(docs.items.map((d) => d.title));
|
|
1938
|
+
const isCopyOfDoc = (s) => isDecisionDraft(s.name) && docTitles.has(s.name.slice("ADR: ".length));
|
|
1845
1939
|
const scorable = [
|
|
1846
1940
|
...guardrails.map((g) => ({
|
|
1847
1941
|
type: "guardrail",
|
|
@@ -1852,12 +1946,12 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1852
1946
|
severity: g.severity,
|
|
1853
1947
|
updatedAt: g.updatedAt
|
|
1854
1948
|
})),
|
|
1855
|
-
...specs.filter((s) => s.status !== "archived").map((s) => {
|
|
1949
|
+
...specs.filter((s) => s.status !== "archived" && !isCopyOfDoc(s)).map((s) => {
|
|
1856
1950
|
refs.set(key("spec", s.draftId), {
|
|
1857
1951
|
type: "spec",
|
|
1858
1952
|
id: s.draftId,
|
|
1859
1953
|
title: s.name,
|
|
1860
|
-
listLine: `- **${s.name}** \u2014 \`draft_id: ${s.draftId}\` (${s.status}, ${s
|
|
1954
|
+
listLine: `- **${s.name}** \u2014 \`draft_id: ${s.draftId}\` (${s.status}, ${specLabel(s)})`,
|
|
1861
1955
|
fetchHint: `get_specs(draft_id="${s.draftId}")`
|
|
1862
1956
|
});
|
|
1863
1957
|
return {
|
|
@@ -1868,7 +1962,7 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1868
1962
|
updatedAt: s.updatedAt
|
|
1869
1963
|
};
|
|
1870
1964
|
}),
|
|
1871
|
-
...docs.items.map((d) => {
|
|
1965
|
+
...docs.items.map((d, position) => {
|
|
1872
1966
|
const section = selectSection(d.outline, taskDescription);
|
|
1873
1967
|
const pointer = section ? formatSectionPointer(d.repo_full_name, d.path, section) : void 0;
|
|
1874
1968
|
refs.set(key("doc", d.id), {
|
|
@@ -1887,7 +1981,9 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1887
1981
|
title: d.title,
|
|
1888
1982
|
content: formatDocHeadline(d),
|
|
1889
1983
|
category: d.doc_type === "adr" ? "architecture" : void 0,
|
|
1890
|
-
updatedAt: d.synced_at
|
|
1984
|
+
updatedAt: d.synced_at,
|
|
1985
|
+
path: d.path,
|
|
1986
|
+
relevance: docs.searched ? 1 - position / (2 * docs.items.length) : void 0
|
|
1891
1987
|
};
|
|
1892
1988
|
})
|
|
1893
1989
|
];
|
|
@@ -1895,9 +1991,16 @@ async function buildContext(api, { taskDescription, filePaths = [], maxTokens })
|
|
|
1895
1991
|
const sections = [];
|
|
1896
1992
|
let overhead = 0;
|
|
1897
1993
|
if (stack.length > 0) {
|
|
1994
|
+
const { text: text3, hidden } = selectStack(
|
|
1995
|
+
stack,
|
|
1996
|
+
[...extractKeywords(taskDescription), ...detectLanguages(filePaths)],
|
|
1997
|
+
Math.floor(budget * STACK_SHARE)
|
|
1998
|
+
);
|
|
1898
1999
|
const section = `## Tech Stack
|
|
1899
2000
|
|
|
1900
|
-
${
|
|
2001
|
+
${text3}` + (hidden > 0 ? `
|
|
2002
|
+
|
|
2003
|
+
_${hidden} more with \`get_tech_stack\`._` : "");
|
|
1901
2004
|
sections.push(section);
|
|
1902
2005
|
overhead += estimateTokens(section);
|
|
1903
2006
|
}
|
|
@@ -2016,9 +2119,10 @@ _Highest-ranked items \u2014 docs are scoped to their most relevant section. Eac
|
|
|
2016
2119
|
const tailLines = [];
|
|
2017
2120
|
for (const meta of tailCandidates) {
|
|
2018
2121
|
const cost = estimateTokens(meta.listLine);
|
|
2019
|
-
|
|
2122
|
+
const named = blocks.length + tailLines.length;
|
|
2123
|
+
if (cost > remaining && named >= MIN_REFERENCES) break;
|
|
2020
2124
|
tailLines.push(meta.listLine);
|
|
2021
|
-
remaining
|
|
2125
|
+
remaining = Math.max(0, remaining - cost);
|
|
2022
2126
|
}
|
|
2023
2127
|
if (tailLines.length > 0) {
|
|
2024
2128
|
sections.push(
|
|
@@ -3001,9 +3105,9 @@ function localDate(now) {
|
|
|
3001
3105
|
return `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}`;
|
|
3002
3106
|
}
|
|
3003
3107
|
function conflictCopyName(path, date, taken) {
|
|
3004
|
-
const
|
|
3108
|
+
const stem2 = path.replace(/\.md$/, "");
|
|
3005
3109
|
for (let n = 1; ; n++) {
|
|
3006
|
-
const name = `${
|
|
3110
|
+
const name = `${stem2}.local-${date}${n === 1 ? "" : `-${n}`}.md`;
|
|
3007
3111
|
if (!taken.has(name.toLowerCase())) return name;
|
|
3008
3112
|
}
|
|
3009
3113
|
}
|
|
@@ -3890,7 +3994,7 @@ function registerTeamSetupTools(server, apiClient, { localFiles }) {
|
|
|
3890
3994
|
}
|
|
3891
3995
|
|
|
3892
3996
|
// src/server.ts
|
|
3893
|
-
var SERVER_VERSION = true ? "0.1.
|
|
3997
|
+
var SERVER_VERSION = true ? "0.1.53" : "0.0.0-dev";
|
|
3894
3998
|
function createServer(apiClient, { localFiles = false, instructions, keyProblem } = {}) {
|
|
3895
3999
|
const server = new McpServer(
|
|
3896
4000
|
{
|