supipowers 1.2.6 → 1.5.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/README.md +118 -56
- package/bin/install.ts +48 -128
- package/package.json +11 -3
- package/skills/code-review/SKILL.md +137 -40
- package/skills/context-mode/SKILL.md +67 -56
- package/skills/creating-supi-agents/SKILL.md +204 -0
- package/skills/debugging/SKILL.md +86 -40
- package/skills/fix-pr/SKILL.md +96 -65
- package/skills/planning/SKILL.md +103 -46
- package/skills/qa-strategy/SKILL.md +68 -46
- package/skills/receiving-code-review/SKILL.md +60 -53
- package/skills/release/SKILL.md +111 -39
- package/skills/tdd/SKILL.md +118 -67
- package/skills/verification/SKILL.md +71 -37
- package/src/bootstrap.ts +27 -5
- package/src/commands/agents.ts +249 -0
- package/src/commands/ai-review.ts +1113 -0
- package/src/commands/config.ts +224 -95
- package/src/commands/doctor.ts +19 -13
- package/src/commands/fix-pr.ts +8 -11
- package/src/commands/generate.ts +200 -0
- package/src/commands/model-picker.ts +5 -15
- package/src/commands/model.ts +4 -5
- package/src/commands/optimize-context.ts +202 -0
- package/src/commands/plan.ts +148 -92
- package/src/commands/qa.ts +14 -23
- package/src/commands/release.ts +504 -275
- package/src/commands/review.ts +643 -86
- package/src/commands/status.ts +44 -17
- package/src/commands/supi.ts +69 -41
- package/src/commands/update.ts +57 -2
- package/src/config/defaults.ts +6 -39
- package/src/config/loader.ts +388 -40
- package/src/config/model-resolver.ts +26 -22
- package/src/config/schema.ts +113 -48
- package/src/context/analyzer.ts +61 -2
- package/src/context/optimizer.ts +199 -0
- package/src/context-mode/compressor.ts +14 -11
- package/src/context-mode/detector.ts +16 -54
- package/src/context-mode/event-extractor.ts +45 -16
- package/src/context-mode/event-store.ts +225 -16
- package/src/context-mode/hooks.ts +195 -22
- package/src/context-mode/knowledge/chunker.ts +235 -0
- package/src/context-mode/knowledge/store.ts +187 -0
- package/src/context-mode/routing.ts +12 -23
- package/src/context-mode/sandbox/executor.ts +183 -0
- package/src/context-mode/sandbox/runners.ts +40 -0
- package/src/context-mode/snapshot-builder.ts +243 -7
- package/src/context-mode/tools.ts +440 -0
- package/src/context-mode/web/fetcher.ts +117 -0
- package/src/context-mode/web/html-to-md.ts +293 -0
- package/src/debug/logger.ts +107 -0
- package/src/deps/registry.ts +0 -20
- package/src/docs/drift.ts +454 -0
- package/src/fix-pr/fetch-comments.ts +66 -0
- package/src/git/commit-msg.ts +2 -1
- package/src/git/commit.ts +123 -141
- package/src/git/conventions.ts +2 -2
- package/src/git/status.ts +4 -1
- package/src/lsp/bridge.ts +138 -12
- package/src/planning/approval-flow.ts +125 -19
- package/src/planning/plan-writer-prompt.ts +4 -11
- package/src/planning/planning-ask-tool.ts +81 -0
- package/src/planning/prompt-builder.ts +9 -169
- package/src/planning/system-prompt.ts +290 -0
- package/src/platform/omp.ts +50 -4
- package/src/platform/progress.ts +182 -0
- package/src/platform/test-utils.ts +4 -1
- package/src/platform/tui-colors.ts +30 -0
- package/src/platform/types.ts +1 -0
- package/src/qa/detect-app-type.ts +102 -0
- package/src/qa/discover-routes.ts +353 -0
- package/src/quality/ai-session.ts +96 -0
- package/src/quality/ai-setup.ts +86 -0
- package/src/quality/gates/ai-review.ts +129 -0
- package/src/quality/gates/build.ts +8 -0
- package/src/quality/gates/command.ts +150 -0
- package/src/quality/gates/format.ts +28 -0
- package/src/quality/gates/lint.ts +22 -0
- package/src/quality/gates/lsp-diagnostics.ts +84 -0
- package/src/quality/gates/test-suite.ts +8 -0
- package/src/quality/gates/typecheck.ts +22 -0
- package/src/quality/registry.ts +25 -0
- package/src/quality/review-gates.ts +33 -0
- package/src/quality/runner.ts +268 -0
- package/src/quality/schemas.ts +48 -0
- package/src/quality/setup.ts +227 -0
- package/src/release/changelog.ts +7 -3
- package/src/release/channels/custom.ts +43 -0
- package/src/release/channels/gitea.ts +35 -0
- package/src/release/channels/github.ts +35 -0
- package/src/release/channels/gitlab.ts +35 -0
- package/src/release/channels/registry.ts +52 -0
- package/src/release/channels/types.ts +27 -0
- package/src/release/detector.ts +10 -63
- package/src/release/executor.ts +61 -51
- package/src/release/prompt.ts +38 -38
- package/src/release/version.ts +129 -10
- package/src/review/agent-loader.ts +331 -0
- package/src/review/consolidator.ts +180 -0
- package/src/review/default-agents/correctness.md +72 -0
- package/src/review/default-agents/maintainability.md +64 -0
- package/src/review/default-agents/security.md +67 -0
- package/src/review/fixer.ts +219 -0
- package/src/review/multi-agent-runner.ts +135 -0
- package/src/review/output.ts +147 -0
- package/src/review/prompts/agent-review-wrapper.md +36 -0
- package/src/review/prompts/fix-findings.md +32 -0
- package/src/review/prompts/fix-output-schema.md +18 -0
- package/src/review/prompts/invalid-output-retry.md +22 -0
- package/src/review/prompts/output-instructions.md +14 -0
- package/src/review/prompts/review-output-schema.md +38 -0
- package/src/review/prompts/single-review.md +53 -0
- package/src/review/prompts/validation-review.md +30 -0
- package/src/review/runner.ts +128 -0
- package/src/review/scope.ts +353 -0
- package/src/review/template.ts +15 -0
- package/src/review/types.ts +296 -0
- package/src/review/validator.ts +160 -0
- package/src/storage/plans.ts +5 -3
- package/src/storage/reports.ts +50 -7
- package/src/storage/review-sessions.ts +117 -0
- package/src/text.ts +19 -0
- package/src/types.ts +336 -26
- package/src/utils/paths.ts +39 -0
- package/src/visual/companion.ts +5 -3
- package/src/visual/start-server.ts +101 -0
- package/src/visual/stop-server.ts +39 -0
- package/bin/ctx-mode-wrapper.mjs +0 -66
- package/src/config/profiles.ts +0 -64
- package/src/context-mode/installer.ts +0 -38
- package/src/quality/ai-review-gate.ts +0 -43
- package/src/quality/gate-runner.ts +0 -67
- package/src/quality/lsp-gate.ts +0 -24
- package/src/quality/test-gate.ts +0 -39
- package/src/visual/scripts/start-server.sh +0 -98
- package/src/visual/scripts/stop-server.sh +0 -21
package/src/config/schema.ts
CHANGED
|
@@ -1,55 +1,120 @@
|
|
|
1
1
|
// src/config/schema.ts
|
|
2
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
2
3
|
import { Type } from "@sinclair/typebox";
|
|
3
4
|
import { Value } from "@sinclair/typebox/value";
|
|
4
|
-
import type { SupipowersConfig
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
5
|
+
import type { SupipowersConfig } from "../types.js";
|
|
6
|
+
import { QualityGatesSchema } from "../quality/schemas.js";
|
|
7
|
+
|
|
8
|
+
const TAG_FORMAT_PATTERN = "^(?:(?!\\$\\{version\\}).)*\\$\\{version\\}(?:(?!\\$\\{version\\}).)*$";
|
|
9
|
+
|
|
10
|
+
export const ConfigSchema = Type.Object(
|
|
11
|
+
{
|
|
12
|
+
version: Type.String(),
|
|
13
|
+
quality: Type.Object(
|
|
14
|
+
{
|
|
15
|
+
gates: QualityGatesSchema,
|
|
16
|
+
},
|
|
17
|
+
{ additionalProperties: false },
|
|
18
|
+
),
|
|
19
|
+
lsp: Type.Object(
|
|
20
|
+
{
|
|
21
|
+
setupGuide: Type.Boolean(),
|
|
22
|
+
},
|
|
23
|
+
{ additionalProperties: false },
|
|
24
|
+
),
|
|
25
|
+
qa: Type.Object(
|
|
26
|
+
{
|
|
27
|
+
framework: Type.Union([Type.String(), Type.Null()]),
|
|
28
|
+
e2e: Type.Boolean(),
|
|
29
|
+
},
|
|
30
|
+
{ additionalProperties: false },
|
|
31
|
+
),
|
|
32
|
+
release: Type.Object(
|
|
33
|
+
{
|
|
34
|
+
channels: Type.Array(Type.String()),
|
|
35
|
+
tagFormat: Type.String({ pattern: TAG_FORMAT_PATTERN }),
|
|
36
|
+
customChannels: Type.Optional(
|
|
37
|
+
Type.Record(
|
|
38
|
+
Type.String(),
|
|
39
|
+
Type.Object({
|
|
40
|
+
label: Type.String(),
|
|
41
|
+
publishCommand: Type.String(),
|
|
42
|
+
detectCommand: Type.Optional(Type.String()),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{ additionalProperties: false },
|
|
48
|
+
),
|
|
49
|
+
contextMode: Type.Object(
|
|
50
|
+
{
|
|
51
|
+
enabled: Type.Boolean(),
|
|
52
|
+
compressionThreshold: Type.Number({ minimum: 1024 }),
|
|
53
|
+
blockHttpCommands: Type.Boolean(),
|
|
54
|
+
routingInstructions: Type.Boolean(),
|
|
55
|
+
eventTracking: Type.Boolean(),
|
|
56
|
+
compaction: Type.Boolean(),
|
|
57
|
+
llmSummarization: Type.Boolean(),
|
|
58
|
+
llmThreshold: Type.Number({ minimum: 4096 }),
|
|
59
|
+
enforceRouting: Type.Boolean(),
|
|
60
|
+
},
|
|
61
|
+
{ additionalProperties: false },
|
|
62
|
+
),
|
|
63
|
+
mcp: Type.Object(
|
|
64
|
+
{
|
|
65
|
+
closeSessionsOnExit: Type.Boolean(),
|
|
66
|
+
},
|
|
67
|
+
{ additionalProperties: false },
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
{ additionalProperties: false },
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export interface ConfigParseError {
|
|
74
|
+
source: "global" | "project";
|
|
75
|
+
path: string;
|
|
76
|
+
message: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ConfigValidationError {
|
|
80
|
+
path: string;
|
|
81
|
+
message: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface InspectionLoadResult {
|
|
85
|
+
mergedConfig: Record<string, unknown>;
|
|
86
|
+
effectiveConfig: SupipowersConfig | null;
|
|
87
|
+
parseErrors: ConfigParseError[];
|
|
88
|
+
validationErrors: ConfigValidationError[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeErrorPath(path: string): string {
|
|
92
|
+
return path.replace(/^\//, "").replace(/\//g, ".") || "(root)";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function collectValidationErrors(schema: TSchema, data: unknown): ConfigValidationError[] {
|
|
96
|
+
return [...Value.Errors(schema, data)].map((error) => ({
|
|
97
|
+
path: normalizeErrorPath(error.path),
|
|
98
|
+
message: error.message,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function validateQualityGates(data: unknown): { valid: boolean; errors: string[] } {
|
|
103
|
+
const errors = collectValidationErrors(QualityGatesSchema, data).map(
|
|
104
|
+
(error) => `${error.path}: ${error.message}`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return { valid: errors.length === 0, errors };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function collectConfigValidationErrors(data: unknown): ConfigValidationError[] {
|
|
111
|
+
return collectValidationErrors(ConfigSchema, data);
|
|
112
|
+
}
|
|
47
113
|
|
|
48
114
|
export function validateConfig(data: unknown): { valid: boolean; errors: string[] } {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const errors = [...Value.Errors(ConfigSchema, data)].map(
|
|
52
|
-
(e) => `${e.path}: ${e.message}`
|
|
115
|
+
const errors = collectConfigValidationErrors(data).map(
|
|
116
|
+
(error) => `${error.path}: ${error.message}`,
|
|
53
117
|
);
|
|
54
|
-
|
|
118
|
+
|
|
119
|
+
return { valid: errors.length === 0, errors };
|
|
55
120
|
}
|
package/src/context/analyzer.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
|
|
1
3
|
/** Estimate token count from text using chars/4 heuristic */
|
|
2
4
|
export function estimateTokens(text: string): number {
|
|
3
5
|
if (text.length === 0) return 0;
|
|
@@ -43,6 +45,63 @@ export function parseSystemPrompt(text: string): PromptSection[] {
|
|
|
43
45
|
return sections;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
// ── Per-Skill Parser ──────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** A single skill extracted from the system prompt */
|
|
51
|
+
export interface ParsedSkill {
|
|
52
|
+
name: string;
|
|
53
|
+
bytes: number;
|
|
54
|
+
tokens: number;
|
|
55
|
+
content: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Extract individual skills from system prompt text */
|
|
59
|
+
export function parseIndividualSkills(systemPrompt: string): ParsedSkill[] {
|
|
60
|
+
if (!systemPrompt) return [];
|
|
61
|
+
|
|
62
|
+
// OMP renders skills as markdown headings under "# Skills":
|
|
63
|
+
// ## skill-name
|
|
64
|
+
// Description text...
|
|
65
|
+
// Find the Skills section bounded by the next h1 heading or end of text.
|
|
66
|
+
const skillsSectionMatch = systemPrompt.match(
|
|
67
|
+
/^# Skills\n[\s\S]*?\n(?=##\s)/m,
|
|
68
|
+
);
|
|
69
|
+
if (!skillsSectionMatch) return [];
|
|
70
|
+
|
|
71
|
+
// Extract the region from first ## to the next # (h1) or end of text
|
|
72
|
+
const sectionStart = skillsSectionMatch.index! + skillsSectionMatch[0].length;
|
|
73
|
+
const afterSection = systemPrompt.slice(sectionStart);
|
|
74
|
+
const nextH1 = afterSection.search(/^# [^#]/m);
|
|
75
|
+
const skillsBody =
|
|
76
|
+
skillsSectionMatch[0].slice(skillsSectionMatch[0].indexOf("\n## ") + 1) +
|
|
77
|
+
(nextH1 === -1 ? afterSection : afterSection.slice(0, nextH1));
|
|
78
|
+
|
|
79
|
+
// Split on ## headings
|
|
80
|
+
const skills: ParsedSkill[] = [];
|
|
81
|
+
const headingRegex = /^## (.+)$/gm;
|
|
82
|
+
const headings: { name: string; index: number }[] = [];
|
|
83
|
+
let match;
|
|
84
|
+
|
|
85
|
+
while ((match = headingRegex.exec(skillsBody)) !== null) {
|
|
86
|
+
headings.push({ name: match[1].trim(), index: match.index });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < headings.length; i++) {
|
|
90
|
+
const start = headings[i].index;
|
|
91
|
+
const end = i + 1 < headings.length ? headings[i + 1].index : skillsBody.length;
|
|
92
|
+
const content = skillsBody.slice(start, end).trimEnd();
|
|
93
|
+
const bytes = byteLength(content);
|
|
94
|
+
skills.push({
|
|
95
|
+
name: headings[i].name,
|
|
96
|
+
bytes,
|
|
97
|
+
tokens: estimateTokens(content),
|
|
98
|
+
content,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return skills;
|
|
103
|
+
}
|
|
104
|
+
|
|
46
105
|
// ── Breakdown Builder ─────────────────────────────────────
|
|
47
106
|
|
|
48
107
|
/** Context usage data from OMP runtime */
|
|
@@ -190,7 +249,7 @@ function extractXmlSections(
|
|
|
190
249
|
const content = match[0];
|
|
191
250
|
const label = filePath.toLowerCase().endsWith("agents.md")
|
|
192
251
|
? "AGENTS.md"
|
|
193
|
-
: `File: ${filePath
|
|
252
|
+
: `File: ${basename(filePath) || filePath}`;
|
|
194
253
|
sections.push({ label, bytes: byteLength(content), content });
|
|
195
254
|
markConsumed(consumed, match.index, match.index + content.length);
|
|
196
255
|
}
|
|
@@ -235,7 +294,7 @@ function extractHeadingSections(
|
|
|
235
294
|
): void {
|
|
236
295
|
const headingPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
237
296
|
{ pattern: /^# Memory Guidance\b/m, label: "Memory" },
|
|
238
|
-
{ pattern: /^# context-mode — MANDATORY routing rules\b/m, label: "Routing rules" },
|
|
297
|
+
{ pattern: /^# (?:supi-)?context-mode — MANDATORY routing rules\b/m, label: "Routing rules" },
|
|
239
298
|
{ pattern: /^## MCP Server Instructions\b/m, label: "MCP instructions" },
|
|
240
299
|
];
|
|
241
300
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Platform } from "../platform/types.js";
|
|
2
|
+
import type { ParsedSkill, PromptSection } from "./analyzer.js";
|
|
3
|
+
import { estimateTokens } from "./analyzer.js";
|
|
4
|
+
|
|
5
|
+
// ── Types ───────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface TechStack {
|
|
8
|
+
languages: string[];
|
|
9
|
+
frameworks: string[];
|
|
10
|
+
tools: string[];
|
|
11
|
+
runtime: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A skill with its token cost — no classification judgment. */
|
|
15
|
+
export interface SkillEntry {
|
|
16
|
+
name: string;
|
|
17
|
+
tokens: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A non-skill section with token cost and optional note. */
|
|
21
|
+
export interface SectionEntry {
|
|
22
|
+
label: string;
|
|
23
|
+
tokens: number;
|
|
24
|
+
note: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Raw context report — data only, no classification. */
|
|
28
|
+
export interface ContextReport {
|
|
29
|
+
totalTokens: number;
|
|
30
|
+
techStack: TechStack;
|
|
31
|
+
skills: SkillEntry[];
|
|
32
|
+
sections: SectionEntry[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Framework / Tool Detection Maps ─────────────────────────
|
|
36
|
+
|
|
37
|
+
/** Maps package.json dependency names → detected framework or tool */
|
|
38
|
+
const DEP_TO_FRAMEWORK: Record<string, string> = {
|
|
39
|
+
react: "react",
|
|
40
|
+
"react-dom": "react",
|
|
41
|
+
next: "next",
|
|
42
|
+
vue: "vue",
|
|
43
|
+
svelte: "svelte",
|
|
44
|
+
"@sveltejs/kit": "svelte",
|
|
45
|
+
express: "express",
|
|
46
|
+
fastify: "fastify",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DEP_TO_TOOL: Record<string, string> = {
|
|
50
|
+
tailwindcss: "tailwind",
|
|
51
|
+
"@playwright/test": "playwright",
|
|
52
|
+
prisma: "prisma",
|
|
53
|
+
"@prisma/client": "prisma",
|
|
54
|
+
"@shadcn/ui": "shadcn",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── Tech Stack Detection ────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** Read a file via platform.exec; returns content or null on failure. */
|
|
60
|
+
async function tryRead(
|
|
61
|
+
platform: Platform,
|
|
62
|
+
cwd: string,
|
|
63
|
+
filename: string,
|
|
64
|
+
): Promise<string | null> {
|
|
65
|
+
const r = await platform.exec("cat", [filename], { cwd });
|
|
66
|
+
return r.code === 0 ? r.stdout : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Check if a file exists at `cwd/filename` via exec. */
|
|
70
|
+
async function fileExists(
|
|
71
|
+
platform: Platform,
|
|
72
|
+
cwd: string,
|
|
73
|
+
filename: string,
|
|
74
|
+
): Promise<boolean> {
|
|
75
|
+
const r = await platform.exec("test", ["-f", filename], { cwd });
|
|
76
|
+
return r.code === 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Deterministic tech-stack detection — no LLM.
|
|
81
|
+
* Inspects package.json, lockfiles, and config files.
|
|
82
|
+
*/
|
|
83
|
+
export async function detectTechStack(
|
|
84
|
+
platform: Platform,
|
|
85
|
+
cwd: string,
|
|
86
|
+
): Promise<TechStack> {
|
|
87
|
+
const languages = new Set<string>();
|
|
88
|
+
const frameworks = new Set<string>();
|
|
89
|
+
const tools = new Set<string>();
|
|
90
|
+
let runtime: string | null = null;
|
|
91
|
+
|
|
92
|
+
// 1. package.json — deps & devDeps
|
|
93
|
+
const pkgRaw = await tryRead(platform, cwd, "package.json");
|
|
94
|
+
if (pkgRaw) {
|
|
95
|
+
try {
|
|
96
|
+
const pkg = JSON.parse(pkgRaw);
|
|
97
|
+
const allDeps: Record<string, string> = {
|
|
98
|
+
...pkg.dependencies,
|
|
99
|
+
...pkg.devDependencies,
|
|
100
|
+
};
|
|
101
|
+
for (const dep of Object.keys(allDeps)) {
|
|
102
|
+
const fw = DEP_TO_FRAMEWORK[dep];
|
|
103
|
+
if (fw) frameworks.add(fw);
|
|
104
|
+
const tl = DEP_TO_TOOL[dep];
|
|
105
|
+
if (tl) tools.add(tl);
|
|
106
|
+
}
|
|
107
|
+
if (allDeps.typescript) languages.add("typescript");
|
|
108
|
+
} catch {
|
|
109
|
+
// malformed package.json — continue with file-based detection
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 2. Lockfiles → runtime (first match wins, most-specific first)
|
|
114
|
+
if (await fileExists(platform, cwd, "bun.lock")) {
|
|
115
|
+
runtime = "bun";
|
|
116
|
+
} else if (await fileExists(platform, cwd, "package-lock.json")) {
|
|
117
|
+
runtime = "node";
|
|
118
|
+
} else if (await fileExists(platform, cwd, "pnpm-lock.yaml")) {
|
|
119
|
+
runtime = "node";
|
|
120
|
+
} else if (await fileExists(platform, cwd, "yarn.lock")) {
|
|
121
|
+
runtime = "node";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Config files → languages
|
|
125
|
+
const configChecks: [string, string][] = [
|
|
126
|
+
["tsconfig.json", "typescript"],
|
|
127
|
+
["Cargo.toml", "rust"],
|
|
128
|
+
["pyproject.toml", "python"],
|
|
129
|
+
["requirements.txt", "python"],
|
|
130
|
+
["go.mod", "go"],
|
|
131
|
+
["Gemfile", "ruby"],
|
|
132
|
+
];
|
|
133
|
+
const results = await Promise.all(
|
|
134
|
+
configChecks.map(([file, lang]) =>
|
|
135
|
+
fileExists(platform, cwd, file).then((exists) =>
|
|
136
|
+
exists ? lang : null,
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
for (const lang of results) {
|
|
141
|
+
if (lang) languages.add(lang);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
languages: [...languages],
|
|
146
|
+
frameworks: [...frameworks],
|
|
147
|
+
tools: [...tools],
|
|
148
|
+
runtime,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Context Report ──────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Build a raw context report from parsed prompt data.
|
|
156
|
+
* Gathers token costs per skill and per section, flags anomalies.
|
|
157
|
+
* Does NOT classify or recommend — that's the LLM's job.
|
|
158
|
+
*/
|
|
159
|
+
export function buildContextReport(
|
|
160
|
+
sections: PromptSection[],
|
|
161
|
+
skills: ParsedSkill[],
|
|
162
|
+
techStack: TechStack,
|
|
163
|
+
): ContextReport {
|
|
164
|
+
const totalTokens = sections.reduce(
|
|
165
|
+
(sum, s) => sum + estimateTokens(s.content),
|
|
166
|
+
0,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const skillEntries: SkillEntry[] = skills.map((s) => ({
|
|
170
|
+
name: s.name,
|
|
171
|
+
tokens: s.tokens,
|
|
172
|
+
}));
|
|
173
|
+
|
|
174
|
+
// Annotate non-skill sections with notes for anomalies
|
|
175
|
+
const sectionEntries: SectionEntry[] = [];
|
|
176
|
+
|
|
177
|
+
const routingSections = sections.filter((s) =>
|
|
178
|
+
s.label.toLowerCase().includes("routing"),
|
|
179
|
+
);
|
|
180
|
+
const hasRoutingDupes = routingSections.length > 1;
|
|
181
|
+
|
|
182
|
+
for (const s of sections) {
|
|
183
|
+
// Skip the aggregate "Skills (N)" section — per-skill data is separate
|
|
184
|
+
if (s.label.toLowerCase().startsWith("skills")) continue;
|
|
185
|
+
|
|
186
|
+
const tokens = estimateTokens(s.content);
|
|
187
|
+
let note = "";
|
|
188
|
+
|
|
189
|
+
if (s.label.toLowerCase().includes("routing") && hasRoutingDupes) {
|
|
190
|
+
note = `Duplicate (${routingSections.length} found) — consolidate`;
|
|
191
|
+
} else if (s.label.toLowerCase().includes("memory") && tokens > 500) {
|
|
192
|
+
note = "Large — consider compressing or summarizing";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
sectionEntries.push({ label: s.label, tokens, note });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { totalTokens, techStack, skills: skillEntries, sections: sectionEntries };
|
|
199
|
+
}
|
|
@@ -14,7 +14,8 @@ interface ToolResultEventResult {
|
|
|
14
14
|
|
|
15
15
|
const BASH_HEAD_LINES = 5;
|
|
16
16
|
const BASH_TAIL_LINES = 10;
|
|
17
|
-
const
|
|
17
|
+
const READ_HEAD_LINES = 80;
|
|
18
|
+
const READ_TAIL_LINES = 30;
|
|
18
19
|
const GREP_MAX_MATCHES = 10;
|
|
19
20
|
const FIND_MAX_PATHS = 20;
|
|
20
21
|
|
|
@@ -68,23 +69,25 @@ function compressBash(text: string, details: unknown): string | undefined {
|
|
|
68
69
|
].join("\n");
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/** Compress read tool output */
|
|
72
|
+
/** Compress read tool output — head+tail with hashline preservation */
|
|
72
73
|
function compressRead(text: string, input: Record<string, unknown>): string | undefined {
|
|
73
|
-
// Scoped reads (offset/limit) are already targeted — pass through
|
|
74
|
-
if (input.offset
|
|
74
|
+
// Scoped reads (offset/limit/sel) are already targeted — pass through
|
|
75
|
+
if (input.offset != null || input.limit != null || input.sel != null) return undefined;
|
|
75
76
|
|
|
76
77
|
const lines = text.split("\n");
|
|
77
78
|
const totalLines = lines.length;
|
|
78
|
-
const path = typeof input.path === "string" ? input.path : "unknown";
|
|
79
79
|
|
|
80
|
-
if (totalLines <=
|
|
80
|
+
if (totalLines <= READ_HEAD_LINES + READ_TAIL_LINES) return undefined;
|
|
81
|
+
|
|
82
|
+
const head = lines.slice(0, READ_HEAD_LINES);
|
|
83
|
+
const tail = lines.slice(-READ_TAIL_LINES);
|
|
84
|
+
const omittedStart = READ_HEAD_LINES + 1;
|
|
85
|
+
const omittedEnd = totalLines - READ_TAIL_LINES;
|
|
81
86
|
|
|
82
|
-
const preview = lines.slice(0, READ_PREVIEW_LINES);
|
|
83
87
|
return [
|
|
84
|
-
|
|
85
|
-
""
|
|
86
|
-
...
|
|
87
|
-
`[...compressed: remaining ${totalLines - READ_PREVIEW_LINES} lines omitted...]`,
|
|
88
|
+
...head,
|
|
89
|
+
`[...${omittedEnd - omittedStart + 1} lines omitted. Use read(path, sel="L${omittedStart}-L${omittedEnd}") to view...]`,
|
|
90
|
+
...tail,
|
|
88
91
|
].join("\n");
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/context-mode/detector.ts
|
|
2
2
|
|
|
3
|
-
/** Which context-mode
|
|
3
|
+
/** Which context-mode tools are available in the current session */
|
|
4
4
|
export interface ContextModeStatus {
|
|
5
5
|
available: boolean;
|
|
6
6
|
tools: {
|
|
@@ -13,59 +13,21 @@ export interface ContextModeStatus {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** Suffixes to match against full MCP-namespaced tool names */
|
|
17
|
-
const TOOL_SUFFIXES: Array<[string, keyof ContextModeStatus["tools"]]> = [
|
|
18
|
-
["ctx_execute", "ctxExecute"],
|
|
19
|
-
["ctx_batch_execute", "ctxBatchExecute"],
|
|
20
|
-
["ctx_execute_file", "ctxExecuteFile"],
|
|
21
|
-
["ctx_index", "ctxIndex"],
|
|
22
|
-
["ctx_search", "ctxSearch"],
|
|
23
|
-
["ctx_fetch_and_index", "ctxFetchAndIndex"],
|
|
24
|
-
];
|
|
25
|
-
|
|
26
16
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* - Claude Code MCP: "mcp__plugin_context-mode_context-mode__ctx_execute"
|
|
31
|
-
* - OMP MCP: "mcp_context_mode_ctx_execute"
|
|
32
|
-
*
|
|
33
|
-
* We match by checking if the tool contains a known context-mode server
|
|
34
|
-
* prefix followed by the suffix, or is the bare suffix itself.
|
|
17
|
+
* Context-mode tools are native (registered by this extension), so they are
|
|
18
|
+
* always available when the extension is loaded. The interface is preserved for
|
|
19
|
+
* backward compatibility with hooks/routing consumers.
|
|
35
20
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Detect context-mode MCP tool availability from the active tools list */
|
|
50
|
-
export function detectContextMode(activeTools: string[]): ContextModeStatus {
|
|
51
|
-
const tools: ContextModeStatus["tools"] = {
|
|
52
|
-
ctxExecute: false,
|
|
53
|
-
ctxBatchExecute: false,
|
|
54
|
-
ctxExecuteFile: false,
|
|
55
|
-
ctxIndex: false,
|
|
56
|
-
ctxSearch: false,
|
|
57
|
-
ctxFetchAndIndex: false,
|
|
21
|
+
export function detectContextMode(_activeTools?: string[]): ContextModeStatus {
|
|
22
|
+
return {
|
|
23
|
+
available: true,
|
|
24
|
+
tools: {
|
|
25
|
+
ctxExecute: true,
|
|
26
|
+
ctxBatchExecute: true,
|
|
27
|
+
ctxExecuteFile: true,
|
|
28
|
+
ctxIndex: true,
|
|
29
|
+
ctxSearch: true,
|
|
30
|
+
ctxFetchAndIndex: true,
|
|
31
|
+
},
|
|
58
32
|
};
|
|
59
|
-
|
|
60
|
-
for (const tool of activeTools) {
|
|
61
|
-
for (const [suffix, key] of TOOL_SUFFIXES) {
|
|
62
|
-
if (matchesSuffix(tool, suffix)) {
|
|
63
|
-
tools[key] = true;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const available = Object.values(tools).some(Boolean);
|
|
70
|
-
return { available, tools };
|
|
71
|
-
}
|
|
33
|
+
}
|