@compr/opscontext-mcp 2.8.0 → 2.8.2
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/CHANGELOG.md +38 -0
- package/dist/ci-status.d.ts +16 -0
- package/dist/ci-status.js +55 -0
- package/dist/cli.js +16 -0
- package/dist/firewall.js +1 -1
- package/dist/index.js +4 -2
- package/dist/source-summary.d.ts +9 -0
- package/dist/source-summary.js +150 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ All notable changes to OpsContext for AI Agents (previously ContextEngine — MC
|
|
|
4
4
|
|
|
5
5
|
> Entries for 2.2.0 through 2.4.0 were not backfilled here; see `docs/sessions/SESSION_19` through `SESSION_21` for those releases.
|
|
6
6
|
|
|
7
|
+
## [2.8.2] 2026-09-08: every source says what it is
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **One-line summary per source in `list_sources` and `contextengine list-sources`**
|
|
12
|
+
(`src/source-summary.ts`): frontmatter `description` when the file has one, else H1 title
|
|
13
|
+
plus first prose line, else the module docstring or header comment for code. Read from the
|
|
14
|
+
first 4 KB of each file only. Why: with 800+ sources an agent opened two or three whole
|
|
15
|
+
documents to find the right one; the summary lets it pick once.
|
|
16
|
+
|
|
17
|
+
## [2.8.1] 2026-09-07: a push is not done until its CI is read
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`end-session` check 3c, CI on HEAD** (`[PUSHED-MEANS-CI-READ]`, `src/ci-status.ts`): every
|
|
22
|
+
workflow run for the exact HEAD sha through `gh run list`; a failed run is a FAIL item (exit 1).
|
|
23
|
+
No gh, no remote, or no runs yet is "not checked", never a pass. Why: main's CI had been red
|
|
24
|
+
on every commit since 2026-09-04 and the Telegram alert fired each time; thirty commits and
|
|
25
|
+
five releases went by with nobody reading it.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- CI installs the activation server's own packages too: the first time the Test step actually
|
|
30
|
+
ran, `server/src/community-rules-server.test.ts` could not load (`ERR_MODULE_NOT_FOUND`).
|
|
31
|
+
- The Doc Freshness gate fails only when 20 or more source lines change without a doc change; a
|
|
32
|
+
one-line lint fix is not a documented change (its first run paged Telegram for a dash).
|
|
33
|
+
- CI runs on Node 20 and 22 with `fail-fast: false`; Node 18 is EOL and eslint 10 needs 20.19+,
|
|
34
|
+
and the 18 job's failure was cancelling the others before tests ran. `engines.node` is now
|
|
35
|
+
`>=20.19.0`, which is what is tested.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- CI was red on every pull request for one `prefer-const` lint error in `src/firewall.ts`.
|
|
40
|
+
- The Doc Freshness workflow failed any push or PR made more than 8 hours after the last
|
|
41
|
+
SKILLS.md commit, whatever the change (two Dependabot bumps on 2026-09-07). It now measures
|
|
42
|
+
the change itself: source touched without a doc touched fails, anything else passes
|
|
43
|
+
(`[DOC-GATE-MEASURES-THE-DIFF]`).
|
|
44
|
+
|
|
7
45
|
## [2.8.0] 2026-09-06: health is measured, never estimated
|
|
8
46
|
|
|
9
47
|
### Added
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CiRun {
|
|
2
|
+
name: string;
|
|
3
|
+
status: string;
|
|
4
|
+
conclusion: string | null;
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CiStatus {
|
|
8
|
+
sha: string;
|
|
9
|
+
state: "ok" | "failed" | "pending" | "no-runs" | "unavailable";
|
|
10
|
+
runs: CiRun[];
|
|
11
|
+
note?: string;
|
|
12
|
+
}
|
|
13
|
+
export type Runner = (cmd: string, args: string[], cwd: string) => string;
|
|
14
|
+
export declare function ciStatusForHead(cwd: string, run?: Runner): CiStatus;
|
|
15
|
+
export declare function formatCiStatus(s: CiStatus): string[];
|
|
16
|
+
//# sourceMappingURL=ci-status.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// [LOCKED] [PUSHED-MEANS-CI-READ] 2026-09-07
|
|
2
|
+
// [NEVER] let end-session pass while a workflow run for HEAD has failed, and [NEVER] count
|
|
3
|
+
// "no runs found" as green.
|
|
4
|
+
// WHY: main's CI had been red on every commit since 2026-09-04 (one lint error, then a Node 18
|
|
5
|
+
// job that eslint 10 cannot run on) and the Telegram alert fired each time. Thirty commits,
|
|
6
|
+
// five releases, nobody read it: the post-commit hook pushes, end-session ran after every
|
|
7
|
+
// push, and nothing in that loop looked at the result. A push is not done until its CI is.
|
|
8
|
+
// FIX: end-session check 3c lists every workflow run for the exact HEAD sha through `gh run
|
|
9
|
+
// list` (hardcoded argv, no shell) and counts a failure as a FAIL item. gh missing, no
|
|
10
|
+
// GitHub remote, or no runs yet is reported as "not checked", never as pass.
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
const defaultRunner = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15_000 }).trim();
|
|
13
|
+
export function ciStatusForHead(cwd, run = defaultRunner) {
|
|
14
|
+
let sha = "";
|
|
15
|
+
try {
|
|
16
|
+
sha = run("git", ["rev-parse", "HEAD"], cwd);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { sha, state: "unavailable", runs: [], note: "not a git repository" };
|
|
20
|
+
}
|
|
21
|
+
let raw = "";
|
|
22
|
+
try {
|
|
23
|
+
raw = run("gh", ["run", "list", "--limit", "40", "--json", "name,status,conclusion,url,headSha"], cwd);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { sha, state: "unavailable", runs: [], note: "gh not available, not logged in, or no GitHub remote" };
|
|
27
|
+
}
|
|
28
|
+
let all = [];
|
|
29
|
+
try {
|
|
30
|
+
all = JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { sha, state: "unavailable", runs: [], note: "gh returned no JSON" };
|
|
34
|
+
}
|
|
35
|
+
const runs = all.filter((r) => r.headSha === sha).map(({ name, status, conclusion, url }) => ({ name, status, conclusion, url }));
|
|
36
|
+
if (runs.length === 0)
|
|
37
|
+
return { sha, state: "no-runs", runs, note: "no workflow run for HEAD yet: pushed seconds ago, or CI not wired" };
|
|
38
|
+
const failed = runs.some((r) => r.conclusion === "failure" || r.conclusion === "timed_out" || r.conclusion === "startup_failure");
|
|
39
|
+
const pending = runs.some((r) => r.status !== "completed");
|
|
40
|
+
return { sha, state: failed ? "failed" : pending ? "pending" : "ok", runs };
|
|
41
|
+
}
|
|
42
|
+
export function formatCiStatus(s) {
|
|
43
|
+
const lines = [];
|
|
44
|
+
if (s.state === "unavailable" || s.state === "no-runs") {
|
|
45
|
+
lines.push(`- ⚠️ CI on HEAD${s.sha ? ` ${s.sha.slice(0, 7)}` : ""} not checked: ${s.note}`);
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
for (const r of s.runs) {
|
|
49
|
+
const bad = r.conclusion === "failure" || r.conclusion === "timed_out" || r.conclusion === "startup_failure";
|
|
50
|
+
const icon = bad ? "❌ FAIL" : r.status !== "completed" ? "⏳" : r.conclusion === "success" ? "✅" : "▫️";
|
|
51
|
+
lines.push(`- ${icon} ${r.name}: ${r.conclusion ?? r.status}${bad ? ` ${r.url}` : ""}`);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=ci-status.js.map
|
package/dist/cli.js
CHANGED
|
@@ -641,6 +641,7 @@ async function runInit() {
|
|
|
641
641
|
// ---------------------------------------------------------------------------
|
|
642
642
|
import { loadSources, loadProjectDirs, loadConfig, resolveProjectDir, findProjectRoot, looksLikePath } from "./config.js";
|
|
643
643
|
import { ingestSources } from "./ingest.js";
|
|
644
|
+
import { summarizeSource } from "./source-summary.js";
|
|
644
645
|
import { searchChunks } from "./search.js";
|
|
645
646
|
import { SERVER_COMMANDS, suggestCommands } from "./cli-commands.js";
|
|
646
647
|
import { collectProjectOps, collectSystemOps } from "./collectors.js";
|
|
@@ -657,6 +658,7 @@ import { getStagedFiles, runSecretScan, runDocCoverage, runCommitMessageRequired
|
|
|
657
658
|
import { safeAppend } from "./audit.js";
|
|
658
659
|
import { listServers, formatServers } from "./server-registry.js";
|
|
659
660
|
import { computeFleetHealth, formatFleetHealth } from "./fleet-health.js";
|
|
661
|
+
import { ciStatusForHead, formatCiStatus } from "./ci-status.js";
|
|
660
662
|
import { installSkill, locateBundledSkill, buildManagedBlock, syncClaudeMd, } from "./claude-integration.js";
|
|
661
663
|
import { fileURLToPath } from "url";
|
|
662
664
|
// ---------------------------------------------------------------------------
|
|
@@ -742,6 +744,9 @@ async function cliListSources() {
|
|
|
742
744
|
const count = chunks.filter((c) => c.source === s.name).length;
|
|
743
745
|
const status = exists ? `✅ ${count} chunks` : "⚠ file not found";
|
|
744
746
|
console.log(` ${s.name}: ${status}`);
|
|
747
|
+
const summary = exists ? summarizeSource(s) : "";
|
|
748
|
+
if (summary)
|
|
749
|
+
console.log(` ${summary}`);
|
|
745
750
|
console.log(` ${s.path}`);
|
|
746
751
|
}
|
|
747
752
|
console.log("");
|
|
@@ -2238,6 +2243,17 @@ async function cliEndSession() {
|
|
|
2238
2243
|
if (fleet.warnings.length > 0)
|
|
2239
2244
|
failCount += fleet.warnings.length;
|
|
2240
2245
|
checks.push("");
|
|
2246
|
+
// --- Check 3c: CI on HEAD ([LOCK] [PUSHED-MEANS-CI-READ]) ---
|
|
2247
|
+
checks.push("## 3c. CI on HEAD\n");
|
|
2248
|
+
const ci = ciStatusForHead(process.cwd());
|
|
2249
|
+
checks.push(...formatCiStatus(ci));
|
|
2250
|
+
if (ci.state === "failed") {
|
|
2251
|
+
failCount++;
|
|
2252
|
+
checks.push("- ❌ FAIL: a workflow run for HEAD failed; a push is not done until its CI is");
|
|
2253
|
+
}
|
|
2254
|
+
else if (ci.state === "ok")
|
|
2255
|
+
passCount++;
|
|
2256
|
+
checks.push("");
|
|
2241
2257
|
checks.push("## 4. Sessions\n");
|
|
2242
2258
|
const sessions = listSessions();
|
|
2243
2259
|
if (sessions.length > 0) {
|
package/dist/firewall.js
CHANGED
|
@@ -185,7 +185,7 @@ export class ProtocolFirewall {
|
|
|
185
185
|
if (sessionUrgent && level === "footer")
|
|
186
186
|
level = "header";
|
|
187
187
|
// Prepend learning injection to response (always, if available)
|
|
188
|
-
|
|
188
|
+
const text = injection ? injection + "\n\n" + responseText : responseText;
|
|
189
189
|
// Build session urgency block (always prepended when overdue)
|
|
190
190
|
const urgentBlock = sessionUrgent ? this.buildSessionUrgentBlock() : null;
|
|
191
191
|
if (level === "silent" && !urgentBlock)
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { loadSources, loadProjectDirs, loadConfig, resolveProjectDir } from "./config.js";
|
|
6
6
|
import { ingestSources } from "./ingest.js";
|
|
7
|
+
import { summarizeSource } from "./source-summary.js";
|
|
7
8
|
import { searchChunks } from "./search.js";
|
|
8
9
|
import { initEmbeddings, embedChunks, embedKeyOf, vectorSearch, isEmbeddingsReady, } from "./embeddings.js";
|
|
9
10
|
import { collectProjectOps, collectSystemOps } from "./collectors.js";
|
|
@@ -575,7 +576,7 @@ server.tool("search_context", "Search across all indexed project knowledge (copi
|
|
|
575
576
|
// ---------------------------------------------------------------------------
|
|
576
577
|
// Tool: list_sources
|
|
577
578
|
// ---------------------------------------------------------------------------
|
|
578
|
-
server.tool("list_sources", "List all knowledge sources indexed by ContextEngine, with
|
|
579
|
+
server.tool("list_sources", "List all knowledge sources indexed by ContextEngine, each with a one-line summary (from the file's own head: frontmatter description, title plus first sentence, or module docstring), status (found/missing) and chunk counts. Read the summary to pick the right source before calling read_source.", {}, async () => {
|
|
579
580
|
const lines = sources.map((s) => {
|
|
580
581
|
const exists = existsSync(s.path);
|
|
581
582
|
const count = chunks.filter((c) => c.source === s.name).length;
|
|
@@ -583,7 +584,8 @@ server.tool("list_sources", "List all knowledge sources indexed by ContextEngine
|
|
|
583
584
|
const status = exists
|
|
584
585
|
? `✅ ${count} chunks${embeddedCount > 0 ? ` (${embeddedCount} embedded)` : ""}`
|
|
585
586
|
: "⚠ file not found";
|
|
586
|
-
|
|
587
|
+
const summary = exists ? summarizeSource(s) : "";
|
|
588
|
+
return `${s.name}: ${status}${summary ? `\n ${summary}` : ""}\n ${s.path}`;
|
|
587
589
|
});
|
|
588
590
|
const embStatus = isEmbeddingsReady()
|
|
589
591
|
? `✅ ${embeddedChunks.length} vectors`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KnowledgeSource } from "./config.js";
|
|
2
|
+
export declare const HEAD_BYTES = 4096;
|
|
3
|
+
export declare const SUMMARY_MAX = 110;
|
|
4
|
+
/** Read at most `bytes` from the start of a file. Empty string on any error. */
|
|
5
|
+
export declare function readHead(path: string, bytes?: number): string;
|
|
6
|
+
/** Summary for a configured source; "" when the file is unreadable or says nothing. */
|
|
7
|
+
export declare function summarizeSource(source: KnowledgeSource): string;
|
|
8
|
+
export declare function summarizeText(text: string, type: "markdown" | "code"): string;
|
|
9
|
+
//# sourceMappingURL=source-summary.d.ts.map
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-line summary per knowledge source, for list_sources (MCP) and
|
|
3
|
+
* `contextengine list-sources` (CLI).
|
|
4
|
+
*
|
|
5
|
+
* Why: with 800+ sources, an agent reading a bare name plus a path opens two
|
|
6
|
+
* or three files to find the right one. Each open puts a whole document into
|
|
7
|
+
* its context (see CLAUDE.md, multi-agent cost). A summary line derived from
|
|
8
|
+
* the first few KB of the file lets it pick once.
|
|
9
|
+
*
|
|
10
|
+
* Reads only the head of the file (HEAD_BYTES), never the whole document, so
|
|
11
|
+
* the cost is bounded no matter how many sources are configured.
|
|
12
|
+
*/
|
|
13
|
+
import { openSync, readSync, closeSync } from "fs";
|
|
14
|
+
export const HEAD_BYTES = 4096;
|
|
15
|
+
export const SUMMARY_MAX = 110;
|
|
16
|
+
/** Read at most `bytes` from the start of a file. Empty string on any error. */
|
|
17
|
+
export function readHead(path, bytes = HEAD_BYTES) {
|
|
18
|
+
let fd;
|
|
19
|
+
try {
|
|
20
|
+
fd = openSync(path, "r");
|
|
21
|
+
const buf = Buffer.alloc(bytes);
|
|
22
|
+
const n = readSync(fd, buf, 0, bytes, 0);
|
|
23
|
+
return buf.subarray(0, n).toString("utf-8");
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (fd !== undefined)
|
|
30
|
+
closeSync(fd);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Summary for a configured source; "" when the file is unreadable or says nothing. */
|
|
34
|
+
export function summarizeSource(source) {
|
|
35
|
+
return summarizeText(readHead(source.path), source.type);
|
|
36
|
+
}
|
|
37
|
+
export function summarizeText(text, type) {
|
|
38
|
+
if (!text)
|
|
39
|
+
return "";
|
|
40
|
+
return clip(type === "code" ? summarizeCode(text) : summarizeMarkdown(text));
|
|
41
|
+
}
|
|
42
|
+
function clip(s) {
|
|
43
|
+
const one = s.replace(/\s+/g, " ").trim();
|
|
44
|
+
if (one.length <= SUMMARY_MAX)
|
|
45
|
+
return one;
|
|
46
|
+
return one.slice(0, SUMMARY_MAX - 3).trimEnd() + "...";
|
|
47
|
+
}
|
|
48
|
+
/** Strip markdown decoration so the line reads as plain text. */
|
|
49
|
+
function plain(line) {
|
|
50
|
+
return line
|
|
51
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, "") // images
|
|
52
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") // links -> text
|
|
53
|
+
.replace(/`([^`]*)`/g, "$1") // inline code
|
|
54
|
+
.replace(/\*\*|__/g, "") // bold
|
|
55
|
+
.replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "") // list bullet
|
|
56
|
+
.replace(/^\s*>\s?/, "") // blockquote
|
|
57
|
+
.trim();
|
|
58
|
+
}
|
|
59
|
+
/** Header metadata lines that say nothing about the content. */
|
|
60
|
+
const META_LINE = /^(updated|last updated|created|date|status|author|owner|version|scope|audience)\b\s*[:\-]/i;
|
|
61
|
+
function summarizeMarkdown(text) {
|
|
62
|
+
let body = text;
|
|
63
|
+
// YAML frontmatter: prefer its description, it IS a one-line summary.
|
|
64
|
+
if (body.startsWith("---\n") || body.startsWith("---\r\n")) {
|
|
65
|
+
const end = body.indexOf("\n---", 4);
|
|
66
|
+
if (end !== -1) {
|
|
67
|
+
const fm = body.slice(4, end);
|
|
68
|
+
const desc = fm.match(/^description:\s*(.+)$/m);
|
|
69
|
+
if (desc)
|
|
70
|
+
return desc[1].trim().replace(/^["']|["']$/g, "");
|
|
71
|
+
body = body.slice(end + 4);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let title = "";
|
|
75
|
+
let prose = "";
|
|
76
|
+
let inFence = false;
|
|
77
|
+
for (const raw of body.split("\n")) {
|
|
78
|
+
const line = raw.trim();
|
|
79
|
+
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
80
|
+
inFence = !inFence;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (inFence || !line)
|
|
84
|
+
continue;
|
|
85
|
+
if (line.startsWith("<!--") || line.startsWith("|") || line.startsWith("![") || line.startsWith("[!"))
|
|
86
|
+
continue;
|
|
87
|
+
if (/^[-=*_]{3,}$/.test(line))
|
|
88
|
+
continue;
|
|
89
|
+
if (line.startsWith("#")) {
|
|
90
|
+
if (!title)
|
|
91
|
+
title = plain(line.replace(/^#+\s*/, ""));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const p = plain(line);
|
|
95
|
+
if (p.length < 12)
|
|
96
|
+
continue; // lone dates, "v2.1", etc.
|
|
97
|
+
if (META_LINE.test(p))
|
|
98
|
+
continue; // "Updated: 2026-03-13", "Status: draft"
|
|
99
|
+
prose = p;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
if (title && prose)
|
|
103
|
+
return `${title}: ${prose}`;
|
|
104
|
+
return title || prose;
|
|
105
|
+
}
|
|
106
|
+
const CODE_SKIP = /^(#!|\/\/\s*eslint|\/\*\s*eslint|@ts-|#\s*-\*-|#\s*coding[:=]|SPDX|Copyright|\[LOCK|\[LOCKED\]|\[NEVER\]|WHY:|FIX:|import |from |export |const |let |var |use strict|"use strict")/i;
|
|
107
|
+
function summarizeCode(text) {
|
|
108
|
+
const lines = text.split("\n");
|
|
109
|
+
let inBlock = false;
|
|
110
|
+
for (const raw of lines) {
|
|
111
|
+
let line = raw.trim();
|
|
112
|
+
if (!line)
|
|
113
|
+
continue;
|
|
114
|
+
if (!inBlock) {
|
|
115
|
+
const opensBlock = line.startsWith("/*") || line.startsWith('"""') || line.startsWith("'''");
|
|
116
|
+
const closesOnSameLine = (line.startsWith("/*") && /\*\/$/.test(line)) ||
|
|
117
|
+
(/^("""|''')/.test(line) && line.length > 3 && /("""|''')$/.test(line));
|
|
118
|
+
if (CODE_SKIP.test(line)) {
|
|
119
|
+
// Pragma, shebang or marker on the raw line: skip it. A one-line
|
|
120
|
+
// block comment ("/* eslint-disable */") must not leave us in a block.
|
|
121
|
+
if (opensBlock && !closesOnSameLine)
|
|
122
|
+
inBlock = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (opensBlock) {
|
|
126
|
+
inBlock = !closesOnSameLine;
|
|
127
|
+
line = line.replace(/^(\/\*+|"""|''')\s*/, "").replace(/(\*\/|"""|''')\s*$/, "").trim();
|
|
128
|
+
}
|
|
129
|
+
else if (line.startsWith("//") || line.startsWith("#")) {
|
|
130
|
+
line = line.replace(/^(\/\/|#)+\s*/, "").trim();
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Code before any comment: nothing worth saying about this file.
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
if (/(\*\/|"""|''')$/.test(line))
|
|
139
|
+
inBlock = false;
|
|
140
|
+
line = line.replace(/^\*+\s*/, "").replace(/(\*\/|"""|''')\s*$/, "").trim();
|
|
141
|
+
}
|
|
142
|
+
if (!line || /^[-=*_#]{3,}$/.test(line) || CODE_SKIP.test(line))
|
|
143
|
+
continue;
|
|
144
|
+
if (line.length < 12)
|
|
145
|
+
continue;
|
|
146
|
+
return line;
|
|
147
|
+
}
|
|
148
|
+
return "";
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=source-summary.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compr/opscontext-mcp",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.2",
|
|
4
4
|
"description": "OpsContext for AI Agents — read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident audit log + policy-as-code hooks. The ops + compliance layer Claude Code can't grow natively.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"email": "yannick@compr.ch"
|
|
60
60
|
},
|
|
61
61
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
62
|
+
"node": ">=20.19.0"
|
|
63
63
|
},
|
|
64
64
|
"files": [
|
|
65
65
|
"dist/",
|