@minhpnq1807/contextos 0.5.7 → 0.5.9
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 +11 -0
- package/README.md +28 -6
- package/bin/ctx.js +101 -0
- package/package.json +1 -1
- package/plugins/ctx/lib/benchmark.js +18 -11
- package/plugins/ctx/lib/reporter.js +49 -18
- package/plugins/ctx/lib/setup-wizard.js +47 -0
- package/plugins/ctx/lib/stats.js +37 -27
- package/plugins/ctx/lib/terminal-ui.js +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.9
|
|
4
|
+
|
|
5
|
+
- Formats `ctx report`, `ctx evidence`, `ctx stats`, and `ctx benchmark` with sectioned terminal tables for easier scanning and analysis.
|
|
6
|
+
- Adds a small shared terminal table formatter used by report, evidence, stats, and benchmark output.
|
|
7
|
+
|
|
8
|
+
## 0.5.8
|
|
9
|
+
|
|
10
|
+
- Adds explicit `ctx setup` interactive onboarding for installing agents, enabling injection, syncing Ruler rules/MCP servers, and syncing skills through skillshare.
|
|
11
|
+
- Adds non-interactive setup flags: `--yes`, `--agents`, `--no-rules`, `--no-skills`, `--quiet`, and `--no-inject`.
|
|
12
|
+
- Keeps npm install lifecycle clean; setup only runs when the user explicitly invokes `ctx setup`.
|
|
13
|
+
|
|
3
14
|
## 0.5.7
|
|
4
15
|
|
|
5
16
|
- Adds thin passthrough commands `ctx ruler -- <args>` and `ctx skillshare -- <args>` for upstream admin/debug workflows without reimplementing those CLIs.
|
package/README.md
CHANGED
|
@@ -11,10 +11,25 @@ Published package: [`@minhpnq1807/contextos`](https://www.npmjs.com/package/@min
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install -g @minhpnq1807/contextos
|
|
13
13
|
ctx --version
|
|
14
|
-
ctx
|
|
14
|
+
ctx setup
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`ctx setup` is the recommended first-run flow. It asks which agents to configure, whether prompt context injection should be enabled, whether to sync project rules/MCP through Ruler, and whether to sync skills through skillshare. It only runs when you explicitly invoke it; npm install does not run setup automatically.
|
|
18
|
+
|
|
19
|
+
For scriptable installs, use:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ctx setup --yes
|
|
23
|
+
ctx setup --yes --agents codex,claude,agy
|
|
15
24
|
```
|
|
16
25
|
|
|
17
|
-
Restart
|
|
26
|
+
Restart your agent after setup, then use it normally. ContextOS runs through agent hooks and the `ctx-mcp` MCP server.
|
|
27
|
+
|
|
28
|
+
Codex-only install is still available:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ctx install
|
|
32
|
+
```
|
|
18
33
|
|
|
19
34
|
Claude Code and Antigravity are supported through their native hook systems:
|
|
20
35
|
|
|
@@ -27,6 +42,7 @@ You can also run without a global install:
|
|
|
27
42
|
|
|
28
43
|
```bash
|
|
29
44
|
npx @minhpnq1807/contextos@latest install
|
|
45
|
+
npx @minhpnq1807/contextos@latest setup
|
|
30
46
|
npx @minhpnq1807/contextos@latest install claude
|
|
31
47
|
npx @minhpnq1807/contextos@latest install agy
|
|
32
48
|
```
|
|
@@ -323,11 +339,17 @@ This warning comes from a transitive dependency in the local embedding/WASM stac
|
|
|
323
339
|
| `ctx install --quiet` | Installs ContextOS in measurement-only mode. | You want reports and stats but do not want visible injected context. | Installs the same hooks, but prompt hooks return empty context. |
|
|
324
340
|
| `ctx install --inject` | Installs ContextOS with explicit injection mode. | You want to be explicit in scripts or docs. | Same runtime behavior as the default install mode; if combined with `--quiet`, `--inject` wins. |
|
|
325
341
|
| `ctx install --copy` | Copies only the plugin payload to `$CODEX_HOME/plugins/ctx`. | Local development or manual plugin experiments. | Does not register marketplace, MCP, or global hooks. |
|
|
342
|
+
| `ctx setup` | Runs the first-run setup wizard. | You want the recommended onboarding flow after `npm install -g @minhpnq1807/contextos`. | Installs selected agents, optionally syncs Ruler rules/MCP and skillshare skills, then prints next steps. |
|
|
343
|
+
| `ctx setup --yes` | Runs setup with defaults non-interactively. | You want scriptable all-agent setup. | Uses `codex,claude,agy`, enables injection, syncs rules, syncs skills, and passes `--yes` to dependency setup prompts. |
|
|
344
|
+
| `ctx setup --agents <list>` | Runs setup for selected agents. | You want only part of the default set. | Accepts comma-separated `codex`, `claude`, `agy`, or `antigravity`. |
|
|
345
|
+
| `ctx setup --no-rules` | Skips Ruler sync during setup. | You only want hooks/MCP install and maybe skill sync. | Does not run `ctx sync --rules`. |
|
|
346
|
+
| `ctx setup --no-skills` | Skips skillshare sync during setup. | You do not want shared skills configured. | Does not run `ctx sync --skills`. |
|
|
347
|
+
| `ctx setup --quiet` | Runs setup in measurement-only mode. | You want reports/stats without visible injected prompt context. | Installs hooks with prompt context injection disabled. |
|
|
326
348
|
| `ctx debug -- "task"` | Runs the scheduler locally for a fake prompt. | You want to see which AGENTS.md rules and files ContextOS would inject before using Codex. | Prints rule scores, scoring reasons, suggested files, and final `additionalContext`. |
|
|
327
|
-
| `ctx report` | Shows the last Stop-hook compliance report for the current workspace. | An agent task has finished and you want the summary again. |
|
|
328
|
-
| `ctx evidence` | Shows detailed evidence behind the last report for the current workspace. | You want to inspect why a rule was marked `followed`, `ignored`, or `
|
|
329
|
-
| `ctx stats` | Shows aggregate runtime metrics for the current workspace. | You want to know whether ContextOS is active and useful over time. | Prints
|
|
330
|
-
| `ctx benchmark -- "task"` | Compares baseline AGENTS.md ordering with ContextOS task-aware scheduling. | You want a before/after signal for lost-in-the-middle risk. | Prints parsed/actionable/filtered
|
|
349
|
+
| `ctx report` | Shows the last Stop-hook compliance report for the current workspace. | An agent task has finished and you want the summary again. | Prints sectioned tables for summary, rule outcomes, suggested files, and runtime telemetry from `~/.ctx/contextos/workspaces/<workspace-id>/last-report.json`. |
|
|
350
|
+
| `ctx evidence` | Shows detailed evidence behind the last report for the current workspace. | You want to inspect why a rule was marked `followed`, `ignored`, `unknown`, or `unmeasurable`. | Prints a compact evidence table plus per-rule detail tables. |
|
|
351
|
+
| `ctx stats` | Shows aggregate runtime metrics for the current workspace. | You want to know whether ContextOS is active and useful over time. | Prints sectioned tables for prompt/report counts, injection rate, efficiency, rule outcomes, hook events, last prompt, and last report. |
|
|
352
|
+
| `ctx benchmark -- "task"` | Compares baseline AGENTS.md ordering with ContextOS task-aware scheduling. | You want a before/after signal for lost-in-the-middle risk. | Prints tables for parsed/actionable/filtered rules, baseline middle-risk, scheduled high/mid rules, recency reminder status, and top scored rules. |
|
|
331
353
|
| `ctx sync --rules` | Syncs project rules and MCP servers through Ruler. | You want Codex, Claude Code, and Antigravity to share one project rule/MCP source of truth. | Ensures `.ruler/ruler.toml`, injects `ctx-mcp`, imports existing MCP servers from Codex and project `.mcp.json`, runs `ruler apply --agents codex,claude,antigravity`, mirrors MCP servers to Antigravity MCP configs, and verifies generated config. |
|
|
332
354
|
| `ctx sync --rules --agents <list>` | Syncs only selected agents through Ruler. | You want to update one or two agents without touching the others. | Accepts comma-separated values such as `codex`, `claude`, `agy`, `antigravity`, or `codex,claude,agy`; `agy` is normalized to Ruler's `antigravity`. |
|
|
333
355
|
| `ctx sync --rules --dry-run` | Previews Ruler sync without writing files or running apply. | You want to inspect behavior before changing project config. | Prints the same flow with dry-run status. |
|
package/bin/ctx.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import readline from "node:readline/promises";
|
|
5
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { execFileSync } from "node:child_process";
|
|
6
8
|
|
|
@@ -25,6 +27,7 @@ import { syncRules } from "../plugins/ctx/lib/ruler-sync.js";
|
|
|
25
27
|
import { syncSkills } from "../plugins/ctx/lib/skillshare-sync.js";
|
|
26
28
|
import { scanSkills, warmSkillEmbeddings } from "../plugins/ctx/lib/skill-discoverer.js";
|
|
27
29
|
import { parsePassthroughArgs, runPassthrough } from "../plugins/ctx/lib/passthrough.js";
|
|
30
|
+
import { parseAgentList, parseSetupArgs, setupSummaryLines } from "../plugins/ctx/lib/setup-wizard.js";
|
|
28
31
|
|
|
29
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
33
|
const rootDir = path.resolve(__dirname, "..");
|
|
@@ -42,6 +45,12 @@ Usage:
|
|
|
42
45
|
ctx install --quiet
|
|
43
46
|
ctx install --inject
|
|
44
47
|
ctx install --copy
|
|
48
|
+
ctx setup
|
|
49
|
+
ctx setup --yes
|
|
50
|
+
ctx setup --agents codex,claude,agy
|
|
51
|
+
ctx setup --no-rules
|
|
52
|
+
ctx setup --no-skills
|
|
53
|
+
ctx setup --quiet
|
|
45
54
|
ctx debug -- "task"
|
|
46
55
|
ctx report
|
|
47
56
|
ctx evidence
|
|
@@ -394,6 +403,96 @@ async function warmEmbeddings(task) {
|
|
|
394
403
|
console.log(`Cache: ${result.cachePath}`);
|
|
395
404
|
}
|
|
396
405
|
|
|
406
|
+
function printSetupBanner() {
|
|
407
|
+
console.log("");
|
|
408
|
+
console.log("╭─ ContextOS setup ─────────────────────────────────────────╮");
|
|
409
|
+
console.log("│ Task-aware rules, MCP sync, and skill discovery for agents │");
|
|
410
|
+
console.log("╰───────────────────────────────────────────────────────────╯");
|
|
411
|
+
console.log("");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function askSetupQuestion(rl, question, defaultValue) {
|
|
415
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
416
|
+
const answer = await rl.question(`◇ ${question}${suffix}: `);
|
|
417
|
+
return answer.trim() || defaultValue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function askSetupYesNo(rl, question, defaultValue = true) {
|
|
421
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
422
|
+
const answer = await askSetupQuestion(rl, question, suffix);
|
|
423
|
+
if (!answer || answer === suffix) return defaultValue;
|
|
424
|
+
return !/^n(o)?$/i.test(answer.trim());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function setup({ args = [], cwd = process.cwd() } = {}) {
|
|
428
|
+
const options = parseSetupArgs(args);
|
|
429
|
+
const interactive = !options.yes && process.stdin.isTTY;
|
|
430
|
+
|
|
431
|
+
printSetupBanner();
|
|
432
|
+
console.log(`◇ Installation directory:\n│ ${cwd}`);
|
|
433
|
+
|
|
434
|
+
if (interactive) {
|
|
435
|
+
const rl = readline.createInterface({ input, output });
|
|
436
|
+
try {
|
|
437
|
+
const proceed = await askSetupYesNo(rl, "Install to this directory?", true);
|
|
438
|
+
if (!proceed) {
|
|
439
|
+
console.log("Setup cancelled.");
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const agents = await askSetupQuestion(rl, "Install for agents? comma-separated", options.agents.join(","));
|
|
443
|
+
options.agents = parseAgentList(agents);
|
|
444
|
+
options.inject = await askSetupYesNo(rl, "Enable prompt context injection?", options.inject);
|
|
445
|
+
options.syncRules = await askSetupYesNo(rl, "Sync project rules and MCP servers through Ruler?", options.syncRules);
|
|
446
|
+
options.syncSkills = await askSetupYesNo(rl, "Sync skills through skillshare?", options.syncSkills);
|
|
447
|
+
} finally {
|
|
448
|
+
rl.close();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log("");
|
|
453
|
+
console.log("◇ Ready to setup:");
|
|
454
|
+
for (const line of setupSummaryLines({ cwd, ...options })) console.log(`│ ${line}`);
|
|
455
|
+
console.log("");
|
|
456
|
+
|
|
457
|
+
if (!options.agents.length) throw new Error("No agents selected. Use --agents codex,claude,agy.");
|
|
458
|
+
|
|
459
|
+
for (const agent of options.agents) {
|
|
460
|
+
console.log(`● Setting up ${agent}...`);
|
|
461
|
+
await install({ agent, inject: options.inject, copy: false });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (options.syncRules) {
|
|
465
|
+
console.log("● Syncing project rules and MCP servers...");
|
|
466
|
+
const syncAgents = options.agents.map((agent) => agent === "agy" ? "antigravity" : agent).join(",");
|
|
467
|
+
const syncArgs = ["--rules", "--agents", syncAgents];
|
|
468
|
+
if (options.yes) syncArgs.push("--yes");
|
|
469
|
+
await syncRules({ cwd, rootDir, args: syncArgs });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (options.syncSkills) {
|
|
473
|
+
console.log("● Syncing skills...");
|
|
474
|
+
const skillAgents = options.agents.map((agent) => agent === "agy" ? "antigravity" : agent).join(",");
|
|
475
|
+
const syncArgs = ["--skills", "--agents", skillAgents];
|
|
476
|
+
if (options.yes) syncArgs.push("--yes");
|
|
477
|
+
await syncSkills({
|
|
478
|
+
cwd,
|
|
479
|
+
args: syncArgs,
|
|
480
|
+
rebuildSkillEmbeddings: async ({ cwd: skillCwd, sourceDir }) => warmSkillEmbeddings({
|
|
481
|
+
cwd: skillCwd,
|
|
482
|
+
dataDir: contextOSDataDir(),
|
|
483
|
+
allowRemote: !isModelCacheReady(contextOSDataDir()),
|
|
484
|
+
skills: scanSkills({ cwd: skillCwd, roots: [sourceDir] })
|
|
485
|
+
})
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
console.log("");
|
|
490
|
+
console.log("╭─ ContextOS is ready ───────────────────────────────────────╮");
|
|
491
|
+
console.log("│ Next: restart/open your agent from this project directory. │");
|
|
492
|
+
console.log("│ Try: ctx debug -- \"Recheck authen flow\" │");
|
|
493
|
+
console.log("╰───────────────────────────────────────────────────────────╯");
|
|
494
|
+
}
|
|
495
|
+
|
|
397
496
|
const args = process.argv.slice(2);
|
|
398
497
|
const command = args[0];
|
|
399
498
|
|
|
@@ -415,6 +514,8 @@ try {
|
|
|
415
514
|
inject: args.includes("--inject") || !args.includes("--quiet"),
|
|
416
515
|
agent: installAgentFromArgs(args)
|
|
417
516
|
});
|
|
517
|
+
} else if (command === "setup") {
|
|
518
|
+
await setup({ args: args.slice(1), cwd: process.cwd() });
|
|
418
519
|
} else if (command === "debug") {
|
|
419
520
|
const marker = args.indexOf("--");
|
|
420
521
|
const task = marker >= 0 ? args.slice(marker + 1).join(" ") : args.slice(1).join(" ");
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseRules, filterActionableRules, scoreRules } from "./analyzer.js";
|
|
2
2
|
import { readAgentsChain } from "./reader.js";
|
|
3
3
|
import { scheduleContext } from "./scheduler.js";
|
|
4
|
+
import { section, table, truncateCell } from "./terminal-ui.js";
|
|
4
5
|
|
|
5
6
|
export function benchmarkContext({ markdown, sources = [], task = "", openFiles = [], topK = 8 } = {}) {
|
|
6
7
|
const parsedRules = parseRules(markdown);
|
|
@@ -55,18 +56,24 @@ export function benchmarkWorkspace({ cwd = process.cwd(), task = "", openFiles =
|
|
|
55
56
|
export function formatBenchmark(result) {
|
|
56
57
|
const lines = [];
|
|
57
58
|
lines.push("ContextOS benchmark");
|
|
58
|
-
lines.push(
|
|
59
|
-
lines.push(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
lines.push(section("Summary"));
|
|
60
|
+
lines.push(table(["Metric", "Value"], [
|
|
61
|
+
["Task", truncateCell(result.task || "(empty)", 100)],
|
|
62
|
+
["Rules parsed", result.rulesParsed],
|
|
63
|
+
["Actionable rules", result.actionableRules],
|
|
64
|
+
["Filtered rules", result.filteredRules],
|
|
65
|
+
["Relevant rules", result.relevantRules],
|
|
66
|
+
["Baseline middle-risk", `${result.baseline.relevantRulesInMiddle}/${result.relevantRules} relevant rules (${result.baseline.middleRiskPercent}%)`],
|
|
67
|
+
["ContextOS scheduled", `${result.contextOS.highRules} high, ${result.contextOS.midRules} mid`],
|
|
68
|
+
["Recency reminder", result.contextOS.repeatsHighRulesAtEnd ? "enabled" : "not needed"]
|
|
69
|
+
]));
|
|
64
70
|
if (result.contextOS.topRules.length) {
|
|
65
|
-
lines.push("Top
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
lines.push(section("Top Rules"));
|
|
72
|
+
lines.push(table(["Score", "Rule", "Reasons"], result.contextOS.topRules.map((rule) => [
|
|
73
|
+
Number(rule.score || 0).toFixed(2),
|
|
74
|
+
truncateCell(rule.content, 88),
|
|
75
|
+
truncateCell(rule.reasons?.join(", ") || "", 40)
|
|
76
|
+
])));
|
|
70
77
|
}
|
|
71
78
|
return lines.join("\n");
|
|
72
79
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSystemUserRule } from "./analyzer.js";
|
|
2
|
+
import { section, table, truncateCell } from "./terminal-ui.js";
|
|
2
3
|
|
|
3
4
|
export function buildReport({ cwd, prompt, relevantFiles, scheduled, gitSnapshot, compliance, runtimeEvidence }) {
|
|
4
5
|
const actionableCompliance = compliance.filter((item) => !isSystemUserRule(item.rule));
|
|
@@ -33,17 +34,33 @@ export function formatReport(report) {
|
|
|
33
34
|
report = sanitizeReport(report);
|
|
34
35
|
const lines = [];
|
|
35
36
|
lines.push("ContextOS report");
|
|
36
|
-
lines.push(
|
|
37
|
-
lines.push(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
lines.push(section("Summary"));
|
|
38
|
+
lines.push(table(["Metric", "Value"], [
|
|
39
|
+
["Efficiency", report.efficiencyScore == null ? "unknown" : `${report.efficiencyScore}%`],
|
|
40
|
+
["Injected rules", report.injectedRuleCount || 0],
|
|
41
|
+
["Measured rules", report.measuredRuleCount ?? ((report.followed?.length || 0) + (report.ignored?.length || 0))],
|
|
42
|
+
["Changed files", report.changedFiles?.length ? report.changedFiles.length : "none detected"]
|
|
43
|
+
]));
|
|
44
|
+
|
|
45
|
+
lines.push(section("Rule Outcomes"));
|
|
46
|
+
lines.push(table(["Status", "Count"], [
|
|
47
|
+
["followed", report.followed?.length || 0],
|
|
48
|
+
["ignored", report.ignored?.length || 0],
|
|
49
|
+
["unknown", report.unknown?.length || 0],
|
|
50
|
+
["unmeasurable", report.unmeasurable?.length || 0]
|
|
51
|
+
]));
|
|
41
52
|
|
|
42
53
|
if (report.relevantFiles?.length) {
|
|
43
|
-
lines.push(
|
|
54
|
+
lines.push(section("Suggested Files"));
|
|
55
|
+
lines.push(table(["#", "Path", "Score"], report.relevantFiles.slice(0, 10).map((file, index) => [
|
|
56
|
+
index + 1,
|
|
57
|
+
truncateCell(file.path, 90),
|
|
58
|
+
typeof file.score === "number" ? file.score.toFixed(2) : ""
|
|
59
|
+
])));
|
|
44
60
|
}
|
|
45
61
|
if (report.runtimeEvidence?.signals?.length) {
|
|
46
|
-
lines.push(
|
|
62
|
+
lines.push(section("Runtime Telemetry"));
|
|
63
|
+
lines.push(table(["#", "Signal"], report.runtimeEvidence.signals.map((signal, index) => [index + 1, signal])));
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
for (const warning of report.warnings || []) lines.push(`Warning: ${warning}`);
|
|
@@ -66,9 +83,12 @@ export function formatEvidence(report) {
|
|
|
66
83
|
report = sanitizeReport(report);
|
|
67
84
|
const lines = [];
|
|
68
85
|
lines.push("ContextOS evidence");
|
|
69
|
-
lines.push(
|
|
70
|
-
lines.push(
|
|
71
|
-
|
|
86
|
+
lines.push(section("Summary"));
|
|
87
|
+
lines.push(table(["Field", "Value"], [
|
|
88
|
+
["Prompt", truncateCell(report.prompt || "(empty)", 100)],
|
|
89
|
+
["Efficiency", report.efficiencyScore == null ? "unknown" : `${report.efficiencyScore}%`],
|
|
90
|
+
["Changed files", report.changedFiles?.length ? report.changedFiles.join(", ") : "none detected"]
|
|
91
|
+
]));
|
|
72
92
|
|
|
73
93
|
for (const warning of report.warnings || []) lines.push(`Warning: ${warning}`);
|
|
74
94
|
|
|
@@ -85,15 +105,26 @@ export function formatEvidence(report) {
|
|
|
85
105
|
return lines.join("\n");
|
|
86
106
|
}
|
|
87
107
|
|
|
108
|
+
lines.push(section("Evidence Table"));
|
|
109
|
+
lines.push(table(["#", "Status", "Score", "Kind", "Rule", "Evidence"], items.map((item, index) => [
|
|
110
|
+
index + 1,
|
|
111
|
+
item.status.toUpperCase(),
|
|
112
|
+
typeof item.rule?.score === "number" ? item.rule.score.toFixed(2) : "",
|
|
113
|
+
item.kind || "",
|
|
114
|
+
truncateCell(item.rule?.content || "(missing rule)", 46),
|
|
115
|
+
truncateCell(item.evidence || "(none)", 58)
|
|
116
|
+
])));
|
|
117
|
+
|
|
88
118
|
items.forEach((item, index) => {
|
|
89
|
-
lines.push(
|
|
90
|
-
lines.push(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
lines.push(section(`${index + 1}. ${item.status.toUpperCase()}`));
|
|
120
|
+
lines.push(table(["Field", "Value"], [
|
|
121
|
+
["Rule", truncateCell(item.rule?.content || "(missing rule)", 120)],
|
|
122
|
+
["Source", item.rule?.sourcePath || ""],
|
|
123
|
+
["Score", typeof item.rule?.score === "number" ? item.rule.score.toFixed(2) : ""],
|
|
124
|
+
["Kind", item.kind || ""],
|
|
125
|
+
["Keywords", item.keywords?.length ? truncateCell(item.keywords.join(", "), 120) : ""],
|
|
126
|
+
["Evidence", truncateCell(item.evidence || "(none)", 120)]
|
|
127
|
+
].filter(([, value]) => value !== "")));
|
|
97
128
|
for (const line of item.matchedLines || []) {
|
|
98
129
|
const where = line.file ? `${line.file}${typeof line.line === "number" ? `:${line.line}` : ""}` : "diff";
|
|
99
130
|
lines.push(`Matched line: ${where} ${truncate(line.content || "", 140)}`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const DEFAULT_AGENTS = ["codex", "claude", "agy"];
|
|
2
|
+
|
|
3
|
+
export function parseSetupArgs(args = []) {
|
|
4
|
+
const agentsFlag = args.indexOf("--agents");
|
|
5
|
+
const agents = agentsFlag >= 0
|
|
6
|
+
? parseAgentList(args[agentsFlag + 1])
|
|
7
|
+
: DEFAULT_AGENTS;
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
agents,
|
|
11
|
+
yes: args.includes("--yes") || args.includes("-y"),
|
|
12
|
+
quiet: args.includes("--quiet"),
|
|
13
|
+
inject: !args.includes("--quiet") && !args.includes("--no-inject"),
|
|
14
|
+
syncRules: !args.includes("--no-rules"),
|
|
15
|
+
syncSkills: !args.includes("--no-skills")
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseAgentList(value = "") {
|
|
20
|
+
const agents = String(value || "")
|
|
21
|
+
.split(",")
|
|
22
|
+
.map((item) => normalizeSetupAgent(item))
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
return [...new Set(agents)];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function normalizeSetupAgent(agent) {
|
|
28
|
+
const normalized = String(agent || "").trim().toLowerCase();
|
|
29
|
+
if (normalized === "antigravity") return "agy";
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function setupSummaryLines({
|
|
34
|
+
cwd = process.cwd(),
|
|
35
|
+
agents = DEFAULT_AGENTS,
|
|
36
|
+
inject = true,
|
|
37
|
+
syncRules = true,
|
|
38
|
+
syncSkills = true
|
|
39
|
+
} = {}) {
|
|
40
|
+
return [
|
|
41
|
+
`Installation directory: ${cwd}`,
|
|
42
|
+
`Agents: ${agents.join(", ") || "(none)"}`,
|
|
43
|
+
`Prompt context injection: ${inject ? "enabled" : "quiet logging only"}`,
|
|
44
|
+
`Ruler rule/MCP sync: ${syncRules ? "enabled" : "skipped"}`,
|
|
45
|
+
`skillshare skill sync: ${syncSkills ? "enabled" : "skipped"}`
|
|
46
|
+
];
|
|
47
|
+
}
|
package/plugins/ctx/lib/stats.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { safeReadText } from "./fs-utils.js";
|
|
5
|
+
import { section, table, truncateCell } from "./terminal-ui.js";
|
|
5
6
|
|
|
6
7
|
function readJsonLines(filePath) {
|
|
7
8
|
return safeReadText(filePath)
|
|
@@ -81,33 +82,47 @@ export function loadStats(dataDir) {
|
|
|
81
82
|
export function formatStats(stats) {
|
|
82
83
|
const lines = [];
|
|
83
84
|
lines.push("ContextOS stats");
|
|
84
|
-
lines.push(
|
|
85
|
-
lines.push(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
lines.push(
|
|
85
|
+
lines.push(section("Summary"));
|
|
86
|
+
lines.push(table(["Metric", "Value"], [
|
|
87
|
+
["Data dir", stats.dataDir],
|
|
88
|
+
["Prompts analyzed", stats.promptCount],
|
|
89
|
+
["Reports generated", stats.reportCount],
|
|
90
|
+
["Prompt mode", `${stats.injectedCount} injected, ${stats.quietCount} quiet (${stats.injectionRate}% injected)`],
|
|
91
|
+
["Average prompt analysis", stats.averagePromptMs == null ? "unknown" : `${stats.averagePromptMs}ms`],
|
|
92
|
+
["Average efficiency", formatAverageEfficiency(stats)]
|
|
93
|
+
]));
|
|
94
|
+
|
|
95
|
+
lines.push(section("Rule Outcomes"));
|
|
96
|
+
lines.push(table(["Status", "Count"], [
|
|
97
|
+
["followed", stats.followed],
|
|
98
|
+
["ignored", stats.ignored],
|
|
99
|
+
["unknown", stats.unknown],
|
|
100
|
+
["unmeasurable", stats.unmeasurable || 0]
|
|
101
|
+
]));
|
|
102
|
+
|
|
103
|
+
lines.push(section("Hook Events"));
|
|
104
|
+
lines.push(Object.keys(stats.events).length
|
|
105
|
+
? table(["Event", "Count"], Object.entries(stats.events))
|
|
106
|
+
: "none");
|
|
96
107
|
|
|
97
108
|
if (stats.lastPrompt) {
|
|
98
|
-
lines.push(
|
|
99
|
-
lines.push(
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
lines.push(section("Last Prompt"));
|
|
110
|
+
lines.push(table(["Field", "Value"], [
|
|
111
|
+
["Prompt", truncateCell(stats.lastPrompt.prompt || "(empty)", 100)],
|
|
112
|
+
["Scheduled rules", scheduledRuleCount(stats.lastPrompt)],
|
|
113
|
+
["Suggested files", (stats.lastPrompt.relevantFiles || []).map((file) => file.path).join(", ") || "none"]
|
|
114
|
+
]));
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
if (stats.lastReport) {
|
|
105
|
-
lines.push(
|
|
106
|
-
lines.push(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
lines.push(section("Last Report"));
|
|
119
|
+
lines.push(table(["Metric", "Value"], [
|
|
120
|
+
["Efficiency", stats.lastReport.efficiencyScore == null ? "unknown" : `${stats.lastReport.efficiencyScore}%`],
|
|
121
|
+
["Measured rules", stats.lastReport.measuredRuleCount ?? ((stats.lastReport.followed?.length || 0) + (stats.lastReport.ignored?.length || 0))],
|
|
122
|
+
["Unknown rules", stats.lastReport.unknownRuleCount ?? (stats.lastReport.unknown?.length || 0)],
|
|
123
|
+
["Unmeasurable rules", stats.lastReport.unmeasurableRuleCount ?? (stats.lastReport.unmeasurable?.length || 0)],
|
|
124
|
+
["Changed files", stats.lastReport.changedFiles?.join(", ") || "none"]
|
|
125
|
+
]));
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
return lines.join("\n");
|
|
@@ -123,8 +138,3 @@ function formatAverageEfficiency(stats) {
|
|
|
123
138
|
function scheduledRuleCount(prompt) {
|
|
124
139
|
return (prompt.scheduled?.highRules?.length || 0) + (prompt.scheduled?.midRules?.length || 0);
|
|
125
140
|
}
|
|
126
|
-
|
|
127
|
-
function truncateLine(value, max) {
|
|
128
|
-
const normalized = String(value).replace(/\s+/g, " ").trim();
|
|
129
|
-
return normalized.length > max ? `${normalized.slice(0, max - 3)}...` : normalized;
|
|
130
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function section(title) {
|
|
2
|
+
return `\n${title}\n${"-".repeat(title.length)}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function table(headers, rows) {
|
|
6
|
+
const normalizedRows = rows.map((row) => row.map((cell) => String(cell ?? "")));
|
|
7
|
+
const widths = headers.map((header, index) => Math.max(
|
|
8
|
+
String(header).length,
|
|
9
|
+
...normalizedRows.map((row) => row[index]?.length || 0)
|
|
10
|
+
));
|
|
11
|
+
const formatRow = (row) => row.map((cell, index) => String(cell ?? "").padEnd(widths[index])).join(" ");
|
|
12
|
+
return [
|
|
13
|
+
formatRow(headers),
|
|
14
|
+
widths.map((width) => "-".repeat(width)).join(" "),
|
|
15
|
+
...normalizedRows.map(formatRow)
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function truncateCell(value, max = 80) {
|
|
20
|
+
const normalized = String(value || "").replace(/\s+/g, " ").trim();
|
|
21
|
+
return normalized.length > max ? `${normalized.slice(0, max - 3)}...` : normalized;
|
|
22
|
+
}
|